diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..88395310 Binary files /dev/null and b/.DS_Store differ diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..4d5ace0c --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,89 @@ +version: 2 +jobs: + test: + docker: # use the docker executor type + - image: circleci/ruby:2.6.3 + environment: + PGHOST: localhost + PGUSER: tutor_virtual + RAILS_ENV: test + BUNDLER_VERSION: 2.0.1 + + - image: postgres:11.5-alpine + environment: + POSTGRES_USER: tutor_virtual + POSTGRES_DB: tutor_virtual + POSTGRES_PASSWORD: "" + + steps: + - checkout + # Restore Cached Dependencies + - type: cache-restore + name: Restore bundle cache + key: tutor_virtual-{{ checksum "Gemfile.lock" }} + + - run: gem install bundler:${BUNDLER_VERSION} + + # Caching Ruby Gems + # Reference: https://www.benpickles.com/articles/76-better-ruby-gem-caching-on-circleci + - restore_cache: + keys: + - bundler-v2-{{ checksum "Gemfile.lock" }} + - bundler-v2- + + - run: bundle install --clean --path vendor/bundle + + - save_cache: + key: bundler-v2-{{ checksum "Gemfile.lock" }} + paths: + - vendor/bundle + + # Cache Dependencies + - type: cache-save + name: Store bundle cache + key: tutor_virtual-v2-{{ checksum "Gemfile.lock" }} + paths: + - vendor/bundle + + # Wait for DB + - run: dockerize -wait tcp://localhost:5432 -timeout 1m + + # # Bundle install dependencies + - run: bundle install + - run: bundle update + + - run: bundle exec rake db:create # creates the database + - run: rails db:migrate + + # Run the tests + - run: rake test + + build_release: + docker: # use the docker executor type + - image: docker:stable-git # the primary container, where the job's commands are run + steps: + - checkout # check out the code in the project directory + # A remote environment will be created, and your current primary container + # will be configured to use it. + # Then, any docker-related commands you use will be safely executed in this new environment. + - setup_remote_docker: + docker_layer_caching: true # Enables Docker Layer Caching here to speed up image building + - run: apk add make # Add MAKE in order to get the version from trendlit's Makefile + - run: docker login -u ${DOCKER_USER} -p ${DOCKER_PASS} + - run: sh 'bin/docker_build.sh' + - run: sh 'bin/docker_check.sh' + - run: sh 'bin/docker_push.sh' + # Heroku deploy + - run: docker login --username=_ --password=${HEROKU_TOKEN} registry.heroku.com + +workflows: + version: 2 + test_and_build_release: + jobs: + - test + - build_release: + requires: + - test + filters: + branches: + only: master diff --git a/.gitignore b/.gitignore index dce9d7bc..6c5e750b 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,5 @@ #Ignore backup Makefile *-e .env + +vendor/ diff --git a/tutor-virtual-2/.ruby-version b/.ruby-version similarity index 100% rename from tutor-virtual-2/.ruby-version rename to .ruby-version diff --git a/Dockerfile b/Dockerfile new file mode 100755 index 00000000..521b349a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,129 @@ +# References: +# https://iridakos.com/tutorials/2019/04/07/dockerizing-a-rails-application.html +# https://blog.codeship.com/running-rails-development-environment-docker/ + +# Step 1: Use the official Ruby 2.6.3 Slim Strech image as base: +FROM ruby:2.6.3-slim-stretch AS runtime + +# Step 2: We'll set the MALLOC_ARENA_MAX for optimization purposes & prevent memory bloat +# https://www.speedshop.co/2017/12/04/malloc-doubles-ruby-memory.html +ENV MALLOC_ARENA_MAX="2" + +# Step 3: We'll set '/usr/src' path as the working directory: +# NOTE: This is a Linux "standard" practice - see: +# - http://www.pathname.com/fhs/2.2/ +# - http://www.pathname.com/fhs/2.2/fhs-4.1.html +# - http://www.pathname.com/fhs/2.2/fhs-4.12.html +WORKDIR /usr/src + +# Step 4: We'll set the working dir as HOME and add the app's binaries path to +# $PATH: +ENV HOME=/usr/src PATH=/usr/src/bin:$PATH + +# Step 5: We'll install curl for later dependencies installations +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + curl + +# Step 6: Add nodejs source +RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - + +# Step 7: Add yarn packages repository +RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ + echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list + +# Step 8: Install the common runtime dependencies: +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + apt-transport-https software-properties-common \ + ca-certificates \ + libpq5 \ + openssl \ + nodejs \ + tzdata \ + yarn && \ + rm -rf /var/lib/apt/lists/* + +# Step 9: Start off from the "runtime" stage: +FROM runtime AS development + +# Step 10: Install the development dependency packages with alpine package +# manager: +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + build-essential \ + libpq-dev && \ + rm -rf /var/lib/apt/lists/* + +# Step 11: Copy the project's Gemfile + lock: +ADD Gemfile* /usr/src/ + +# Step 12: Install bundler ~2.0 +RUN gem install bundler -v 2.0.1 + +# Step 13: Install the current project gems - they can be safely changed later +# during development via `bundle install` or `bundle update`: +RUN bundle install --jobs=20 --retry=5 + +# Step 14: Set the default command: +CMD [ "rails", "server", "-b", "0.0.0.0" ] + +# Step 15: Add the current directory +ADD . /usr/src + +# Step 16: Install Yarn packages: +RUN yarn install + +# Step 17: Start off from the development stage image: +FROM development AS builder + +# Step 18: Precompile assets: +RUN export DATABASE_URL=postgres://postgres@example.com:5432/fakedb \ + SECRET_KEY_BASE=10167c7f7654ed02b3557b05b88ece \ + RAILS_ENV=production && \ + rails assets:precompile && \ + rails secret > /dev/null + +# Step 19: Remove installed gems that belong to the development & test groups - +# we'll copy the remaining system gems into the deployable image on the next +# stage: +RUN bundle config without development:test && bundle clean + +# Step 20: Remove files not used on release image: +RUN rm -rf \ + bin/setup \ + bin/update \ + entrypoint.sh \ + config/spring.rb \ + node_modules \ + spec \ + tmp/* + +# V: Release stage: ============================================================ +# In this stage, we build the final, deployable Docker image, which will be +# smaller than the images generated on previous stages: + +# Step 21: Start off from the runtime stage image: +FROM runtime AS release + +# Step 22: Copy the remaining installed gems from the "builder" stage: +COPY --from=builder /usr/local/bundle /usr/local/bundle + +# Step 23: Copy from app code from the "builder" stage, which at this point +# should have the assets from the asset pipeline already compiled: +COPY --from=builder /usr/src /usr/src + +# Step 24: Set the RAILS/RACK_ENV and PORT default values: +ENV RAILS_ENV=production RACK_ENV=production PORT=3000 + +# Step 25: Generate the temporary directories in case they don't already exist: +RUN mkdir -p /usr/src/tmp/cache && \ + mkdir -p /usr/src/tmp/pids && \ + mkdir -p /usr/src/tmp/sockets && \ + chown -R nobody /usr/src + +# Step 26: Set the container user to 'nobody': +USER nobody + +# Step 27: Set the default command: +CMD [ "puma" ] \ No newline at end of file diff --git a/tutor-virtual-2/Gemfile b/Gemfile similarity index 100% rename from tutor-virtual-2/Gemfile rename to Gemfile diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000..6d067a12 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,230 @@ +GEM + remote: https://rubygems.org/ + specs: + actioncable (5.2.3) + actionpack (= 5.2.3) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + actionmailer (5.2.3) + actionpack (= 5.2.3) + actionview (= 5.2.3) + activejob (= 5.2.3) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.2.3) + actionview (= 5.2.3) + activesupport (= 5.2.3) + rack (~> 2.0) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.2.3) + activesupport (= 5.2.3) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.2.3) + activesupport (= 5.2.3) + globalid (>= 0.3.6) + activemodel (5.2.3) + activesupport (= 5.2.3) + activerecord (5.2.3) + activemodel (= 5.2.3) + activesupport (= 5.2.3) + arel (>= 9.0) + activestorage (5.2.3) + actionpack (= 5.2.3) + activerecord (= 5.2.3) + marcel (~> 0.3.1) + activesupport (5.2.3) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) + archive-zip (0.12.0) + io-like (~> 0.3.0) + arel (9.0.0) + bcrypt (3.1.13) + bindex (0.8.1) + bootsnap (1.4.5) + msgpack (~> 1.0) + builder (3.2.3) + byebug (11.0.1) + capybara (3.29.0) + addressable + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (~> 1.5) + xpath (~> 3.2) + childprocess (3.0.0) + chromedriver-helper (2.1.1) + archive-zip (~> 0.10) + nokogiri (~> 1.8) + coffee-rails (4.2.2) + coffee-script (>= 2.2.0) + railties (>= 4.0.0) + coffee-script (2.4.1) + coffee-script-source + execjs + coffee-script-source (1.12.2) + concurrent-ruby (1.1.5) + crass (1.0.5) + devise (4.7.1) + bcrypt (~> 3.0) + orm_adapter (~> 0.1) + railties (>= 4.1.0) + responders + warden (~> 1.2.3) + erubi (1.9.0) + execjs (2.7.0) + ffi (1.11.1) + globalid (0.4.2) + activesupport (>= 4.2.0) + i18n (1.7.0) + concurrent-ruby (~> 1.0) + io-like (0.3.0) + jbuilder (2.9.1) + activesupport (>= 4.2.0) + listen (3.1.5) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + ruby_dep (~> 1.2) + loofah (2.3.0) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.7.1) + mini_mime (>= 0.1.1) + marcel (0.3.3) + mimemagic (~> 0.3.2) + method_source (0.9.2) + mimemagic (0.3.3) + mini_mime (1.0.2) + mini_portile2 (2.4.0) + minitest (5.12.2) + msgpack (1.3.1) + nio4r (2.5.2) + nokogiri (1.10.4) + mini_portile2 (~> 2.4.0) + orm_adapter (0.5.0) + pg (1.1.4) + public_suffix (4.0.1) + puma (3.12.1) + rack (2.0.7) + rack-test (1.1.0) + rack (>= 1.0, < 3) + rails (5.2.3) + actioncable (= 5.2.3) + actionmailer (= 5.2.3) + actionpack (= 5.2.3) + actionview (= 5.2.3) + activejob (= 5.2.3) + activemodel (= 5.2.3) + activerecord (= 5.2.3) + activestorage (= 5.2.3) + activesupport (= 5.2.3) + bundler (>= 1.3.0) + railties (= 5.2.3) + sprockets-rails (>= 2.0.0) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.3.0) + loofah (~> 2.3) + railties (5.2.3) + actionpack (= 5.2.3) + activesupport (= 5.2.3) + method_source + rake (>= 0.8.7) + thor (>= 0.19.0, < 2.0) + rake (13.0.0) + rb-fsevent (0.10.3) + rb-inotify (0.10.0) + ffi (~> 1.0) + regexp_parser (1.6.0) + responders (3.0.0) + actionpack (>= 5.0) + railties (>= 5.0) + ruby_dep (1.5.0) + rubyzip (2.0.0) + sass (3.7.4) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + sass-rails (5.1.0) + railties (>= 5.2.0) + sass (~> 3.1) + sprockets (>= 2.8, < 4.0) + sprockets-rails (>= 2.0, < 4.0) + tilt (>= 1.1, < 3) + selenium-webdriver (3.142.6) + childprocess (>= 0.5, < 4.0) + rubyzip (>= 1.2.2) + spring (2.1.0) + spring-watcher-listen (2.0.1) + listen (>= 2.7, < 4.0) + spring (>= 1.2, < 3.0) + sprockets (3.7.2) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + thor (0.20.3) + thread_safe (0.3.6) + tilt (2.0.10) + turbolinks (5.2.1) + turbolinks-source (~> 5.2) + turbolinks-source (5.2.0) + tzinfo (1.2.5) + thread_safe (~> 0.1) + uglifier (4.2.0) + execjs (>= 0.3.0, < 3) + warden (1.2.8) + rack (>= 2.0.6) + web-console (3.7.0) + actionview (>= 5.0) + activemodel (>= 5.0) + bindex (>= 0.4.0) + railties (>= 5.0) + websocket-driver (0.7.1) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.4) + xpath (3.2.0) + nokogiri (~> 1.8) + +PLATFORMS + ruby + +DEPENDENCIES + bootsnap (>= 1.1.0) + byebug + capybara (>= 2.15) + chromedriver-helper + coffee-rails (~> 4.2) + devise + jbuilder (~> 2.5) + listen (>= 3.0.5, < 3.2) + pg (>= 0.18, < 2.0) + puma (~> 3.11) + rails (~> 5.2.3) + sass-rails (~> 5.0) + selenium-webdriver + spring + spring-watcher-listen (~> 2.0.0) + turbolinks (~> 5) + tzinfo-data + uglifier (>= 1.3.0) + web-console (>= 3.3.0) + +RUBY VERSION + ruby 2.6.3p62 + +BUNDLED WITH + 1.17.3 diff --git a/Makefile b/Makefile new file mode 100755 index 00000000..1cc30db2 --- /dev/null +++ b/Makefile @@ -0,0 +1,43 @@ +APP=tutor_virtual +PROJECT=github.com/ProyectoIntegrador2018/tutor_virtual +RELEASE?=0.0.1 + +COMMIT?=$(shell git rev-parse HEAD) +BUILD_TIME?=$(shell date -u '+%Y-%m-%d_%H:%M:%S') + +.DEFAULT_GOAL := help + +build:## Spins that beautiful container! + @./bin/docker_build.sh + +bump:## Bumps version + @./bin/bump_version.sh + +check:## Check if the tag that is going to be pushed is unique. In other words, if RELEASE variable was updated in the Makefile. + @./bin/docker_check.sh + +help: ##Show this help. + @fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sed -e 's/##//' + + +prod:##prod: Run latest built simulating a production env. + @echo 'Prod triggered' + @./bin/docker_run_local_production.sh + +##push: push docker image to docker hub +push: check + @./bin/docker_push.sh + +rmi:## Removes docker image + @./bin/docker_rmi.sh + +run:## Run latest built + @echo 'Run triggered' + @./bin/docker_run.sh + +test: ##Run all automated tests. + @echo 'Test triggered' + +version: ##Prints current version + @echo -n ${RELEASE} + @echo diff --git a/README.md b/README.md new file mode 100644 index 00000000..761722b1 --- /dev/null +++ b/README.md @@ -0,0 +1,95 @@ +# Tutor Virtual + +[![Maintainability](https://api.codeclimate.com/v1/badges/ba4ca1e8e93e5cef25d7/maintainability)](https://codeclimate.com/github/ProyectoIntegrador2018/tutor_virtual/maintainability) + +El sistema tiene como objetivo el automatizar los procesos que lleva a cabo nuestra cliente, Dora Elizabeth García Olivier, perteneciente del Centro Virtual de Aprendizajes. Dentro de sus actividades que actualmente realiza de manera manual se incluyen el: Dar de alta a alumnos, profesores y directivos asociados al servicio social Aprendizaje Verde. + +## Tabla de contenidos + +* TBD + +## Detalles del Cliente + +| Nombre | Email | Rol | +| ------------------- | ------------------- | ---------------- | +| Dora García Olivier | degolivier@itesm.mx | Coordinador CVA | + + +## Ambientes del Sistema + +* **Producción** - [TutorVirtual](http://tutorvirtual.herokuapp.com/) +* **Desarrollo** - [Dev-TutorVirtual](http://dev-tutorvirtual.herokuapp.com/) + +Equipo: AD 2019 + +| Nombre | Email | Rol | +| ------------------ | ------------------ | ------------ | +| Sergio Diaz | a01192313@itesm.mx | Scrum Master | +| Patricio Forbes | A01192455@itesm.mx | PO Proxy | +| Arturo González | A01193188@itesm.mx | Desarrollo | + +## Herramientas + +Pide acceso a las siguientes herramientas de no ser que no lo tengas: + +* [Github repo](https://github.com/ProyectoIntegrador2018/tutor_virtual) +* [Backlog](https://github.com/ProyectoIntegrador2018/tutor_virtual/projects/2) +* [Documentation](https://drive.google.com/drive/folders/16hcLTaW8YtWHzEUo9VfwR-Qjewcsap-G?usp=sharing) + +## Configuración del proyecto + +### Pre-condiciones +- Install docker and docker-compose. + + +### Build and Run + +EL siguiente comando usa un multi-stage build para usar compilaciones de +varias etapas, y levantar la aplicación con un solo comando: + +``` +docker-compose up web +``` + +### Test + +EL siguiente comando usa un multi-stage build para usar compilaciones de +varias etapas, y levantar la aplicación en el ambiente de ```test``` con un +solo comando: + +``` +docker-compose run --rm test bash +``` + + +El comando anterior construirá la imagen si no existe, llamada: `proyecto_integrador / tutor_virtual: development`. + +### Debbuging +La estructura del proyecto permite a cualquiera ejecutar fácilmente una consola +de bash para poder ejecutar cualquier tipo de instrucción. Por ejemplo algo como ```rails db:create```, ```rails db:migrate```, o ```rails db:seed``` + + +``` +docker-compose run --rm web bash +``` + +### Pruebas +Si se ejecuta ```rails db:seed```, se agregarán dos usuarios de prueba a la base +de datos. Uno con permisos normales y otro con permisos de administrador. +``` +user: user@example.com +password: 123456 + +user: admin@example.com +password: 123456 + +``` +## Stack Tecnológico + +### Librerías Front End: +* Jquery +* CSS + +### Librerías Back End: + +* Ruby on Rails diff --git a/tutor-virtual-2/Rakefile b/Rakefile similarity index 100% rename from tutor-virtual-2/Rakefile rename to Rakefile diff --git a/tutor-virtual-2/app/assets/config/manifest.js b/app/assets/config/manifest.js similarity index 100% rename from tutor-virtual-2/app/assets/config/manifest.js rename to app/assets/config/manifest.js diff --git a/tutor-virtual-2/app/assets/images/.keep b/app/assets/images/.keep similarity index 100% rename from tutor-virtual-2/app/assets/images/.keep rename to app/assets/images/.keep diff --git a/tutor-virtual-2/app/assets/javascripts/application.js b/app/assets/javascripts/application.js similarity index 100% rename from tutor-virtual-2/app/assets/javascripts/application.js rename to app/assets/javascripts/application.js diff --git a/tutor-virtual-2/app/assets/javascripts/cable.js b/app/assets/javascripts/cable.js similarity index 100% rename from tutor-virtual-2/app/assets/javascripts/cable.js rename to app/assets/javascripts/cable.js diff --git a/tutor-virtual-2/app/assets/javascripts/channels/.keep b/app/assets/javascripts/channels/.keep similarity index 100% rename from tutor-virtual-2/app/assets/javascripts/channels/.keep rename to app/assets/javascripts/channels/.keep diff --git a/tutor-virtual-2/app/assets/javascripts/coordinators.coffee b/app/assets/javascripts/coordinators.coffee similarity index 100% rename from tutor-virtual-2/app/assets/javascripts/coordinators.coffee rename to app/assets/javascripts/coordinators.coffee diff --git a/tutor-virtual-2/app/assets/javascripts/courses.coffee b/app/assets/javascripts/courses.coffee similarity index 100% rename from tutor-virtual-2/app/assets/javascripts/courses.coffee rename to app/assets/javascripts/courses.coffee diff --git a/tutor-virtual-2/app/assets/javascripts/home.coffee b/app/assets/javascripts/home.coffee similarity index 100% rename from tutor-virtual-2/app/assets/javascripts/home.coffee rename to app/assets/javascripts/home.coffee diff --git a/tutor-virtual-2/app/assets/javascripts/stakeholders.coffee b/app/assets/javascripts/stakeholders.coffee similarity index 100% rename from tutor-virtual-2/app/assets/javascripts/stakeholders.coffee rename to app/assets/javascripts/stakeholders.coffee diff --git a/tutor-virtual-2/app/assets/javascripts/students.coffee b/app/assets/javascripts/students.coffee similarity index 100% rename from tutor-virtual-2/app/assets/javascripts/students.coffee rename to app/assets/javascripts/students.coffee diff --git a/tutor-virtual-2/app/assets/javascripts/supervisors.coffee b/app/assets/javascripts/supervisors.coffee similarity index 100% rename from tutor-virtual-2/app/assets/javascripts/supervisors.coffee rename to app/assets/javascripts/supervisors.coffee diff --git a/tutor-virtual-2/app/assets/javascripts/tutors.coffee b/app/assets/javascripts/tutors.coffee similarity index 100% rename from tutor-virtual-2/app/assets/javascripts/tutors.coffee rename to app/assets/javascripts/tutors.coffee diff --git a/tutor-virtual-2/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css similarity index 100% rename from tutor-virtual-2/app/assets/stylesheets/application.css rename to app/assets/stylesheets/application.css diff --git a/tutor-virtual-2/app/assets/stylesheets/coordinators.scss b/app/assets/stylesheets/coordinators.scss similarity index 100% rename from tutor-virtual-2/app/assets/stylesheets/coordinators.scss rename to app/assets/stylesheets/coordinators.scss diff --git a/tutor-virtual-2/app/assets/stylesheets/courses.scss b/app/assets/stylesheets/courses.scss similarity index 100% rename from tutor-virtual-2/app/assets/stylesheets/courses.scss rename to app/assets/stylesheets/courses.scss diff --git a/tutor-virtual-2/app/assets/stylesheets/home.scss b/app/assets/stylesheets/home.scss similarity index 100% rename from tutor-virtual-2/app/assets/stylesheets/home.scss rename to app/assets/stylesheets/home.scss diff --git a/tutor-virtual-2/app/assets/stylesheets/scaffolds.scss b/app/assets/stylesheets/scaffolds.scss similarity index 100% rename from tutor-virtual-2/app/assets/stylesheets/scaffolds.scss rename to app/assets/stylesheets/scaffolds.scss diff --git a/tutor-virtual-2/app/assets/stylesheets/stakeholders.scss b/app/assets/stylesheets/stakeholders.scss similarity index 100% rename from tutor-virtual-2/app/assets/stylesheets/stakeholders.scss rename to app/assets/stylesheets/stakeholders.scss diff --git a/tutor-virtual-2/app/assets/stylesheets/students.scss b/app/assets/stylesheets/students.scss similarity index 100% rename from tutor-virtual-2/app/assets/stylesheets/students.scss rename to app/assets/stylesheets/students.scss diff --git a/tutor-virtual-2/app/assets/stylesheets/supervisors.scss b/app/assets/stylesheets/supervisors.scss similarity index 100% rename from tutor-virtual-2/app/assets/stylesheets/supervisors.scss rename to app/assets/stylesheets/supervisors.scss diff --git a/tutor-virtual-2/app/assets/stylesheets/tutors.scss b/app/assets/stylesheets/tutors.scss similarity index 100% rename from tutor-virtual-2/app/assets/stylesheets/tutors.scss rename to app/assets/stylesheets/tutors.scss diff --git a/tutor-virtual-2/app/channels/application_cable/channel.rb b/app/channels/application_cable/channel.rb similarity index 100% rename from tutor-virtual-2/app/channels/application_cable/channel.rb rename to app/channels/application_cable/channel.rb diff --git a/tutor-virtual-2/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb similarity index 100% rename from tutor-virtual-2/app/channels/application_cable/connection.rb rename to app/channels/application_cable/connection.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb new file mode 100644 index 00000000..8f280dcf --- /dev/null +++ b/app/controllers/application_controller.rb @@ -0,0 +1,10 @@ +class ApplicationController < ActionController::Base + + private + + def require_login + unless current_user || request.env['PATH_INFO'] == "/login" + redirect_to login_url + end + end +end diff --git a/tutor-virtual-2/app/controllers/concerns/.keep b/app/controllers/concerns/.keep similarity index 100% rename from tutor-virtual-2/app/controllers/concerns/.keep rename to app/controllers/concerns/.keep diff --git a/tutor-virtual-2/app/controllers/coordinators_controller.rb b/app/controllers/coordinators_controller.rb similarity index 100% rename from tutor-virtual-2/app/controllers/coordinators_controller.rb rename to app/controllers/coordinators_controller.rb diff --git a/tutor-virtual-2/app/controllers/courses_controller.rb b/app/controllers/courses_controller.rb similarity index 100% rename from tutor-virtual-2/app/controllers/courses_controller.rb rename to app/controllers/courses_controller.rb diff --git a/tutor-virtual-2/app/controllers/home_controller.rb b/app/controllers/home_controller.rb similarity index 100% rename from tutor-virtual-2/app/controllers/home_controller.rb rename to app/controllers/home_controller.rb diff --git a/tutor-virtual-2/app/controllers/stakeholders_controller.rb b/app/controllers/stakeholders_controller.rb similarity index 100% rename from tutor-virtual-2/app/controllers/stakeholders_controller.rb rename to app/controllers/stakeholders_controller.rb diff --git a/tutor-virtual-2/app/controllers/students_controller.rb b/app/controllers/students_controller.rb similarity index 100% rename from tutor-virtual-2/app/controllers/students_controller.rb rename to app/controllers/students_controller.rb diff --git a/tutor-virtual-2/app/controllers/supervisors_controller.rb b/app/controllers/supervisors_controller.rb similarity index 100% rename from tutor-virtual-2/app/controllers/supervisors_controller.rb rename to app/controllers/supervisors_controller.rb diff --git a/tutor-virtual-2/app/controllers/tutors_controller.rb b/app/controllers/tutors_controller.rb similarity index 100% rename from tutor-virtual-2/app/controllers/tutors_controller.rb rename to app/controllers/tutors_controller.rb diff --git a/tutor-virtual-2/app/helpers/application_helper.rb b/app/helpers/application_helper.rb similarity index 100% rename from tutor-virtual-2/app/helpers/application_helper.rb rename to app/helpers/application_helper.rb diff --git a/tutor-virtual-2/app/helpers/coordinators_helper.rb b/app/helpers/coordinators_helper.rb similarity index 100% rename from tutor-virtual-2/app/helpers/coordinators_helper.rb rename to app/helpers/coordinators_helper.rb diff --git a/tutor-virtual-2/app/helpers/courses_helper.rb b/app/helpers/courses_helper.rb similarity index 100% rename from tutor-virtual-2/app/helpers/courses_helper.rb rename to app/helpers/courses_helper.rb diff --git a/tutor-virtual-2/app/helpers/home_helper.rb b/app/helpers/home_helper.rb similarity index 100% rename from tutor-virtual-2/app/helpers/home_helper.rb rename to app/helpers/home_helper.rb diff --git a/tutor-virtual-2/app/helpers/stakeholders_helper.rb b/app/helpers/stakeholders_helper.rb similarity index 100% rename from tutor-virtual-2/app/helpers/stakeholders_helper.rb rename to app/helpers/stakeholders_helper.rb diff --git a/tutor-virtual-2/app/helpers/students_helper.rb b/app/helpers/students_helper.rb similarity index 100% rename from tutor-virtual-2/app/helpers/students_helper.rb rename to app/helpers/students_helper.rb diff --git a/tutor-virtual-2/app/helpers/supervisors_helper.rb b/app/helpers/supervisors_helper.rb similarity index 100% rename from tutor-virtual-2/app/helpers/supervisors_helper.rb rename to app/helpers/supervisors_helper.rb diff --git a/tutor-virtual-2/app/helpers/tutors_helper.rb b/app/helpers/tutors_helper.rb similarity index 100% rename from tutor-virtual-2/app/helpers/tutors_helper.rb rename to app/helpers/tutors_helper.rb diff --git a/tutor-virtual-2/app/jobs/application_job.rb b/app/jobs/application_job.rb similarity index 100% rename from tutor-virtual-2/app/jobs/application_job.rb rename to app/jobs/application_job.rb diff --git a/tutor-virtual-2/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb similarity index 100% rename from tutor-virtual-2/app/mailers/application_mailer.rb rename to app/mailers/application_mailer.rb diff --git a/tutor-virtual-2/app/models/application_record.rb b/app/models/application_record.rb similarity index 100% rename from tutor-virtual-2/app/models/application_record.rb rename to app/models/application_record.rb diff --git a/tutor-virtual-2/app/models/concerns/.keep b/app/models/concerns/.keep similarity index 100% rename from tutor-virtual-2/app/models/concerns/.keep rename to app/models/concerns/.keep diff --git a/tutor-virtual-2/app/models/coordinator.rb b/app/models/coordinator.rb similarity index 100% rename from tutor-virtual-2/app/models/coordinator.rb rename to app/models/coordinator.rb diff --git a/tutor-virtual-2/app/models/course.rb b/app/models/course.rb similarity index 100% rename from tutor-virtual-2/app/models/course.rb rename to app/models/course.rb diff --git a/tutor-virtual-2/app/models/stakeholder.rb b/app/models/stakeholder.rb similarity index 100% rename from tutor-virtual-2/app/models/stakeholder.rb rename to app/models/stakeholder.rb diff --git a/tutor-virtual-2/app/models/student.rb b/app/models/student.rb similarity index 100% rename from tutor-virtual-2/app/models/student.rb rename to app/models/student.rb diff --git a/tutor-virtual-2/app/models/supervisor.rb b/app/models/supervisor.rb similarity index 100% rename from tutor-virtual-2/app/models/supervisor.rb rename to app/models/supervisor.rb diff --git a/tutor-virtual-2/app/models/tutor.rb b/app/models/tutor.rb similarity index 100% rename from tutor-virtual-2/app/models/tutor.rb rename to app/models/tutor.rb diff --git a/tutor-virtual-2/app/models/user.rb b/app/models/user.rb similarity index 100% rename from tutor-virtual-2/app/models/user.rb rename to app/models/user.rb diff --git a/tutor-virtual-2/app/views/coordinators/_coordinator.json.jbuilder b/app/views/coordinators/_coordinator.json.jbuilder similarity index 100% rename from tutor-virtual-2/app/views/coordinators/_coordinator.json.jbuilder rename to app/views/coordinators/_coordinator.json.jbuilder diff --git a/tutor-virtual-2/app/views/coordinators/_form.html.erb b/app/views/coordinators/_form.html.erb similarity index 100% rename from tutor-virtual-2/app/views/coordinators/_form.html.erb rename to app/views/coordinators/_form.html.erb diff --git a/tutor-virtual-2/app/views/coordinators/edit.html.erb b/app/views/coordinators/edit.html.erb similarity index 100% rename from tutor-virtual-2/app/views/coordinators/edit.html.erb rename to app/views/coordinators/edit.html.erb diff --git a/tutor-virtual-2/app/views/coordinators/index.html.erb b/app/views/coordinators/index.html.erb similarity index 100% rename from tutor-virtual-2/app/views/coordinators/index.html.erb rename to app/views/coordinators/index.html.erb diff --git a/tutor-virtual-2/app/views/coordinators/index.json.jbuilder b/app/views/coordinators/index.json.jbuilder similarity index 100% rename from tutor-virtual-2/app/views/coordinators/index.json.jbuilder rename to app/views/coordinators/index.json.jbuilder diff --git a/tutor-virtual-2/app/views/coordinators/new.html.erb b/app/views/coordinators/new.html.erb similarity index 100% rename from tutor-virtual-2/app/views/coordinators/new.html.erb rename to app/views/coordinators/new.html.erb diff --git a/tutor-virtual-2/app/views/coordinators/show.html.erb b/app/views/coordinators/show.html.erb similarity index 100% rename from tutor-virtual-2/app/views/coordinators/show.html.erb rename to app/views/coordinators/show.html.erb diff --git a/tutor-virtual-2/app/views/coordinators/show.json.jbuilder b/app/views/coordinators/show.json.jbuilder similarity index 100% rename from tutor-virtual-2/app/views/coordinators/show.json.jbuilder rename to app/views/coordinators/show.json.jbuilder diff --git a/tutor-virtual-2/app/views/courses/_course.json.jbuilder b/app/views/courses/_course.json.jbuilder similarity index 100% rename from tutor-virtual-2/app/views/courses/_course.json.jbuilder rename to app/views/courses/_course.json.jbuilder diff --git a/tutor-virtual-2/app/views/courses/_form.html.erb b/app/views/courses/_form.html.erb similarity index 100% rename from tutor-virtual-2/app/views/courses/_form.html.erb rename to app/views/courses/_form.html.erb diff --git a/tutor-virtual-2/app/views/courses/edit.html.erb b/app/views/courses/edit.html.erb similarity index 100% rename from tutor-virtual-2/app/views/courses/edit.html.erb rename to app/views/courses/edit.html.erb diff --git a/tutor-virtual-2/app/views/courses/index.html.erb b/app/views/courses/index.html.erb similarity index 100% rename from tutor-virtual-2/app/views/courses/index.html.erb rename to app/views/courses/index.html.erb diff --git a/tutor-virtual-2/app/views/courses/index.json.jbuilder b/app/views/courses/index.json.jbuilder similarity index 100% rename from tutor-virtual-2/app/views/courses/index.json.jbuilder rename to app/views/courses/index.json.jbuilder diff --git a/tutor-virtual-2/app/views/courses/new.html.erb b/app/views/courses/new.html.erb similarity index 100% rename from tutor-virtual-2/app/views/courses/new.html.erb rename to app/views/courses/new.html.erb diff --git a/tutor-virtual-2/app/views/courses/show.html.erb b/app/views/courses/show.html.erb similarity index 100% rename from tutor-virtual-2/app/views/courses/show.html.erb rename to app/views/courses/show.html.erb diff --git a/tutor-virtual-2/app/views/courses/show.json.jbuilder b/app/views/courses/show.json.jbuilder similarity index 100% rename from tutor-virtual-2/app/views/courses/show.json.jbuilder rename to app/views/courses/show.json.jbuilder diff --git a/tutor-virtual-2/app/views/devise/confirmations/new.html.erb b/app/views/devise/confirmations/new.html.erb similarity index 100% rename from tutor-virtual-2/app/views/devise/confirmations/new.html.erb rename to app/views/devise/confirmations/new.html.erb diff --git a/tutor-virtual-2/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb similarity index 100% rename from tutor-virtual-2/app/views/devise/mailer/confirmation_instructions.html.erb rename to app/views/devise/mailer/confirmation_instructions.html.erb diff --git a/tutor-virtual-2/app/views/devise/mailer/email_changed.html.erb b/app/views/devise/mailer/email_changed.html.erb similarity index 100% rename from tutor-virtual-2/app/views/devise/mailer/email_changed.html.erb rename to app/views/devise/mailer/email_changed.html.erb diff --git a/tutor-virtual-2/app/views/devise/mailer/password_change.html.erb b/app/views/devise/mailer/password_change.html.erb similarity index 100% rename from tutor-virtual-2/app/views/devise/mailer/password_change.html.erb rename to app/views/devise/mailer/password_change.html.erb diff --git a/tutor-virtual-2/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb similarity index 100% rename from tutor-virtual-2/app/views/devise/mailer/reset_password_instructions.html.erb rename to app/views/devise/mailer/reset_password_instructions.html.erb diff --git a/tutor-virtual-2/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb similarity index 100% rename from tutor-virtual-2/app/views/devise/mailer/unlock_instructions.html.erb rename to app/views/devise/mailer/unlock_instructions.html.erb diff --git a/tutor-virtual-2/app/views/devise/passwords/edit.html.erb b/app/views/devise/passwords/edit.html.erb similarity index 100% rename from tutor-virtual-2/app/views/devise/passwords/edit.html.erb rename to app/views/devise/passwords/edit.html.erb diff --git a/tutor-virtual-2/app/views/devise/passwords/new.html.erb b/app/views/devise/passwords/new.html.erb similarity index 100% rename from tutor-virtual-2/app/views/devise/passwords/new.html.erb rename to app/views/devise/passwords/new.html.erb diff --git a/tutor-virtual-2/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb similarity index 100% rename from tutor-virtual-2/app/views/devise/registrations/edit.html.erb rename to app/views/devise/registrations/edit.html.erb diff --git a/tutor-virtual-2/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb similarity index 100% rename from tutor-virtual-2/app/views/devise/registrations/new.html.erb rename to app/views/devise/registrations/new.html.erb diff --git a/tutor-virtual-2/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb similarity index 100% rename from tutor-virtual-2/app/views/devise/sessions/new.html.erb rename to app/views/devise/sessions/new.html.erb diff --git a/tutor-virtual-2/app/views/devise/shared/_error_messages.html.erb b/app/views/devise/shared/_error_messages.html.erb similarity index 100% rename from tutor-virtual-2/app/views/devise/shared/_error_messages.html.erb rename to app/views/devise/shared/_error_messages.html.erb diff --git a/tutor-virtual-2/app/views/devise/shared/_links.html.erb b/app/views/devise/shared/_links.html.erb similarity index 100% rename from tutor-virtual-2/app/views/devise/shared/_links.html.erb rename to app/views/devise/shared/_links.html.erb diff --git a/tutor-virtual-2/app/views/devise/unlocks/new.html.erb b/app/views/devise/unlocks/new.html.erb similarity index 100% rename from tutor-virtual-2/app/views/devise/unlocks/new.html.erb rename to app/views/devise/unlocks/new.html.erb diff --git a/tutor-virtual-2/app/views/home/index.html.erb b/app/views/home/index.html.erb similarity index 100% rename from tutor-virtual-2/app/views/home/index.html.erb rename to app/views/home/index.html.erb diff --git a/tutor-virtual-2/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb similarity index 100% rename from tutor-virtual-2/app/views/layouts/application.html.erb rename to app/views/layouts/application.html.erb diff --git a/tutor-virtual-2/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb similarity index 100% rename from tutor-virtual-2/app/views/layouts/mailer.html.erb rename to app/views/layouts/mailer.html.erb diff --git a/tutor-virtual-2/app/views/layouts/mailer.text.erb b/app/views/layouts/mailer.text.erb similarity index 100% rename from tutor-virtual-2/app/views/layouts/mailer.text.erb rename to app/views/layouts/mailer.text.erb diff --git a/tutor-virtual-2/app/views/stakeholders/_form.html.erb b/app/views/stakeholders/_form.html.erb similarity index 100% rename from tutor-virtual-2/app/views/stakeholders/_form.html.erb rename to app/views/stakeholders/_form.html.erb diff --git a/tutor-virtual-2/app/views/stakeholders/_stakeholder.json.jbuilder b/app/views/stakeholders/_stakeholder.json.jbuilder similarity index 100% rename from tutor-virtual-2/app/views/stakeholders/_stakeholder.json.jbuilder rename to app/views/stakeholders/_stakeholder.json.jbuilder diff --git a/tutor-virtual-2/app/views/stakeholders/edit.html.erb b/app/views/stakeholders/edit.html.erb similarity index 100% rename from tutor-virtual-2/app/views/stakeholders/edit.html.erb rename to app/views/stakeholders/edit.html.erb diff --git a/tutor-virtual-2/app/views/stakeholders/index.html.erb b/app/views/stakeholders/index.html.erb similarity index 100% rename from tutor-virtual-2/app/views/stakeholders/index.html.erb rename to app/views/stakeholders/index.html.erb diff --git a/tutor-virtual-2/app/views/stakeholders/index.json.jbuilder b/app/views/stakeholders/index.json.jbuilder similarity index 100% rename from tutor-virtual-2/app/views/stakeholders/index.json.jbuilder rename to app/views/stakeholders/index.json.jbuilder diff --git a/tutor-virtual-2/app/views/stakeholders/new.html.erb b/app/views/stakeholders/new.html.erb similarity index 100% rename from tutor-virtual-2/app/views/stakeholders/new.html.erb rename to app/views/stakeholders/new.html.erb diff --git a/tutor-virtual-2/app/views/stakeholders/show.html.erb b/app/views/stakeholders/show.html.erb similarity index 100% rename from tutor-virtual-2/app/views/stakeholders/show.html.erb rename to app/views/stakeholders/show.html.erb diff --git a/tutor-virtual-2/app/views/stakeholders/show.json.jbuilder b/app/views/stakeholders/show.json.jbuilder similarity index 100% rename from tutor-virtual-2/app/views/stakeholders/show.json.jbuilder rename to app/views/stakeholders/show.json.jbuilder diff --git a/tutor-virtual-2/app/views/students/_form.html.erb b/app/views/students/_form.html.erb similarity index 100% rename from tutor-virtual-2/app/views/students/_form.html.erb rename to app/views/students/_form.html.erb diff --git a/tutor-virtual-2/app/views/students/_student.json.jbuilder b/app/views/students/_student.json.jbuilder similarity index 100% rename from tutor-virtual-2/app/views/students/_student.json.jbuilder rename to app/views/students/_student.json.jbuilder diff --git a/tutor-virtual-2/app/views/students/edit.html.erb b/app/views/students/edit.html.erb similarity index 100% rename from tutor-virtual-2/app/views/students/edit.html.erb rename to app/views/students/edit.html.erb diff --git a/tutor-virtual-2/app/views/students/index.html.erb b/app/views/students/index.html.erb similarity index 100% rename from tutor-virtual-2/app/views/students/index.html.erb rename to app/views/students/index.html.erb diff --git a/tutor-virtual-2/app/views/students/index.json.jbuilder b/app/views/students/index.json.jbuilder similarity index 100% rename from tutor-virtual-2/app/views/students/index.json.jbuilder rename to app/views/students/index.json.jbuilder diff --git a/tutor-virtual-2/app/views/students/new.html.erb b/app/views/students/new.html.erb similarity index 100% rename from tutor-virtual-2/app/views/students/new.html.erb rename to app/views/students/new.html.erb diff --git a/tutor-virtual-2/app/views/students/show.html.erb b/app/views/students/show.html.erb similarity index 100% rename from tutor-virtual-2/app/views/students/show.html.erb rename to app/views/students/show.html.erb diff --git a/tutor-virtual-2/app/views/students/show.json.jbuilder b/app/views/students/show.json.jbuilder similarity index 100% rename from tutor-virtual-2/app/views/students/show.json.jbuilder rename to app/views/students/show.json.jbuilder diff --git a/tutor-virtual-2/app/views/supervisors/_form.html.erb b/app/views/supervisors/_form.html.erb similarity index 100% rename from tutor-virtual-2/app/views/supervisors/_form.html.erb rename to app/views/supervisors/_form.html.erb diff --git a/tutor-virtual-2/app/views/supervisors/_supervisor.json.jbuilder b/app/views/supervisors/_supervisor.json.jbuilder similarity index 100% rename from tutor-virtual-2/app/views/supervisors/_supervisor.json.jbuilder rename to app/views/supervisors/_supervisor.json.jbuilder diff --git a/tutor-virtual-2/app/views/supervisors/edit.html.erb b/app/views/supervisors/edit.html.erb similarity index 100% rename from tutor-virtual-2/app/views/supervisors/edit.html.erb rename to app/views/supervisors/edit.html.erb diff --git a/tutor-virtual-2/app/views/supervisors/index.html.erb b/app/views/supervisors/index.html.erb similarity index 100% rename from tutor-virtual-2/app/views/supervisors/index.html.erb rename to app/views/supervisors/index.html.erb diff --git a/tutor-virtual-2/app/views/supervisors/index.json.jbuilder b/app/views/supervisors/index.json.jbuilder similarity index 100% rename from tutor-virtual-2/app/views/supervisors/index.json.jbuilder rename to app/views/supervisors/index.json.jbuilder diff --git a/tutor-virtual-2/app/views/supervisors/new.html.erb b/app/views/supervisors/new.html.erb similarity index 100% rename from tutor-virtual-2/app/views/supervisors/new.html.erb rename to app/views/supervisors/new.html.erb diff --git a/tutor-virtual-2/app/views/supervisors/show.html.erb b/app/views/supervisors/show.html.erb similarity index 100% rename from tutor-virtual-2/app/views/supervisors/show.html.erb rename to app/views/supervisors/show.html.erb diff --git a/tutor-virtual-2/app/views/supervisors/show.json.jbuilder b/app/views/supervisors/show.json.jbuilder similarity index 100% rename from tutor-virtual-2/app/views/supervisors/show.json.jbuilder rename to app/views/supervisors/show.json.jbuilder diff --git a/tutor-virtual-2/app/views/tutors/_form.html.erb b/app/views/tutors/_form.html.erb similarity index 100% rename from tutor-virtual-2/app/views/tutors/_form.html.erb rename to app/views/tutors/_form.html.erb diff --git a/tutor-virtual-2/app/views/tutors/_tutor.json.jbuilder b/app/views/tutors/_tutor.json.jbuilder similarity index 100% rename from tutor-virtual-2/app/views/tutors/_tutor.json.jbuilder rename to app/views/tutors/_tutor.json.jbuilder diff --git a/tutor-virtual-2/app/views/tutors/edit.html.erb b/app/views/tutors/edit.html.erb similarity index 100% rename from tutor-virtual-2/app/views/tutors/edit.html.erb rename to app/views/tutors/edit.html.erb diff --git a/tutor-virtual-2/app/views/tutors/index.html.erb b/app/views/tutors/index.html.erb similarity index 100% rename from tutor-virtual-2/app/views/tutors/index.html.erb rename to app/views/tutors/index.html.erb diff --git a/tutor-virtual-2/app/views/tutors/index.json.jbuilder b/app/views/tutors/index.json.jbuilder similarity index 100% rename from tutor-virtual-2/app/views/tutors/index.json.jbuilder rename to app/views/tutors/index.json.jbuilder diff --git a/tutor-virtual-2/app/views/tutors/new.html.erb b/app/views/tutors/new.html.erb similarity index 100% rename from tutor-virtual-2/app/views/tutors/new.html.erb rename to app/views/tutors/new.html.erb diff --git a/tutor-virtual-2/app/views/tutors/show.html.erb b/app/views/tutors/show.html.erb similarity index 100% rename from tutor-virtual-2/app/views/tutors/show.html.erb rename to app/views/tutors/show.html.erb diff --git a/tutor-virtual-2/app/views/tutors/show.json.jbuilder b/app/views/tutors/show.json.jbuilder similarity index 100% rename from tutor-virtual-2/app/views/tutors/show.json.jbuilder rename to app/views/tutors/show.json.jbuilder diff --git a/bin/bump_version.sh b/bin/bump_version.sh new file mode 100755 index 00000000..6e2c2299 --- /dev/null +++ b/bin/bump_version.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -e + +current_version=$(grep RELEASE?= ./Makefile | sed -e 's/RELEASE?=\(.*\)/\1/g') +next_version=$(echo $current_version | awk -F. '{$NF = $NF + 1;} 1' | sed 's/ /./g') + +sed -i -e "s/\(RELEASE?=\).*/\1$next_version/" Makefile diff --git a/tutor-virtual-2/bin/bundle b/bin/bundle similarity index 100% rename from tutor-virtual-2/bin/bundle rename to bin/bundle diff --git a/bin/checkdb b/bin/checkdb new file mode 100755 index 00000000..c5506244 --- /dev/null +++ b/bin/checkdb @@ -0,0 +1,42 @@ +#!/usr/bin/env ruby + +# This script is used by this project's Docker development entrypoint script +# to check if the app database (and any of the configured database schemas) +# exists, tripping out if they are missing. +# +# If this script trips out, the entrypoint script will then try to run the +# database setup. This is part of the magic that allows us the "clone & run" +# configuration. +require 'rubygems' +require 'rake' +require 'bundler' + +Bundler.setup(:default) + +require 'active_record' +require 'erb' +require 'yaml' + +def connection_to_database? + connection = ActiveRecord::Base.establish_connection + curr_ver = ActiveRecord::Migrator.current_version + connection && \ + ActiveRecord::Migrator.current_version +end + +begin + connection_tries ||= 3 + exit 1 unless connection_to_database? + exit 0 + rescue PG::ConnectionBad + unless (connection_tries -= 1).zero? + puts "Retrying DB connection #{connection_tries} more times..." + sleep ENV.fetch("APP_SETUP_WAIT", "5").to_i + retry + end + exit 2 +rescue ActiveRecord::NoDatabaseError + exit 3 +ensure + ActiveRecord::Base.clear_all_connections! +end diff --git a/bin/docker_build.sh b/bin/docker_build.sh new file mode 100755 index 00000000..4f9e954d --- /dev/null +++ b/bin/docker_build.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -e + +echo "build image" + +CONTAINER_REPOSITORY=shekodn +IMAGE=tutor_virtual +TAG=`make version` +HASH=`git log --format="%H" -n 1 | cut -c1-6` + +docker build --build-arg RELEASE=${TAG} -t ${CONTAINER_REPOSITORY}/${IMAGE}:${TAG} . +docker build --build-arg RELEASE=${TAG} -t ${CONTAINER_REPOSITORY}/${IMAGE}:${HASH} . +docker tag ${CONTAINER_REPOSITORY}/${IMAGE}:${HASH} ${CONTAINER_REPOSITORY}/${IMAGE}:latest diff --git a/bin/docker_check.sh b/bin/docker_check.sh new file mode 100755 index 00000000..0a8d541b --- /dev/null +++ b/bin/docker_check.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -e + +# The goal of this script is to check if the tag that is going to be pushed is unique. +# In other words, if RELEASE variable was updated in the Makefile. +echo "check if the tag wasn't pushed before" + +CONTAINER_REPOSITORY=shekodn +IMAGE=tutor_virtual +TAG=`make version` + +! docker pull ${CONTAINER_REPOSITORY}/${IMAGE}:${TAG} diff --git a/bin/docker_push.sh b/bin/docker_push.sh new file mode 100755 index 00000000..0e215fcc --- /dev/null +++ b/bin/docker_push.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -e + +echo "push image" + +CONTAINER_REPOSITORY=shekodn +IMAGE=tutor_virtual +TAG=`make version` +HASH=`git log --format="%H" -n 1 | cut -c1-6` + +docker push ${CONTAINER_REPOSITORY}/${IMAGE}:${TAG} +docker push ${CONTAINER_REPOSITORY}/${IMAGE}:${HASH} +docker push ${CONTAINER_REPOSITORY}/${IMAGE}:latest diff --git a/bin/docker_rmi.sh b/bin/docker_rmi.sh new file mode 100755 index 00000000..37acb19f --- /dev/null +++ b/bin/docker_rmi.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -e + +echo "remove image" + +CONTAINER_REPOSITORY=shekodn +IMAGE=tutor_virtual +TAG=`make version` +HASH=`git log --format="%H" -n 1 | cut -c1-6` + +docker rmi ${CONTAINER_REPOSITORY}/${IMAGE}:${TAG} +docker rmi ${CONTAINER_REPOSITORY}/${IMAGE}:${HASH} diff --git a/bin/docker_run.sh b/bin/docker_run.sh new file mode 100755 index 00000000..12ae85ba --- /dev/null +++ b/bin/docker_run.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -e + +echo "run container" + +CONTAINER_REPOSITORY=shekodn +IMAGE=tutor_virtual +TAG=`make version` + +PORT=80 + +docker run -p ${PORT}:${PORT} -e "PORT=${PORT}" \ + ${CONTAINER_REPOSITORY}/${IMAGE}:${TAG} diff --git a/bin/docker_run_local_production.sh b/bin/docker_run_local_production.sh new file mode 100755 index 00000000..41361029 --- /dev/null +++ b/bin/docker_run_local_production.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -e + +echo "run container" + +CONTAINER_REPOSITORY=shekodn +IMAGE=tutor_virtual +TAG=`make version` + +PORT=3000 + +docker run -p ${PORT}:${PORT} -e "PORT=${PORT}" -e "SECRET_KEY_BASE=123456" \ + ${CONTAINER_REPOSITORY}/${IMAGE}:${TAG} diff --git a/bin/entrypoint.sh b/bin/entrypoint.sh new file mode 100755 index 00000000..ca32041f --- /dev/null +++ b/bin/entrypoint.sh @@ -0,0 +1,50 @@ +#! /bin/sh + +# The Docker App Container's development entrypoint. +# This is a script used by the project's Docker development environment to +# setup the app containers and databases upon runnning. +set -e + +: ${APP_PATH:="/usr/src"} +: ${APP_TEMP_PATH:="$APP_PATH/tmp"} +: ${APP_SETUP_LOCK:="$APP_TEMP_PATH/setup.lock"} +: ${APP_SETUP_WAIT:="5"} + +# 1: Define the functions lock and unlock our app containers setup processes: +lock_setup() { mkdir -p $APP_TEMP_PATH && touch $APP_SETUP_LOCK; } +unlock_setup() { rm -rf $APP_SETUP_LOCK; } +wait_setup() { echo "Waiting for app setup to finish..."; sleep $APP_SETUP_WAIT; } + +# 2: 'Unlock' the setup process if the script exits prematurely: +trap unlock_setup HUP INT QUIT KILL TERM EXIT + +# 3: Specify a default command, in case it wasn't issued: +if [ -z "$1" ]; then set -- rails server -p 3000 -b 0.0.0.0 "$@"; fi + +if [ "$1" = "rails" ] || [ "$1" = "sidekiq" ] || [ "$1" = "rspec" ] +then + + # 4: Wait until the setup 'lock' file no longer exists: + while [ -f $APP_SETUP_LOCK ]; do wait_setup; done + + # 5: 'Lock' the setup process, to prevent a race condition when the project's + # app containers will try to install gems and setup the database concurrently: + lock_setup + + # 6: Check if the gem dependencies are met, or install + bundle check || bundle install + + bundle exec ${APP_PATH}/bin/checkdb || rails db:setup + + # 9: 'Unlock' the setup process: + unlock_setup + + # 10: If the command to execute is 'rails server', then force it to write the + # pid file into a non-shared container directory. Suddenly killing and + # removing app containers without this would leave a pidfile in the project's + # tmp dir, preventing the app container from starting up on further attempts: + if [ "$2" = "s" ] || [ "$2" = "server" ]; then rm -rf /usr/src/tmp/pids/server.pid; fi +fi + +# 11: Execute the given or default command: +exec "$@" diff --git a/bin/heroku_deploy.sh b/bin/heroku_deploy.sh new file mode 100755 index 00000000..2f7c4240 --- /dev/null +++ b/bin/heroku_deploy.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -e + +echo "Pushing image to Heroku" + +HEROKU_APP=tutor-virtual-ad19 +CONTAINER_REPOSITORY=shekodn +IMAGE=tutor_virtual +TAG=`make version` +HEROKU_TAG=`echo ${TAG} | sed 's/[.]/_/g'` + + +# docker build -t registry.heroku.com/${HEROKU_APP}/web . +docker login --username=_ --password=${HEROKU_TOKEN} registry.heroku.com + +docker build -t registry.heroku.com/${HEROKU_APP}/${IMAGE}:${TAG} . + +# docker build --build-arg RELEASE=${TAG} -t ${CONTAINER_REPOSITORY}/${IMAGE}:${TAG} . +# docker pull ${CONTAINER_REPOSITORY}/${IMAGE}:${TAG} +docker push registry.heroku.com/${HEROKU_APP}/${IMAGE}:${TAG} diff --git a/bin/heroku_push.sh b/bin/heroku_push.sh new file mode 100755 index 00000000..852fa4f1 --- /dev/null +++ b/bin/heroku_push.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -e + +echo "Pushing image to Heroku" + +HEROKU_APP=tutor-virtual-ad19 +IMAGE=tutor_virtual +TAG=`make version` +HEROKU_TAG=`echo ${TAG} | sed 's/[.]/_/g'` + +heroku container:push "${IMAGE}_${HEROKU_TAG}" --app ${HEROKU_APP} diff --git a/bin/heroku_release.sh b/bin/heroku_release.sh new file mode 100755 index 00000000..6f9a880d --- /dev/null +++ b/bin/heroku_release.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -e + +echo "Pushing image to Heroku" + +HEROKU_APP=tutor-virtual-ad19 +IMAGE=tutor_virtual +TAG=`make version` +HEROKU_TAG=`echo ${TAG} | sed 's/[.]/_/g'` + +heroku container:release "${IMAGE}_${HEROKU_TAG}" --app ${HEROKU_APP} diff --git a/tutor-virtual-2/bin/rails b/bin/rails similarity index 100% rename from tutor-virtual-2/bin/rails rename to bin/rails diff --git a/tutor-virtual-2/bin/rake b/bin/rake similarity index 100% rename from tutor-virtual-2/bin/rake rename to bin/rake diff --git a/tutor-virtual-2/bin/setup b/bin/setup similarity index 100% rename from tutor-virtual-2/bin/setup rename to bin/setup diff --git a/tutor-virtual-2/bin/spring b/bin/spring similarity index 100% rename from tutor-virtual-2/bin/spring rename to bin/spring diff --git a/tutor-virtual-2/bin/update b/bin/update similarity index 100% rename from tutor-virtual-2/bin/update rename to bin/update diff --git a/tutor-virtual-2/bin/yarn b/bin/yarn similarity index 100% rename from tutor-virtual-2/bin/yarn rename to bin/yarn diff --git a/tutor-virtual-2/config.ru b/config.ru similarity index 100% rename from tutor-virtual-2/config.ru rename to config.ru diff --git a/tutor-virtual-2/config/application.rb b/config/application.rb similarity index 100% rename from tutor-virtual-2/config/application.rb rename to config/application.rb diff --git a/tutor-virtual-2/config/boot.rb b/config/boot.rb similarity index 100% rename from tutor-virtual-2/config/boot.rb rename to config/boot.rb diff --git a/tutor-virtual-2/config/cable.yml b/config/cable.yml similarity index 100% rename from tutor-virtual-2/config/cable.yml rename to config/cable.yml diff --git a/tutor-virtual-2/config/credentials.yml.enc b/config/credentials.yml.enc similarity index 100% rename from tutor-virtual-2/config/credentials.yml.enc rename to config/credentials.yml.enc diff --git a/tutor-virtual-2/config/database.yml b/config/database.yml similarity index 100% rename from tutor-virtual-2/config/database.yml rename to config/database.yml diff --git a/tutor-virtual-2/config/environment.rb b/config/environment.rb similarity index 100% rename from tutor-virtual-2/config/environment.rb rename to config/environment.rb diff --git a/tutor-virtual-2/config/environments/development.rb b/config/environments/development.rb similarity index 100% rename from tutor-virtual-2/config/environments/development.rb rename to config/environments/development.rb diff --git a/tutor-virtual-2/config/environments/production.rb b/config/environments/production.rb similarity index 97% rename from tutor-virtual-2/config/environments/production.rb rename to config/environments/production.rb index a6c1f6fb..dfb65dec 100644 --- a/tutor-virtual-2/config/environments/production.rb +++ b/config/environments/production.rb @@ -71,7 +71,8 @@ # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation cannot be found). - config.i18n.fallbacks = true + # config.i18n.fallbacks = true + config.i18n.fallbacks = [I18n.default_locale] # Send deprecation notices to registered listeners. config.active_support.deprecation = :notify diff --git a/tutor-virtual-2/config/environments/test.rb b/config/environments/test.rb similarity index 100% rename from tutor-virtual-2/config/environments/test.rb rename to config/environments/test.rb diff --git a/tutor-virtual-2/config/initializers/application_controller_renderer.rb b/config/initializers/application_controller_renderer.rb similarity index 100% rename from tutor-virtual-2/config/initializers/application_controller_renderer.rb rename to config/initializers/application_controller_renderer.rb diff --git a/tutor-virtual-2/config/initializers/assets.rb b/config/initializers/assets.rb similarity index 100% rename from tutor-virtual-2/config/initializers/assets.rb rename to config/initializers/assets.rb diff --git a/tutor-virtual-2/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb similarity index 100% rename from tutor-virtual-2/config/initializers/backtrace_silencers.rb rename to config/initializers/backtrace_silencers.rb diff --git a/tutor-virtual-2/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb similarity index 100% rename from tutor-virtual-2/config/initializers/content_security_policy.rb rename to config/initializers/content_security_policy.rb diff --git a/tutor-virtual-2/config/initializers/cookies_serializer.rb b/config/initializers/cookies_serializer.rb similarity index 100% rename from tutor-virtual-2/config/initializers/cookies_serializer.rb rename to config/initializers/cookies_serializer.rb diff --git a/tutor-virtual-2/config/initializers/devise.rb b/config/initializers/devise.rb similarity index 100% rename from tutor-virtual-2/config/initializers/devise.rb rename to config/initializers/devise.rb diff --git a/tutor-virtual-2/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb similarity index 100% rename from tutor-virtual-2/config/initializers/filter_parameter_logging.rb rename to config/initializers/filter_parameter_logging.rb diff --git a/tutor-virtual-2/config/initializers/inflections.rb b/config/initializers/inflections.rb similarity index 100% rename from tutor-virtual-2/config/initializers/inflections.rb rename to config/initializers/inflections.rb diff --git a/tutor-virtual-2/config/initializers/mime_types.rb b/config/initializers/mime_types.rb similarity index 100% rename from tutor-virtual-2/config/initializers/mime_types.rb rename to config/initializers/mime_types.rb diff --git a/tutor-virtual-2/config/initializers/wrap_parameters.rb b/config/initializers/wrap_parameters.rb similarity index 100% rename from tutor-virtual-2/config/initializers/wrap_parameters.rb rename to config/initializers/wrap_parameters.rb diff --git a/tutor-virtual-2/config/locales/devise.en.yml b/config/locales/devise.en.yml similarity index 100% rename from tutor-virtual-2/config/locales/devise.en.yml rename to config/locales/devise.en.yml diff --git a/tutor-virtual-2/config/locales/en.yml b/config/locales/en.yml similarity index 100% rename from tutor-virtual-2/config/locales/en.yml rename to config/locales/en.yml diff --git a/tutor-virtual-2/config/puma.rb b/config/puma.rb similarity index 100% rename from tutor-virtual-2/config/puma.rb rename to config/puma.rb diff --git a/tutor-virtual-2/config/routes.rb b/config/routes.rb similarity index 100% rename from tutor-virtual-2/config/routes.rb rename to config/routes.rb diff --git a/tutor-virtual-2/config/spring.rb b/config/spring.rb similarity index 100% rename from tutor-virtual-2/config/spring.rb rename to config/spring.rb diff --git a/tutor-virtual-2/config/storage.yml b/config/storage.yml similarity index 100% rename from tutor-virtual-2/config/storage.yml rename to config/storage.yml diff --git a/tutor-virtual-2/db/migrate/20191016191056_create_courses.rb b/db/migrate/20191016191056_create_courses.rb similarity index 100% rename from tutor-virtual-2/db/migrate/20191016191056_create_courses.rb rename to db/migrate/20191016191056_create_courses.rb diff --git a/tutor-virtual-2/db/migrate/20191016191421_create_students.rb b/db/migrate/20191016191421_create_students.rb similarity index 100% rename from tutor-virtual-2/db/migrate/20191016191421_create_students.rb rename to db/migrate/20191016191421_create_students.rb diff --git a/tutor-virtual-2/db/migrate/20191016191621_create_coordinators.rb b/db/migrate/20191016191621_create_coordinators.rb similarity index 100% rename from tutor-virtual-2/db/migrate/20191016191621_create_coordinators.rb rename to db/migrate/20191016191621_create_coordinators.rb diff --git a/tutor-virtual-2/db/migrate/20191016191633_create_stakeholders.rb b/db/migrate/20191016191633_create_stakeholders.rb similarity index 100% rename from tutor-virtual-2/db/migrate/20191016191633_create_stakeholders.rb rename to db/migrate/20191016191633_create_stakeholders.rb diff --git a/tutor-virtual-2/db/migrate/20191016191649_create_supervisors.rb b/db/migrate/20191016191649_create_supervisors.rb similarity index 100% rename from tutor-virtual-2/db/migrate/20191016191649_create_supervisors.rb rename to db/migrate/20191016191649_create_supervisors.rb diff --git a/tutor-virtual-2/db/migrate/20191016191700_create_tutors.rb b/db/migrate/20191016191700_create_tutors.rb similarity index 100% rename from tutor-virtual-2/db/migrate/20191016191700_create_tutors.rb rename to db/migrate/20191016191700_create_tutors.rb diff --git a/tutor-virtual-2/db/migrate/20191016191914_create_join_table_courses_students.rb b/db/migrate/20191016191914_create_join_table_courses_students.rb similarity index 100% rename from tutor-virtual-2/db/migrate/20191016191914_create_join_table_courses_students.rb rename to db/migrate/20191016191914_create_join_table_courses_students.rb diff --git a/tutor-virtual-2/db/migrate/20191016233214_devise_create_users.rb b/db/migrate/20191016233214_devise_create_users.rb similarity index 100% rename from tutor-virtual-2/db/migrate/20191016233214_devise_create_users.rb rename to db/migrate/20191016233214_devise_create_users.rb diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 00000000..e392d3a4 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,167 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema.define(version: 2019_10_16_233214) do + + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + + create_table "coordinators", force: :cascade do |t| + t.string "username" + t.string "internal_password" + t.string "name" + t.string "first_last_name" + t.string "second_last_name" + t.string "email" + t.string "country" + t.string "state" + t.string "city" + t.string "partner" + t.string "organization_code" + t.integer "gender" + t.date "dob" + t.string "phone_number" + t.string "language" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "courses", force: :cascade do |t| + t.string "name" + t.string "course_code" + t.date "start_date" + t.date "end_date" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "courses_students", id: false, force: :cascade do |t| + t.bigint "course_id", null: false + t.bigint "student_id", null: false + t.index ["course_id", "student_id"], name: "index_courses_students_on_course_id_and_student_id" + t.index ["student_id", "course_id"], name: "index_courses_students_on_student_id_and_course_id" + end + + create_table "preinscritos", force: :cascade do |t| + t.string "nombre", null: false + t.string "apellido_paterno", null: false + t.string "apellido_materno", null: false + t.string "correo_contacto" + t.string "cca" + t.boolean "estatus_user_mensajeria" + t.boolean "estatus_ins_mensajeria" + t.string "fecha_inscripcion" + t.string "genero" + t.string "fecha_nacimieto" + t.string "pais" + t.string "estado" + t.string "ciudad" + t.string "idioma" + t.integer "id_organizacion" + t.string "rol", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "stakeholders", force: :cascade do |t| + t.string "username" + t.string "internal_password" + t.string "name" + t.string "first_last_name" + t.string "second_last_name" + t.string "email" + t.string "country" + t.string "state" + t.string "city" + t.string "partner" + t.string "organization_code" + t.integer "gender" + t.date "dob" + t.string "phone_number" + t.string "language" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "students", force: :cascade do |t| + t.string "username" + t.string "internal_password" + t.string "name" + t.string "first_last_name" + t.string "second_last_name" + t.string "email" + t.string "country" + t.string "state" + t.string "city" + t.string "partner" + t.string "organization_code" + t.integer "gender" + t.date "dob" + t.string "phone_number" + t.string "language" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "supervisors", force: :cascade do |t| + t.string "username" + t.string "internal_password" + t.string "name" + t.string "first_last_name" + t.string "second_last_name" + t.string "email" + t.string "country" + t.string "state" + t.string "city" + t.string "partner" + t.string "organization_code" + t.integer "gender" + t.date "dob" + t.string "phone_number" + t.string "language" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "tutors", force: :cascade do |t| + t.string "username" + t.string "internal_password" + t.string "name" + t.string "first_last_name" + t.string "second_last_name" + t.string "email" + t.string "country" + t.string "state" + t.string "city" + t.string "partner" + t.string "organization_code" + t.integer "gender" + t.date "dob" + t.string "phone_number" + t.string "language" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "users", force: :cascade do |t| + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false + t.string "reset_password_token" + t.datetime "reset_password_sent_at" + t.datetime "remember_created_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["email"], name: "index_users_on_email", unique: true + t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true + end + +end diff --git a/tutor-virtual-2/db/seeds.rb b/db/seeds.rb similarity index 100% rename from tutor-virtual-2/db/seeds.rb rename to db/seeds.rb diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100755 index 00000000..0104dfe9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,74 @@ +version: '2.4' + +volumes: + postgres_data: + app_node_modules: # Used to store the app's node modules... + +networks: + backend: + +services: + postgres: + image: postgres:11.5-alpine + volumes: + - postgres_data:/var/lib/postgresql/data + networks: + - backend + environment: + POSTGRES_PASSWORD: 3x4mpl3P455w0rd + ports: + - "5432:5432" + web: &app + build: + context: . + dockerfile: Dockerfile + target: development + image: proyecto_integrador/tutor_virtual:development + entrypoint: /usr/src/bin/entrypoint.sh + volumes: + # Mount the app code into the app containers at the "/usr/src" folder: + - .:/usr/src + + # After mounting the app code, this replaces the local 'node_modules' + # folder inside the container with a Docker volume. This is done for + # several reasons: + # - So we can run the frontend app either from the host (i.e. macOS) or + # using containers without having the host & container clobber the npm + # each other's packages, or avoid conflicting versions for macOS / Linux + # - Helps when running on macOS/Windows to speed up the npm install from, + # zero, since a local volume bind on mac/win is noticeably slower than + # a Docker volume - and node module install is very susceptible to + # I/O performance + - app_node_modules:/usr/src/node_modules + + networks: + - backend + + # Keep the stdin open, so we can attach to our app container's process + # and do things such as byebug, etc: + stdin_open: true + + # Enable sending signals (CTRL+C, CTRL+P + CTRL+Q) into the container: + tty: true + + # Link to our postgres and redis containers, so they can be visible from our + # app containers: + depends_on: + - postgres + command: rails server -p 3000 -b 0.0.0.0 + environment: &env + DATABASE_URL: postgres://postgres:3x4mpl3P455w0rd@postgres:5432/tutor_virtual_development + RAILS_ENV: development + RACK_ENV: development + RAILS_LOG_TO_STDOUT: "true" + ports: + - ${TUTOR_VIRTUAL_WEB_PORT:-3000}:3000 + + test: + <<: *app + environment: + <<: *env + DATABASE_URL: postgres://postgres:3x4mpl3P455w0rd@postgres:5432/tutor_virtual_test + RAILS_ENV: test + RACK_ENV: test + RAILS_LOG_TO_STDOUT: "true" diff --git a/tutor-virtual-2/lib/assets/.keep b/lib/assets/.keep similarity index 100% rename from tutor-virtual-2/lib/assets/.keep rename to lib/assets/.keep diff --git a/tutor-virtual-2/lib/tasks/.keep b/lib/tasks/.keep similarity index 100% rename from tutor-virtual-2/lib/tasks/.keep rename to lib/tasks/.keep diff --git a/tutor-virtual-2/log/.keep b/log/.keep similarity index 100% rename from tutor-virtual-2/log/.keep rename to log/.keep diff --git a/tutor-virtual-2/package.json b/package.json similarity index 100% rename from tutor-virtual-2/package.json rename to package.json diff --git a/path/ruby/2.6.0/bin/byebug b/path/ruby/2.6.0/bin/byebug new file mode 100755 index 00000000..f8ea228b --- /dev/null +++ b/path/ruby/2.6.0/bin/byebug @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby_executable_hooks +# +# This file was generated by RubyGems. +# +# The application 'byebug' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('byebug', 'byebug', version) +else +gem "byebug", version +load Gem.bin_path("byebug", "byebug", version) +end diff --git a/path/ruby/2.6.0/bin/chromedriver-helper b/path/ruby/2.6.0/bin/chromedriver-helper new file mode 100755 index 00000000..7fe0cd1e --- /dev/null +++ b/path/ruby/2.6.0/bin/chromedriver-helper @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby_executable_hooks +# +# This file was generated by RubyGems. +# +# The application 'chromedriver-helper' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('chromedriver-helper', 'chromedriver-helper', version) +else +gem "chromedriver-helper", version +load Gem.bin_path("chromedriver-helper", "chromedriver-helper", version) +end diff --git a/path/ruby/2.6.0/bin/chromedriver-update b/path/ruby/2.6.0/bin/chromedriver-update new file mode 100755 index 00000000..58f22089 --- /dev/null +++ b/path/ruby/2.6.0/bin/chromedriver-update @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby_executable_hooks +# +# This file was generated by RubyGems. +# +# The application 'chromedriver-helper' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('chromedriver-helper', 'chromedriver-update', version) +else +gem "chromedriver-helper", version +load Gem.bin_path("chromedriver-helper", "chromedriver-update", version) +end diff --git a/path/ruby/2.6.0/bin/listen b/path/ruby/2.6.0/bin/listen new file mode 100755 index 00000000..e2e32591 --- /dev/null +++ b/path/ruby/2.6.0/bin/listen @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby_executable_hooks +# +# This file was generated by RubyGems. +# +# The application 'listen' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('listen', 'listen', version) +else +gem "listen", version +load Gem.bin_path("listen", "listen", version) +end diff --git a/path/ruby/2.6.0/bin/nokogiri b/path/ruby/2.6.0/bin/nokogiri new file mode 100755 index 00000000..d8ef5349 --- /dev/null +++ b/path/ruby/2.6.0/bin/nokogiri @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby_executable_hooks +# +# This file was generated by RubyGems. +# +# The application 'nokogiri' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('nokogiri', 'nokogiri', version) +else +gem "nokogiri", version +load Gem.bin_path("nokogiri", "nokogiri", version) +end diff --git a/path/ruby/2.6.0/bin/puma b/path/ruby/2.6.0/bin/puma new file mode 100755 index 00000000..ba9636e3 --- /dev/null +++ b/path/ruby/2.6.0/bin/puma @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby_executable_hooks +# +# This file was generated by RubyGems. +# +# The application 'puma' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('puma', 'puma', version) +else +gem "puma", version +load Gem.bin_path("puma", "puma", version) +end diff --git a/path/ruby/2.6.0/bin/pumactl b/path/ruby/2.6.0/bin/pumactl new file mode 100755 index 00000000..31bb2e4a --- /dev/null +++ b/path/ruby/2.6.0/bin/pumactl @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby_executable_hooks +# +# This file was generated by RubyGems. +# +# The application 'puma' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('puma', 'pumactl', version) +else +gem "puma", version +load Gem.bin_path("puma", "pumactl", version) +end diff --git a/path/ruby/2.6.0/bin/rackup b/path/ruby/2.6.0/bin/rackup new file mode 100755 index 00000000..033c72c8 --- /dev/null +++ b/path/ruby/2.6.0/bin/rackup @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby_executable_hooks +# +# This file was generated by RubyGems. +# +# The application 'rack' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('rack', 'rackup', version) +else +gem "rack", version +load Gem.bin_path("rack", "rackup", version) +end diff --git a/path/ruby/2.6.0/bin/rails b/path/ruby/2.6.0/bin/rails new file mode 100755 index 00000000..d694ac54 --- /dev/null +++ b/path/ruby/2.6.0/bin/rails @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby_executable_hooks +# +# This file was generated by RubyGems. +# +# The application 'railties' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('railties', 'rails', version) +else +gem "railties", version +load Gem.bin_path("railties", "rails", version) +end diff --git a/path/ruby/2.6.0/bin/rake b/path/ruby/2.6.0/bin/rake new file mode 100755 index 00000000..a8392f07 --- /dev/null +++ b/path/ruby/2.6.0/bin/rake @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby_executable_hooks +# +# This file was generated by RubyGems. +# +# The application 'rake' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('rake', 'rake', version) +else +gem "rake", version +load Gem.bin_path("rake", "rake", version) +end diff --git a/path/ruby/2.6.0/bin/ruby_executable_hooks b/path/ruby/2.6.0/bin/ruby_executable_hooks new file mode 100755 index 00000000..34680944 --- /dev/null +++ b/path/ruby/2.6.0/bin/ruby_executable_hooks @@ -0,0 +1,25 @@ +#!/usr/bin/env ruby + +title = "ruby #{ARGV*" "}" +$0 = ARGV.shift +Process.setproctitle(title) if Process.methods.include?(:setproctitle) + +require 'rubygems' +begin + require 'executable-hooks/hooks' + Gem::ExecutableHooks.run($0) +rescue LoadError + warn "unable to load executable-hooks/hooks" if ENV.key?('ExecutableHooks_DEBUG') +end unless $0.end_with?('/executable-hooks-uninstaller') + +content = File.read($0) + +if + (index = content.index("\n#!ruby\n")) && index > 0 +then + skipped_content = content.slice!(0..index) + start_line = skipped_content.count("\n") + 1 + eval content, binding, $0, start_line +else + eval content, binding, $0 +end diff --git a/path/ruby/2.6.0/bin/sass b/path/ruby/2.6.0/bin/sass new file mode 100755 index 00000000..061cb248 --- /dev/null +++ b/path/ruby/2.6.0/bin/sass @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby_executable_hooks +# +# This file was generated by RubyGems. +# +# The application 'sass' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('sass', 'sass', version) +else +gem "sass", version +load Gem.bin_path("sass", "sass", version) +end diff --git a/path/ruby/2.6.0/bin/sass-convert b/path/ruby/2.6.0/bin/sass-convert new file mode 100755 index 00000000..fae96dc6 --- /dev/null +++ b/path/ruby/2.6.0/bin/sass-convert @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby_executable_hooks +# +# This file was generated by RubyGems. +# +# The application 'sass' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('sass', 'sass-convert', version) +else +gem "sass", version +load Gem.bin_path("sass", "sass-convert", version) +end diff --git a/path/ruby/2.6.0/bin/scss b/path/ruby/2.6.0/bin/scss new file mode 100755 index 00000000..d04c34e2 --- /dev/null +++ b/path/ruby/2.6.0/bin/scss @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby_executable_hooks +# +# This file was generated by RubyGems. +# +# The application 'sass' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('sass', 'scss', version) +else +gem "sass", version +load Gem.bin_path("sass", "scss", version) +end diff --git a/path/ruby/2.6.0/bin/spring b/path/ruby/2.6.0/bin/spring new file mode 100755 index 00000000..52fb039d --- /dev/null +++ b/path/ruby/2.6.0/bin/spring @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby_executable_hooks +# +# This file was generated by RubyGems. +# +# The application 'spring' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('spring', 'spring', version) +else +gem "spring", version +load Gem.bin_path("spring", "spring", version) +end diff --git a/path/ruby/2.6.0/bin/sprockets b/path/ruby/2.6.0/bin/sprockets new file mode 100755 index 00000000..feac0930 --- /dev/null +++ b/path/ruby/2.6.0/bin/sprockets @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby_executable_hooks +# +# This file was generated by RubyGems. +# +# The application 'sprockets' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('sprockets', 'sprockets', version) +else +gem "sprockets", version +load Gem.bin_path("sprockets", "sprockets", version) +end diff --git a/path/ruby/2.6.0/bin/thor b/path/ruby/2.6.0/bin/thor new file mode 100755 index 00000000..e056ddf1 --- /dev/null +++ b/path/ruby/2.6.0/bin/thor @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby_executable_hooks +# +# This file was generated by RubyGems. +# +# The application 'thor' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('thor', 'thor', version) +else +gem "thor", version +load Gem.bin_path("thor", "thor", version) +end diff --git a/path/ruby/2.6.0/bin/tilt b/path/ruby/2.6.0/bin/tilt new file mode 100755 index 00000000..d862eab1 --- /dev/null +++ b/path/ruby/2.6.0/bin/tilt @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby_executable_hooks +# +# This file was generated by RubyGems. +# +# The application 'tilt' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'rubygems' + +version = ">= 0.a" + +str = ARGV.first +if str + str = str.b[/\A_(.*)_\z/, 1] + if str and Gem::Version.correct?(str) + version = str + ARGV.shift + end +end + +if Gem.respond_to?(:activate_bin_path) +load Gem.activate_bin_path('tilt', 'tilt', version) +else +gem "tilt", version +load Gem.bin_path("tilt", "tilt", version) +end diff --git a/path/ruby/2.6.0/cache/actioncable-5.2.3.gem b/path/ruby/2.6.0/cache/actioncable-5.2.3.gem new file mode 100644 index 00000000..a85488c6 Binary files /dev/null and b/path/ruby/2.6.0/cache/actioncable-5.2.3.gem differ diff --git a/path/ruby/2.6.0/cache/actionmailer-5.2.3.gem b/path/ruby/2.6.0/cache/actionmailer-5.2.3.gem new file mode 100644 index 00000000..36135a73 Binary files /dev/null and b/path/ruby/2.6.0/cache/actionmailer-5.2.3.gem differ diff --git a/path/ruby/2.6.0/cache/actionpack-5.2.3.gem b/path/ruby/2.6.0/cache/actionpack-5.2.3.gem new file mode 100644 index 00000000..a9c46cd1 Binary files /dev/null and b/path/ruby/2.6.0/cache/actionpack-5.2.3.gem differ diff --git a/path/ruby/2.6.0/cache/actionview-5.2.3.gem b/path/ruby/2.6.0/cache/actionview-5.2.3.gem new file mode 100644 index 00000000..94a49b9c Binary files /dev/null and b/path/ruby/2.6.0/cache/actionview-5.2.3.gem differ diff --git a/path/ruby/2.6.0/cache/activejob-5.2.3.gem b/path/ruby/2.6.0/cache/activejob-5.2.3.gem new file mode 100644 index 00000000..2b75d32b Binary files /dev/null and b/path/ruby/2.6.0/cache/activejob-5.2.3.gem differ diff --git a/path/ruby/2.6.0/cache/activemodel-5.2.3.gem b/path/ruby/2.6.0/cache/activemodel-5.2.3.gem new file mode 100644 index 00000000..d0532a33 Binary files /dev/null and b/path/ruby/2.6.0/cache/activemodel-5.2.3.gem differ diff --git a/path/ruby/2.6.0/cache/activerecord-5.2.3.gem b/path/ruby/2.6.0/cache/activerecord-5.2.3.gem new file mode 100644 index 00000000..5bd11819 Binary files /dev/null and b/path/ruby/2.6.0/cache/activerecord-5.2.3.gem differ diff --git a/path/ruby/2.6.0/cache/activestorage-5.2.3.gem b/path/ruby/2.6.0/cache/activestorage-5.2.3.gem new file mode 100644 index 00000000..0623cc8f Binary files /dev/null and b/path/ruby/2.6.0/cache/activestorage-5.2.3.gem differ diff --git a/path/ruby/2.6.0/cache/activesupport-5.2.3.gem b/path/ruby/2.6.0/cache/activesupport-5.2.3.gem new file mode 100644 index 00000000..acaae6e3 Binary files /dev/null and b/path/ruby/2.6.0/cache/activesupport-5.2.3.gem differ diff --git a/path/ruby/2.6.0/cache/addressable-2.7.0.gem b/path/ruby/2.6.0/cache/addressable-2.7.0.gem new file mode 100644 index 00000000..263158b3 Binary files /dev/null and b/path/ruby/2.6.0/cache/addressable-2.7.0.gem differ diff --git a/path/ruby/2.6.0/cache/archive-zip-0.12.0.gem b/path/ruby/2.6.0/cache/archive-zip-0.12.0.gem new file mode 100644 index 00000000..7e171f3d Binary files /dev/null and b/path/ruby/2.6.0/cache/archive-zip-0.12.0.gem differ diff --git a/path/ruby/2.6.0/cache/arel-9.0.0.gem b/path/ruby/2.6.0/cache/arel-9.0.0.gem new file mode 100644 index 00000000..a2c51af4 Binary files /dev/null and b/path/ruby/2.6.0/cache/arel-9.0.0.gem differ diff --git a/path/ruby/2.6.0/cache/bcrypt-3.1.13.gem b/path/ruby/2.6.0/cache/bcrypt-3.1.13.gem new file mode 100644 index 00000000..97719674 Binary files /dev/null and b/path/ruby/2.6.0/cache/bcrypt-3.1.13.gem differ diff --git a/path/ruby/2.6.0/cache/bindex-0.8.1.gem b/path/ruby/2.6.0/cache/bindex-0.8.1.gem new file mode 100644 index 00000000..ebf48db9 Binary files /dev/null and b/path/ruby/2.6.0/cache/bindex-0.8.1.gem differ diff --git a/path/ruby/2.6.0/cache/bootsnap-1.4.5.gem b/path/ruby/2.6.0/cache/bootsnap-1.4.5.gem new file mode 100644 index 00000000..c51c8189 Binary files /dev/null and b/path/ruby/2.6.0/cache/bootsnap-1.4.5.gem differ diff --git a/path/ruby/2.6.0/cache/builder-3.2.3.gem b/path/ruby/2.6.0/cache/builder-3.2.3.gem new file mode 100644 index 00000000..3500c804 Binary files /dev/null and b/path/ruby/2.6.0/cache/builder-3.2.3.gem differ diff --git a/path/ruby/2.6.0/cache/byebug-11.0.1.gem b/path/ruby/2.6.0/cache/byebug-11.0.1.gem new file mode 100644 index 00000000..c4dae2b2 Binary files /dev/null and b/path/ruby/2.6.0/cache/byebug-11.0.1.gem differ diff --git a/path/ruby/2.6.0/cache/capybara-3.29.0.gem b/path/ruby/2.6.0/cache/capybara-3.29.0.gem new file mode 100644 index 00000000..97090c48 Binary files /dev/null and b/path/ruby/2.6.0/cache/capybara-3.29.0.gem differ diff --git a/path/ruby/2.6.0/cache/childprocess-3.0.0.gem b/path/ruby/2.6.0/cache/childprocess-3.0.0.gem new file mode 100644 index 00000000..7cf28fe7 Binary files /dev/null and b/path/ruby/2.6.0/cache/childprocess-3.0.0.gem differ diff --git a/path/ruby/2.6.0/cache/chromedriver-helper-2.1.1.gem b/path/ruby/2.6.0/cache/chromedriver-helper-2.1.1.gem new file mode 100644 index 00000000..c5de7290 Binary files /dev/null and b/path/ruby/2.6.0/cache/chromedriver-helper-2.1.1.gem differ diff --git a/path/ruby/2.6.0/cache/coffee-rails-4.2.2.gem b/path/ruby/2.6.0/cache/coffee-rails-4.2.2.gem new file mode 100644 index 00000000..762e50e5 Binary files /dev/null and b/path/ruby/2.6.0/cache/coffee-rails-4.2.2.gem differ diff --git a/path/ruby/2.6.0/cache/coffee-script-2.4.1.gem b/path/ruby/2.6.0/cache/coffee-script-2.4.1.gem new file mode 100644 index 00000000..7e4066d1 Binary files /dev/null and b/path/ruby/2.6.0/cache/coffee-script-2.4.1.gem differ diff --git a/path/ruby/2.6.0/cache/coffee-script-source-1.12.2.gem b/path/ruby/2.6.0/cache/coffee-script-source-1.12.2.gem new file mode 100644 index 00000000..80fabc69 Binary files /dev/null and b/path/ruby/2.6.0/cache/coffee-script-source-1.12.2.gem differ diff --git a/path/ruby/2.6.0/cache/concurrent-ruby-1.1.5.gem b/path/ruby/2.6.0/cache/concurrent-ruby-1.1.5.gem new file mode 100644 index 00000000..ce1284af Binary files /dev/null and b/path/ruby/2.6.0/cache/concurrent-ruby-1.1.5.gem differ diff --git a/path/ruby/2.6.0/cache/crass-1.0.5.gem b/path/ruby/2.6.0/cache/crass-1.0.5.gem new file mode 100644 index 00000000..f02a4488 Binary files /dev/null and b/path/ruby/2.6.0/cache/crass-1.0.5.gem differ diff --git a/path/ruby/2.6.0/cache/devise-4.7.1.gem b/path/ruby/2.6.0/cache/devise-4.7.1.gem new file mode 100644 index 00000000..da246a68 Binary files /dev/null and b/path/ruby/2.6.0/cache/devise-4.7.1.gem differ diff --git a/path/ruby/2.6.0/cache/erubi-1.9.0.gem b/path/ruby/2.6.0/cache/erubi-1.9.0.gem new file mode 100644 index 00000000..e169e658 Binary files /dev/null and b/path/ruby/2.6.0/cache/erubi-1.9.0.gem differ diff --git a/path/ruby/2.6.0/cache/execjs-2.7.0.gem b/path/ruby/2.6.0/cache/execjs-2.7.0.gem new file mode 100644 index 00000000..1247cce4 Binary files /dev/null and b/path/ruby/2.6.0/cache/execjs-2.7.0.gem differ diff --git a/path/ruby/2.6.0/cache/ffi-1.11.1.gem b/path/ruby/2.6.0/cache/ffi-1.11.1.gem new file mode 100644 index 00000000..a1ec14a5 Binary files /dev/null and b/path/ruby/2.6.0/cache/ffi-1.11.1.gem differ diff --git a/path/ruby/2.6.0/cache/globalid-0.4.2.gem b/path/ruby/2.6.0/cache/globalid-0.4.2.gem new file mode 100644 index 00000000..8dcf8e31 Binary files /dev/null and b/path/ruby/2.6.0/cache/globalid-0.4.2.gem differ diff --git a/path/ruby/2.6.0/cache/i18n-1.7.0.gem b/path/ruby/2.6.0/cache/i18n-1.7.0.gem new file mode 100644 index 00000000..de92a6f4 Binary files /dev/null and b/path/ruby/2.6.0/cache/i18n-1.7.0.gem differ diff --git a/path/ruby/2.6.0/cache/io-like-0.3.0.gem b/path/ruby/2.6.0/cache/io-like-0.3.0.gem new file mode 100644 index 00000000..8d27fefe Binary files /dev/null and b/path/ruby/2.6.0/cache/io-like-0.3.0.gem differ diff --git a/path/ruby/2.6.0/cache/jbuilder-2.9.1.gem b/path/ruby/2.6.0/cache/jbuilder-2.9.1.gem new file mode 100644 index 00000000..4a7ca59f Binary files /dev/null and b/path/ruby/2.6.0/cache/jbuilder-2.9.1.gem differ diff --git a/path/ruby/2.6.0/cache/listen-3.1.5.gem b/path/ruby/2.6.0/cache/listen-3.1.5.gem new file mode 100644 index 00000000..3508492c Binary files /dev/null and b/path/ruby/2.6.0/cache/listen-3.1.5.gem differ diff --git a/path/ruby/2.6.0/cache/loofah-2.3.0.gem b/path/ruby/2.6.0/cache/loofah-2.3.0.gem new file mode 100644 index 00000000..8f7eee04 Binary files /dev/null and b/path/ruby/2.6.0/cache/loofah-2.3.0.gem differ diff --git a/path/ruby/2.6.0/cache/mail-2.7.1.gem b/path/ruby/2.6.0/cache/mail-2.7.1.gem new file mode 100644 index 00000000..066ef0f0 Binary files /dev/null and b/path/ruby/2.6.0/cache/mail-2.7.1.gem differ diff --git a/path/ruby/2.6.0/cache/marcel-0.3.3.gem b/path/ruby/2.6.0/cache/marcel-0.3.3.gem new file mode 100644 index 00000000..72c623f4 Binary files /dev/null and b/path/ruby/2.6.0/cache/marcel-0.3.3.gem differ diff --git a/path/ruby/2.6.0/cache/method_source-0.9.2.gem b/path/ruby/2.6.0/cache/method_source-0.9.2.gem new file mode 100644 index 00000000..c12e3423 Binary files /dev/null and b/path/ruby/2.6.0/cache/method_source-0.9.2.gem differ diff --git a/path/ruby/2.6.0/cache/mimemagic-0.3.3.gem b/path/ruby/2.6.0/cache/mimemagic-0.3.3.gem new file mode 100644 index 00000000..ad552a36 Binary files /dev/null and b/path/ruby/2.6.0/cache/mimemagic-0.3.3.gem differ diff --git a/path/ruby/2.6.0/cache/mini_mime-1.0.2.gem b/path/ruby/2.6.0/cache/mini_mime-1.0.2.gem new file mode 100644 index 00000000..974ef106 Binary files /dev/null and b/path/ruby/2.6.0/cache/mini_mime-1.0.2.gem differ diff --git a/path/ruby/2.6.0/cache/mini_portile2-2.4.0.gem b/path/ruby/2.6.0/cache/mini_portile2-2.4.0.gem new file mode 100644 index 00000000..2d3f3478 Binary files /dev/null and b/path/ruby/2.6.0/cache/mini_portile2-2.4.0.gem differ diff --git a/path/ruby/2.6.0/cache/minitest-5.12.2.gem b/path/ruby/2.6.0/cache/minitest-5.12.2.gem new file mode 100644 index 00000000..ca2c6a7c Binary files /dev/null and b/path/ruby/2.6.0/cache/minitest-5.12.2.gem differ diff --git a/path/ruby/2.6.0/cache/msgpack-1.3.1.gem b/path/ruby/2.6.0/cache/msgpack-1.3.1.gem new file mode 100644 index 00000000..a4c77540 Binary files /dev/null and b/path/ruby/2.6.0/cache/msgpack-1.3.1.gem differ diff --git a/path/ruby/2.6.0/cache/nio4r-2.5.2.gem b/path/ruby/2.6.0/cache/nio4r-2.5.2.gem new file mode 100644 index 00000000..4500941e Binary files /dev/null and b/path/ruby/2.6.0/cache/nio4r-2.5.2.gem differ diff --git a/path/ruby/2.6.0/cache/nokogiri-1.10.4.gem b/path/ruby/2.6.0/cache/nokogiri-1.10.4.gem new file mode 100644 index 00000000..369459df Binary files /dev/null and b/path/ruby/2.6.0/cache/nokogiri-1.10.4.gem differ diff --git a/path/ruby/2.6.0/cache/orm_adapter-0.5.0.gem b/path/ruby/2.6.0/cache/orm_adapter-0.5.0.gem new file mode 100644 index 00000000..fbc5b178 Binary files /dev/null and b/path/ruby/2.6.0/cache/orm_adapter-0.5.0.gem differ diff --git a/path/ruby/2.6.0/cache/pg-1.1.4.gem b/path/ruby/2.6.0/cache/pg-1.1.4.gem new file mode 100644 index 00000000..af5c62e5 Binary files /dev/null and b/path/ruby/2.6.0/cache/pg-1.1.4.gem differ diff --git a/path/ruby/2.6.0/cache/public_suffix-4.0.1.gem b/path/ruby/2.6.0/cache/public_suffix-4.0.1.gem new file mode 100644 index 00000000..aee506b6 Binary files /dev/null and b/path/ruby/2.6.0/cache/public_suffix-4.0.1.gem differ diff --git a/path/ruby/2.6.0/cache/puma-3.12.1.gem b/path/ruby/2.6.0/cache/puma-3.12.1.gem new file mode 100644 index 00000000..47d413f6 Binary files /dev/null and b/path/ruby/2.6.0/cache/puma-3.12.1.gem differ diff --git a/path/ruby/2.6.0/cache/rack-2.0.7.gem b/path/ruby/2.6.0/cache/rack-2.0.7.gem new file mode 100644 index 00000000..eb1952d1 Binary files /dev/null and b/path/ruby/2.6.0/cache/rack-2.0.7.gem differ diff --git a/path/ruby/2.6.0/cache/rack-test-1.1.0.gem b/path/ruby/2.6.0/cache/rack-test-1.1.0.gem new file mode 100644 index 00000000..3fb2d3a8 Binary files /dev/null and b/path/ruby/2.6.0/cache/rack-test-1.1.0.gem differ diff --git a/path/ruby/2.6.0/cache/rails-5.2.3.gem b/path/ruby/2.6.0/cache/rails-5.2.3.gem new file mode 100644 index 00000000..7b915743 Binary files /dev/null and b/path/ruby/2.6.0/cache/rails-5.2.3.gem differ diff --git a/path/ruby/2.6.0/cache/rails-dom-testing-2.0.3.gem b/path/ruby/2.6.0/cache/rails-dom-testing-2.0.3.gem new file mode 100644 index 00000000..f3111306 Binary files /dev/null and b/path/ruby/2.6.0/cache/rails-dom-testing-2.0.3.gem differ diff --git a/path/ruby/2.6.0/cache/rails-html-sanitizer-1.3.0.gem b/path/ruby/2.6.0/cache/rails-html-sanitizer-1.3.0.gem new file mode 100644 index 00000000..bd8e8aad Binary files /dev/null and b/path/ruby/2.6.0/cache/rails-html-sanitizer-1.3.0.gem differ diff --git a/path/ruby/2.6.0/cache/railties-5.2.3.gem b/path/ruby/2.6.0/cache/railties-5.2.3.gem new file mode 100644 index 00000000..30d320de Binary files /dev/null and b/path/ruby/2.6.0/cache/railties-5.2.3.gem differ diff --git a/path/ruby/2.6.0/cache/rake-10.5.0.gem b/path/ruby/2.6.0/cache/rake-10.5.0.gem new file mode 100644 index 00000000..0fe2757c Binary files /dev/null and b/path/ruby/2.6.0/cache/rake-10.5.0.gem differ diff --git a/path/ruby/2.6.0/cache/rake-12.3.3.gem b/path/ruby/2.6.0/cache/rake-12.3.3.gem new file mode 100644 index 00000000..e21bbafe Binary files /dev/null and b/path/ruby/2.6.0/cache/rake-12.3.3.gem differ diff --git a/path/ruby/2.6.0/cache/rake-13.0.0.gem b/path/ruby/2.6.0/cache/rake-13.0.0.gem new file mode 100644 index 00000000..7ed72f40 Binary files /dev/null and b/path/ruby/2.6.0/cache/rake-13.0.0.gem differ diff --git a/path/ruby/2.6.0/cache/rb-fsevent-0.10.3.gem b/path/ruby/2.6.0/cache/rb-fsevent-0.10.3.gem new file mode 100644 index 00000000..e8870824 Binary files /dev/null and b/path/ruby/2.6.0/cache/rb-fsevent-0.10.3.gem differ diff --git a/path/ruby/2.6.0/cache/rb-inotify-0.10.0.gem b/path/ruby/2.6.0/cache/rb-inotify-0.10.0.gem new file mode 100644 index 00000000..a5b9a85a Binary files /dev/null and b/path/ruby/2.6.0/cache/rb-inotify-0.10.0.gem differ diff --git a/path/ruby/2.6.0/cache/regexp_parser-1.6.0.gem b/path/ruby/2.6.0/cache/regexp_parser-1.6.0.gem new file mode 100644 index 00000000..f6fef74e Binary files /dev/null and b/path/ruby/2.6.0/cache/regexp_parser-1.6.0.gem differ diff --git a/path/ruby/2.6.0/cache/responders-3.0.0.gem b/path/ruby/2.6.0/cache/responders-3.0.0.gem new file mode 100644 index 00000000..fae2e738 Binary files /dev/null and b/path/ruby/2.6.0/cache/responders-3.0.0.gem differ diff --git a/path/ruby/2.6.0/cache/ruby_dep-1.5.0.gem b/path/ruby/2.6.0/cache/ruby_dep-1.5.0.gem new file mode 100644 index 00000000..04b33040 Binary files /dev/null and b/path/ruby/2.6.0/cache/ruby_dep-1.5.0.gem differ diff --git a/path/ruby/2.6.0/cache/rubyzip-2.0.0.gem b/path/ruby/2.6.0/cache/rubyzip-2.0.0.gem new file mode 100644 index 00000000..30a06509 Binary files /dev/null and b/path/ruby/2.6.0/cache/rubyzip-2.0.0.gem differ diff --git a/path/ruby/2.6.0/cache/sass-3.7.4.gem b/path/ruby/2.6.0/cache/sass-3.7.4.gem new file mode 100644 index 00000000..b48c3b85 Binary files /dev/null and b/path/ruby/2.6.0/cache/sass-3.7.4.gem differ diff --git a/path/ruby/2.6.0/cache/sass-listen-4.0.0.gem b/path/ruby/2.6.0/cache/sass-listen-4.0.0.gem new file mode 100644 index 00000000..94646539 Binary files /dev/null and b/path/ruby/2.6.0/cache/sass-listen-4.0.0.gem differ diff --git a/path/ruby/2.6.0/cache/sass-rails-5.1.0.gem b/path/ruby/2.6.0/cache/sass-rails-5.1.0.gem new file mode 100644 index 00000000..587fd393 Binary files /dev/null and b/path/ruby/2.6.0/cache/sass-rails-5.1.0.gem differ diff --git a/path/ruby/2.6.0/cache/selenium-webdriver-3.142.6.gem b/path/ruby/2.6.0/cache/selenium-webdriver-3.142.6.gem new file mode 100644 index 00000000..028121f0 Binary files /dev/null and b/path/ruby/2.6.0/cache/selenium-webdriver-3.142.6.gem differ diff --git a/path/ruby/2.6.0/cache/spring-2.1.0.gem b/path/ruby/2.6.0/cache/spring-2.1.0.gem new file mode 100644 index 00000000..76fa64fa Binary files /dev/null and b/path/ruby/2.6.0/cache/spring-2.1.0.gem differ diff --git a/path/ruby/2.6.0/cache/spring-watcher-listen-2.0.1.gem b/path/ruby/2.6.0/cache/spring-watcher-listen-2.0.1.gem new file mode 100644 index 00000000..aabf22ce Binary files /dev/null and b/path/ruby/2.6.0/cache/spring-watcher-listen-2.0.1.gem differ diff --git a/path/ruby/2.6.0/cache/sprockets-3.7.2.gem b/path/ruby/2.6.0/cache/sprockets-3.7.2.gem new file mode 100644 index 00000000..d74211a2 Binary files /dev/null and b/path/ruby/2.6.0/cache/sprockets-3.7.2.gem differ diff --git a/path/ruby/2.6.0/cache/sprockets-rails-3.2.1.gem b/path/ruby/2.6.0/cache/sprockets-rails-3.2.1.gem new file mode 100644 index 00000000..58921be3 Binary files /dev/null and b/path/ruby/2.6.0/cache/sprockets-rails-3.2.1.gem differ diff --git a/path/ruby/2.6.0/cache/thor-0.20.3.gem b/path/ruby/2.6.0/cache/thor-0.20.3.gem new file mode 100644 index 00000000..9a88db7b Binary files /dev/null and b/path/ruby/2.6.0/cache/thor-0.20.3.gem differ diff --git a/path/ruby/2.6.0/cache/thread_safe-0.3.6.gem b/path/ruby/2.6.0/cache/thread_safe-0.3.6.gem new file mode 100644 index 00000000..7ee950f8 Binary files /dev/null and b/path/ruby/2.6.0/cache/thread_safe-0.3.6.gem differ diff --git a/path/ruby/2.6.0/cache/tilt-2.0.10.gem b/path/ruby/2.6.0/cache/tilt-2.0.10.gem new file mode 100644 index 00000000..b432e159 Binary files /dev/null and b/path/ruby/2.6.0/cache/tilt-2.0.10.gem differ diff --git a/path/ruby/2.6.0/cache/turbolinks-5.2.1.gem b/path/ruby/2.6.0/cache/turbolinks-5.2.1.gem new file mode 100644 index 00000000..08cd6cc4 Binary files /dev/null and b/path/ruby/2.6.0/cache/turbolinks-5.2.1.gem differ diff --git a/path/ruby/2.6.0/cache/turbolinks-source-5.2.0.gem b/path/ruby/2.6.0/cache/turbolinks-source-5.2.0.gem new file mode 100644 index 00000000..d66bd73e Binary files /dev/null and b/path/ruby/2.6.0/cache/turbolinks-source-5.2.0.gem differ diff --git a/path/ruby/2.6.0/cache/tzinfo-1.2.5.gem b/path/ruby/2.6.0/cache/tzinfo-1.2.5.gem new file mode 100644 index 00000000..fd75e904 Binary files /dev/null and b/path/ruby/2.6.0/cache/tzinfo-1.2.5.gem differ diff --git a/path/ruby/2.6.0/cache/uglifier-4.2.0.gem b/path/ruby/2.6.0/cache/uglifier-4.2.0.gem new file mode 100644 index 00000000..9b97e3c8 Binary files /dev/null and b/path/ruby/2.6.0/cache/uglifier-4.2.0.gem differ diff --git a/path/ruby/2.6.0/cache/warden-1.2.8.gem b/path/ruby/2.6.0/cache/warden-1.2.8.gem new file mode 100644 index 00000000..549ef412 Binary files /dev/null and b/path/ruby/2.6.0/cache/warden-1.2.8.gem differ diff --git a/path/ruby/2.6.0/cache/web-console-3.7.0.gem b/path/ruby/2.6.0/cache/web-console-3.7.0.gem new file mode 100644 index 00000000..82e5bde3 Binary files /dev/null and b/path/ruby/2.6.0/cache/web-console-3.7.0.gem differ diff --git a/path/ruby/2.6.0/cache/websocket-driver-0.7.1.gem b/path/ruby/2.6.0/cache/websocket-driver-0.7.1.gem new file mode 100644 index 00000000..b32e50e9 Binary files /dev/null and b/path/ruby/2.6.0/cache/websocket-driver-0.7.1.gem differ diff --git a/path/ruby/2.6.0/cache/websocket-extensions-0.1.4.gem b/path/ruby/2.6.0/cache/websocket-extensions-0.1.4.gem new file mode 100644 index 00000000..f8ed3cb1 Binary files /dev/null and b/path/ruby/2.6.0/cache/websocket-extensions-0.1.4.gem differ diff --git a/path/ruby/2.6.0/cache/xpath-3.2.0.gem b/path/ruby/2.6.0/cache/xpath-3.2.0.gem new file mode 100644 index 00000000..734ed7d5 Binary files /dev/null and b/path/ruby/2.6.0/cache/xpath-3.2.0.gem differ diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/bcrypt-3.1.13/bcrypt_ext.bundle b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/bcrypt-3.1.13/bcrypt_ext.bundle new file mode 100755 index 00000000..0fa7010a Binary files /dev/null and b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/bcrypt-3.1.13/bcrypt_ext.bundle differ diff --git a/tutor-virtual-2/public/apple-touch-icon-precomposed.png b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/bcrypt-3.1.13/gem.build_complete similarity index 100% rename from tutor-virtual-2/public/apple-touch-icon-precomposed.png rename to path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/bcrypt-3.1.13/gem.build_complete diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/bcrypt-3.1.13/gem_make.out b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/bcrypt-3.1.13/gem_make.out new file mode 100644 index 00000000..493f2b57 --- /dev/null +++ b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/bcrypt-3.1.13/gem_make.out @@ -0,0 +1,35 @@ +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri +/Users/sergio/.rvm/rubies/ruby-2.6.3/bin/ruby -I /Users/sergio/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0 -r ./siteconf20191018-47188-33lvc5.rb extconf.rb +creating Makefile + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri +make "DESTDIR=" clean + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri +make "DESTDIR=" +compiling bcrypt_ext.c +bcrypt_ext.c:17:25: warning: implicit conversion loses integer precision: 'long' to 'int' [-Wshorten-64-to-32] + NIL_P(input) ? 0 : RSTRING_LEN(input)); + ^~~~~~~~~~~~~~~~~~ +/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/ruby.h:1000:6: note: expanded from macro 'RSTRING_LEN' + RSTRING_EMBED_LEN(str) : \ + ^~~~~~~~~~~~~~~~~~~~~~ +/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/ruby.h:996:6: note: expanded from macro 'RSTRING_EMBED_LEN' + (long)((RBASIC(str)->flags >> RSTRING_EMBED_LEN_SHIFT) & \ + ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +bcrypt_ext.c:17:25: warning: implicit conversion loses integer precision: 'long' to 'int' [-Wshorten-64-to-32] + NIL_P(input) ? 0 : RSTRING_LEN(input)); + ^~~~~~~~~~~~~~~~~~ +/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/ruby.h:1001:28: note: expanded from macro 'RSTRING_LEN' + RSTRING(str)->as.heap.len) + ~~~~~~~~~~~~~~~~~~~~~~^~~ +2 warnings generated. +compiling crypt_blowfish.c +gcc -D__SKIP_GNU -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -c -o x86.o x86.S +compiling crypt_gensalt.c +compiling wrapper.c +linking shared-object bcrypt_ext.bundle + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri +make "DESTDIR=" install +/usr/local/opt/coreutils/bin/ginstall -c -m 0755 bcrypt_ext.bundle ./.gem.20191018-47188-1cv438l diff --git a/tutor-virtual-2/public/apple-touch-icon.png b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/bindex-0.8.1/gem.build_complete similarity index 100% rename from tutor-virtual-2/public/apple-touch-icon.png rename to path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/bindex-0.8.1/gem.build_complete diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/bindex-0.8.1/gem_make.out b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/bindex-0.8.1/gem_make.out new file mode 100644 index 00000000..05a4994d --- /dev/null +++ b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/bindex-0.8.1/gem_make.out @@ -0,0 +1,15 @@ +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace +/Users/sergio/.rvm/rubies/ruby-2.6.3/bin/ruby -I /Users/sergio/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0 -r ./siteconf20191018-47188-1uo6oji.rb extconf.rb +creating Makefile + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace +make "DESTDIR=" clean + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace +make "DESTDIR=" +compiling cruby.c +linking shared-object skiptrace/internal/cruby.bundle + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace +make "DESTDIR=" install +/usr/local/opt/coreutils/bin/ginstall -c -m 0755 cruby.bundle ./.gem.20191018-47188-1f390n2/skiptrace/internal diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/bindex-0.8.1/skiptrace/internal/cruby.bundle b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/bindex-0.8.1/skiptrace/internal/cruby.bundle new file mode 100755 index 00000000..13e37355 Binary files /dev/null and b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/bindex-0.8.1/skiptrace/internal/cruby.bundle differ diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/bootsnap-1.4.5/bootsnap/bootsnap.bundle b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/bootsnap-1.4.5/bootsnap/bootsnap.bundle new file mode 100755 index 00000000..a5afd1c9 Binary files /dev/null and b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/bootsnap-1.4.5/bootsnap/bootsnap.bundle differ diff --git a/tutor-virtual-2/public/favicon.ico b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/bootsnap-1.4.5/gem.build_complete similarity index 100% rename from tutor-virtual-2/public/favicon.ico rename to path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/bootsnap-1.4.5/gem.build_complete diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/bootsnap-1.4.5/gem_make.out b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/bootsnap-1.4.5/gem_make.out new file mode 100644 index 00000000..b85f360d --- /dev/null +++ b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/bootsnap-1.4.5/gem_make.out @@ -0,0 +1,15 @@ +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/bootsnap-1.4.5/ext/bootsnap +/Users/sergio/.rvm/rubies/ruby-2.6.3/bin/ruby -I /Users/sergio/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0 -r ./siteconf20191018-47188-1k7i5sb.rb extconf.rb +creating Makefile + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/bootsnap-1.4.5/ext/bootsnap +make "DESTDIR=" clean + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/bootsnap-1.4.5/ext/bootsnap +make "DESTDIR=" +compiling bootsnap.c +linking shared-object bootsnap/bootsnap.bundle + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/bootsnap-1.4.5/ext/bootsnap +make "DESTDIR=" install +/usr/local/opt/coreutils/bin/ginstall -c -m 0755 bootsnap.bundle ./.gem.20191018-47188-19iu98h/bootsnap diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/byebug-11.0.1/byebug/byebug.bundle b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/byebug-11.0.1/byebug/byebug.bundle new file mode 100755 index 00000000..ae31c0c5 Binary files /dev/null and b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/byebug-11.0.1/byebug/byebug.bundle differ diff --git a/tutor-virtual-2/storage/.keep b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/byebug-11.0.1/gem.build_complete similarity index 100% rename from tutor-virtual-2/storage/.keep rename to path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/byebug-11.0.1/gem.build_complete diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/byebug-11.0.1/gem_make.out b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/byebug-11.0.1/gem_make.out new file mode 100644 index 00000000..683c6416 --- /dev/null +++ b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/byebug-11.0.1/gem_make.out @@ -0,0 +1,19 @@ +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug +/Users/sergio/.rvm/rubies/ruby-2.6.3/bin/ruby -I /Users/sergio/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0 -r ./siteconf20191018-47188-1pg5al7.rb extconf.rb +creating Makefile + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug +make "DESTDIR=" clean + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug +make "DESTDIR=" +compiling breakpoint.c +compiling byebug.c +compiling context.c +compiling locker.c +compiling threads.c +linking shared-object byebug/byebug.bundle + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug +make "DESTDIR=" install +/usr/local/opt/coreutils/bin/ginstall -c -m 0755 byebug.bundle ./.gem.20191018-47188-1jvhki/byebug diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/ffi-1.11.1/ffi_c.bundle b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/ffi-1.11.1/ffi_c.bundle new file mode 100755 index 00000000..569ddd49 Binary files /dev/null and b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/ffi-1.11.1/ffi_c.bundle differ diff --git a/tutor-virtual-2/test/controllers/.keep b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/ffi-1.11.1/gem.build_complete similarity index 100% rename from tutor-virtual-2/test/controllers/.keep rename to path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/ffi-1.11.1/gem.build_complete diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/ffi-1.11.1/gem_make.out b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/ffi-1.11.1/gem_make.out new file mode 100644 index 00000000..6ea14fd4 --- /dev/null +++ b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/ffi-1.11.1/gem_make.out @@ -0,0 +1,128 @@ +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c +/Users/sergio/.rvm/rubies/ruby-2.6.3/bin/ruby -I /Users/sergio/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0 -r ./siteconf20191018-47188-1xrzmfo.rb extconf.rb +checking for ffi.h... no +checking for ffi.h in /usr/local/include,/usr/include/ffi... no +checking for shlwapi.h... no +checking for rb_thread_call_without_gvl()... yes +checking for ruby_native_thread_p()... yes +checking for ruby_thread_has_gvl_p()... yes +creating extconf.h +creating Makefile + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c +make "DESTDIR=" clean + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c +make "DESTDIR=" +Configuring libffi +clang: error: unsupported option '-print-multi-os-directory' +clang: error: no input files +cd "/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi-x86_64-darwin18" && /Applications/Xcode.app/Contents/Developer/usr/bin/make +/Applications/Xcode.app/Contents/Developer/usr/bin/make 'AR_FLAGS=' 'CC_FOR_BUILD=' 'CFLAGS=-Wall -fexceptions' 'CXXFLAGS=-g -O2' 'CFLAGS_FOR_BUILD=' 'CFLAGS_FOR_TARGET=' 'INSTALL=/usr/local/bin/ginstall -c' 'INSTALL_DATA=/usr/local/bin/ginstall -c -m 644' 'INSTALL_PROGRAM=/usr/local/bin/ginstall -c' 'INSTALL_SCRIPT=/usr/local/bin/ginstall -c' 'JC1FLAGS=' 'LDFLAGS=' 'LIBCFLAGS=' 'LIBCFLAGS_FOR_TARGET=' 'MAKE=/Applications/Xcode.app/Contents/Developer/usr/bin/make' 'MAKEINFO=/bin/sh /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/missing makeinfo ' 'PICFLAG=' 'PICFLAG_FOR_TARGET=' 'RUNTESTFLAGS=' 'SHELL=/bin/sh' 'exec_prefix=/usr/local' 'infodir=/usr/local/share/info' 'libdir=/usr/local/lib' 'mandir=/usr/local/share/man' 'prefix=/usr/local' 'AR=ar' 'AS=as' 'CC=gcc' 'CXX=g++' 'LD=ld' 'NM=/usr/bin/nm -B' 'RANLIB=ranlib' 'DESTDIR=' all-recursive +Making all in include +make[3]: Nothing to be done for `all'. +Making all in testsuite +make[3]: Nothing to be done for `all'. +Making all in man +make[3]: Nothing to be done for `all'. +/bin/sh ./libtool --tag=CC --mode=compile gcc -DHAVE_CONFIG_H -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/include -Iinclude -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src -Wall -fexceptions -c -o src/prep_cif.lo /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src/prep_cif.c +libtool: compile: gcc -DHAVE_CONFIG_H -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/include -Iinclude -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src -Wall -fexceptions -c /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src/prep_cif.c -fno-common -DPIC -o src/.libs/prep_cif.o +/bin/sh ./libtool --tag=CC --mode=compile gcc -DHAVE_CONFIG_H -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/include -Iinclude -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src -Wall -fexceptions -c -o src/types.lo /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src/types.c +libtool: compile: gcc -DHAVE_CONFIG_H -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/include -Iinclude -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src -Wall -fexceptions -c /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src/types.c -fno-common -DPIC -o src/.libs/types.o +/bin/sh ./libtool --tag=CC --mode=compile gcc -DHAVE_CONFIG_H -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/include -Iinclude -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src -Wall -fexceptions -c -o src/raw_api.lo /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src/raw_api.c +libtool: compile: gcc -DHAVE_CONFIG_H -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/include -Iinclude -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src -Wall -fexceptions -c /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src/raw_api.c -fno-common -DPIC -o src/.libs/raw_api.o +/bin/sh ./libtool --tag=CC --mode=compile gcc -DHAVE_CONFIG_H -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/include -Iinclude -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src -Wall -fexceptions -c -o src/java_raw_api.lo /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src/java_raw_api.c +libtool: compile: gcc -DHAVE_CONFIG_H -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/include -Iinclude -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src -Wall -fexceptions -c /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src/java_raw_api.c -fno-common -DPIC -o src/.libs/java_raw_api.o +/bin/sh ./libtool --tag=CC --mode=compile gcc -DHAVE_CONFIG_H -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/include -Iinclude -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src -Wall -fexceptions -c -o src/closures.lo /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src/closures.c +libtool: compile: gcc -DHAVE_CONFIG_H -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/include -Iinclude -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src -Wall -fexceptions -c /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src/closures.c -fno-common -DPIC -o src/.libs/closures.o +/bin/sh ./libtool --tag=CC --mode=compile gcc -DHAVE_CONFIG_H -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/include -Iinclude -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src -Wall -fexceptions -c -o src/x86/ffi64.lo /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src/x86/ffi64.c +libtool: compile: gcc -DHAVE_CONFIG_H -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/include -Iinclude -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src -Wall -fexceptions -c /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src/x86/ffi64.c -fno-common -DPIC -o src/x86/.libs/ffi64.o +/bin/sh ./libtool --mode=compile gcc -DHAVE_CONFIG_H -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/include -Iinclude -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/include -Iinclude -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src -c -o src/x86/unix64.lo /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src/x86/unix64.S +libtool: compile: gcc -DHAVE_CONFIG_H -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/include -Iinclude -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/include -Iinclude -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src -c /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src/x86/unix64.S -fno-common -DPIC -o src/x86/.libs/unix64.o +/bin/sh ./libtool --tag=CC --mode=compile gcc -DHAVE_CONFIG_H -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/include -Iinclude -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src -Wall -fexceptions -c -o src/x86/ffiw64.lo /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src/x86/ffiw64.c +libtool: compile: gcc -DHAVE_CONFIG_H -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/include -Iinclude -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src -Wall -fexceptions -c /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src/x86/ffiw64.c -fno-common -DPIC -o src/x86/.libs/ffiw64.o +/bin/sh ./libtool --mode=compile gcc -DHAVE_CONFIG_H -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/include -Iinclude -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/include -Iinclude -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src -c -o src/x86/win64.lo /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src/x86/win64.S +libtool: compile: gcc -DHAVE_CONFIG_H -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/include -Iinclude -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/include -Iinclude -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src -c /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/src/x86/win64.S -fno-common -DPIC -o src/x86/.libs/win64.o +/bin/sh ./libtool --tag=CC --mode=link gcc -Wall -fexceptions -o libffi_convenience.la src/prep_cif.lo src/types.lo src/raw_api.lo src/java_raw_api.lo src/closures.lo src/x86/ffi64.lo src/x86/unix64.lo src/x86/ffiw64.lo src/x86/win64.lo +libtool: link: ar cru .libs/libffi_convenience.a src/.libs/prep_cif.o src/.libs/types.o src/.libs/raw_api.o src/.libs/java_raw_api.o src/.libs/closures.o src/x86/.libs/ffi64.o src/x86/.libs/unix64.o src/x86/.libs/ffiw64.o src/x86/.libs/win64.o +libtool: link: ranlib .libs/libffi_convenience.a +libtool: link: ( cd ".libs" && rm -f "libffi_convenience.la" && ln -s "../libffi_convenience.la" "libffi_convenience.la" ) +/bin/sh ./libtool --tag=CC --mode=link gcc -Wall -fexceptions -no-undefined -version-info `grep -v '^#' /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/libtool-version` -o libffi.la -rpath /usr/local/lib src/prep_cif.lo src/types.lo src/raw_api.lo src/java_raw_api.lo src/closures.lo src/x86/ffi64.lo src/x86/unix64.lo src/x86/ffiw64.lo src/x86/win64.lo +libtool: link: gcc -dynamiclib -o .libs/libffi.7.dylib src/.libs/prep_cif.o src/.libs/types.o src/.libs/raw_api.o src/.libs/java_raw_api.o src/.libs/closures.o src/x86/.libs/ffi64.o src/x86/.libs/unix64.o src/x86/.libs/ffiw64.o src/x86/.libs/win64.o -install_name /usr/local/lib/libffi.7.dylib -compatibility_version 9 -current_version 9.0 -Wl,-single_module +libtool: link: (cd ".libs" && rm -f "libffi.dylib" && ln -s "libffi.7.dylib" "libffi.dylib") +libtool: link: ( cd ".libs" && rm -f "libffi.la" && ln -s "../libffi.la" "libffi.la" ) +compiling AbstractMemory.c +compiling ArrayType.c +compiling Buffer.c +compiling Call.c +compiling ClosurePool.c +compiling DynamicLibrary.c +compiling Function.c +Function.c:867:17: warning: 'ffi_prep_closure' is deprecated [-Wdeprecated-declarations] + ffiStatus = ffi_prep_closure(code, &fnInfo->ffi_cif, callback_invoke, closure); + ^ +/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi-x86_64-darwin18/include/ffi.h:339:18: note: 'ffi_prep_closure' has been explicitly marked deprecated here + __attribute__((deprecated)) + ^ +1 warning generated. +compiling FunctionInfo.c +compiling LastError.c +compiling LongDouble.c +compiling MappedType.c +compiling MemoryPointer.c +compiling MethodHandle.c +compiling Platform.c +compiling Pointer.c +compiling Struct.c +compiling StructByValue.c +compiling StructLayout.c +compiling Thread.c +compiling Type.c +compiling Types.c +compiling Variadic.c +compiling ffi.c +linking shared-object ffi_c.bundle + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c +make "DESTDIR=" install +cd "/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi-x86_64-darwin18" && /Applications/Xcode.app/Contents/Developer/usr/bin/make +/Applications/Xcode.app/Contents/Developer/usr/bin/make 'AR_FLAGS=' 'CC_FOR_BUILD=' 'CFLAGS=-Wall -fexceptions' 'CXXFLAGS=-g -O2' 'CFLAGS_FOR_BUILD=' 'CFLAGS_FOR_TARGET=' 'INSTALL=/usr/local/bin/ginstall -c' 'INSTALL_DATA=/usr/local/bin/ginstall -c -m 644' 'INSTALL_PROGRAM=/usr/local/bin/ginstall -c' 'INSTALL_SCRIPT=/usr/local/bin/ginstall -c' 'JC1FLAGS=' 'LDFLAGS=' 'LIBCFLAGS=' 'LIBCFLAGS_FOR_TARGET=' 'MAKE=/Applications/Xcode.app/Contents/Developer/usr/bin/make' 'MAKEINFO=/bin/sh /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi/missing makeinfo ' 'PICFLAG=' 'PICFLAG_FOR_TARGET=' 'RUNTESTFLAGS=' 'SHELL=/bin/sh' 'exec_prefix=/usr/local' 'infodir=/usr/local/share/info' 'libdir=/usr/local/lib' 'mandir=/usr/local/share/man' 'prefix=/usr/local' 'AR=ar' 'AS=as' 'CC=gcc' 'CXX=g++' 'LD=ld' 'NM=/usr/bin/nm -B' 'RANLIB=ranlib' 'DESTDIR=' all-recursive +Making all in include +make[3]: Nothing to be done for `all'. +Making all in testsuite +make[3]: Nothing to be done for `all'. +Making all in man +make[3]: Nothing to be done for `all'. +make[3]: Nothing to be done for `all-am'. +compiling AbstractMemory.c +compiling ArrayType.c +compiling Buffer.c +compiling Call.c +compiling ClosurePool.c +compiling DynamicLibrary.c +compiling Function.c +Function.c:867:17: warning: 'ffi_prep_closure' is deprecated [-Wdeprecated-declarations] + ffiStatus = ffi_prep_closure(code, &fnInfo->ffi_cif, callback_invoke, closure); + ^ +/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/ffi-1.11.1/ext/ffi_c/libffi-x86_64-darwin18/include/ffi.h:339:18: note: 'ffi_prep_closure' has been explicitly marked deprecated here + __attribute__((deprecated)) + ^ +1 warning generated. +compiling FunctionInfo.c +compiling LastError.c +compiling LongDouble.c +compiling MappedType.c +compiling MemoryPointer.c +compiling MethodHandle.c +compiling Platform.c +compiling Pointer.c +compiling Struct.c +compiling StructByValue.c +compiling StructLayout.c +compiling Thread.c +compiling Type.c +compiling Types.c +compiling Variadic.c +compiling ffi.c +linking shared-object ffi_c.bundle +/usr/local/opt/coreutils/bin/ginstall -c -m 0755 ffi_c.bundle ./.gem.20191018-47188-1jreps2 diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/ffi-1.11.1/mkmf.log b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/ffi-1.11.1/mkmf.log new file mode 100644 index 00000000..090ad50e --- /dev/null +++ b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/ffi-1.11.1/mkmf.log @@ -0,0 +1,215 @@ +"pkg-config --exists libffi" +package configuration for libffi is not found +have_header: checking for ffi.h... -------------------- no + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -lruby.2.6 " +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main(int argc, char **argv) +4: { +5: return 0; +6: } +/* end */ + +"gcc -E -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -o conftest.i" +conftest.c:3:10: fatal error: 'ffi.h' file not found +#include + ^~~~~~~ +1 error generated. +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +find_header: checking for ffi.h in /usr/local/include,/usr/include/ffi... -------------------- no + +"gcc -E -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -o conftest.i" +conftest.c:3:10: fatal error: 'ffi.h' file not found +#include + ^~~~~~~ +1 error generated. +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +"gcc -E -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I/usr/local/include conftest.c -o conftest.i" +conftest.c:3:10: fatal error: 'ffi.h' file not found +#include + ^~~~~~~ +1 error generated. +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +"gcc -E -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I/usr/include/ffi conftest.c -o conftest.i" +conftest.c:3:10: fatal error: 'ffi.h' file not found +#include + ^~~~~~~ +1 error generated. +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +have_header: checking for shlwapi.h... -------------------- no + +"gcc -E -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -o conftest.i" +conftest.c:3:10: fatal error: 'shlwapi.h' file not found +#include + ^~~~~~~~~~~ +1 error generated. +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +have_func: checking for rb_thread_call_without_gvl()... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -lruby.2.6 " +conftest.c:14:57: error: use of undeclared identifier 'rb_thread_call_without_gvl' +int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_thread_call_without_gvl; return !p; } + ^ +1 error generated. +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_thread_call_without_gvl; return !p; } +/* end */ + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -lruby.2.6 " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: extern void rb_thread_call_without_gvl(); +15: int t(void) { rb_thread_call_without_gvl(); return 0; } +/* end */ + +-------------------- + +have_func: checking for ruby_native_thread_p()... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -lruby.2.6 " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))ruby_native_thread_p; return !p; } +/* end */ + +-------------------- + +have_func: checking for ruby_thread_has_gvl_p()... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -lruby.2.6 " +conftest.c:14:57: error: use of undeclared identifier 'ruby_thread_has_gvl_p' +int t(void) { void ((*volatile p)()); p = (void ((*)()))ruby_thread_has_gvl_p; return !p; } + ^ +1 error generated. +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))ruby_thread_has_gvl_p; return !p; } +/* end */ + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -lruby.2.6 " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: extern void ruby_thread_has_gvl_p(); +15: int t(void) { ruby_thread_has_gvl_p(); return 0; } +/* end */ + +-------------------- + +extconf.h is: +/* begin */ +1: #ifndef EXTCONF_H +2: #define EXTCONF_H +3: #define HAVE_RB_THREAD_CALL_WITHOUT_GVL 1 +4: #define HAVE_RUBY_NATIVE_THREAD_P 1 +5: #define HAVE_RUBY_THREAD_HAS_GVL_P 1 +6: #define HAVE_FFI_PREP_CIF_VAR 1 +7: #define USE_INTERNAL_LIBFFI 1 +8: #define RUBY_1_9 1 +9: #endif +/* end */ + diff --git a/tutor-virtual-2/test/fixtures/.keep b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/msgpack-1.3.1/gem.build_complete similarity index 100% rename from tutor-virtual-2/test/fixtures/.keep rename to path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/msgpack-1.3.1/gem.build_complete diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/msgpack-1.3.1/gem_make.out b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/msgpack-1.3.1/gem_make.out new file mode 100644 index 00000000..2f6f0456 --- /dev/null +++ b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/msgpack-1.3.1/gem_make.out @@ -0,0 +1,42 @@ +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/msgpack-1.3.1/ext/msgpack +/Users/sergio/.rvm/rubies/ruby-2.6.3/bin/ruby -I /Users/sergio/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0 -r ./siteconf20191018-47188-1hit4ag.rb extconf.rb +checking for ruby/st.h... yes +checking for st.h... yes +checking for rb_str_replace() in ruby.h... yes +checking for rb_intern_str() in ruby.h... yes +checking for rb_sym2str() in ruby.h... yes +checking for rb_str_intern() in ruby.h... yes +checking for rb_block_lambda() in ruby.h... yes +checking for rb_hash_dup() in ruby.h... yes +checking for rb_hash_clear() in ruby.h... yes +creating Makefile + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/msgpack-1.3.1/ext/msgpack +make "DESTDIR=" clean + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/msgpack-1.3.1/ext/msgpack +make "DESTDIR=" +compiling buffer.c +compiling buffer_class.c +compiling extension_value_class.c +compiling factory_class.c +compiling packer.c +compiling packer_class.c +compiling packer_ext_registry.c +compiling rbinit.c +compiling rmem.c +compiling unpacker.c +compiling unpacker_class.c +unpacker_class.c:131:1: warning: function 'raise_unpacker_error' could be declared with attribute 'noreturn' [-Wmissing-noreturn] +{ +^ +unpacker_class.c:219:14: warning: unused function 'Unpacker_peek_next_type' [-Wunused-function] +static VALUE Unpacker_peek_next_type(VALUE self) + ^ +2 warnings generated. +compiling unpacker_ext_registry.c +linking shared-object msgpack/msgpack.bundle + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/msgpack-1.3.1/ext/msgpack +make "DESTDIR=" install +/usr/local/opt/coreutils/bin/ginstall -c -m 0755 msgpack.bundle ./.gem.20191018-47188-1ft8ahw/msgpack diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/msgpack-1.3.1/mkmf.log b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/msgpack-1.3.1/mkmf.log new file mode 100644 index 00000000..18203839 --- /dev/null +++ b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/msgpack-1.3.1/mkmf.log @@ -0,0 +1,215 @@ +have_header: checking for ruby/st.h... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -lruby.2.6 " +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main(int argc, char **argv) +4: { +5: return 0; +6: } +/* end */ + +"gcc -E -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -o conftest.i" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +have_header: checking for st.h... -------------------- yes + +"gcc -E -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -o conftest.i" +In file included from conftest.c:3: +/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward/st.h:2:2: warning: use "ruby/st.h" instead of bare "st.h" [-W#warnings] +#warning use "ruby/st.h" instead of bare "st.h" + ^ +1 warning generated. +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +have_func: checking for rb_str_replace() in ruby.h... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -lruby.2.6 " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return 0; +15: } +16: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_str_replace; return !p; } +/* end */ + +-------------------- + +have_func: checking for rb_intern_str() in ruby.h... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -lruby.2.6 " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return 0; +15: } +16: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_intern_str; return !p; } +/* end */ + +-------------------- + +have_func: checking for rb_sym2str() in ruby.h... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -lruby.2.6 " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return 0; +15: } +16: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_sym2str; return !p; } +/* end */ + +-------------------- + +have_func: checking for rb_str_intern() in ruby.h... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -lruby.2.6 " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return 0; +15: } +16: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_str_intern; return !p; } +/* end */ + +-------------------- + +have_func: checking for rb_block_lambda() in ruby.h... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -lruby.2.6 " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return 0; +15: } +16: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_block_lambda; return !p; } +/* end */ + +-------------------- + +have_func: checking for rb_hash_dup() in ruby.h... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -lruby.2.6 " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return 0; +15: } +16: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_hash_dup; return !p; } +/* end */ + +-------------------- + +have_func: checking for rb_hash_clear() in ruby.h... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -lruby.2.6 " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return 0; +15: } +16: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_hash_clear; return !p; } +/* end */ + +-------------------- + diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/msgpack-1.3.1/msgpack/msgpack.bundle b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/msgpack-1.3.1/msgpack/msgpack.bundle new file mode 100755 index 00000000..b431e7e4 Binary files /dev/null and b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/msgpack-1.3.1/msgpack/msgpack.bundle differ diff --git a/tutor-virtual-2/test/fixtures/files/.keep b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/nio4r-2.5.2/gem.build_complete similarity index 100% rename from tutor-virtual-2/test/fixtures/files/.keep rename to path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/nio4r-2.5.2/gem.build_complete diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/nio4r-2.5.2/gem_make.out b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/nio4r-2.5.2/gem_make.out new file mode 100644 index 00000000..57eea2c2 --- /dev/null +++ b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/nio4r-2.5.2/gem_make.out @@ -0,0 +1,95 @@ +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nio4r-2.5.2/ext/nio4r +/Users/sergio/.rvm/rubies/ruby-2.6.3/bin/ruby -I /Users/sergio/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0 -r ./siteconf20191018-47188-1mhhrxm.rb extconf.rb +checking for unistd.h... yes +checking for linux/aio_abi.h... no +checking for sys/select.h... yes +checking for port_event_t in poll.h... no +checking for sys/epoll.h... no +checking for sys/event.h... yes +checking for sys/queue.h... yes +checking for port_event_t in port.h... no +checking for sys/resource.h... yes +creating Makefile + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nio4r-2.5.2/ext/nio4r +make "DESTDIR=" clean + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nio4r-2.5.2/ext/nio4r +make "DESTDIR=" +compiling bytebuffer.c +compiling monitor.c +monitor.c:182:40: warning: implicit conversion loses integer precision: 'VALUE' (aka 'unsigned long') to 'int' [-Wshorten-64-to-32] + NIO_Monitor_update_interests(self, interest); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^~~~~~~~ +monitor.c:192:40: warning: implicit conversion loses integer precision: 'VALUE' (aka 'unsigned long') to 'int' [-Wshorten-64-to-32] + NIO_Monitor_update_interests(self, interest); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^~~~~~~~ +2 warnings generated. +compiling nio4r_ext.c +In file included from nio4r_ext.c:7: +./../libev/ev.c:511:48: warning: '/*' within block comment [-Wcomment] +/*#define MIN_INTERVAL 0.00000095367431640625 /* 1/2**20, good till 2200 */ + ^ +./../libev/ev.c:1096:26: warning: implicit conversion loses integer precision: 'uint64_t' (aka 'unsigned long long') to 'unsigned int' [-Wshorten-64-to-32] + return ecb_popcount32 (x) + ecb_popcount32 (x >> 32); + ~~~~~~~~~~~~~~~~^~ +./../libev/ev.c:958:49: note: expanded from macro 'ecb_popcount32' + #define ecb_popcount32(x) __builtin_popcount (x) + ~~~~~~~~~~~~~~~~~~ ^ +./../libev/ev.c:1302:13: warning: comparison of integers of different signs: 'unsigned int' and 'int' [-Wsign-compare] + if (e < (14 - 24)) /* might not be sharp, but is good enough */ + ~ ^ ~~~~~~~ +./../libev/ev.c:1867:31: warning: 'extern' variable has an initializer [-Wextern-initializer] + EV_API_DECL struct ev_loop *ev_default_loop_ptr = 0; /* needs to be initialised to make it a definition despite extern */ + ^ +./../libev/ev.c:1956:7: warning: implicit conversion loses integer precision: 'long' to '__darwin_suseconds_t' (aka 'int') [-Wshorten-64-to-32] + EV_TV_SET (tv, delay); + ^~~~~~~~~~~~~~~~~~~~~ +./../libev/ev.c:516:64: note: expanded from macro 'EV_TV_SET' +#define EV_TV_SET(tv,t) do { tv.tv_sec = (long)t; tv.tv_usec = (long)((t - tv.tv_sec) * 1e6); } while (0) + ~ ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +./../libev/ev.c:1978:19: warning: comparison of integers of different signs: 'int' and 'unsigned long' [-Wsign-compare] + if (elem * ncur > MALLOC_ROUND - sizeof (void *) * 4) + ~~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +./../libev/ev.c:2329:18: warning: implicit conversion loses integer precision: 'long' to 'int' [-Wshorten-64-to-32] + k = minpos - heap; + ~ ~~~~~~~^~~~~~ +In file included from nio4r_ext.c:7: +In file included from ./../libev/ev.c:2739: +./../libev/ev_kqueue.c:116:34: warning: implicit conversion loses integer precision: 'uintptr_t' (aka 'unsigned long') to 'int' [-Wshorten-64-to-32] + int fd = kqueue_events [i].ident; + ~~ ~~~~~~~~~~~~~~~~~~^~~~~ +./../libev/ev_kqueue.c:120:39: warning: implicit conversion loses integer precision: 'intptr_t' (aka 'long') to 'int' [-Wshorten-64-to-32] + int err = kqueue_events [i].data; + ~~~ ~~~~~~~~~~~~~~~~~~^~~~ +In file included from nio4r_ext.c:7: +In file included from ./../libev/ev.c:2751: +./../libev/ev_select.c:109:24: warning: implicit conversion loses integer precision: 'unsigned long' to 'fd_mask' (aka 'int') [-Wshorten-64-to-32] + fd_mask mask = 1UL << (fd % NFDBITS); + ~~~~ ~~~~^~~~~~~~~~~~~~~~~ +./../libev/ev_select.c:147:3: warning: implicit conversion loses integer precision: 'long' to '__darwin_suseconds_t' (aka 'int') [-Wshorten-64-to-32] + EV_TV_SET (tv, timeout); + ^~~~~~~~~~~~~~~~~~~~~~~ +./../libev/ev.c:516:64: note: expanded from macro 'EV_TV_SET' +#define EV_TV_SET(tv,t) do { tv.tv_sec = (long)t; tv.tv_usec = (long)((t - tv.tv_sec) * 1e6); } while (0) + ~ ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +In file included from nio4r_ext.c:7: +In file included from ./../libev/ev.c:2751: +./../libev/ev_select.c:259:34: warning: implicit conversion loses integer precision: 'unsigned long' to 'fd_mask' (aka 'int') [-Wshorten-64-to-32] + fd_mask mask = 1UL << bit; + ~~~~ ~~~~^~~~~~ +In file included from nio4r_ext.c:7: +./../libev/ev.c:4014:34: warning: '&' within '|' [-Wbitwise-op-parentheses] + fd_change (EV_A_ fd, w->events & EV__IOFDSET | EV_ANFD_REIFY); + ~~~~~~~~~~^~~~~~~~~~~~~ ~ +./../libev/ev.c:4014:34: note: place parentheses around the '&' expression to silence this warning + fd_change (EV_A_ fd, w->events & EV__IOFDSET | EV_ANFD_REIFY); + ^ + ( ) +13 warnings generated. +compiling selector.c +linking shared-object nio4r_ext.bundle + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nio4r-2.5.2/ext/nio4r +make "DESTDIR=" install +/usr/local/opt/coreutils/bin/ginstall -c -m 0755 nio4r_ext.bundle ./.gem.20191018-47188-1qeypjl diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/nio4r-2.5.2/mkmf.log b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/nio4r-2.5.2/mkmf.log new file mode 100644 index 00000000..670c8cfd --- /dev/null +++ b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/nio4r-2.5.2/mkmf.log @@ -0,0 +1,143 @@ +have_header: checking for unistd.h... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -lruby.2.6 " +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main(int argc, char **argv) +4: { +5: return 0; +6: } +/* end */ + +"gcc -E -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -o conftest.i" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +have_header: checking for linux/aio_abi.h... -------------------- no + +"gcc -E -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -o conftest.i" +conftest.c:3:10: fatal error: 'linux/aio_abi.h' file not found +#include + ^~~~~~~~~~~~~~~~~ +1 error generated. +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +have_header: checking for sys/select.h... -------------------- yes + +"gcc -E -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -o conftest.i" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +have_type: checking for port_event_t in poll.h... -------------------- no + +"gcc -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -c conftest.c" +conftest.c:6:9: error: unknown type name 'port_event_t' +typedef port_event_t conftest_type; + ^ +1 error generated. +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +4: +5: /*top*/ +6: typedef port_event_t conftest_type; +7: int conftestval[sizeof(conftest_type)?1:-1]; +/* end */ + +-------------------- + +have_header: checking for sys/epoll.h... -------------------- no + +"gcc -E -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -o conftest.i" +conftest.c:3:10: fatal error: 'sys/epoll.h' file not found +#include + ^~~~~~~~~~~~~ +1 error generated. +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +have_header: checking for sys/event.h... -------------------- yes + +"gcc -E -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -o conftest.i" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +have_header: checking for sys/queue.h... -------------------- yes + +"gcc -E -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -o conftest.i" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +have_type: checking for port_event_t in port.h... -------------------- no + +"gcc -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -c conftest.c" +conftest.c:3:10: fatal error: 'port.h' file not found +#include + ^~~~~~~~ +1 error generated. +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +4: +5: /*top*/ +6: typedef port_event_t conftest_type; +7: int conftestval[sizeof(conftest_type)?1:-1]; +/* end */ + +-------------------- + +have_header: checking for sys/resource.h... -------------------- yes + +"gcc -E -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -o conftest.i" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/nio4r-2.5.2/nio4r_ext.bundle b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/nio4r-2.5.2/nio4r_ext.bundle new file mode 100755 index 00000000..153731e3 Binary files /dev/null and b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/nio4r-2.5.2/nio4r_ext.bundle differ diff --git a/tutor-virtual-2/test/helpers/.keep b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/nokogiri-1.10.4/gem.build_complete similarity index 100% rename from tutor-virtual-2/test/helpers/.keep rename to path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/nokogiri-1.10.4/gem.build_complete diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/nokogiri-1.10.4/gem_make.out b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/nokogiri-1.10.4/gem_make.out new file mode 100644 index 00000000..665236d2 --- /dev/null +++ b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/nokogiri-1.10.4/gem_make.out @@ -0,0 +1,222 @@ +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ext/nokogiri +/Users/sergio/.rvm/rubies/ruby-2.6.3/bin/ruby -I /Users/sergio/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0 -r ./siteconf20191018-47188-1swihp3.rb extconf.rb +checking if the C compiler accepts -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2... yes +checking if the C compiler accepts -Wno-error=unused-command-line-argument-hard-error-in-future... no +Building nokogiri using packaged libraries. +Using mini_portile version 2.4.0 +checking for iconv.h... yes +checking for gzdopen() in -lz... yes +checking for iconv using --with-opt-* flags... yes +************************************************************************ +IMPORTANT NOTICE: + +Building Nokogiri with a packaged version of libxml2-2.9.9 +with the following patches applied: + - 0001-Revert-Do-not-URI-escape-in-server-side-includes.patch + - 0002-Remove-script-macro-support.patch + - 0003-Update-entities-to-remove-handling-of-ssi.patch + +Team Nokogiri will keep on doing their best to provide security +updates in a timely manner, but if this is a concern for you and want +to use the system library instead; abort this installation process and +reinstall nokogiri as follows: + + gem install nokogiri -- --use-system-libraries + [--with-xml2-config=/path/to/xml2-config] + [--with-xslt-config=/path/to/xslt-config] + +If you are using Bundler, tell it to use the option: + + bundle config build.nokogiri --use-system-libraries + bundle install + +Note, however, that nokogiri is not fully compatible with arbitrary +versions of libxml2 provided by OS/package vendors. +************************************************************************ +Extracting libxml2-2.9.9.tar.gz into tmp/x86_64-apple-darwin18.5.0/ports/libxml2/2.9.9... OK +Running git apply with /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/patches/libxml2/0001-Revert-Do-not-URI-escape-in-server-side-includes.patch... OK +Running git apply with /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/patches/libxml2/0002-Remove-script-macro-support.patch... OK +Running git apply with /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/patches/libxml2/0003-Update-entities-to-remove-handling-of-ssi.patch... OK +Running 'configure' for libxml2 2.9.9... OK +Running 'compile' for libxml2 2.9.9... OK +Running 'install' for libxml2 2.9.9... OK +Activating libxml2 2.9.9 (from /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9)... +************************************************************************ +IMPORTANT NOTICE: + +Building Nokogiri with a packaged version of libxslt-1.1.33 +with the following patches applied: + - 0001-Fix-security-framework-bypass.patch + +Team Nokogiri will keep on doing their best to provide security +updates in a timely manner, but if this is a concern for you and want +to use the system library instead; abort this installation process and +reinstall nokogiri as follows: + + gem install nokogiri -- --use-system-libraries + [--with-xml2-config=/path/to/xml2-config] + [--with-xslt-config=/path/to/xslt-config] + +If you are using Bundler, tell it to use the option: + + bundle config build.nokogiri --use-system-libraries + bundle install +************************************************************************ +Extracting libxslt-1.1.33.tar.gz into tmp/x86_64-apple-darwin18.5.0/ports/libxslt/1.1.33... OK +Running git apply with /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/patches/libxslt/0001-Fix-security-framework-bypass.patch... OK +Running 'configure' for libxslt 1.1.33... OK +Running 'compile' for libxslt 1.1.33... OK +Running 'install' for libxslt 1.1.33... OK +Activating libxslt 1.1.33 (from /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33)... +checking for -llzma... yes +checking for xmlParseDoc() in libxml/parser.h... yes +checking for xsltParseStylesheetDoc() in libxslt/xslt.h... yes +checking for exsltFuncRegister() in libexslt/exslt.h... yes +checking for xmlHasFeature()... yes +checking for xmlFirstElementChild()... yes +checking for xmlRelaxNGSetParserStructuredErrors()... yes +checking for xmlRelaxNGSetParserStructuredErrors()... yes +checking for xmlRelaxNGSetValidStructuredErrors()... yes +checking for xmlSchemaSetValidStructuredErrors()... yes +checking for xmlSchemaSetParserStructuredErrors()... yes +creating Makefile + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ext/nokogiri +make "DESTDIR=" clean + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ext/nokogiri +make "DESTDIR=" +compiling html_document.c +html_document.c:16:28: warning: cast from 'const char *' to 'char *' drops const qualifier [-Wcast-qual] + rb_scan_args(argc, argv, "0*", &rest); + ^ +1 warning generated. +compiling html_element_description.c +compiling html_entity_lookup.c +compiling html_sax_parser_context.c +compiling html_sax_push_parser.c +compiling nokogiri.c +compiling xml_attr.c +xml_attr.c:61:28: warning: cast from 'const char *' to 'char *' drops const qualifier [-Wcast-qual] + rb_scan_args(argc, argv, "2*", &document, &name, &rest); + ^ +1 warning generated. +compiling xml_attribute_decl.c +compiling xml_cdata.c +xml_cdata.c:23:28: warning: cast from 'const char *' to 'char *' drops const qualifier [-Wcast-qual] + rb_scan_args(argc, argv, "2*", &doc, &content, &rest); + ^ +xml_cdata.c:29:23: warning: implicit conversion loses integer precision: 'long' to 'int' [-Wshorten-64-to-32] + content_str_len = RSTRING_LEN(content); + ~ ^~~~~~~~~~~~~~~~~~~~ +/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/ruby.h:1000:6: note: expanded from macro 'RSTRING_LEN' + RSTRING_EMBED_LEN(str) : \ + ^~~~~~~~~~~~~~~~~~~~~~ +/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/ruby.h:996:6: note: expanded from macro 'RSTRING_EMBED_LEN' + (long)((RBASIC(str)->flags >> RSTRING_EMBED_LEN_SHIFT) & \ + ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +xml_cdata.c:29:23: warning: implicit conversion loses integer precision: 'long' to 'int' [-Wshorten-64-to-32] + content_str_len = RSTRING_LEN(content); + ~ ^~~~~~~~~~~~~~~~~~~~ +/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/ruby.h:1001:28: note: expanded from macro 'RSTRING_LEN' + RSTRING(str)->as.heap.len) + ~~~~~~~~~~~~~~~~~~~~~~^~~ +3 warnings generated. +compiling xml_comment.c +xml_comment.c:21:28: warning: cast from 'const char *' to 'char *' drops const qualifier [-Wcast-qual] + rb_scan_args(argc, argv, "2*", &document, &content, &rest); + ^ +1 warning generated. +compiling xml_document.c +xml_document.c:330:31: warning: cast from 'const char *' to 'char *' drops const qualifier [-Wcast-qual] + if(rb_scan_args(argc, argv, "01", &level) == 0) + ^ +xml_document.c:357:28: warning: cast from 'const char *' to 'char *' drops const qualifier [-Wcast-qual] + rb_scan_args(argc, argv, "0*", &rest); + ^ +xml_document.c:436:28: warning: cast from 'const char *' to 'char *' drops const qualifier [-Wcast-qual] + rb_scan_args(argc, argv, "14", &name, &type, &external_id, &system_id, + ^ +xml_document.c:511:28: warning: cast from 'const char *' to 'char *' drops const qualifier [-Wcast-qual] + rb_scan_args(argc, argv, "03", &mode, &incl_ns, &with_comments); + ^ +4 warnings generated. +compiling xml_document_fragment.c +xml_document_fragment.c:17:28: warning: cast from 'const char *' to 'char *' drops const qualifier [-Wcast-qual] + rb_scan_args(argc, argv, "1*", &document, &rest); + ^ +1 warning generated. +compiling xml_dtd.c +compiling xml_element_content.c +compiling xml_element_decl.c +compiling xml_encoding_handler.c +compiling xml_entity_decl.c +compiling xml_entity_reference.c +xml_entity_reference.c:18:28: warning: cast from 'const char *' to 'char *' drops const qualifier [-Wcast-qual] + rb_scan_args(argc, argv, "2*", &document, &name, &rest); + ^ +1 warning generated. +compiling xml_io.c +compiling xml_libxml2_hacks.c +compiling xml_namespace.c +compiling xml_node.c +xml_node.c:304:15: warning: passing 'const xmlChar *' (aka 'const unsigned char *') to parameter of type 'void *' discards qualifiers [-Wincompatible-pointer-types-discards-qualifiers] + xmlFree(reparentee->ns->prefix); + ^~~~~~~~~~~~~~~~~~~~~~ +xml_node.c:550:37: warning: cast from 'const char *' to 'char *' drops const qualifier [-Wcast-qual] + n_args = rb_scan_args(argc, argv, "02", &r_level, &r_new_parent_doc); + ^ +xml_node.c:1393:28: warning: cast from 'const char *' to 'char *' drops const qualifier [-Wcast-qual] + rb_scan_args(argc, argv, "2*", &name, &document, &rest); + ^ +3 warnings generated. +compiling xml_node_set.c +xml_node_set.c:294:30: warning: cast from 'const char *' to 'char *' drops const qualifier [-Wcast-qual] + rb_scan_args(argc, argv, "11", NULL, NULL); + ^ +1 warning generated. +compiling xml_processing_instruction.c +xml_processing_instruction.c:20:28: warning: cast from 'const char *' to 'char *' drops const qualifier [-Wcast-qual] + rb_scan_args(argc, argv, "3*", &document, &name, &content, &rest); + ^ +1 warning generated. +compiling xml_reader.c +xml_reader.c:533:28: warning: cast from 'const char *' to 'char *' drops const qualifier [-Wcast-qual] + rb_scan_args(argc, argv, "13", &rb_buffer, &rb_url, &encoding, &rb_options); + ^ +xml_reader.c:577:28: warning: cast from 'const char *' to 'char *' drops const qualifier [-Wcast-qual] + rb_scan_args(argc, argv, "13", &rb_io, &rb_url, &encoding, &rb_options); + ^ +2 warnings generated. +compiling xml_relax_ng.c +compiling xml_sax_parser.c +compiling xml_sax_parser_context.c +compiling xml_sax_push_parser.c +compiling xml_schema.c +compiling xml_syntax_error.c +compiling xml_text.c +xml_text.c:18:28: warning: cast from 'const char *' to 'char *' drops const qualifier [-Wcast-qual] + rb_scan_args(argc, argv, "2*", &string, &document, &rest); + ^ +1 warning generated. +compiling xml_xpath_context.c +xml_xpath_context.c:200:31: warning: cast from 'const char *' to 'char *' drops const qualifier [-Wcast-qual] + if(rb_scan_args(argc, argv, "11", &search_path, &xpath_handler) == 1) + ^ +1 warning generated. +compiling xslt_stylesheet.c +xslt_stylesheet.c:141:30: warning: cast from 'const char *' to 'char *' drops const qualifier [-Wcast-qual] + rb_scan_args(argc, argv, "11", &xmldoc, ¶mobj); + ^ +xslt_stylesheet.c:112:13: warning: unused function 'swallow_superfluous_xml_errors' [-Wunused-function] +static void swallow_superfluous_xml_errors(void * userdata, xmlErrorPtr error, ...) + ^ +2 warnings generated. +linking shared-object nokogiri/nokogiri.bundle +Cleaning files only used during build. +rm -rf /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ext/nokogiri/tmp/x86_64-apple-darwin18.5.0/ports +rm -rf /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ext/nokogiri +make "DESTDIR=" install +/usr/local/opt/coreutils/bin/ginstall -c -m 0755 nokogiri.bundle ./.gem.20191018-47188-dzp0u9/nokogiri diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/nokogiri-1.10.4/mkmf.log b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/nokogiri-1.10.4/mkmf.log new file mode 100644 index 00000000..caa5c074 --- /dev/null +++ b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/nokogiri-1.10.4/mkmf.log @@ -0,0 +1,562 @@ +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2 conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -lruby.2.6 " +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main(int argc, char **argv) +4: { +5: return 0; +6: } +/* end */ + +"gcc -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2 -Werror -c conftest.c" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main() {return 0;} +/* end */ + +"gcc -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2 -Wno-error=unused-command-line-argument-hard-error-in-future -Werror -c conftest.c" +error: unknown warning option '-Werror=unused-command-line-argument-hard-error-in-future'; did you mean '-Werror=unused-command-line-argument'? [-Werror,-Wunknown-warning-option] +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main() {return 0;} +/* end */ + +have_header: checking for iconv.h... -------------------- yes + +"gcc -E -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2 -O3 -Wall -Wcast-qual -Wwrite-strings -Wmissing-noreturn -Winline conftest.c -o conftest.i" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +have_library: checking for gzdopen() in -lz... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2 -O3 -Wall -Wcast-qual -Wwrite-strings -Wmissing-noreturn -Winline conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -lruby.2.6 -lz " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return 0; +15: } +16: int t(void) { void ((*volatile p)()); p = (void ((*)()))gzdopen; return !p; } +/* end */ + +-------------------- + +have_iconv?: checking for iconv using --with-opt-* flags... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2 -O3 -Wall -Wcast-qual -Wwrite-strings -Wmissing-noreturn -Winline conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -lruby.2.6 " +Undefined symbols for architecture x86_64: + "_iconv", referenced from: + _main in conftest-2bf45a.o + "_iconv_open", referenced from: + _main in conftest-2bf45a.o +ld: symbol(s) not found for architecture x86_64 +clang: error: linker command failed with exit code 1 (use -v to see invocation) +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: #include + 5: + 6: int main(void) + 7: { + 8: iconv_t cd = iconv_open("", ""); + 9: iconv(cd, NULL, NULL, NULL, NULL); +10: return EXIT_SUCCESS; +11: } +/* end */ + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2 -O3 -Wall -Wcast-qual -Wwrite-strings -Wmissing-noreturn -Winline conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -lruby.2.6 -liconv " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: #include + 5: + 6: int main(void) + 7: { + 8: iconv_t cd = iconv_open("", ""); + 9: iconv(cd, NULL, NULL, NULL, NULL); +10: return EXIT_SUCCESS; +11: } +/* end */ + +-------------------- + +have_library: checking for -llzma... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2 -O3 -Wall -Wcast-qual -Wwrite-strings -Wmissing-noreturn -Winline -DNOKOGIRI_USE_PACKAGED_LIBRARIES conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -lruby.2.6 -llzma " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: +15: int t(void) { ; return 0; } +/* end */ + +-------------------- + +have_func: checking for xmlParseDoc() in libxml/parser.h... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/include -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT "-DNOKOGIRI_LIBXML2_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9\"" "-DNOKOGIRI_LIBXML2_PATCHES=\"0001-Revert-Do-not-URI-escape-in-server-side-includes.patch 0002-Remove-script-macro-support.patch 0003-Update-entities-to-remove-handling-of-ssi.patch\"" "-DNOKOGIRI_LIBXSLT_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33\"" "-DNOKOGIRI_LIBXSLT_PATCHES=\"0001-Fix-security-framework-bypass.patch\"" -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2 -O3 -Wall -Wcast-qual -Wwrite-strings -Wmissing-noreturn -Winline -DNOKOGIRI_USE_PACKAGED_LIBRARIES conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L/usr/local/Cellar/xz/5.2.4/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma -lruby.2.6 /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return 0; +15: } +16: int t(void) { void ((*volatile p)()); p = (void ((*)()))xmlParseDoc; return !p; } +/* end */ + +-------------------- + +have_func: checking for xsltParseStylesheetDoc() in libxslt/xslt.h... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/include -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT "-DNOKOGIRI_LIBXML2_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9\"" "-DNOKOGIRI_LIBXML2_PATCHES=\"0001-Revert-Do-not-URI-escape-in-server-side-includes.patch 0002-Remove-script-macro-support.patch 0003-Update-entities-to-remove-handling-of-ssi.patch\"" "-DNOKOGIRI_LIBXSLT_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33\"" "-DNOKOGIRI_LIBXSLT_PATCHES=\"0001-Fix-security-framework-bypass.patch\"" -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2 -O3 -Wall -Wcast-qual -Wwrite-strings -Wmissing-noreturn -Winline -DNOKOGIRI_USE_PACKAGED_LIBRARIES conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L/usr/local/Cellar/xz/5.2.4/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma -lruby.2.6 /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma " +conftest.c:16:57: error: use of undeclared identifier 'xsltParseStylesheetDoc' +int t(void) { void ((*volatile p)()); p = (void ((*)()))xsltParseStylesheetDoc; return !p; } + ^ +1 error generated. +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return 0; +15: } +16: int t(void) { void ((*volatile p)()); p = (void ((*)()))xsltParseStylesheetDoc; return !p; } +/* end */ + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/include -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT "-DNOKOGIRI_LIBXML2_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9\"" "-DNOKOGIRI_LIBXML2_PATCHES=\"0001-Revert-Do-not-URI-escape-in-server-side-includes.patch 0002-Remove-script-macro-support.patch 0003-Update-entities-to-remove-handling-of-ssi.patch\"" "-DNOKOGIRI_LIBXSLT_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33\"" "-DNOKOGIRI_LIBXSLT_PATCHES=\"0001-Fix-security-framework-bypass.patch\"" -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2 -O3 -Wall -Wcast-qual -Wwrite-strings -Wmissing-noreturn -Winline -DNOKOGIRI_USE_PACKAGED_LIBRARIES conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L/usr/local/Cellar/xz/5.2.4/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma -lruby.2.6 /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return 0; +15: } +16: extern void xsltParseStylesheetDoc(); +17: int t(void) { xsltParseStylesheetDoc(); return 0; } +/* end */ + +-------------------- + +have_func: checking for exsltFuncRegister() in libexslt/exslt.h... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/include -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT "-DNOKOGIRI_LIBXML2_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9\"" "-DNOKOGIRI_LIBXML2_PATCHES=\"0001-Revert-Do-not-URI-escape-in-server-side-includes.patch 0002-Remove-script-macro-support.patch 0003-Update-entities-to-remove-handling-of-ssi.patch\"" "-DNOKOGIRI_LIBXSLT_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33\"" "-DNOKOGIRI_LIBXSLT_PATCHES=\"0001-Fix-security-framework-bypass.patch\"" -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2 -O3 -Wall -Wcast-qual -Wwrite-strings -Wmissing-noreturn -Winline -DNOKOGIRI_USE_PACKAGED_LIBRARIES conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L/usr/local/Cellar/xz/5.2.4/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma -lruby.2.6 /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return 0; +15: } +16: int t(void) { void ((*volatile p)()); p = (void ((*)()))exsltFuncRegister; return !p; } +/* end */ + +-------------------- + +have_func: checking for xmlHasFeature()... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/include -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT "-DNOKOGIRI_LIBXML2_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9\"" "-DNOKOGIRI_LIBXML2_PATCHES=\"0001-Revert-Do-not-URI-escape-in-server-side-includes.patch 0002-Remove-script-macro-support.patch 0003-Update-entities-to-remove-handling-of-ssi.patch\"" "-DNOKOGIRI_LIBXSLT_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33\"" "-DNOKOGIRI_LIBXSLT_PATCHES=\"0001-Fix-security-framework-bypass.patch\"" -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2 -O3 -Wall -Wcast-qual -Wwrite-strings -Wmissing-noreturn -Winline -DNOKOGIRI_USE_PACKAGED_LIBRARIES conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L/usr/local/Cellar/xz/5.2.4/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma -lruby.2.6 /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma " +conftest.c:14:57: error: use of undeclared identifier 'xmlHasFeature' +int t(void) { void ((*volatile p)()); p = (void ((*)()))xmlHasFeature; return !p; } + ^ +1 error generated. +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))xmlHasFeature; return !p; } +/* end */ + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/include -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT "-DNOKOGIRI_LIBXML2_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9\"" "-DNOKOGIRI_LIBXML2_PATCHES=\"0001-Revert-Do-not-URI-escape-in-server-side-includes.patch 0002-Remove-script-macro-support.patch 0003-Update-entities-to-remove-handling-of-ssi.patch\"" "-DNOKOGIRI_LIBXSLT_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33\"" "-DNOKOGIRI_LIBXSLT_PATCHES=\"0001-Fix-security-framework-bypass.patch\"" -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2 -O3 -Wall -Wcast-qual -Wwrite-strings -Wmissing-noreturn -Winline -DNOKOGIRI_USE_PACKAGED_LIBRARIES conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L/usr/local/Cellar/xz/5.2.4/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma -lruby.2.6 /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: extern void xmlHasFeature(); +15: int t(void) { xmlHasFeature(); return 0; } +/* end */ + +-------------------- + +have_func: checking for xmlFirstElementChild()... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/include -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT "-DNOKOGIRI_LIBXML2_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9\"" "-DNOKOGIRI_LIBXML2_PATCHES=\"0001-Revert-Do-not-URI-escape-in-server-side-includes.patch 0002-Remove-script-macro-support.patch 0003-Update-entities-to-remove-handling-of-ssi.patch\"" "-DNOKOGIRI_LIBXSLT_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33\"" "-DNOKOGIRI_LIBXSLT_PATCHES=\"0001-Fix-security-framework-bypass.patch\"" -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2 -O3 -Wall -Wcast-qual -Wwrite-strings -Wmissing-noreturn -Winline -DNOKOGIRI_USE_PACKAGED_LIBRARIES conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L/usr/local/Cellar/xz/5.2.4/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma -lruby.2.6 /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma " +conftest.c:14:57: error: use of undeclared identifier 'xmlFirstElementChild' +int t(void) { void ((*volatile p)()); p = (void ((*)()))xmlFirstElementChild; return !p; } + ^ +1 error generated. +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))xmlFirstElementChild; return !p; } +/* end */ + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/include -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT "-DNOKOGIRI_LIBXML2_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9\"" "-DNOKOGIRI_LIBXML2_PATCHES=\"0001-Revert-Do-not-URI-escape-in-server-side-includes.patch 0002-Remove-script-macro-support.patch 0003-Update-entities-to-remove-handling-of-ssi.patch\"" "-DNOKOGIRI_LIBXSLT_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33\"" "-DNOKOGIRI_LIBXSLT_PATCHES=\"0001-Fix-security-framework-bypass.patch\"" -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2 -O3 -Wall -Wcast-qual -Wwrite-strings -Wmissing-noreturn -Winline -DNOKOGIRI_USE_PACKAGED_LIBRARIES conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L/usr/local/Cellar/xz/5.2.4/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma -lruby.2.6 /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: extern void xmlFirstElementChild(); +15: int t(void) { xmlFirstElementChild(); return 0; } +/* end */ + +-------------------- + +have_func: checking for xmlRelaxNGSetParserStructuredErrors()... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/include -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT "-DNOKOGIRI_LIBXML2_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9\"" "-DNOKOGIRI_LIBXML2_PATCHES=\"0001-Revert-Do-not-URI-escape-in-server-side-includes.patch 0002-Remove-script-macro-support.patch 0003-Update-entities-to-remove-handling-of-ssi.patch\"" "-DNOKOGIRI_LIBXSLT_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33\"" "-DNOKOGIRI_LIBXSLT_PATCHES=\"0001-Fix-security-framework-bypass.patch\"" -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2 -O3 -Wall -Wcast-qual -Wwrite-strings -Wmissing-noreturn -Winline -DNOKOGIRI_USE_PACKAGED_LIBRARIES conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L/usr/local/Cellar/xz/5.2.4/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma -lruby.2.6 /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma " +conftest.c:14:57: error: use of undeclared identifier 'xmlRelaxNGSetParserStructuredErrors' +int t(void) { void ((*volatile p)()); p = (void ((*)()))xmlRelaxNGSetParserStructuredErrors; return !p; } + ^ +1 error generated. +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))xmlRelaxNGSetParserStructuredErrors; return !p; } +/* end */ + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/include -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT "-DNOKOGIRI_LIBXML2_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9\"" "-DNOKOGIRI_LIBXML2_PATCHES=\"0001-Revert-Do-not-URI-escape-in-server-side-includes.patch 0002-Remove-script-macro-support.patch 0003-Update-entities-to-remove-handling-of-ssi.patch\"" "-DNOKOGIRI_LIBXSLT_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33\"" "-DNOKOGIRI_LIBXSLT_PATCHES=\"0001-Fix-security-framework-bypass.patch\"" -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2 -O3 -Wall -Wcast-qual -Wwrite-strings -Wmissing-noreturn -Winline -DNOKOGIRI_USE_PACKAGED_LIBRARIES conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L/usr/local/Cellar/xz/5.2.4/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma -lruby.2.6 /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: extern void xmlRelaxNGSetParserStructuredErrors(); +15: int t(void) { xmlRelaxNGSetParserStructuredErrors(); return 0; } +/* end */ + +-------------------- + +have_func: checking for xmlRelaxNGSetParserStructuredErrors()... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/include -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT "-DNOKOGIRI_LIBXML2_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9\"" "-DNOKOGIRI_LIBXML2_PATCHES=\"0001-Revert-Do-not-URI-escape-in-server-side-includes.patch 0002-Remove-script-macro-support.patch 0003-Update-entities-to-remove-handling-of-ssi.patch\"" "-DNOKOGIRI_LIBXSLT_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33\"" "-DNOKOGIRI_LIBXSLT_PATCHES=\"0001-Fix-security-framework-bypass.patch\"" -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2 -O3 -Wall -Wcast-qual -Wwrite-strings -Wmissing-noreturn -Winline -DNOKOGIRI_USE_PACKAGED_LIBRARIES conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L/usr/local/Cellar/xz/5.2.4/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma -lruby.2.6 /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma " +conftest.c:14:57: error: use of undeclared identifier 'xmlRelaxNGSetParserStructuredErrors' +int t(void) { void ((*volatile p)()); p = (void ((*)()))xmlRelaxNGSetParserStructuredErrors; return !p; } + ^ +1 error generated. +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))xmlRelaxNGSetParserStructuredErrors; return !p; } +/* end */ + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/include -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT "-DNOKOGIRI_LIBXML2_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9\"" "-DNOKOGIRI_LIBXML2_PATCHES=\"0001-Revert-Do-not-URI-escape-in-server-side-includes.patch 0002-Remove-script-macro-support.patch 0003-Update-entities-to-remove-handling-of-ssi.patch\"" "-DNOKOGIRI_LIBXSLT_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33\"" "-DNOKOGIRI_LIBXSLT_PATCHES=\"0001-Fix-security-framework-bypass.patch\"" -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2 -O3 -Wall -Wcast-qual -Wwrite-strings -Wmissing-noreturn -Winline -DNOKOGIRI_USE_PACKAGED_LIBRARIES conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L/usr/local/Cellar/xz/5.2.4/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma -lruby.2.6 /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: extern void xmlRelaxNGSetParserStructuredErrors(); +15: int t(void) { xmlRelaxNGSetParserStructuredErrors(); return 0; } +/* end */ + +-------------------- + +have_func: checking for xmlRelaxNGSetValidStructuredErrors()... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/include -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT "-DNOKOGIRI_LIBXML2_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9\"" "-DNOKOGIRI_LIBXML2_PATCHES=\"0001-Revert-Do-not-URI-escape-in-server-side-includes.patch 0002-Remove-script-macro-support.patch 0003-Update-entities-to-remove-handling-of-ssi.patch\"" "-DNOKOGIRI_LIBXSLT_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33\"" "-DNOKOGIRI_LIBXSLT_PATCHES=\"0001-Fix-security-framework-bypass.patch\"" -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2 -O3 -Wall -Wcast-qual -Wwrite-strings -Wmissing-noreturn -Winline -DNOKOGIRI_USE_PACKAGED_LIBRARIES conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L/usr/local/Cellar/xz/5.2.4/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma -lruby.2.6 /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma " +conftest.c:14:57: error: use of undeclared identifier 'xmlRelaxNGSetValidStructuredErrors' +int t(void) { void ((*volatile p)()); p = (void ((*)()))xmlRelaxNGSetValidStructuredErrors; return !p; } + ^ +1 error generated. +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))xmlRelaxNGSetValidStructuredErrors; return !p; } +/* end */ + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/include -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT "-DNOKOGIRI_LIBXML2_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9\"" "-DNOKOGIRI_LIBXML2_PATCHES=\"0001-Revert-Do-not-URI-escape-in-server-side-includes.patch 0002-Remove-script-macro-support.patch 0003-Update-entities-to-remove-handling-of-ssi.patch\"" "-DNOKOGIRI_LIBXSLT_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33\"" "-DNOKOGIRI_LIBXSLT_PATCHES=\"0001-Fix-security-framework-bypass.patch\"" -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2 -O3 -Wall -Wcast-qual -Wwrite-strings -Wmissing-noreturn -Winline -DNOKOGIRI_USE_PACKAGED_LIBRARIES conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L/usr/local/Cellar/xz/5.2.4/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma -lruby.2.6 /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: extern void xmlRelaxNGSetValidStructuredErrors(); +15: int t(void) { xmlRelaxNGSetValidStructuredErrors(); return 0; } +/* end */ + +-------------------- + +have_func: checking for xmlSchemaSetValidStructuredErrors()... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/include -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT "-DNOKOGIRI_LIBXML2_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9\"" "-DNOKOGIRI_LIBXML2_PATCHES=\"0001-Revert-Do-not-URI-escape-in-server-side-includes.patch 0002-Remove-script-macro-support.patch 0003-Update-entities-to-remove-handling-of-ssi.patch\"" "-DNOKOGIRI_LIBXSLT_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33\"" "-DNOKOGIRI_LIBXSLT_PATCHES=\"0001-Fix-security-framework-bypass.patch\"" -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2 -O3 -Wall -Wcast-qual -Wwrite-strings -Wmissing-noreturn -Winline -DNOKOGIRI_USE_PACKAGED_LIBRARIES conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L/usr/local/Cellar/xz/5.2.4/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma -lruby.2.6 /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma " +conftest.c:14:57: error: use of undeclared identifier 'xmlSchemaSetValidStructuredErrors' +int t(void) { void ((*volatile p)()); p = (void ((*)()))xmlSchemaSetValidStructuredErrors; return !p; } + ^ +1 error generated. +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))xmlSchemaSetValidStructuredErrors; return !p; } +/* end */ + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/include -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT "-DNOKOGIRI_LIBXML2_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9\"" "-DNOKOGIRI_LIBXML2_PATCHES=\"0001-Revert-Do-not-URI-escape-in-server-side-includes.patch 0002-Remove-script-macro-support.patch 0003-Update-entities-to-remove-handling-of-ssi.patch\"" "-DNOKOGIRI_LIBXSLT_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33\"" "-DNOKOGIRI_LIBXSLT_PATCHES=\"0001-Fix-security-framework-bypass.patch\"" -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2 -O3 -Wall -Wcast-qual -Wwrite-strings -Wmissing-noreturn -Winline -DNOKOGIRI_USE_PACKAGED_LIBRARIES conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L/usr/local/Cellar/xz/5.2.4/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma -lruby.2.6 /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: extern void xmlSchemaSetValidStructuredErrors(); +15: int t(void) { xmlSchemaSetValidStructuredErrors(); return 0; } +/* end */ + +-------------------- + +have_func: checking for xmlSchemaSetParserStructuredErrors()... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/include -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT "-DNOKOGIRI_LIBXML2_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9\"" "-DNOKOGIRI_LIBXML2_PATCHES=\"0001-Revert-Do-not-URI-escape-in-server-side-includes.patch 0002-Remove-script-macro-support.patch 0003-Update-entities-to-remove-handling-of-ssi.patch\"" "-DNOKOGIRI_LIBXSLT_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33\"" "-DNOKOGIRI_LIBXSLT_PATCHES=\"0001-Fix-security-framework-bypass.patch\"" -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2 -O3 -Wall -Wcast-qual -Wwrite-strings -Wmissing-noreturn -Winline -DNOKOGIRI_USE_PACKAGED_LIBRARIES conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L/usr/local/Cellar/xz/5.2.4/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma -lruby.2.6 /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma " +conftest.c:14:57: error: use of undeclared identifier 'xmlSchemaSetParserStructuredErrors' +int t(void) { void ((*volatile p)()); p = (void ((*)()))xmlSchemaSetParserStructuredErrors; return !p; } + ^ +1 error generated. +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))xmlSchemaSetParserStructuredErrors; return !p; } +/* end */ + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/include -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/include/libxml2 -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT "-DNOKOGIRI_LIBXML2_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9\"" "-DNOKOGIRI_LIBXML2_PATCHES=\"0001-Revert-Do-not-URI-escape-in-server-side-includes.patch 0002-Remove-script-macro-support.patch 0003-Update-entities-to-remove-handling-of-ssi.patch\"" "-DNOKOGIRI_LIBXSLT_PATH=\"/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33\"" "-DNOKOGIRI_LIBXSLT_PATCHES=\"0001-Fix-security-framework-bypass.patch\"" -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -I /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/libxml2 -O3 -Wall -Wcast-qual -Wwrite-strings -Wmissing-noreturn -Winline -DNOKOGIRI_USE_PACKAGED_LIBRARIES conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib -L/Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L/usr/local/Cellar/xz/5.2.4/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma -lruby.2.6 /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libexslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxslt/1.1.33/lib/libxslt.a -lm -liconv -lpthread -llzma -lz /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/nokogiri-1.10.4/ports/x86_64-apple-darwin18.5.0/libxml2/2.9.9/lib/libxml2.a -llzma " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: extern void xmlSchemaSetParserStructuredErrors(); +15: int t(void) { xmlSchemaSetParserStructuredErrors(); return 0; } +/* end */ + +-------------------- + diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/nokogiri-1.10.4/nokogiri/nokogiri.bundle b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/nokogiri-1.10.4/nokogiri/nokogiri.bundle new file mode 100755 index 00000000..26a47c23 Binary files /dev/null and b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/nokogiri-1.10.4/nokogiri/nokogiri.bundle differ diff --git a/tutor-virtual-2/test/integration/.keep b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/pg-1.1.4/gem.build_complete similarity index 100% rename from tutor-virtual-2/test/integration/.keep rename to path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/pg-1.1.4/gem.build_complete diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/pg-1.1.4/gem_make.out b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/pg-1.1.4/gem_make.out new file mode 100644 index 00000000..316bfdda --- /dev/null +++ b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/pg-1.1.4/gem_make.out @@ -0,0 +1,135 @@ +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/pg-1.1.4/ext +/Users/sergio/.rvm/rubies/ruby-2.6.3/bin/ruby -I /Users/sergio/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0 -r ./siteconf20191018-47188-1chb9zt.rb extconf.rb +checking for pg_config... yes +Using config values from /usr/local/bin/pg_config +checking for libpq-fe.h... yes +checking for libpq/libpq-fs.h... yes +checking for pg_config_manual.h... yes +checking for PQconnectdb() in -lpq... yes +checking for PQsetSingleRowMode()... yes +checking for PQconninfo()... yes +checking for PQsslAttribute()... yes +checking for PQencryptPasswordConn()... yes +checking for timegm()... yes +checking for rb_gc_adjust_memory_usage()... yes +checking for PG_DIAG_TABLE_NAME in libpq-fe.h... yes +checking for unistd.h... yes +checking for inttypes.h... yes +checking for C99 variable length arrays... yes +creating extconf.h +creating Makefile + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/pg-1.1.4/ext +make "DESTDIR=" clean + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/pg-1.1.4/ext +make "DESTDIR=" +compiling gvl_wrappers.c +compiling pg.c +compiling pg_binary_decoder.c +compiling pg_binary_encoder.c +compiling pg_coder.c +pg_coder.c:206:34: warning: implicit conversion loses integer precision: 'long' to 'int' [-Wshorten-64-to-32] + res = this->dec_func(this, val, RSTRING_LEN(argv[0]), tuple, field, ENCODING_GET(argv[0])); + ~~~~ ^~~~~~~~~~~~~~~~~~~~ +/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/ruby.h:1000:6: note: expanded from macro 'RSTRING_LEN' + RSTRING_EMBED_LEN(str) : \ + ^~~~~~~~~~~~~~~~~~~~~~ +/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/ruby.h:996:6: note: expanded from macro 'RSTRING_EMBED_LEN' + (long)((RBASIC(str)->flags >> RSTRING_EMBED_LEN_SHIFT) & \ + ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +pg_coder.c:206:34: warning: implicit conversion loses integer precision: 'long' to 'int' [-Wshorten-64-to-32] + res = this->dec_func(this, val, RSTRING_LEN(argv[0]), tuple, field, ENCODING_GET(argv[0])); + ~~~~ ^~~~~~~~~~~~~~~~~~~~ +/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/ruby.h:1001:28: note: expanded from macro 'RSTRING_LEN' + RSTRING(str)->as.heap.len) + ~~~~~~~~~~~~~~~~~~~~~~^~~ +2 warnings generated. +compiling pg_connection.c +compiling pg_copy_coder.c +pg_copy_coder.c:225:15: warning: implicit conversion loses integer precision: 'long' to 'int' [-Wshorten-64-to-32] + strlen = RSTRING_LEN(subint); + ~ ^~~~~~~~~~~~~~~~~~~ +/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/ruby.h:1000:6: note: expanded from macro 'RSTRING_LEN' + RSTRING_EMBED_LEN(str) : \ + ^~~~~~~~~~~~~~~~~~~~~~ +/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/ruby.h:996:6: note: expanded from macro 'RSTRING_EMBED_LEN' + (long)((RBASIC(str)->flags >> RSTRING_EMBED_LEN_SHIFT) & \ + ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +pg_copy_coder.c:225:15: warning: implicit conversion loses integer precision: 'long' to 'int' [-Wshorten-64-to-32] + strlen = RSTRING_LEN(subint); + ~ ^~~~~~~~~~~~~~~~~~~ +/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/ruby.h:1001:28: note: expanded from macro 'RSTRING_LEN' + RSTRING(str)->as.heap.len) + ~~~~~~~~~~~~~~~~~~~~~~^~~ +pg_copy_coder.c:531:23: warning: implicit conversion loses integer precision: 'long' to 'int' [-Wshorten-64-to-32] + input_len = end_ptr - start_ptr; + ~ ~~~~~~~~^~~~~~~~~~~ +3 warnings generated. +compiling pg_errors.c +compiling pg_result.c +compiling pg_text_decoder.c +compiling pg_text_encoder.c +pg_text_encoder.c:170:14: warning: implicit conversion loses integer precision: 'long' to 'int' [-Wshorten-64-to-32] + len = out - start; + ~ ~~~~^~~~~~~ +pg_text_encoder.c:289:15: warning: implicit conversion loses integer precision: 'long' to 'int' [-Wshorten-64-to-32] + return optr - out; + ~~~~~~ ~~~~~^~~~~ +pg_text_encoder.c:293:12: warning: implicit conversion loses integer precision: 'long' to 'int' [-Wshorten-64-to-32] + return 2 + RSTRING_LEN(*intermediate) * 2; + ~~~~~~ ~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +pg_text_encoder.c:500:13: warning: implicit conversion loses integer precision: 'long' to 'int' [-Wshorten-64-to-32] + nr_elems = RARRAY_LEN(value); + ~ ^~~~~~~~~~~~~~~~~ +/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/ruby.h:1058:23: note: expanded from macro 'RARRAY_LEN' +#define RARRAY_LEN(a) rb_array_len(a) + ^~~~~~~~~~~~~~~ +4 warnings generated. +compiling pg_tuple.c +pg_tuple.c:475:15: warning: implicit conversion loses integer precision: 'long' to 'int' [-Wshorten-64-to-32] + num_fields = RARRAY_LEN(values); + ~ ^~~~~~~~~~~~~~~~~~ +/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/ruby.h:1058:23: note: expanded from macro 'RARRAY_LEN' +#define RARRAY_LEN(a) rb_array_len(a) + ^~~~~~~~~~~~~~~ +1 warning generated. +compiling pg_type_map.c +compiling pg_type_map_all_strings.c +compiling pg_type_map_by_class.c +compiling pg_type_map_by_column.c +pg_type_map_by_column.c:161:52: warning: implicit conversion loses integer precision: 'long' to 'int' [-Wshorten-64-to-32] + return dec_func( p_coder, RSTRING_PTR(field_str), RSTRING_LEN(field_str), 0, fieldno, enc_idx ); + ~~~~~~~~ ^~~~~~~~~~~~~~~~~~~~~~ +/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/ruby.h:1000:6: note: expanded from macro 'RSTRING_LEN' + RSTRING_EMBED_LEN(str) : \ + ^~~~~~~~~~~~~~~~~~~~~~ +/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/ruby.h:996:6: note: expanded from macro 'RSTRING_EMBED_LEN' + (long)((RBASIC(str)->flags >> RSTRING_EMBED_LEN_SHIFT) & \ + ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +pg_type_map_by_column.c:161:52: warning: implicit conversion loses integer precision: 'long' to 'int' [-Wshorten-64-to-32] + return dec_func( p_coder, RSTRING_PTR(field_str), RSTRING_LEN(field_str), 0, fieldno, enc_idx ); + ~~~~~~~~ ^~~~~~~~~~~~~~~~~~~~~~ +/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/ruby.h:1001:28: note: expanded from macro 'RSTRING_LEN' + RSTRING(str)->as.heap.len) + ~~~~~~~~~~~~~~~~~~~~~~^~~ +pg_type_map_by_column.c:230:17: warning: implicit conversion loses integer precision: 'long' to 'int' [-Wshorten-64-to-32] + conv_ary_len = RARRAY_LEN(conv_ary); + ~ ^~~~~~~~~~~~~~~~~~~~ +/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/ruby.h:1058:23: note: expanded from macro 'RARRAY_LEN' +#define RARRAY_LEN(a) rb_array_len(a) + ^~~~~~~~~~~~~~~ +3 warnings generated. +compiling pg_type_map_by_mri_type.c +compiling pg_type_map_by_oid.c +compiling pg_type_map_in_ruby.c +compiling util.c +util.c:119:24: warning: implicit conversion loses integer precision: 'long' to 'int' [-Wshorten-64-to-32] + return (char*)out_ptr - out; + ~~~~~~ ~~~~~~~~~~~~~~~^~~~~ +1 warning generated. +linking shared-object pg_ext.bundle + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/pg-1.1.4/ext +make "DESTDIR=" install +/usr/local/opt/coreutils/bin/ginstall -c -m 0755 pg_ext.bundle ./.gem.20191018-47188-j80dfk diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/pg-1.1.4/mkmf.log b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/pg-1.1.4/mkmf.log new file mode 100644 index 00000000..8834513c --- /dev/null +++ b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/pg-1.1.4/mkmf.log @@ -0,0 +1,415 @@ +find_executable: checking for pg_config... -------------------- yes + +-------------------- + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/Cellar/postgresql/11.5_1/include -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -lruby.2.6 " +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main(int argc, char **argv) +4: { +5: return 0; +6: } +/* end */ + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/Cellar/postgresql/11.5_1/include -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -lruby.2.6 -Wl,-rpath,/usr/local/lib " +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main() {return 0;} +/* end */ + +find_header: checking for libpq-fe.h... -------------------- yes + +"gcc -E -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/Cellar/postgresql/11.5_1/include -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -o conftest.i" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +find_header: checking for libpq/libpq-fs.h... -------------------- yes + +"gcc -E -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/Cellar/postgresql/11.5_1/include -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -o conftest.i" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +find_header: checking for pg_config_manual.h... -------------------- yes + +"gcc -E -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/Cellar/postgresql/11.5_1/include -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -o conftest.i" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +have_library: checking for PQconnectdb() in -lpq... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/Cellar/postgresql/11.5_1/include -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -Wl,-rpath,/usr/local/lib -lruby.2.6 -lpq " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: #include + 4: + 5: /*top*/ + 6: extern int t(void); + 7: int main(int argc, char **argv) + 8: { + 9: if (argc > 1000000) { +10: int (* volatile tp)(void)=(int (*)(void))&t; +11: printf("%d", (*tp)()); +12: } +13: +14: return 0; +15: } +16: int t(void) { void ((*volatile p)()); p = (void ((*)()))PQconnectdb; return !p; } +/* end */ + +-------------------- + +have_func: checking for PQsetSingleRowMode()... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/Cellar/postgresql/11.5_1/include -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -Wl,-rpath,/usr/local/lib -lpq -lruby.2.6 -lpq " +conftest.c:14:57: error: use of undeclared identifier 'PQsetSingleRowMode' +int t(void) { void ((*volatile p)()); p = (void ((*)()))PQsetSingleRowMode; return !p; } + ^ +1 error generated. +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))PQsetSingleRowMode; return !p; } +/* end */ + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/Cellar/postgresql/11.5_1/include -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -Wl,-rpath,/usr/local/lib -lpq -lruby.2.6 -lpq " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: extern void PQsetSingleRowMode(); +15: int t(void) { PQsetSingleRowMode(); return 0; } +/* end */ + +-------------------- + +have_func: checking for PQconninfo()... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/Cellar/postgresql/11.5_1/include -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -Wl,-rpath,/usr/local/lib -lpq -lruby.2.6 -lpq " +conftest.c:14:57: error: use of undeclared identifier 'PQconninfo' +int t(void) { void ((*volatile p)()); p = (void ((*)()))PQconninfo; return !p; } + ^ +1 error generated. +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))PQconninfo; return !p; } +/* end */ + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/Cellar/postgresql/11.5_1/include -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -Wl,-rpath,/usr/local/lib -lpq -lruby.2.6 -lpq " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: extern void PQconninfo(); +15: int t(void) { PQconninfo(); return 0; } +/* end */ + +-------------------- + +have_func: checking for PQsslAttribute()... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/Cellar/postgresql/11.5_1/include -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -Wl,-rpath,/usr/local/lib -lpq -lruby.2.6 -lpq " +conftest.c:14:57: error: use of undeclared identifier 'PQsslAttribute' +int t(void) { void ((*volatile p)()); p = (void ((*)()))PQsslAttribute; return !p; } + ^ +1 error generated. +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))PQsslAttribute; return !p; } +/* end */ + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/Cellar/postgresql/11.5_1/include -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -Wl,-rpath,/usr/local/lib -lpq -lruby.2.6 -lpq " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: extern void PQsslAttribute(); +15: int t(void) { PQsslAttribute(); return 0; } +/* end */ + +-------------------- + +have_func: checking for PQencryptPasswordConn()... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/Cellar/postgresql/11.5_1/include -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -Wl,-rpath,/usr/local/lib -lpq -lruby.2.6 -lpq " +conftest.c:14:57: error: use of undeclared identifier 'PQencryptPasswordConn' +int t(void) { void ((*volatile p)()); p = (void ((*)()))PQencryptPasswordConn; return !p; } + ^ +1 error generated. +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))PQencryptPasswordConn; return !p; } +/* end */ + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/Cellar/postgresql/11.5_1/include -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -Wl,-rpath,/usr/local/lib -lpq -lruby.2.6 -lpq " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: extern void PQencryptPasswordConn(); +15: int t(void) { PQencryptPasswordConn(); return 0; } +/* end */ + +-------------------- + +have_func: checking for timegm()... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/Cellar/postgresql/11.5_1/include -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -Wl,-rpath,/usr/local/lib -lpq -lruby.2.6 -lpq " +conftest.c:14:57: error: use of undeclared identifier 'timegm' +int t(void) { void ((*volatile p)()); p = (void ((*)()))timegm; return !p; } + ^ +1 error generated. +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))timegm; return !p; } +/* end */ + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/Cellar/postgresql/11.5_1/include -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -Wl,-rpath,/usr/local/lib -lpq -lruby.2.6 -lpq " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: extern void timegm(); +15: int t(void) { timegm(); return 0; } +/* end */ + +-------------------- + +have_func: checking for rb_gc_adjust_memory_usage()... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/Cellar/postgresql/11.5_1/include -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -Wl,-rpath,/usr/local/lib -lpq -lruby.2.6 -lpq " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))rb_gc_adjust_memory_usage; return !p; } +/* end */ + +-------------------- + +have_const: checking for PG_DIAG_TABLE_NAME in libpq-fe.h... -------------------- yes + +"gcc -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/Cellar/postgresql/11.5_1/include -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -c conftest.c" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +4: +5: /*top*/ +6: typedef int conftest_type; +7: conftest_type conftestval = (int)PG_DIAG_TABLE_NAME; +/* end */ + +-------------------- + +have_header: checking for unistd.h... -------------------- yes + +"gcc -E -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/Cellar/postgresql/11.5_1/include -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -o conftest.i" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +have_header: checking for inttypes.h... -------------------- yes + +"gcc -E -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/Cellar/postgresql/11.5_1/include -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -o conftest.i" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + +checking for C99 variable length arrays... -------------------- yes + +"gcc -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/Cellar/postgresql/11.5_1/include -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe -c conftest.c" +conftest.c:3:27: warning: unused variable 'vla' [-Wunused-variable] +void test_vla(int l){ int vla[l]; } + ^ +1 warning generated. +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: void test_vla(int l){ int vla[l]; } +/* end */ + +-------------------- + +extconf.h is: +/* begin */ + 1: #ifndef EXTCONF_H + 2: #define EXTCONF_H + 3: #define HAVE_PQSETSINGLEROWMODE 1 + 4: #define HAVE_PQCONNINFO 1 + 5: #define HAVE_PQSSLATTRIBUTE 1 + 6: #define HAVE_PQENCRYPTPASSWORDCONN 1 + 7: #define HAVE_TIMEGM 1 + 8: #define HAVE_RB_GC_ADJUST_MEMORY_USAGE 1 + 9: #define HAVE_CONST_PG_DIAG_TABLE_NAME 1 +10: #define HAVE_UNISTD_H 1 +11: #define HAVE_INTTYPES_H 1 +12: #define HAVE_VARIABLE_LENGTH_ARRAYS 1 +13: #endif +/* end */ + diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/pg-1.1.4/pg_ext.bundle b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/pg-1.1.4/pg_ext.bundle new file mode 100755 index 00000000..651849c3 Binary files /dev/null and b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/pg-1.1.4/pg_ext.bundle differ diff --git a/tutor-virtual-2/test/mailers/.keep b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/puma-3.12.1/gem.build_complete similarity index 100% rename from tutor-virtual-2/test/mailers/.keep rename to path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/puma-3.12.1/gem.build_complete diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/puma-3.12.1/gem_make.out b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/puma-3.12.1/gem_make.out new file mode 100644 index 00000000..66b1dcf9 --- /dev/null +++ b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/puma-3.12.1/gem_make.out @@ -0,0 +1,41 @@ +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/puma-3.12.1/ext/puma_http11 +/Users/sergio/.rvm/rubies/ruby-2.6.3/bin/ruby -I /Users/sergio/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0 -r ./siteconf20191018-47188-65nbuo.rb extconf.rb +checking for BIO_read() in -lcrypto... yes +checking for SSL_CTX_new() in -lssl... yes +checking for openssl/bio.h... yes +creating Makefile + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/puma-3.12.1/ext/puma_http11 +make "DESTDIR=" clean + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/puma-3.12.1/ext/puma_http11 +make "DESTDIR=" +compiling http11_parser.c +ext/puma_http11/http11_parser.c:44:18: warning: unused variable 'puma_parser_en_main' [-Wunused-const-variable] +static const int puma_parser_en_main = 1; + ^ +1 warning generated. +compiling io_buffer.c +compiling mini_ssl.c +mini_ssl.c:220:27: warning: 'DTLSv1_method' is deprecated [-Wdeprecated-declarations] + conn->ctx = SSL_CTX_new(DTLSv1_method()); + ^ +/usr/local/opt/openssl@1.1/include/openssl/ssl.h:1895:1: note: 'DTLSv1_method' has been explicitly marked deprecated here +DEPRECATEDIN_1_1_0(__owur const SSL_METHOD *DTLSv1_method(void)) /* DTLSv1.0 */ +^ +/usr/local/opt/openssl@1.1/include/openssl/opensslconf.h:155:34: note: expanded from macro 'DEPRECATEDIN_1_1_0' +# define DEPRECATEDIN_1_1_0(f) DECLARE_DEPRECATED(f) + ^ +/usr/local/opt/openssl@1.1/include/openssl/opensslconf.h:118:55: note: expanded from macro 'DECLARE_DEPRECATED' +# define DECLARE_DEPRECATED(f) f __attribute__ ((deprecated)); + ^ +mini_ssl.c:250:40: warning: function 'raise_error' could be declared with attribute 'noreturn' [-Wmissing-noreturn] +void raise_error(SSL* ssl, int result) { + ^ +2 warnings generated. +compiling puma_http11.c +linking shared-object puma/puma_http11.bundle + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/puma-3.12.1/ext/puma_http11 +make "DESTDIR=" install +/usr/local/opt/coreutils/bin/ginstall -c -m 0755 puma_http11.bundle ./.gem.20191018-47188-1l9wv87/puma diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/puma-3.12.1/mkmf.log b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/puma-3.12.1/mkmf.log new file mode 100644 index 00000000..f58f51b5 --- /dev/null +++ b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/puma-3.12.1/mkmf.log @@ -0,0 +1,117 @@ +have_library: checking for BIO_read() in -lcrypto... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -lruby.2.6 " +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: int main(int argc, char **argv) +4: { +5: return 0; +6: } +/* end */ + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -lruby.2.6 -lcrypto " +conftest.c:14:57: error: use of undeclared identifier 'BIO_read' +int t(void) { void ((*volatile p)()); p = (void ((*)()))BIO_read; return !p; } + ^ +1 error generated. +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))BIO_read; return !p; } +/* end */ + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -lruby.2.6 -lcrypto " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: extern void BIO_read(); +15: int t(void) { BIO_read(); return 0; } +/* end */ + +-------------------- + +have_library: checking for SSL_CTX_new() in -lssl... -------------------- yes + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -lcrypto -lruby.2.6 -lssl -lcrypto " +conftest.c:14:57: error: use of undeclared identifier 'SSL_CTX_new' +int t(void) { void ((*volatile p)()); p = (void ((*)()))SSL_CTX_new; return !p; } + ^ +1 error generated. +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: int t(void) { void ((*volatile p)()); p = (void ((*)()))SSL_CTX_new; return !p; } +/* end */ + +"gcc -o conftest -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -L. -L/Users/sergio/.rvm/rubies/ruby-2.6.3/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib -lcrypto -lruby.2.6 -lssl -lcrypto " +checked program was: +/* begin */ + 1: #include "ruby.h" + 2: + 3: /*top*/ + 4: extern int t(void); + 5: int main(int argc, char **argv) + 6: { + 7: if (argc > 1000000) { + 8: int (* volatile tp)(void)=(int (*)(void))&t; + 9: printf("%d", (*tp)()); +10: } +11: +12: return 0; +13: } +14: extern void SSL_CTX_new(); +15: int t(void) { SSL_CTX_new(); return 0; } +/* end */ + +-------------------- + +have_header: checking for openssl/bio.h... -------------------- yes + +"gcc -E -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/ruby/backward -I/Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 -I. -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT -O3 -ggdb3 -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens -fno-common -pipe conftest.c -o conftest.i" +checked program was: +/* begin */ +1: #include "ruby.h" +2: +3: #include +/* end */ + +-------------------- + diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/puma-3.12.1/puma/puma_http11.bundle b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/puma-3.12.1/puma/puma_http11.bundle new file mode 100755 index 00000000..45883dbc Binary files /dev/null and b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/puma-3.12.1/puma/puma_http11.bundle differ diff --git a/tutor-virtual-2/test/models/.keep b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/websocket-driver-0.7.1/gem.build_complete similarity index 100% rename from tutor-virtual-2/test/models/.keep rename to path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/websocket-driver-0.7.1/gem.build_complete diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/websocket-driver-0.7.1/gem_make.out b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/websocket-driver-0.7.1/gem_make.out new file mode 100644 index 00000000..b24ad6da --- /dev/null +++ b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/websocket-driver-0.7.1/gem_make.out @@ -0,0 +1,15 @@ +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/websocket-driver-0.7.1/ext/websocket-driver +/Users/sergio/.rvm/rubies/ruby-2.6.3/bin/ruby -I /Users/sergio/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0 -r ./siteconf20191018-47188-1xr9swa.rb extconf.rb +creating Makefile + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/websocket-driver-0.7.1/ext/websocket-driver +make "DESTDIR=" clean + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/websocket-driver-0.7.1/ext/websocket-driver +make "DESTDIR=" +compiling websocket_mask.c +linking shared-object websocket_mask.bundle + +current directory: /Users/sergio/sdn/tec/tutor_virtual/path/ruby/2.6.0/gems/websocket-driver-0.7.1/ext/websocket-driver +make "DESTDIR=" install +/usr/local/opt/coreutils/bin/ginstall -c -m 0755 websocket_mask.bundle ./.gem.20191018-47188-mvnx1r diff --git a/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/websocket-driver-0.7.1/websocket_mask.bundle b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/websocket-driver-0.7.1/websocket_mask.bundle new file mode 100755 index 00000000..939120d5 Binary files /dev/null and b/path/ruby/2.6.0/extensions/x86_64-darwin-18/2.6.0/websocket-driver-0.7.1/websocket_mask.bundle differ diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/CHANGELOG.md b/path/ruby/2.6.0/gems/actioncable-5.2.3/CHANGELOG.md new file mode 100644 index 00000000..158c3851 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/CHANGELOG.md @@ -0,0 +1,54 @@ +## Rails 5.2.3 (March 27, 2019) ## + +* No changes. + + +## Rails 5.2.2.1 (March 11, 2019) ## + +* No changes. + + +## Rails 5.2.2 (December 04, 2018) ## + +* No changes. + + +## Rails 5.2.1.1 (November 27, 2018) ## + +* No changes. + + +## Rails 5.2.1 (August 07, 2018) ## + +* No changes. + + +## Rails 5.2.0 (April 09, 2018) ## + +* Removed deprecated evented redis adapter. + + *Rafael Mendonça França* + +* Support redis-rb 4.0. + + *Jeremy Daer* + +* Hash long stream identifiers when using PostgreSQL adapter. + + PostgreSQL has a limit on identifiers length (63 chars, [docs](https://www.postgresql.org/docs/current/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS)). + Provided fix minifies identifiers longer than 63 chars by hashing them with SHA1. + + Fixes #28751. + + *Vladimir Dementyev* + +* Action Cable's `redis` adapter allows for other common redis-rb options (`host`, `port`, `db`, `password`) in cable.yml. + + Previously, it accepts only a [redis:// url](https://www.iana.org/assignments/uri-schemes/prov/redis) as an option. + While we can add all of these options to the `url` itself, it is not explicitly documented. This alternative setup + is shown as the first example in the [Redis rubygem](https://github.com/redis/redis-rb#getting-started), which + makes this set of options as sensible as using just the `url`. + + *Marc Rendl Ignacio* + +Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/actioncable/CHANGELOG.md) for previous changes. diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/MIT-LICENSE b/path/ruby/2.6.0/gems/actioncable-5.2.3/MIT-LICENSE new file mode 100644 index 00000000..a42759f0 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2015-2018 Basecamp, LLC + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/README.md b/path/ruby/2.6.0/gems/actioncable-5.2.3/README.md new file mode 100644 index 00000000..bb6f0009 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/README.md @@ -0,0 +1,567 @@ +# Action Cable – Integrated WebSockets for Rails + +Action Cable seamlessly integrates WebSockets with the rest of your Rails application. +It allows for real-time features to be written in Ruby in the same style +and form as the rest of your Rails application, while still being performant +and scalable. It's a full-stack offering that provides both a client-side +JavaScript framework and a server-side Ruby framework. You have access to your full +domain model written with Active Record or your ORM of choice. + +## Terminology + +A single Action Cable server can handle multiple connection instances. It has one +connection instance per WebSocket connection. A single user may have multiple +WebSockets open to your application if they use multiple browser tabs or devices. +The client of a WebSocket connection is called the consumer. + +Each consumer can in turn subscribe to multiple cable channels. Each channel encapsulates +a logical unit of work, similar to what a controller does in a regular MVC setup. For example, +you could have a `ChatChannel` and an `AppearancesChannel`, and a consumer could be subscribed to either +or to both of these channels. At the very least, a consumer should be subscribed to one channel. + +When the consumer is subscribed to a channel, they act as a subscriber. The connection between +the subscriber and the channel is, surprise-surprise, called a subscription. A consumer +can act as a subscriber to a given channel any number of times. For example, a consumer +could subscribe to multiple chat rooms at the same time. (And remember that a physical user may +have multiple consumers, one per tab/device open to your connection). + +Each channel can then again be streaming zero or more broadcastings. A broadcasting is a +pubsub link where anything transmitted by the broadcaster is sent directly to the channel +subscribers who are streaming that named broadcasting. + +As you can see, this is a fairly deep architectural stack. There's a lot of new terminology +to identify the new pieces, and on top of that, you're dealing with both client and server side +reflections of each unit. + +## Examples + +### A full-stack example + +The first thing you must do is define your `ApplicationCable::Connection` class in Ruby. This +is the place where you authorize the incoming connection, and proceed to establish it, +if all is well. Here's the simplest example starting with the server-side connection class: + +```ruby +# app/channels/application_cable/connection.rb +module ApplicationCable + class Connection < ActionCable::Connection::Base + identified_by :current_user + + def connect + self.current_user = find_verified_user + end + + private + def find_verified_user + if verified_user = User.find_by(id: cookies.encrypted[:user_id]) + verified_user + else + reject_unauthorized_connection + end + end + end +end +``` +Here `identified_by` is a connection identifier that can be used to find the specific connection again or later. +Note that anything marked as an identifier will automatically create a delegate by the same name on any channel instances created off the connection. + +This relies on the fact that you will already have handled authentication of the user, and +that a successful authentication sets a signed cookie with the `user_id`. This cookie is then +automatically sent to the connection instance when a new connection is attempted, and you +use that to set the `current_user`. By identifying the connection by this same current_user, +you're also ensuring that you can later retrieve all open connections by a given user (and +potentially disconnect them all if the user is deleted or deauthorized). + +Next, you should define your `ApplicationCable::Channel` class in Ruby. This is the place where you put +shared logic between your channels. + +```ruby +# app/channels/application_cable/channel.rb +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end +``` + +The client-side needs to setup a consumer instance of this connection. That's done like so: + +```js +// app/assets/javascripts/cable.js +//= require action_cable +//= require_self +//= require_tree ./channels + +(function() { + this.App || (this.App = {}); + + App.cable = ActionCable.createConsumer("ws://cable.example.com"); +}).call(this); +``` + +The `ws://cable.example.com` address must point to your Action Cable server(s), and it +must share a cookie namespace with the rest of the application (which may live under http://example.com). +This ensures that the signed cookie will be correctly sent. + +That's all you need to establish the connection! But of course, this isn't very useful in +itself. This just gives you the plumbing. To make stuff happen, you need content. That content +is defined by declaring channels on the server and allowing the consumer to subscribe to them. + + +### Channel example 1: User appearances + +Here's a simple example of a channel that tracks whether a user is online or not, and also what page they are currently on. +(This is useful for creating presence features like showing a green dot next to a user's name if they're online). + +First you declare the server-side channel: + +```ruby +# app/channels/appearance_channel.rb +class AppearanceChannel < ApplicationCable::Channel + def subscribed + current_user.appear + end + + def unsubscribed + current_user.disappear + end + + def appear(data) + current_user.appear on: data['appearing_on'] + end + + def away + current_user.away + end +end +``` + +The `#subscribed` callback is invoked when, as we'll show below, a client-side subscription is initiated. In this case, +we take that opportunity to say "the current user has indeed appeared". That appear/disappear API could be backed by +Redis or a database or whatever else. Here's what the client-side of that looks like: + +```coffeescript +# app/assets/javascripts/cable/subscriptions/appearance.coffee +App.cable.subscriptions.create "AppearanceChannel", + # Called when the subscription is ready for use on the server + connected: -> + @install() + @appear() + + # Called when the WebSocket connection is closed + disconnected: -> + @uninstall() + + # Called when the subscription is rejected by the server + rejected: -> + @uninstall() + + appear: -> + # Calls `AppearanceChannel#appear(data)` on the server + @perform("appear", appearing_on: $("main").data("appearing-on")) + + away: -> + # Calls `AppearanceChannel#away` on the server + @perform("away") + + + buttonSelector = "[data-behavior~=appear_away]" + + install: -> + $(document).on "turbolinks:load.appearance", => + @appear() + + $(document).on "click.appearance", buttonSelector, => + @away() + false + + $(buttonSelector).show() + + uninstall: -> + $(document).off(".appearance") + $(buttonSelector).hide() +``` + +Simply calling `App.cable.subscriptions.create` will setup the subscription, which will call `AppearanceChannel#subscribed`, +which in turn is linked to the original `App.cable` -> `ApplicationCable::Connection` instances. + +Next, we link the client-side `appear` method to `AppearanceChannel#appear(data)`. This is possible because the server-side +channel instance will automatically expose the public methods declared on the class (minus the callbacks), so that these +can be reached as remote procedure calls via a subscription's `perform` method. + +### Channel example 2: Receiving new web notifications + +The appearance example was all about exposing server functionality to client-side invocation over the WebSocket connection. +But the great thing about WebSockets is that it's a two-way street. So now let's show an example where the server invokes +an action on the client. + +This is a web notification channel that allows you to trigger client-side web notifications when you broadcast to the right +streams: + +```ruby +# app/channels/web_notifications_channel.rb +class WebNotificationsChannel < ApplicationCable::Channel + def subscribed + stream_from "web_notifications_#{current_user.id}" + end +end +``` + +```coffeescript +# Client-side, which assumes you've already requested the right to send web notifications +App.cable.subscriptions.create "WebNotificationsChannel", + received: (data) -> + new Notification data["title"], body: data["body"] +``` + +```ruby +# Somewhere in your app this is called, perhaps from a NewCommentJob +ActionCable.server.broadcast \ + "web_notifications_#{current_user.id}", { title: 'New things!', body: 'All the news that is fit to print' } +``` + +The `ActionCable.server.broadcast` call places a message in the Action Cable pubsub queue under a separate broadcasting name for each user. For a user with an ID of 1, the broadcasting name would be `web_notifications_1`. +The channel has been instructed to stream everything that arrives at `web_notifications_1` directly to the client by invoking the +`#received(data)` callback. The data is the hash sent as the second parameter to the server-side broadcast call, JSON encoded for the trip +across the wire, and unpacked for the data argument arriving to `#received`. + + +### Passing Parameters to Channel + +You can pass parameters from the client side to the server side when creating a subscription. For example: + +```ruby +# app/channels/chat_channel.rb +class ChatChannel < ApplicationCable::Channel + def subscribed + stream_from "chat_#{params[:room]}" + end +end +``` + +If you pass an object as the first argument to `subscriptions.create`, that object will become the params hash in your cable channel. The keyword `channel` is required. + +```coffeescript +# Client-side, which assumes you've already requested the right to send web notifications +App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" }, + received: (data) -> + @appendLine(data) + + appendLine: (data) -> + html = @createLine(data) + $("[data-chat-room='Best Room']").append(html) + + createLine: (data) -> + """ +
+ #{data["sent_by"]} + #{data["body"]} +
+ """ +``` + +```ruby +# Somewhere in your app this is called, perhaps from a NewCommentJob +ActionCable.server.broadcast \ + "chat_#{room}", { sent_by: 'Paul', body: 'This is a cool chat app.' } +``` + + +### Rebroadcasting message + +A common use case is to rebroadcast a message sent by one client to any other connected clients. + +```ruby +# app/channels/chat_channel.rb +class ChatChannel < ApplicationCable::Channel + def subscribed + stream_from "chat_#{params[:room]}" + end + + def receive(data) + ActionCable.server.broadcast "chat_#{params[:room]}", data + end +end +``` + +```coffeescript +# Client-side, which assumes you've already requested the right to send web notifications +App.chatChannel = App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" }, + received: (data) -> + # data => { sent_by: "Paul", body: "This is a cool chat app." } + +App.chatChannel.send({ sent_by: "Paul", body: "This is a cool chat app." }) +``` + +The rebroadcast will be received by all connected clients, _including_ the client that sent the message. Note that params are the same as they were when you subscribed to the channel. + + +### More complete examples + +See the [rails/actioncable-examples](https://github.com/rails/actioncable-examples) repository for a full example of how to setup Action Cable in a Rails app, and how to add channels. + +## Configuration + +Action Cable has three required configurations: a subscription adapter, allowed request origins, and the cable server URL (which can optionally be set on the client side). + +### Redis + +By default, `ActionCable::Server::Base` will look for a configuration file in `Rails.root.join('config/cable.yml')`. +This file must specify an adapter and a URL for each Rails environment. It may use the following format: + +```yaml +production: &production + adapter: redis + url: redis://10.10.3.153:6381 +development: &development + adapter: redis + url: redis://localhost:6379 +test: *development +``` + +You can also change the location of the Action Cable config file in a Rails initializer with something like: + +```ruby +Rails.application.paths.add "config/cable", with: "somewhere/else/cable.yml" +``` + +### Allowed Request Origins + +Action Cable will only accept requests from specific origins. + +By default, only an origin matching the cable server itself will be permitted. +Additional origins can be specified using strings or regular expressions, provided in an array. + +```ruby +Rails.application.config.action_cable.allowed_request_origins = ['http://rubyonrails.com', /http:\/\/ruby.*/] +``` + +When running in the development environment, this defaults to "http://localhost:3000". + +To disable protection and allow requests from any origin: + +```ruby +Rails.application.config.action_cable.disable_request_forgery_protection = true +``` + +To disable automatic access for same-origin requests, and strictly allow +only the configured origins: + +```ruby +Rails.application.config.action_cable.allow_same_origin_as_host = false +``` + +### Consumer Configuration + +Once you have decided how to run your cable server (see below), you must provide the server URL (or path) to your client-side setup. +There are two ways you can do this. + +The first is to simply pass it in when creating your consumer. For a standalone server, +this would be something like: `App.cable = ActionCable.createConsumer("ws://example.com:28080")`, and for an in-app server, +something like: `App.cable = ActionCable.createConsumer("/cable")`. + +The second option is to pass the server URL through the `action_cable_meta_tag` in your layout. +This uses a URL or path typically set via `config.action_cable.url` in the environment configuration files, or defaults to "/cable". + +This method is especially useful if your WebSocket URL might change between environments. If you host your production server via https, you will need to use the wss scheme +for your Action Cable server, but development might remain http and use the ws scheme. You might use localhost in development and your +domain in production. + +In any case, to vary the WebSocket URL between environments, add the following configuration to each environment: + +```ruby +config.action_cable.url = "ws://example.com:28080" +``` + +Then add the following line to your layout before your JavaScript tag: + +```erb +<%= action_cable_meta_tag %> +``` + +And finally, create your consumer like so: + +```coffeescript +App.cable = ActionCable.createConsumer() +``` + +### Other Configurations + +The other common option to configure is the log tags applied to the per-connection logger. Here's an example that uses the user account id if available, else "no-account" while tagging: + +```ruby +config.action_cable.log_tags = [ + -> request { request.env['user_account_id'] || "no-account" }, + :action_cable, + -> request { request.uuid } +] +``` + +For a full list of all configuration options, see the `ActionCable::Server::Configuration` class. + +Also note that your server must provide at least the same number of database connections as you have workers. The default worker pool is set to 4, so that means you have to make at least that available. You can change that in `config/database.yml` through the `pool` attribute. + + +## Running the cable server + +### Standalone +The cable server(s) is separated from your normal application server. It's still a Rack application, but it is its own Rack +application. The recommended basic setup is as follows: + +```ruby +# cable/config.ru +require_relative '../config/environment' +Rails.application.eager_load! + +run ActionCable.server +``` + +Then you start the server using a binstub in bin/cable ala: +```sh +#!/bin/bash +bundle exec puma -p 28080 cable/config.ru +``` + +The above will start a cable server on port 28080. + +### In app + +If you are using a server that supports the [Rack socket hijacking API](http://www.rubydoc.info/github/rack/rack/file/SPEC#Hijacking), Action Cable can run alongside your Rails application. For example, to listen for WebSocket requests on `/websocket`, specify that path to `config.action_cable.mount_path`: + +```ruby +# config/application.rb +class Application < Rails::Application + config.action_cable.mount_path = '/websocket' +end +``` + +For every instance of your server you create and for every worker your server spawns, you will also have a new instance of Action Cable, but the use of Redis keeps messages synced across connections. + +### Notes + +Beware that currently, the cable server will _not_ auto-reload any changes in the framework. As we've discussed, long-running cable connections mean long-running objects. We don't yet have a way of reloading the classes of those objects in a safe manner. So when you change your channels, or the model your channels use, you must restart the cable server. + +We'll get all this abstracted properly when the framework is integrated into Rails. + +The WebSocket server doesn't have access to the session, but it has access to the cookies. This can be used when you need to handle authentication. You can see one way of doing that with Devise in this [article](http://www.rubytutorial.io/actioncable-devise-authentication). + +## Dependencies + +Action Cable provides a subscription adapter interface to process its pubsub internals. By default, asynchronous, inline, PostgreSQL, and Redis adapters are included. The default adapter in new Rails applications is the asynchronous (`async`) adapter. To create your own adapter, you can look at `ActionCable::SubscriptionAdapter::Base` for all methods that must be implemented, and any of the adapters included within Action Cable as example implementations. + +The Ruby side of things is built on top of [websocket-driver](https://github.com/faye/websocket-driver-ruby), [nio4r](https://github.com/celluloid/nio4r), and [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby). + + +## Deployment + +Action Cable is powered by a combination of WebSockets and threads. All of the +connection management is handled internally by utilizing Ruby's native thread +support, which means you can use all your regular Rails models with no problems +as long as you haven't committed any thread-safety sins. + +The Action Cable server does _not_ need to be a multi-threaded application server. +This is because Action Cable uses the [Rack socket hijacking API](http://www.rubydoc.info/github/rack/rack/file/SPEC#Hijacking) +to take over control of connections from the application server. Action Cable +then manages connections internally, in a multithreaded manner, regardless of +whether the application server is multi-threaded or not. So Action Cable works +with all the popular application servers -- Unicorn, Puma and Passenger. + +Action Cable does not work with WEBrick, because WEBrick does not support the +Rack socket hijacking API. + +## Frontend assets + +Action Cable's frontend assets are distributed through two channels: the +official gem and npm package, both titled `actioncable`. + +### Gem usage + +Through the `actioncable` gem, Action Cable's frontend assets are +available through the Rails Asset Pipeline. Create a `cable.js` or +`cable.coffee` file (this is automatically done for you with Rails +generators), and then simply require the assets: + +In JavaScript... + +```javascript +//= require action_cable +``` + +... and in CoffeeScript: + +```coffeescript +#= require action_cable +``` + +### npm usage + +In addition to being available through the `actioncable` gem, Action Cable's +frontend JS assets are also bundled in an officially supported npm module, +intended for usage in standalone frontend applications that communicate with a +Rails application. A common use case for this could be if you have a decoupled +frontend application written in React, Ember.js, etc. and want to add real-time +WebSocket functionality. + +### Installation + +``` +npm install actioncable --save +``` + +### Usage + +The `ActionCable` constant is available as a `require`-able module, so +you only have to require the package to gain access to the API that is +provided. + +In JavaScript... + +```javascript +ActionCable = require('actioncable') + +var cable = ActionCable.createConsumer('wss://RAILS-API-PATH.com/cable') + +cable.subscriptions.create('AppearanceChannel', { + // normal channel code goes here... +}); +``` + +and in CoffeeScript... + +```coffeescript +ActionCable = require('actioncable') + +cable = ActionCable.createConsumer('wss://RAILS-API-PATH.com/cable') + +cable.subscriptions.create 'AppearanceChannel', + # normal channel code goes here... +``` + +## Download and Installation + +The latest version of Action Cable can be installed with [RubyGems](#gem-usage), +or with [npm](#npm-usage). + +Source code can be downloaded as part of the Rails project on GitHub + +* https://github.com/rails/rails/tree/5-2-stable/actioncable + +## License + +Action Cable is released under the MIT license: + +* https://opensource.org/licenses/MIT + + +## Support + +API documentation is at: + +* http://api.rubyonrails.org + +Bug reports for the Ruby on Rails project can be filed here: + +* https://github.com/rails/rails/issues + +Feature requests should be discussed on the rails-core mailing list here: + +* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable.rb new file mode 100644 index 00000000..e7456e3c --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +#-- +# Copyright (c) 2015-2018 Basecamp, LLC +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ + +require "active_support" +require "active_support/rails" +require "action_cable/version" + +module ActionCable + extend ActiveSupport::Autoload + + INTERNAL = { + message_types: { + welcome: "welcome".freeze, + ping: "ping".freeze, + confirmation: "confirm_subscription".freeze, + rejection: "reject_subscription".freeze + }, + default_mount_path: "/cable".freeze, + protocols: ["actioncable-v1-json".freeze, "actioncable-unsupported".freeze].freeze + } + + # Singleton instance of the server + module_function def server + @server ||= ActionCable::Server::Base.new + end + + autoload :Server + autoload :Connection + autoload :Channel + autoload :RemoteConnections + autoload :SubscriptionAdapter +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/channel.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/channel.rb new file mode 100644 index 00000000..d2f6fbbb --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/channel.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module ActionCable + module Channel + extend ActiveSupport::Autoload + + eager_autoload do + autoload :Base + autoload :Broadcasting + autoload :Callbacks + autoload :Naming + autoload :PeriodicTimers + autoload :Streams + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/channel/base.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/channel/base.rb new file mode 100644 index 00000000..c5ad749b --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/channel/base.rb @@ -0,0 +1,305 @@ +# frozen_string_literal: true + +require "set" + +module ActionCable + module Channel + # The channel provides the basic structure of grouping behavior into logical units when communicating over the WebSocket connection. + # You can think of a channel like a form of controller, but one that's capable of pushing content to the subscriber in addition to simply + # responding to the subscriber's direct requests. + # + # Channel instances are long-lived. A channel object will be instantiated when the cable consumer becomes a subscriber, and then + # lives until the consumer disconnects. This may be seconds, minutes, hours, or even days. That means you have to take special care + # not to do anything silly in a channel that would balloon its memory footprint or whatever. The references are forever, so they won't be released + # as is normally the case with a controller instance that gets thrown away after every request. + # + # Long-lived channels (and connections) also mean you're responsible for ensuring that the data is fresh. If you hold a reference to a user + # record, but the name is changed while that reference is held, you may be sending stale data if you don't take precautions to avoid it. + # + # The upside of long-lived channel instances is that you can use instance variables to keep reference to objects that future subscriber requests + # can interact with. Here's a quick example: + # + # class ChatChannel < ApplicationCable::Channel + # def subscribed + # @room = Chat::Room[params[:room_number]] + # end + # + # def speak(data) + # @room.speak data, user: current_user + # end + # end + # + # The #speak action simply uses the Chat::Room object that was created when the channel was first subscribed to by the consumer when that + # subscriber wants to say something in the room. + # + # == Action processing + # + # Unlike subclasses of ActionController::Base, channels do not follow a RESTful + # constraint form for their actions. Instead, Action Cable operates through a + # remote-procedure call model. You can declare any public method on the + # channel (optionally taking a data argument), and this method is + # automatically exposed as callable to the client. + # + # Example: + # + # class AppearanceChannel < ApplicationCable::Channel + # def subscribed + # @connection_token = generate_connection_token + # end + # + # def unsubscribed + # current_user.disappear @connection_token + # end + # + # def appear(data) + # current_user.appear @connection_token, on: data['appearing_on'] + # end + # + # def away + # current_user.away @connection_token + # end + # + # private + # def generate_connection_token + # SecureRandom.hex(36) + # end + # end + # + # In this example, the subscribed and unsubscribed methods are not callable methods, as they + # were already declared in ActionCable::Channel::Base, but #appear + # and #away are. #generate_connection_token is also not + # callable, since it's a private method. You'll see that appear accepts a data + # parameter, which it then uses as part of its model call. #away + # does not, since it's simply a trigger action. + # + # Also note that in this example, current_user is available because + # it was marked as an identifying attribute on the connection. All such + # identifiers will automatically create a delegation method of the same name + # on the channel instance. + # + # == Rejecting subscription requests + # + # A channel can reject a subscription request in the #subscribed callback by + # invoking the #reject method: + # + # class ChatChannel < ApplicationCable::Channel + # def subscribed + # @room = Chat::Room[params[:room_number]] + # reject unless current_user.can_access?(@room) + # end + # end + # + # In this example, the subscription will be rejected if the + # current_user does not have access to the chat room. On the + # client-side, the Channel#rejected callback will get invoked when + # the server rejects the subscription request. + class Base + include Callbacks + include PeriodicTimers + include Streams + include Naming + include Broadcasting + + attr_reader :params, :connection, :identifier + delegate :logger, to: :connection + + class << self + # A list of method names that should be considered actions. This + # includes all public instance methods on a channel, less + # any internal methods (defined on Base), adding back in + # any methods that are internal, but still exist on the class + # itself. + # + # ==== Returns + # * Set - A set of all methods that should be considered actions. + def action_methods + @action_methods ||= begin + # All public instance methods of this class, including ancestors + methods = (public_instance_methods(true) - + # Except for public instance methods of Base and its ancestors + ActionCable::Channel::Base.public_instance_methods(true) + + # Be sure to include shadowed public instance methods of this class + public_instance_methods(false)).uniq.map(&:to_s) + methods.to_set + end + end + + private + # action_methods are cached and there is sometimes need to refresh + # them. ::clear_action_methods! allows you to do that, so next time + # you run action_methods, they will be recalculated. + def clear_action_methods! # :doc: + @action_methods = nil + end + + # Refresh the cached action_methods when a new action_method is added. + def method_added(name) # :doc: + super + clear_action_methods! + end + end + + def initialize(connection, identifier, params = {}) + @connection = connection + @identifier = identifier + @params = params + + # When a channel is streaming via pubsub, we want to delay the confirmation + # transmission until pubsub subscription is confirmed. + # + # The counter starts at 1 because it's awaiting a call to #subscribe_to_channel + @defer_subscription_confirmation_counter = Concurrent::AtomicFixnum.new(1) + + @reject_subscription = nil + @subscription_confirmation_sent = nil + + delegate_connection_identifiers + end + + # Extract the action name from the passed data and process it via the channel. The process will ensure + # that the action requested is a public method on the channel declared by the user (so not one of the callbacks + # like #subscribed). + def perform_action(data) + action = extract_action(data) + + if processable_action?(action) + payload = { channel_class: self.class.name, action: action, data: data } + ActiveSupport::Notifications.instrument("perform_action.action_cable", payload) do + dispatch_action(action, data) + end + else + logger.error "Unable to process #{action_signature(action, data)}" + end + end + + # This method is called after subscription has been added to the connection + # and confirms or rejects the subscription. + def subscribe_to_channel + run_callbacks :subscribe do + subscribed + end + + reject_subscription if subscription_rejected? + ensure_confirmation_sent + end + + # Called by the cable connection when it's cut, so the channel has a chance to cleanup with callbacks. + # This method is not intended to be called directly by the user. Instead, overwrite the #unsubscribed callback. + def unsubscribe_from_channel # :nodoc: + run_callbacks :unsubscribe do + unsubscribed + end + end + + private + # Called once a consumer has become a subscriber of the channel. Usually the place to setup any streams + # you want this channel to be sending to the subscriber. + def subscribed # :doc: + # Override in subclasses + end + + # Called once a consumer has cut its cable connection. Can be used for cleaning up connections or marking + # users as offline or the like. + def unsubscribed # :doc: + # Override in subclasses + end + + # Transmit a hash of data to the subscriber. The hash will automatically be wrapped in a JSON envelope with + # the proper channel identifier marked as the recipient. + def transmit(data, via: nil) # :doc: + status = "#{self.class.name} transmitting #{data.inspect.truncate(300)}" + status += " (via #{via})" if via + logger.debug(status) + + payload = { channel_class: self.class.name, data: data, via: via } + ActiveSupport::Notifications.instrument("transmit.action_cable", payload) do + connection.transmit identifier: @identifier, message: data + end + end + + def ensure_confirmation_sent # :doc: + return if subscription_rejected? + @defer_subscription_confirmation_counter.decrement + transmit_subscription_confirmation unless defer_subscription_confirmation? + end + + def defer_subscription_confirmation! # :doc: + @defer_subscription_confirmation_counter.increment + end + + def defer_subscription_confirmation? # :doc: + @defer_subscription_confirmation_counter.value > 0 + end + + def subscription_confirmation_sent? # :doc: + @subscription_confirmation_sent + end + + def reject # :doc: + @reject_subscription = true + end + + def subscription_rejected? # :doc: + @reject_subscription + end + + def delegate_connection_identifiers + connection.identifiers.each do |identifier| + define_singleton_method(identifier) do + connection.send(identifier) + end + end + end + + def extract_action(data) + (data["action"].presence || :receive).to_sym + end + + def processable_action?(action) + self.class.action_methods.include?(action.to_s) unless subscription_rejected? + end + + def dispatch_action(action, data) + logger.info action_signature(action, data) + + if method(action).arity == 1 + public_send action, data + else + public_send action + end + end + + def action_signature(action, data) + "#{self.class.name}##{action}".dup.tap do |signature| + if (arguments = data.except("action")).any? + signature << "(#{arguments.inspect})" + end + end + end + + def transmit_subscription_confirmation + unless subscription_confirmation_sent? + logger.info "#{self.class.name} is transmitting the subscription confirmation" + + ActiveSupport::Notifications.instrument("transmit_subscription_confirmation.action_cable", channel_class: self.class.name) do + connection.transmit identifier: @identifier, type: ActionCable::INTERNAL[:message_types][:confirmation] + @subscription_confirmation_sent = true + end + end + end + + def reject_subscription + connection.subscriptions.remove_subscription self + transmit_subscription_rejection + end + + def transmit_subscription_rejection + logger.info "#{self.class.name} is transmitting the subscription rejection" + + ActiveSupport::Notifications.instrument("transmit_subscription_rejection.action_cable", channel_class: self.class.name) do + connection.transmit identifier: @identifier, type: ActionCable::INTERNAL[:message_types][:rejection] + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/channel/broadcasting.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/channel/broadcasting.rb new file mode 100644 index 00000000..9a96720f --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/channel/broadcasting.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/to_param" + +module ActionCable + module Channel + module Broadcasting + extend ActiveSupport::Concern + + delegate :broadcasting_for, to: :class + + module ClassMethods + # Broadcast a hash to a unique broadcasting for this model in this channel. + def broadcast_to(model, message) + ActionCable.server.broadcast(broadcasting_for([ channel_name, model ]), message) + end + + def broadcasting_for(model) #:nodoc: + case + when model.is_a?(Array) + model.map { |m| broadcasting_for(m) }.join(":") + when model.respond_to?(:to_gid_param) + model.to_gid_param + else + model.to_param + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/channel/callbacks.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/channel/callbacks.rb new file mode 100644 index 00000000..e4cb19b2 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/channel/callbacks.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require "active_support/callbacks" + +module ActionCable + module Channel + module Callbacks + extend ActiveSupport::Concern + include ActiveSupport::Callbacks + + included do + define_callbacks :subscribe + define_callbacks :unsubscribe + end + + module ClassMethods + def before_subscribe(*methods, &block) + set_callback(:subscribe, :before, *methods, &block) + end + + def after_subscribe(*methods, &block) + set_callback(:subscribe, :after, *methods, &block) + end + alias_method :on_subscribe, :after_subscribe + + def before_unsubscribe(*methods, &block) + set_callback(:unsubscribe, :before, *methods, &block) + end + + def after_unsubscribe(*methods, &block) + set_callback(:unsubscribe, :after, *methods, &block) + end + alias_method :on_unsubscribe, :after_unsubscribe + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/channel/naming.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/channel/naming.rb new file mode 100644 index 00000000..9c324a2a --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/channel/naming.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module ActionCable + module Channel + module Naming + extend ActiveSupport::Concern + + module ClassMethods + # Returns the name of the channel, underscored, without the Channel ending. + # If the channel is in a namespace, then the namespaces are represented by single + # colon separators in the channel name. + # + # ChatChannel.channel_name # => 'chat' + # Chats::AppearancesChannel.channel_name # => 'chats:appearances' + # FooChats::BarAppearancesChannel.channel_name # => 'foo_chats:bar_appearances' + def channel_name + @channel_name ||= name.sub(/Channel$/, "").gsub("::", ":").underscore + end + end + + # Delegates to the class' channel_name + delegate :channel_name, to: :class + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/channel/periodic_timers.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/channel/periodic_timers.rb new file mode 100644 index 00000000..830b3efa --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/channel/periodic_timers.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +module ActionCable + module Channel + module PeriodicTimers + extend ActiveSupport::Concern + + included do + class_attribute :periodic_timers, instance_reader: false, default: [] + + after_subscribe :start_periodic_timers + after_unsubscribe :stop_periodic_timers + end + + module ClassMethods + # Periodically performs a task on the channel, like updating an online + # user counter, polling a backend for new status messages, sending + # regular "heartbeat" messages, or doing some internal work and giving + # progress updates. + # + # Pass a method name or lambda argument or provide a block to call. + # Specify the calling period in seconds using the every: + # keyword argument. + # + # periodically :transmit_progress, every: 5.seconds + # + # periodically every: 3.minutes do + # transmit action: :update_count, count: current_count + # end + # + def periodically(callback_or_method_name = nil, every:, &block) + callback = + if block_given? + raise ArgumentError, "Pass a block or provide a callback arg, not both" if callback_or_method_name + block + else + case callback_or_method_name + when Proc + callback_or_method_name + when Symbol + -> { __send__ callback_or_method_name } + else + raise ArgumentError, "Expected a Symbol method name or a Proc, got #{callback_or_method_name.inspect}" + end + end + + unless every.kind_of?(Numeric) && every > 0 + raise ArgumentError, "Expected every: to be a positive number of seconds, got #{every.inspect}" + end + + self.periodic_timers += [[ callback, every: every ]] + end + end + + private + def active_periodic_timers + @active_periodic_timers ||= [] + end + + def start_periodic_timers + self.class.periodic_timers.each do |callback, options| + active_periodic_timers << start_periodic_timer(callback, every: options.fetch(:every)) + end + end + + def start_periodic_timer(callback, every:) + connection.server.event_loop.timer every do + connection.worker_pool.async_exec self, connection: connection, &callback + end + end + + def stop_periodic_timers + active_periodic_timers.each { |timer| timer.shutdown } + active_periodic_timers.clear + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/channel/streams.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/channel/streams.rb new file mode 100644 index 00000000..81c2c380 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/channel/streams.rb @@ -0,0 +1,176 @@ +# frozen_string_literal: true + +module ActionCable + module Channel + # Streams allow channels to route broadcastings to the subscriber. A broadcasting is, as discussed elsewhere, a pubsub queue where any data + # placed into it is automatically sent to the clients that are connected at that time. It's purely an online queue, though. If you're not + # streaming a broadcasting at the very moment it sends out an update, you will not get that update, even if you connect after it has been sent. + # + # Most commonly, the streamed broadcast is sent straight to the subscriber on the client-side. The channel just acts as a connector between + # the two parties (the broadcaster and the channel subscriber). Here's an example of a channel that allows subscribers to get all new + # comments on a given page: + # + # class CommentsChannel < ApplicationCable::Channel + # def follow(data) + # stream_from "comments_for_#{data['recording_id']}" + # end + # + # def unfollow + # stop_all_streams + # end + # end + # + # Based on the above example, the subscribers of this channel will get whatever data is put into the, + # let's say, comments_for_45 broadcasting as soon as it's put there. + # + # An example broadcasting for this channel looks like so: + # + # ActionCable.server.broadcast "comments_for_45", author: 'DHH', content: 'Rails is just swell' + # + # If you have a stream that is related to a model, then the broadcasting used can be generated from the model and channel. + # The following example would subscribe to a broadcasting like comments:Z2lkOi8vVGVzdEFwcC9Qb3N0LzE. + # + # class CommentsChannel < ApplicationCable::Channel + # def subscribed + # post = Post.find(params[:id]) + # stream_for post + # end + # end + # + # You can then broadcast to this channel using: + # + # CommentsChannel.broadcast_to(@post, @comment) + # + # If you don't just want to parlay the broadcast unfiltered to the subscriber, you can also supply a callback that lets you alter what is sent out. + # The below example shows how you can use this to provide performance introspection in the process: + # + # class ChatChannel < ApplicationCable::Channel + # def subscribed + # @room = Chat::Room[params[:room_number]] + # + # stream_for @room, coder: ActiveSupport::JSON do |message| + # if message['originated_at'].present? + # elapsed_time = (Time.now.to_f - message['originated_at']).round(2) + # + # ActiveSupport::Notifications.instrument :performance, measurement: 'Chat.message_delay', value: elapsed_time, action: :timing + # logger.info "Message took #{elapsed_time}s to arrive" + # end + # + # transmit message + # end + # end + # end + # + # You can stop streaming from all broadcasts by calling #stop_all_streams. + module Streams + extend ActiveSupport::Concern + + included do + on_unsubscribe :stop_all_streams + end + + # Start streaming from the named broadcasting pubsub queue. Optionally, you can pass a callback that'll be used + # instead of the default of just transmitting the updates straight to the subscriber. + # Pass coder: ActiveSupport::JSON to decode messages as JSON before passing to the callback. + # Defaults to coder: nil which does no decoding, passes raw messages. + def stream_from(broadcasting, callback = nil, coder: nil, &block) + broadcasting = String(broadcasting) + + # Don't send the confirmation until pubsub#subscribe is successful + defer_subscription_confirmation! + + # Build a stream handler by wrapping the user-provided callback with + # a decoder or defaulting to a JSON-decoding retransmitter. + handler = worker_pool_stream_handler(broadcasting, callback || block, coder: coder) + streams << [ broadcasting, handler ] + + connection.server.event_loop.post do + pubsub.subscribe(broadcasting, handler, lambda do + ensure_confirmation_sent + logger.info "#{self.class.name} is streaming from #{broadcasting}" + end) + end + end + + # Start streaming the pubsub queue for the model in this channel. Optionally, you can pass a + # callback that'll be used instead of the default of just transmitting the updates straight + # to the subscriber. + # + # Pass coder: ActiveSupport::JSON to decode messages as JSON before passing to the callback. + # Defaults to coder: nil which does no decoding, passes raw messages. + def stream_for(model, callback = nil, coder: nil, &block) + stream_from(broadcasting_for([ channel_name, model ]), callback || block, coder: coder) + end + + # Unsubscribes all streams associated with this channel from the pubsub queue. + def stop_all_streams + streams.each do |broadcasting, callback| + pubsub.unsubscribe broadcasting, callback + logger.info "#{self.class.name} stopped streaming from #{broadcasting}" + end.clear + end + + private + delegate :pubsub, to: :connection + + def streams + @_streams ||= [] + end + + # Always wrap the outermost handler to invoke the user handler on the + # worker pool rather than blocking the event loop. + def worker_pool_stream_handler(broadcasting, user_handler, coder: nil) + handler = stream_handler(broadcasting, user_handler, coder: coder) + + -> message do + connection.worker_pool.async_invoke handler, :call, message, connection: connection + end + end + + # May be overridden to add instrumentation, logging, specialized error + # handling, or other forms of handler decoration. + # + # TODO: Tests demonstrating this. + def stream_handler(broadcasting, user_handler, coder: nil) + if user_handler + stream_decoder user_handler, coder: coder + else + default_stream_handler broadcasting, coder: coder + end + end + + # May be overridden to change the default stream handling behavior + # which decodes JSON and transmits to the client. + # + # TODO: Tests demonstrating this. + # + # TODO: Room for optimization. Update transmit API to be coder-aware + # so we can no-op when pubsub and connection are both JSON-encoded. + # Then we can skip decode+encode if we're just proxying messages. + def default_stream_handler(broadcasting, coder:) + coder ||= ActiveSupport::JSON + stream_transmitter stream_decoder(coder: coder), broadcasting: broadcasting + end + + def stream_decoder(handler = identity_handler, coder:) + if coder + -> message { handler.(coder.decode(message)) } + else + handler + end + end + + def stream_transmitter(handler = identity_handler, broadcasting:) + via = "streamed from #{broadcasting}" + + -> (message) do + transmit handler.(message), via: via + end + end + + def identity_handler + -> message { message } + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection.rb new file mode 100644 index 00000000..804b89a7 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module ActionCable + module Connection + extend ActiveSupport::Autoload + + eager_autoload do + autoload :Authorization + autoload :Base + autoload :ClientSocket + autoload :Identification + autoload :InternalChannel + autoload :MessageBuffer + autoload :Stream + autoload :StreamEventLoop + autoload :Subscriptions + autoload :TaggedLoggerProxy + autoload :WebSocket + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/authorization.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/authorization.rb new file mode 100644 index 00000000..a22179d9 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/authorization.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActionCable + module Connection + module Authorization + class UnauthorizedError < StandardError; end + + # Closes the \WebSocket connection if it is open and returns a 404 "File not Found" response. + def reject_unauthorized_connection + logger.error "An unauthorized connection attempt was rejected" + raise UnauthorizedError + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/base.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/base.rb new file mode 100644 index 00000000..84053db9 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/base.rb @@ -0,0 +1,260 @@ +# frozen_string_literal: true + +require "action_dispatch" + +module ActionCable + module Connection + # For every WebSocket connection the Action Cable server accepts, a Connection object will be instantiated. This instance becomes the parent + # of all of the channel subscriptions that are created from there on. Incoming messages are then routed to these channel subscriptions + # based on an identifier sent by the Action Cable consumer. The Connection itself does not deal with any specific application logic beyond + # authentication and authorization. + # + # Here's a basic example: + # + # module ApplicationCable + # class Connection < ActionCable::Connection::Base + # identified_by :current_user + # + # def connect + # self.current_user = find_verified_user + # logger.add_tags current_user.name + # end + # + # def disconnect + # # Any cleanup work needed when the cable connection is cut. + # end + # + # private + # def find_verified_user + # User.find_by_identity(cookies.encrypted[:identity_id]) || + # reject_unauthorized_connection + # end + # end + # end + # + # First, we declare that this connection can be identified by its current_user. This allows us to later be able to find all connections + # established for that current_user (and potentially disconnect them). You can declare as many + # identification indexes as you like. Declaring an identification means that an attr_accessor is automatically set for that key. + # + # Second, we rely on the fact that the WebSocket connection is established with the cookies from the domain being sent along. This makes + # it easy to use signed cookies that were set when logging in via a web interface to authorize the WebSocket connection. + # + # Finally, we add a tag to the connection-specific logger with the name of the current user to easily distinguish their messages in the log. + # + # Pretty simple, eh? + class Base + include Identification + include InternalChannel + include Authorization + + attr_reader :server, :env, :subscriptions, :logger, :worker_pool, :protocol + delegate :event_loop, :pubsub, to: :server + + def initialize(server, env, coder: ActiveSupport::JSON) + @server, @env, @coder = server, env, coder + + @worker_pool = server.worker_pool + @logger = new_tagged_logger + + @websocket = ActionCable::Connection::WebSocket.new(env, self, event_loop) + @subscriptions = ActionCable::Connection::Subscriptions.new(self) + @message_buffer = ActionCable::Connection::MessageBuffer.new(self) + + @_internal_subscriptions = nil + @started_at = Time.now + end + + # Called by the server when a new WebSocket connection is established. This configures the callbacks intended for overwriting by the user. + # This method should not be called directly -- instead rely upon on the #connect (and #disconnect) callbacks. + def process #:nodoc: + logger.info started_request_message + + if websocket.possible? && allow_request_origin? + respond_to_successful_request + else + respond_to_invalid_request + end + end + + # Decodes WebSocket messages and dispatches them to subscribed channels. + # WebSocket message transfer encoding is always JSON. + def receive(websocket_message) #:nodoc: + send_async :dispatch_websocket_message, websocket_message + end + + def dispatch_websocket_message(websocket_message) #:nodoc: + if websocket.alive? + subscriptions.execute_command decode(websocket_message) + else + logger.error "Ignoring message processed after the WebSocket was closed: #{websocket_message.inspect})" + end + end + + def transmit(cable_message) # :nodoc: + websocket.transmit encode(cable_message) + end + + # Close the WebSocket connection. + def close + websocket.close + end + + # Invoke a method on the connection asynchronously through the pool of thread workers. + def send_async(method, *arguments) + worker_pool.async_invoke(self, method, *arguments) + end + + # Return a basic hash of statistics for the connection keyed with identifier, started_at, subscriptions, and request_id. + # This can be returned by a health check against the connection. + def statistics + { + identifier: connection_identifier, + started_at: @started_at, + subscriptions: subscriptions.identifiers, + request_id: @env["action_dispatch.request_id"] + } + end + + def beat + transmit type: ActionCable::INTERNAL[:message_types][:ping], message: Time.now.to_i + end + + def on_open # :nodoc: + send_async :handle_open + end + + def on_message(message) # :nodoc: + message_buffer.append message + end + + def on_error(message) # :nodoc: + # log errors to make diagnosing socket errors easier + logger.error "WebSocket error occurred: #{message}" + end + + def on_close(reason, code) # :nodoc: + send_async :handle_close + end + + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected + attr_reader :websocket + attr_reader :message_buffer + + private + # The request that initiated the WebSocket connection is available here. This gives access to the environment, cookies, etc. + def request # :doc: + @request ||= begin + environment = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application + ActionDispatch::Request.new(environment || env) + end + end + + # The cookies of the request that initiated the WebSocket connection. Useful for performing authorization checks. + def cookies # :doc: + request.cookie_jar + end + + def encode(cable_message) + @coder.encode cable_message + end + + def decode(websocket_message) + @coder.decode websocket_message + end + + def handle_open + @protocol = websocket.protocol + connect if respond_to?(:connect) + subscribe_to_internal_channel + send_welcome_message + + message_buffer.process! + server.add_connection(self) + rescue ActionCable::Connection::Authorization::UnauthorizedError + respond_to_invalid_request + end + + def handle_close + logger.info finished_request_message + + server.remove_connection(self) + + subscriptions.unsubscribe_from_all + unsubscribe_from_internal_channel + + disconnect if respond_to?(:disconnect) + end + + def send_welcome_message + # Send welcome message to the internal connection monitor channel. + # This ensures the connection monitor state is reset after a successful + # websocket connection. + transmit type: ActionCable::INTERNAL[:message_types][:welcome] + end + + def allow_request_origin? + return true if server.config.disable_request_forgery_protection + + proto = Rack::Request.new(env).ssl? ? "https" : "http" + if server.config.allow_same_origin_as_host && env["HTTP_ORIGIN"] == "#{proto}://#{env['HTTP_HOST']}" + true + elsif Array(server.config.allowed_request_origins).any? { |allowed_origin| allowed_origin === env["HTTP_ORIGIN"] } + true + else + logger.error("Request origin not allowed: #{env['HTTP_ORIGIN']}") + false + end + end + + def respond_to_successful_request + logger.info successful_request_message + websocket.rack_response + end + + def respond_to_invalid_request + close if websocket.alive? + + logger.error invalid_request_message + logger.info finished_request_message + [ 404, { "Content-Type" => "text/plain" }, [ "Page not found" ] ] + end + + # Tags are declared in the server but computed in the connection. This allows us per-connection tailored tags. + def new_tagged_logger + TaggedLoggerProxy.new server.logger, + tags: server.config.log_tags.map { |tag| tag.respond_to?(:call) ? tag.call(request) : tag.to_s.camelize } + end + + def started_request_message + 'Started %s "%s"%s for %s at %s' % [ + request.request_method, + request.filtered_path, + websocket.possible? ? " [WebSocket]" : "[non-WebSocket]", + request.ip, + Time.now.to_s ] + end + + def finished_request_message + 'Finished "%s"%s for %s at %s' % [ + request.filtered_path, + websocket.possible? ? " [WebSocket]" : "[non-WebSocket]", + request.ip, + Time.now.to_s ] + end + + def invalid_request_message + "Failed to upgrade to WebSocket (REQUEST_METHOD: %s, HTTP_CONNECTION: %s, HTTP_UPGRADE: %s)" % [ + env["REQUEST_METHOD"], env["HTTP_CONNECTION"], env["HTTP_UPGRADE"] + ] + end + + def successful_request_message + "Successfully upgraded to WebSocket (REQUEST_METHOD: %s, HTTP_CONNECTION: %s, HTTP_UPGRADE: %s)" % [ + env["REQUEST_METHOD"], env["HTTP_CONNECTION"], env["HTTP_UPGRADE"] + ] + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/client_socket.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/client_socket.rb new file mode 100644 index 00000000..4b1964c4 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/client_socket.rb @@ -0,0 +1,157 @@ +# frozen_string_literal: true + +require "websocket/driver" + +module ActionCable + module Connection + #-- + # This class is heavily based on faye-websocket-ruby + # + # Copyright (c) 2010-2015 James Coglan + class ClientSocket # :nodoc: + def self.determine_url(env) + scheme = secure_request?(env) ? "wss:" : "ws:" + "#{ scheme }//#{ env['HTTP_HOST'] }#{ env['REQUEST_URI'] }" + end + + def self.secure_request?(env) + return true if env["HTTPS"] == "on" + return true if env["HTTP_X_FORWARDED_SSL"] == "on" + return true if env["HTTP_X_FORWARDED_SCHEME"] == "https" + return true if env["HTTP_X_FORWARDED_PROTO"] == "https" + return true if env["rack.url_scheme"] == "https" + + false + end + + CONNECTING = 0 + OPEN = 1 + CLOSING = 2 + CLOSED = 3 + + attr_reader :env, :url + + def initialize(env, event_target, event_loop, protocols) + @env = env + @event_target = event_target + @event_loop = event_loop + + @url = ClientSocket.determine_url(@env) + + @driver = @driver_started = nil + @close_params = ["", 1006] + + @ready_state = CONNECTING + + # The driver calls +env+, +url+, and +write+ + @driver = ::WebSocket::Driver.rack(self, protocols: protocols) + + @driver.on(:open) { |e| open } + @driver.on(:message) { |e| receive_message(e.data) } + @driver.on(:close) { |e| begin_close(e.reason, e.code) } + @driver.on(:error) { |e| emit_error(e.message) } + + @stream = ActionCable::Connection::Stream.new(@event_loop, self) + end + + def start_driver + return if @driver.nil? || @driver_started + @stream.hijack_rack_socket + + if callback = @env["async.callback"] + callback.call([101, {}, @stream]) + end + + @driver_started = true + @driver.start + end + + def rack_response + start_driver + [ -1, {}, [] ] + end + + def write(data) + @stream.write(data) + rescue => e + emit_error e.message + end + + def transmit(message) + return false if @ready_state > OPEN + case message + when Numeric then @driver.text(message.to_s) + when String then @driver.text(message) + when Array then @driver.binary(message) + else false + end + end + + def close(code = nil, reason = nil) + code ||= 1000 + reason ||= "" + + unless code == 1000 || (code >= 3000 && code <= 4999) + raise ArgumentError, "Failed to execute 'close' on WebSocket: " \ + "The code must be either 1000, or between 3000 and 4999. " \ + "#{code} is neither." + end + + @ready_state = CLOSING unless @ready_state == CLOSED + @driver.close(reason, code) + end + + def parse(data) + @driver.parse(data) + end + + def client_gone + finalize_close + end + + def alive? + @ready_state == OPEN + end + + def protocol + @driver.protocol + end + + private + def open + return unless @ready_state == CONNECTING + @ready_state = OPEN + + @event_target.on_open + end + + def receive_message(data) + return unless @ready_state == OPEN + + @event_target.on_message(data) + end + + def emit_error(message) + return if @ready_state >= CLOSING + + @event_target.on_error(message) + end + + def begin_close(reason, code) + return if @ready_state == CLOSED + @ready_state = CLOSING + @close_params = [reason, code] + + @stream.shutdown if @stream + finalize_close + end + + def finalize_close + return if @ready_state == CLOSED + @ready_state = CLOSED + + @event_target.on_close(*@close_params) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/identification.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/identification.rb new file mode 100644 index 00000000..cc544685 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/identification.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require "set" + +module ActionCable + module Connection + module Identification + extend ActiveSupport::Concern + + included do + class_attribute :identifiers, default: Set.new + end + + module ClassMethods + # Mark a key as being a connection identifier index that can then be used to find the specific connection again later. + # Common identifiers are current_user and current_account, but could be anything, really. + # + # Note that anything marked as an identifier will automatically create a delegate by the same name on any + # channel instances created off the connection. + def identified_by(*identifiers) + Array(identifiers).each { |identifier| attr_accessor identifier } + self.identifiers += identifiers + end + end + + # Return a single connection identifier that combines the value of all the registered identifiers into a single gid. + def connection_identifier + unless defined? @connection_identifier + @connection_identifier = connection_gid identifiers.map { |id| instance_variable_get("@#{id}") }.compact + end + + @connection_identifier + end + + private + def connection_gid(ids) + ids.map do |o| + if o.respond_to? :to_gid_param + o.to_gid_param + else + o.to_s + end + end.sort.join(":") + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/internal_channel.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/internal_channel.rb new file mode 100644 index 00000000..f0390413 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/internal_channel.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module ActionCable + module Connection + # Makes it possible for the RemoteConnection to disconnect a specific connection. + module InternalChannel + extend ActiveSupport::Concern + + private + def internal_channel + "action_cable/#{connection_identifier}" + end + + def subscribe_to_internal_channel + if connection_identifier.present? + callback = -> (message) { process_internal_message decode(message) } + @_internal_subscriptions ||= [] + @_internal_subscriptions << [ internal_channel, callback ] + + server.event_loop.post { pubsub.subscribe(internal_channel, callback) } + logger.info "Registered connection (#{connection_identifier})" + end + end + + def unsubscribe_from_internal_channel + if @_internal_subscriptions.present? + @_internal_subscriptions.each { |channel, callback| server.event_loop.post { pubsub.unsubscribe(channel, callback) } } + end + end + + def process_internal_message(message) + case message["type"] + when "disconnect" + logger.info "Removing connection (#{connection_identifier})" + websocket.close + end + rescue Exception => e + logger.error "There was an exception - #{e.class}(#{e.message})" + logger.error e.backtrace.join("\n") + + close + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/message_buffer.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/message_buffer.rb new file mode 100644 index 00000000..f151a470 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/message_buffer.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module ActionCable + module Connection + # Allows us to buffer messages received from the WebSocket before the Connection has been fully initialized, and is ready to receive them. + class MessageBuffer # :nodoc: + def initialize(connection) + @connection = connection + @buffered_messages = [] + end + + def append(message) + if valid? message + if processing? + receive message + else + buffer message + end + else + connection.logger.error "Couldn't handle non-string message: #{message.class}" + end + end + + def processing? + @processing + end + + def process! + @processing = true + receive_buffered_messages + end + + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected + attr_reader :connection + attr_reader :buffered_messages + + private + def valid?(message) + message.is_a?(String) + end + + def receive(message) + connection.receive message + end + + def buffer(message) + buffered_messages << message + end + + def receive_buffered_messages + receive buffered_messages.shift until buffered_messages.empty? + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/stream.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/stream.rb new file mode 100644 index 00000000..4873026b --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/stream.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +require "thread" + +module ActionCable + module Connection + #-- + # This class is heavily based on faye-websocket-ruby + # + # Copyright (c) 2010-2015 James Coglan + class Stream # :nodoc: + def initialize(event_loop, socket) + @event_loop = event_loop + @socket_object = socket + @stream_send = socket.env["stream.send"] + + @rack_hijack_io = nil + @write_lock = Mutex.new + + @write_head = nil + @write_buffer = Queue.new + end + + def each(&callback) + @stream_send ||= callback + end + + def close + shutdown + @socket_object.client_gone + end + + def shutdown + clean_rack_hijack + end + + def write(data) + if @stream_send + return @stream_send.call(data) + end + + if @write_lock.try_lock + begin + if @write_head.nil? && @write_buffer.empty? + written = @rack_hijack_io.write_nonblock(data, exception: false) + + case written + when :wait_writable + # proceed below + when data.bytesize + return data.bytesize + else + @write_head = data.byteslice(written, data.bytesize) + @event_loop.writes_pending @rack_hijack_io + + return data.bytesize + end + end + ensure + @write_lock.unlock + end + end + + @write_buffer << data + @event_loop.writes_pending @rack_hijack_io + + data.bytesize + rescue EOFError, Errno::ECONNRESET + @socket_object.client_gone + end + + def flush_write_buffer + @write_lock.synchronize do + loop do + if @write_head.nil? + return true if @write_buffer.empty? + @write_head = @write_buffer.pop + end + + written = @rack_hijack_io.write_nonblock(@write_head, exception: false) + case written + when :wait_writable + return false + when @write_head.bytesize + @write_head = nil + else + @write_head = @write_head.byteslice(written, @write_head.bytesize) + return false + end + end + end + end + + def receive(data) + @socket_object.parse(data) + end + + def hijack_rack_socket + return unless @socket_object.env["rack.hijack"] + + @socket_object.env["rack.hijack"].call + @rack_hijack_io = @socket_object.env["rack.hijack_io"] + + @event_loop.attach(@rack_hijack_io, self) + end + + private + def clean_rack_hijack + return unless @rack_hijack_io + @event_loop.detach(@rack_hijack_io, self) + @rack_hijack_io = nil + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/stream_event_loop.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/stream_event_loop.rb new file mode 100644 index 00000000..d95afc50 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/stream_event_loop.rb @@ -0,0 +1,136 @@ +# frozen_string_literal: true + +require "nio" +require "thread" + +module ActionCable + module Connection + class StreamEventLoop + def initialize + @nio = @executor = @thread = nil + @map = {} + @stopping = false + @todo = Queue.new + + @spawn_mutex = Mutex.new + end + + def timer(interval, &block) + Concurrent::TimerTask.new(execution_interval: interval, &block).tap(&:execute) + end + + def post(task = nil, &block) + task ||= block + + spawn + @executor << task + end + + def attach(io, stream) + @todo << lambda do + @map[io] = @nio.register(io, :r) + @map[io].value = stream + end + wakeup + end + + def detach(io, stream) + @todo << lambda do + @nio.deregister io + @map.delete io + io.close + end + wakeup + end + + def writes_pending(io) + @todo << lambda do + if monitor = @map[io] + monitor.interests = :rw + end + end + wakeup + end + + def stop + @stopping = true + wakeup if @nio + end + + private + def spawn + return if @thread && @thread.status + + @spawn_mutex.synchronize do + return if @thread && @thread.status + + @nio ||= NIO::Selector.new + + @executor ||= Concurrent::ThreadPoolExecutor.new( + min_threads: 1, + max_threads: 10, + max_queue: 0, + ) + + @thread = Thread.new { run } + + return true + end + end + + def wakeup + spawn || @nio.wakeup + end + + def run + loop do + if @stopping + @nio.close + break + end + + until @todo.empty? + @todo.pop(true).call + end + + next unless monitors = @nio.select + + monitors.each do |monitor| + io = monitor.io + stream = monitor.value + + begin + if monitor.writable? + if stream.flush_write_buffer + monitor.interests = :r + end + next unless monitor.readable? + end + + incoming = io.read_nonblock(4096, exception: false) + case incoming + when :wait_readable + next + when nil + stream.close + else + stream.receive incoming + end + rescue + # We expect one of EOFError or Errno::ECONNRESET in + # normal operation (when the client goes away). But if + # anything else goes wrong, this is still the best way + # to handle it. + begin + stream.close + rescue + @nio.deregister io + @map.delete io + end + end + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/subscriptions.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/subscriptions.rb new file mode 100644 index 00000000..bb8d64e2 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/subscriptions.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/indifferent_access" + +module ActionCable + module Connection + # Collection class for all the channel subscriptions established on a given connection. Responsible for routing incoming commands that arrive on + # the connection to the proper channel. + class Subscriptions # :nodoc: + def initialize(connection) + @connection = connection + @subscriptions = {} + end + + def execute_command(data) + case data["command"] + when "subscribe" then add data + when "unsubscribe" then remove data + when "message" then perform_action data + else + logger.error "Received unrecognized command in #{data.inspect}" + end + rescue Exception => e + logger.error "Could not execute command from (#{data.inspect}) [#{e.class} - #{e.message}]: #{e.backtrace.first(5).join(" | ")}" + end + + def add(data) + id_key = data["identifier"] + id_options = ActiveSupport::JSON.decode(id_key).with_indifferent_access + + return if subscriptions.key?(id_key) + + subscription_klass = id_options[:channel].safe_constantize + + if subscription_klass && ActionCable::Channel::Base >= subscription_klass + subscription = subscription_klass.new(connection, id_key, id_options) + subscriptions[id_key] = subscription + subscription.subscribe_to_channel + else + logger.error "Subscription class not found: #{id_options[:channel].inspect}" + end + end + + def remove(data) + logger.info "Unsubscribing from channel: #{data['identifier']}" + remove_subscription find(data) + end + + def remove_subscription(subscription) + subscription.unsubscribe_from_channel + subscriptions.delete(subscription.identifier) + end + + def perform_action(data) + find(data).perform_action ActiveSupport::JSON.decode(data["data"]) + end + + def identifiers + subscriptions.keys + end + + def unsubscribe_from_all + subscriptions.each { |id, channel| remove_subscription(channel) } + end + + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected + attr_reader :connection, :subscriptions + + private + delegate :logger, to: :connection + + def find(data) + if subscription = subscriptions[data["identifier"]] + subscription + else + raise "Unable to find subscription with identifier: #{data['identifier']}" + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/tagged_logger_proxy.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/tagged_logger_proxy.rb new file mode 100644 index 00000000..85831806 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/tagged_logger_proxy.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module ActionCable + module Connection + # Allows the use of per-connection tags against the server logger. This wouldn't work using the traditional + # ActiveSupport::TaggedLogging enhanced Rails.logger, as that logger will reset the tags between requests. + # The connection is long-lived, so it needs its own set of tags for its independent duration. + class TaggedLoggerProxy + attr_reader :tags + + def initialize(logger, tags:) + @logger = logger + @tags = tags.flatten + end + + def add_tags(*tags) + @tags += tags.flatten + @tags = @tags.uniq + end + + def tag(logger) + if logger.respond_to?(:tagged) + current_tags = tags - logger.formatter.current_tags + logger.tagged(*current_tags) { yield } + else + yield + end + end + + %i( debug info warn error fatal unknown ).each do |severity| + define_method(severity) do |message| + log severity, message + end + end + + private + def log(type, message) # :doc: + tag(@logger) { @logger.send type, message } + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/web_socket.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/web_socket.rb new file mode 100644 index 00000000..81233ace --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/connection/web_socket.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require "websocket/driver" + +module ActionCable + module Connection + # Wrap the real socket to minimize the externally-presented API + class WebSocket # :nodoc: + def initialize(env, event_target, event_loop, protocols: ActionCable::INTERNAL[:protocols]) + @websocket = ::WebSocket::Driver.websocket?(env) ? ClientSocket.new(env, event_target, event_loop, protocols) : nil + end + + def possible? + websocket + end + + def alive? + websocket && websocket.alive? + end + + def transmit(data) + websocket.transmit data + end + + def close + websocket.close + end + + def protocol + websocket.protocol + end + + def rack_response + websocket.rack_response + end + + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected + attr_reader :websocket + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/engine.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/engine.rb new file mode 100644 index 00000000..53cbb597 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/engine.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require "rails" +require "action_cable" +require "action_cable/helpers/action_cable_helper" +require "active_support/core_ext/hash/indifferent_access" + +module ActionCable + class Engine < Rails::Engine # :nodoc: + config.action_cable = ActiveSupport::OrderedOptions.new + config.action_cable.mount_path = ActionCable::INTERNAL[:default_mount_path] + + config.eager_load_namespaces << ActionCable + + initializer "action_cable.helpers" do + ActiveSupport.on_load(:action_view) do + include ActionCable::Helpers::ActionCableHelper + end + end + + initializer "action_cable.logger" do + ActiveSupport.on_load(:action_cable) { self.logger ||= ::Rails.logger } + end + + initializer "action_cable.set_configs" do |app| + options = app.config.action_cable + options.allowed_request_origins ||= /https?:\/\/localhost:\d+/ if ::Rails.env.development? + + app.paths.add "config/cable", with: "config/cable.yml" + + ActiveSupport.on_load(:action_cable) do + if (config_path = Pathname.new(app.config.paths["config/cable"].first)).exist? + self.cable = Rails.application.config_for(config_path).with_indifferent_access + end + + previous_connection_class = connection_class + self.connection_class = -> { "ApplicationCable::Connection".safe_constantize || previous_connection_class.call } + + options.each { |k, v| send("#{k}=", v) } + end + end + + initializer "action_cable.routes" do + config.after_initialize do |app| + config = app.config + unless config.action_cable.mount_path.nil? + app.routes.prepend do + mount ActionCable.server => config.action_cable.mount_path, internal: true + end + end + end + end + + initializer "action_cable.set_work_hooks" do |app| + ActiveSupport.on_load(:action_cable) do + ActionCable::Server::Worker.set_callback :work, :around, prepend: true do |_, inner| + app.executor.wrap do + # If we took a while to get the lock, we may have been halted + # in the meantime. As we haven't started doing any real work + # yet, we should pretend that we never made it off the queue. + unless stopping? + inner.call + end + end + end + + wrap = lambda do |_, inner| + app.executor.wrap(&inner) + end + ActionCable::Channel::Base.set_callback :subscribe, :around, prepend: true, &wrap + ActionCable::Channel::Base.set_callback :unsubscribe, :around, prepend: true, &wrap + + app.reloader.before_class_unload do + ActionCable.server.restart + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/gem_version.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/gem_version.rb new file mode 100644 index 00000000..f63d773c --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/gem_version.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActionCable + # Returns the version of the currently loaded Action Cable as a Gem::Version. + def self.gem_version + Gem::Version.new VERSION::STRING + end + + module VERSION + MAJOR = 5 + MINOR = 2 + TINY = 3 + PRE = nil + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/helpers/action_cable_helper.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/helpers/action_cable_helper.rb new file mode 100644 index 00000000..df16c02e --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/helpers/action_cable_helper.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module ActionCable + module Helpers + module ActionCableHelper + # Returns an "action-cable-url" meta tag with the value of the URL specified in your + # configuration. Ensure this is above your JavaScript tag: + # + # + # <%= action_cable_meta_tag %> + # <%= javascript_include_tag 'application', 'data-turbolinks-track' => 'reload' %> + # + # + # This is then used by Action Cable to determine the URL of your WebSocket server. + # Your CoffeeScript can then connect to the server without needing to specify the + # URL directly: + # + # #= require cable + # @App = {} + # App.cable = Cable.createConsumer() + # + # Make sure to specify the correct server location in each of your environment + # config files: + # + # config.action_cable.mount_path = "/cable123" + # <%= action_cable_meta_tag %> would render: + # => + # + # config.action_cable.url = "ws://actioncable.com" + # <%= action_cable_meta_tag %> would render: + # => + # + def action_cable_meta_tag + tag "meta", name: "action-cable-url", content: ( + ActionCable.server.config.url || + ActionCable.server.config.mount_path || + raise("No Action Cable URL configured -- please configure this at config.action_cable.url") + ) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/remote_connections.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/remote_connections.rb new file mode 100644 index 00000000..283400d9 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/remote_connections.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/redefine_method" + +module ActionCable + # If you need to disconnect a given connection, you can go through the + # RemoteConnections. You can find the connections you're looking for by + # searching for the identifier declared on the connection. For example: + # + # module ApplicationCable + # class Connection < ActionCable::Connection::Base + # identified_by :current_user + # .... + # end + # end + # + # ActionCable.server.remote_connections.where(current_user: User.find(1)).disconnect + # + # This will disconnect all the connections established for + # User.find(1), across all servers running on all machines, because + # it uses the internal channel that all of these servers are subscribed to. + class RemoteConnections + attr_reader :server + + def initialize(server) + @server = server + end + + def where(identifier) + RemoteConnection.new(server, identifier) + end + + private + # Represents a single remote connection found via ActionCable.server.remote_connections.where(*). + # Exists solely for the purpose of calling #disconnect on that connection. + class RemoteConnection + class InvalidIdentifiersError < StandardError; end + + include Connection::Identification, Connection::InternalChannel + + def initialize(server, ids) + @server = server + set_identifier_instance_vars(ids) + end + + # Uses the internal channel to disconnect the connection. + def disconnect + server.broadcast internal_channel, type: "disconnect" + end + + # Returns all the identifiers that were applied to this connection. + redefine_method :identifiers do + server.connection_identifiers + end + + protected + attr_reader :server + + private + def set_identifier_instance_vars(ids) + raise InvalidIdentifiersError unless valid_identifiers?(ids) + ids.each { |k, v| instance_variable_set("@#{k}", v) } + end + + def valid_identifiers?(ids) + keys = ids.keys + identifiers.all? { |id| keys.include?(id) } + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/server.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/server.rb new file mode 100644 index 00000000..8d485a44 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/server.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActionCable + module Server + extend ActiveSupport::Autoload + + eager_autoload do + autoload :Base + autoload :Broadcasting + autoload :Connections + autoload :Configuration + + autoload :Worker + autoload :ActiveRecordConnectionManagement, "action_cable/server/worker/active_record_connection_management" + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/server/base.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/server/base.rb new file mode 100644 index 00000000..1ee03f6d --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/server/base.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require "monitor" + +module ActionCable + module Server + # A singleton ActionCable::Server instance is available via ActionCable.server. It's used by the Rack process that starts the Action Cable server, but + # is also used by the user to reach the RemoteConnections object, which is used for finding and disconnecting connections across all servers. + # + # Also, this is the server instance used for broadcasting. See Broadcasting for more information. + class Base + include ActionCable::Server::Broadcasting + include ActionCable::Server::Connections + + cattr_accessor :config, instance_accessor: true, default: ActionCable::Server::Configuration.new + + def self.logger; config.logger; end + delegate :logger, to: :config + + attr_reader :mutex + + def initialize + @mutex = Monitor.new + @remote_connections = @event_loop = @worker_pool = @pubsub = nil + end + + # Called by Rack to setup the server. + def call(env) + setup_heartbeat_timer + config.connection_class.call.new(self, env).process + end + + # Disconnect all the connections identified by +identifiers+ on this server or any others via RemoteConnections. + def disconnect(identifiers) + remote_connections.where(identifiers).disconnect + end + + def restart + connections.each(&:close) + + @mutex.synchronize do + # Shutdown the worker pool + @worker_pool.halt if @worker_pool + @worker_pool = nil + + # Shutdown the pub/sub adapter + @pubsub.shutdown if @pubsub + @pubsub = nil + end + end + + # Gateway to RemoteConnections. See that class for details. + def remote_connections + @remote_connections || @mutex.synchronize { @remote_connections ||= RemoteConnections.new(self) } + end + + def event_loop + @event_loop || @mutex.synchronize { @event_loop ||= ActionCable::Connection::StreamEventLoop.new } + end + + # The worker pool is where we run connection callbacks and channel actions. We do as little as possible on the server's main thread. + # The worker pool is an executor service that's backed by a pool of threads working from a task queue. The thread pool size maxes out + # at 4 worker threads by default. Tune the size yourself with config.action_cable.worker_pool_size. + # + # Using Active Record, Redis, etc within your channel actions means you'll get a separate connection from each thread in the worker pool. + # Plan your deployment accordingly: 5 servers each running 5 Puma workers each running an 8-thread worker pool means at least 200 database + # connections. + # + # Also, ensure that your database connection pool size is as least as large as your worker pool size. Otherwise, workers may oversubscribe + # the database connection pool and block while they wait for other workers to release their connections. Use a smaller worker pool or a larger + # database connection pool instead. + def worker_pool + @worker_pool || @mutex.synchronize { @worker_pool ||= ActionCable::Server::Worker.new(max_size: config.worker_pool_size) } + end + + # Adapter used for all streams/broadcasting. + def pubsub + @pubsub || @mutex.synchronize { @pubsub ||= config.pubsub_adapter.new(self) } + end + + # All of the identifiers applied to the connection class associated with this server. + def connection_identifiers + config.connection_class.call.identifiers + end + end + + ActiveSupport.run_load_hooks(:action_cable, Base.config) + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/server/broadcasting.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/server/broadcasting.rb new file mode 100644 index 00000000..bc54d784 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/server/broadcasting.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module ActionCable + module Server + # Broadcasting is how other parts of your application can send messages to a channel's subscribers. As explained in Channel, most of the time, these + # broadcastings are streamed directly to the clients subscribed to the named broadcasting. Let's explain with a full-stack example: + # + # class WebNotificationsChannel < ApplicationCable::Channel + # def subscribed + # stream_from "web_notifications_#{current_user.id}" + # end + # end + # + # # Somewhere in your app this is called, perhaps from a NewCommentJob: + # ActionCable.server.broadcast \ + # "web_notifications_1", { title: "New things!", body: "All that's fit for print" } + # + # # Client-side CoffeeScript, which assumes you've already requested the right to send web notifications: + # App.cable.subscriptions.create "WebNotificationsChannel", + # received: (data) -> + # new Notification data['title'], body: data['body'] + module Broadcasting + # Broadcast a hash directly to a named broadcasting. This will later be JSON encoded. + def broadcast(broadcasting, message, coder: ActiveSupport::JSON) + broadcaster_for(broadcasting, coder: coder).broadcast(message) + end + + # Returns a broadcaster for a named broadcasting that can be reused. Useful when you have an object that + # may need multiple spots to transmit to a specific broadcasting over and over. + def broadcaster_for(broadcasting, coder: ActiveSupport::JSON) + Broadcaster.new(self, String(broadcasting), coder: coder) + end + + private + class Broadcaster + attr_reader :server, :broadcasting, :coder + + def initialize(server, broadcasting, coder:) + @server, @broadcasting, @coder = server, broadcasting, coder + end + + def broadcast(message) + server.logger.debug "[ActionCable] Broadcasting to #{broadcasting}: #{message.inspect}" + + payload = { broadcasting: broadcasting, message: message, coder: coder } + ActiveSupport::Notifications.instrument("broadcast.action_cable", payload) do + encoded = coder ? coder.encode(message) : message + server.pubsub.broadcast broadcasting, encoded + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/server/configuration.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/server/configuration.rb new file mode 100644 index 00000000..26209537 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/server/configuration.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module ActionCable + module Server + # An instance of this configuration object is available via ActionCable.server.config, which allows you to tweak Action Cable configuration + # in a Rails config initializer. + class Configuration + attr_accessor :logger, :log_tags + attr_accessor :connection_class, :worker_pool_size + attr_accessor :disable_request_forgery_protection, :allowed_request_origins, :allow_same_origin_as_host + attr_accessor :cable, :url, :mount_path + + def initialize + @log_tags = [] + + @connection_class = -> { ActionCable::Connection::Base } + @worker_pool_size = 4 + + @disable_request_forgery_protection = false + @allow_same_origin_as_host = true + end + + # Returns constant of subscription adapter specified in config/cable.yml. + # If the adapter cannot be found, this will default to the Redis adapter. + # Also makes sure proper dependencies are required. + def pubsub_adapter + adapter = (cable.fetch("adapter") { "redis" }) + + # Require the adapter itself and give useful feedback about + # 1. Missing adapter gems and + # 2. Adapter gems' missing dependencies. + path_to_adapter = "action_cable/subscription_adapter/#{adapter}" + begin + require path_to_adapter + rescue LoadError => e + # We couldn't require the adapter itself. Raise an exception that + # points out config typos and missing gems. + if e.path == path_to_adapter + # We can assume that a non-builtin adapter was specified, so it's + # either misspelled or missing from Gemfile. + raise e.class, "Could not load the '#{adapter}' Action Cable pubsub adapter. Ensure that the adapter is spelled correctly in config/cable.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace + + # Bubbled up from the adapter require. Prefix the exception message + # with some guidance about how to address it and reraise. + else + raise e.class, "Error loading the '#{adapter}' Action Cable pubsub adapter. Missing a gem it depends on? #{e.message}", e.backtrace + end + end + + adapter = adapter.camelize + adapter = "PostgreSQL" if adapter == "Postgresql" + "ActionCable::SubscriptionAdapter::#{adapter}".constantize + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/server/connections.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/server/connections.rb new file mode 100644 index 00000000..39557d63 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/server/connections.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module ActionCable + module Server + # Collection class for all the connections that have been established on this specific server. Remember, usually you'll run many Action Cable servers, so + # you can't use this collection as a full list of all of the connections established against your application. Instead, use RemoteConnections for that. + module Connections # :nodoc: + BEAT_INTERVAL = 3 + + def connections + @connections ||= [] + end + + def add_connection(connection) + connections << connection + end + + def remove_connection(connection) + connections.delete connection + end + + # WebSocket connection implementations differ on when they'll mark a connection as stale. We basically never want a connection to go stale, as you + # then can't rely on being able to communicate with the connection. To solve this, a 3 second heartbeat runs on all connections. If the beat fails, we automatically + # disconnect. + def setup_heartbeat_timer + @heartbeat_timer ||= event_loop.timer(BEAT_INTERVAL) do + event_loop.post { connections.map(&:beat) } + end + end + + def open_connections_statistics + connections.map(&:statistics) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/server/worker.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/server/worker.rb new file mode 100644 index 00000000..c69cc4ac --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/server/worker.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require "active_support/callbacks" +require "active_support/core_ext/module/attribute_accessors_per_thread" +require "concurrent" + +module ActionCable + module Server + # Worker used by Server.send_async to do connection work in threads. + class Worker # :nodoc: + include ActiveSupport::Callbacks + + thread_mattr_accessor :connection + define_callbacks :work + include ActiveRecordConnectionManagement + + attr_reader :executor + + def initialize(max_size: 5) + @executor = Concurrent::ThreadPoolExecutor.new( + min_threads: 1, + max_threads: max_size, + max_queue: 0, + ) + end + + # Stop processing work: any work that has not already started + # running will be discarded from the queue + def halt + @executor.shutdown + end + + def stopping? + @executor.shuttingdown? + end + + def work(connection) + self.connection = connection + + run_callbacks :work do + yield + end + ensure + self.connection = nil + end + + def async_exec(receiver, *args, connection:, &block) + async_invoke receiver, :instance_exec, *args, connection: connection, &block + end + + def async_invoke(receiver, method, *args, connection: receiver, &block) + @executor.post do + invoke(receiver, method, *args, connection: connection, &block) + end + end + + def invoke(receiver, method, *args, connection:, &block) + work(connection) do + begin + receiver.send method, *args, &block + rescue Exception => e + logger.error "There was an exception - #{e.class}(#{e.message})" + logger.error e.backtrace.join("\n") + + receiver.handle_exception if receiver.respond_to?(:handle_exception) + end + end + end + + private + + def logger + ActionCable.server.logger + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/server/worker/active_record_connection_management.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/server/worker/active_record_connection_management.rb new file mode 100644 index 00000000..2e378d4b --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/server/worker/active_record_connection_management.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module ActionCable + module Server + class Worker + module ActiveRecordConnectionManagement + extend ActiveSupport::Concern + + included do + if defined?(ActiveRecord::Base) + set_callback :work, :around, :with_database_connections + end + end + + def with_database_connections + connection.logger.tag(ActiveRecord::Base.logger) { yield } + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/subscription_adapter.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/subscription_adapter.rb new file mode 100644 index 00000000..bcece8d3 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/subscription_adapter.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module ActionCable + module SubscriptionAdapter + extend ActiveSupport::Autoload + + autoload :Base + autoload :SubscriberMap + autoload :ChannelPrefix + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/subscription_adapter/async.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/subscription_adapter/async.rb new file mode 100644 index 00000000..c9930299 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/subscription_adapter/async.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require "action_cable/subscription_adapter/inline" + +module ActionCable + module SubscriptionAdapter + class Async < Inline # :nodoc: + private + def new_subscriber_map + AsyncSubscriberMap.new(server.event_loop) + end + + class AsyncSubscriberMap < SubscriberMap + def initialize(event_loop) + @event_loop = event_loop + super() + end + + def add_subscriber(*) + @event_loop.post { super } + end + + def invoke_callback(*) + @event_loop.post { super } + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/subscription_adapter/base.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/subscription_adapter/base.rb new file mode 100644 index 00000000..34077707 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/subscription_adapter/base.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module ActionCable + module SubscriptionAdapter + class Base + attr_reader :logger, :server + + def initialize(server) + @server = server + @logger = @server.logger + end + + def broadcast(channel, payload) + raise NotImplementedError + end + + def subscribe(channel, message_callback, success_callback = nil) + raise NotImplementedError + end + + def unsubscribe(channel, message_callback) + raise NotImplementedError + end + + def shutdown + raise NotImplementedError + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/subscription_adapter/channel_prefix.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/subscription_adapter/channel_prefix.rb new file mode 100644 index 00000000..df0aa040 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/subscription_adapter/channel_prefix.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module ActionCable + module SubscriptionAdapter + module ChannelPrefix # :nodoc: + def broadcast(channel, payload) + channel = channel_with_prefix(channel) + super + end + + def subscribe(channel, callback, success_callback = nil) + channel = channel_with_prefix(channel) + super + end + + def unsubscribe(channel, callback) + channel = channel_with_prefix(channel) + super + end + + private + # Returns the channel name, including channel_prefix specified in cable.yml + def channel_with_prefix(channel) + [@server.config.cable[:channel_prefix], channel].compact.join(":") + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/subscription_adapter/inline.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/subscription_adapter/inline.rb new file mode 100644 index 00000000..d2c85c1c --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/subscription_adapter/inline.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module ActionCable + module SubscriptionAdapter + class Inline < Base # :nodoc: + def initialize(*) + super + @subscriber_map = nil + end + + def broadcast(channel, payload) + subscriber_map.broadcast(channel, payload) + end + + def subscribe(channel, callback, success_callback = nil) + subscriber_map.add_subscriber(channel, callback, success_callback) + end + + def unsubscribe(channel, callback) + subscriber_map.remove_subscriber(channel, callback) + end + + def shutdown + # nothing to do + end + + private + def subscriber_map + @subscriber_map || @server.mutex.synchronize { @subscriber_map ||= new_subscriber_map } + end + + def new_subscriber_map + SubscriberMap.new + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/subscription_adapter/postgresql.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/subscription_adapter/postgresql.rb new file mode 100644 index 00000000..e384ea4a --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/subscription_adapter/postgresql.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +gem "pg", ">= 0.18", "< 2.0" +require "pg" +require "thread" +require "digest/sha1" + +module ActionCable + module SubscriptionAdapter + class PostgreSQL < Base # :nodoc: + def initialize(*) + super + @listener = nil + end + + def broadcast(channel, payload) + with_connection do |pg_conn| + pg_conn.exec("NOTIFY #{pg_conn.escape_identifier(channel_identifier(channel))}, '#{pg_conn.escape_string(payload)}'") + end + end + + def subscribe(channel, callback, success_callback = nil) + listener.add_subscriber(channel_identifier(channel), callback, success_callback) + end + + def unsubscribe(channel, callback) + listener.remove_subscriber(channel_identifier(channel), callback) + end + + def shutdown + listener.shutdown + end + + def with_connection(&block) # :nodoc: + ActiveRecord::Base.connection_pool.with_connection do |ar_conn| + pg_conn = ar_conn.raw_connection + + unless pg_conn.is_a?(PG::Connection) + raise "The Active Record database must be PostgreSQL in order to use the PostgreSQL Action Cable storage adapter" + end + + yield pg_conn + end + end + + private + def channel_identifier(channel) + channel.size > 63 ? Digest::SHA1.hexdigest(channel) : channel + end + + def listener + @listener || @server.mutex.synchronize { @listener ||= Listener.new(self, @server.event_loop) } + end + + class Listener < SubscriberMap + def initialize(adapter, event_loop) + super() + + @adapter = adapter + @event_loop = event_loop + @queue = Queue.new + + @thread = Thread.new do + Thread.current.abort_on_exception = true + listen + end + end + + def listen + @adapter.with_connection do |pg_conn| + catch :shutdown do + loop do + until @queue.empty? + action, channel, callback = @queue.pop(true) + + case action + when :listen + pg_conn.exec("LISTEN #{pg_conn.escape_identifier channel}") + @event_loop.post(&callback) if callback + when :unlisten + pg_conn.exec("UNLISTEN #{pg_conn.escape_identifier channel}") + when :shutdown + throw :shutdown + end + end + + pg_conn.wait_for_notify(1) do |chan, pid, message| + broadcast(chan, message) + end + end + end + end + end + + def shutdown + @queue.push([:shutdown]) + Thread.pass while @thread.alive? + end + + def add_channel(channel, on_success) + @queue.push([:listen, channel, on_success]) + end + + def remove_channel(channel) + @queue.push([:unlisten, channel]) + end + + def invoke_callback(*) + @event_loop.post { super } + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/subscription_adapter/redis.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/subscription_adapter/redis.rb new file mode 100644 index 00000000..c2895160 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/subscription_adapter/redis.rb @@ -0,0 +1,178 @@ +# frozen_string_literal: true + +require "thread" + +gem "redis", ">= 3", "< 5" +require "redis" + +module ActionCable + module SubscriptionAdapter + class Redis < Base # :nodoc: + prepend ChannelPrefix + + # Overwrite this factory method for Redis connections if you want to use a different Redis library than the redis gem. + # This is needed, for example, when using Makara proxies for distributed Redis. + cattr_accessor :redis_connector, default: ->(config) do + ::Redis.new(config.slice(:url, :host, :port, :db, :password)) + end + + def initialize(*) + super + @listener = nil + @redis_connection_for_broadcasts = nil + end + + def broadcast(channel, payload) + redis_connection_for_broadcasts.publish(channel, payload) + end + + def subscribe(channel, callback, success_callback = nil) + listener.add_subscriber(channel, callback, success_callback) + end + + def unsubscribe(channel, callback) + listener.remove_subscriber(channel, callback) + end + + def shutdown + @listener.shutdown if @listener + end + + def redis_connection_for_subscriptions + redis_connection + end + + private + def listener + @listener || @server.mutex.synchronize { @listener ||= Listener.new(self, @server.event_loop) } + end + + def redis_connection_for_broadcasts + @redis_connection_for_broadcasts || @server.mutex.synchronize do + @redis_connection_for_broadcasts ||= redis_connection + end + end + + def redis_connection + self.class.redis_connector.call(@server.config.cable) + end + + class Listener < SubscriberMap + def initialize(adapter, event_loop) + super() + + @adapter = adapter + @event_loop = event_loop + + @subscribe_callbacks = Hash.new { |h, k| h[k] = [] } + @subscription_lock = Mutex.new + + @raw_client = nil + + @when_connected = [] + + @thread = nil + end + + def listen(conn) + conn.without_reconnect do + original_client = conn.respond_to?(:_client) ? conn._client : conn.client + + conn.subscribe("_action_cable_internal") do |on| + on.subscribe do |chan, count| + @subscription_lock.synchronize do + if count == 1 + @raw_client = original_client + + until @when_connected.empty? + @when_connected.shift.call + end + end + + if callbacks = @subscribe_callbacks[chan] + next_callback = callbacks.shift + @event_loop.post(&next_callback) if next_callback + @subscribe_callbacks.delete(chan) if callbacks.empty? + end + end + end + + on.message do |chan, message| + broadcast(chan, message) + end + + on.unsubscribe do |chan, count| + if count == 0 + @subscription_lock.synchronize do + @raw_client = nil + end + end + end + end + end + end + + def shutdown + @subscription_lock.synchronize do + return if @thread.nil? + + when_connected do + send_command("unsubscribe") + @raw_client = nil + end + end + + Thread.pass while @thread.alive? + end + + def add_channel(channel, on_success) + @subscription_lock.synchronize do + ensure_listener_running + @subscribe_callbacks[channel] << on_success + when_connected { send_command("subscribe", channel) } + end + end + + def remove_channel(channel) + @subscription_lock.synchronize do + when_connected { send_command("unsubscribe", channel) } + end + end + + def invoke_callback(*) + @event_loop.post { super } + end + + private + def ensure_listener_running + @thread ||= Thread.new do + Thread.current.abort_on_exception = true + + conn = @adapter.redis_connection_for_subscriptions + listen conn + end + end + + def when_connected(&block) + if @raw_client + block.call + else + @when_connected << block + end + end + + def send_command(*command) + @raw_client.write(command) + + very_raw_connection = + @raw_client.connection.instance_variable_defined?(:@connection) && + @raw_client.connection.instance_variable_get(:@connection) + + if very_raw_connection && very_raw_connection.respond_to?(:flush) + very_raw_connection.flush + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/subscription_adapter/subscriber_map.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/subscription_adapter/subscriber_map.rb new file mode 100644 index 00000000..01cdc2df --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/subscription_adapter/subscriber_map.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module ActionCable + module SubscriptionAdapter + class SubscriberMap + def initialize + @subscribers = Hash.new { |h, k| h[k] = [] } + @sync = Mutex.new + end + + def add_subscriber(channel, subscriber, on_success) + @sync.synchronize do + new_channel = !@subscribers.key?(channel) + + @subscribers[channel] << subscriber + + if new_channel + add_channel channel, on_success + elsif on_success + on_success.call + end + end + end + + def remove_subscriber(channel, subscriber) + @sync.synchronize do + @subscribers[channel].delete(subscriber) + + if @subscribers[channel].empty? + @subscribers.delete channel + remove_channel channel + end + end + end + + def broadcast(channel, message) + list = @sync.synchronize do + return if !@subscribers.key?(channel) + @subscribers[channel].dup + end + + list.each do |subscriber| + invoke_callback(subscriber, message) + end + end + + def add_channel(channel, on_success) + on_success.call if on_success + end + + def remove_channel(channel) + end + + def invoke_callback(callback, message) + callback.call message + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/version.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/version.rb new file mode 100644 index 00000000..86115c60 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/action_cable/version.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require_relative "gem_version" + +module ActionCable + # Returns the version of the currently loaded Action Cable as a Gem::Version + def self.version + gem_version + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/assets/compiled/action_cable.js b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/assets/compiled/action_cable.js new file mode 100644 index 00000000..960f8516 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/assets/compiled/action_cable.js @@ -0,0 +1,601 @@ +(function() { + var context = this; + + (function() { + (function() { + var slice = [].slice; + + this.ActionCable = { + INTERNAL: { + "message_types": { + "welcome": "welcome", + "ping": "ping", + "confirmation": "confirm_subscription", + "rejection": "reject_subscription" + }, + "default_mount_path": "/cable", + "protocols": ["actioncable-v1-json", "actioncable-unsupported"] + }, + WebSocket: window.WebSocket, + logger: window.console, + createConsumer: function(url) { + var ref; + if (url == null) { + url = (ref = this.getConfig("url")) != null ? ref : this.INTERNAL.default_mount_path; + } + return new ActionCable.Consumer(this.createWebSocketURL(url)); + }, + getConfig: function(name) { + var element; + element = document.head.querySelector("meta[name='action-cable-" + name + "']"); + return element != null ? element.getAttribute("content") : void 0; + }, + createWebSocketURL: function(url) { + var a; + if (url && !/^wss?:/i.test(url)) { + a = document.createElement("a"); + a.href = url; + a.href = a.href; + a.protocol = a.protocol.replace("http", "ws"); + return a.href; + } else { + return url; + } + }, + startDebugging: function() { + return this.debugging = true; + }, + stopDebugging: function() { + return this.debugging = null; + }, + log: function() { + var messages, ref; + messages = 1 <= arguments.length ? slice.call(arguments, 0) : []; + if (this.debugging) { + messages.push(Date.now()); + return (ref = this.logger).log.apply(ref, ["[ActionCable]"].concat(slice.call(messages))); + } + } + }; + + }).call(this); + }).call(context); + + var ActionCable = context.ActionCable; + + (function() { + (function() { + var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + + ActionCable.ConnectionMonitor = (function() { + var clamp, now, secondsSince; + + ConnectionMonitor.pollInterval = { + min: 3, + max: 30 + }; + + ConnectionMonitor.staleThreshold = 6; + + function ConnectionMonitor(connection) { + this.connection = connection; + this.visibilityDidChange = bind(this.visibilityDidChange, this); + this.reconnectAttempts = 0; + } + + ConnectionMonitor.prototype.start = function() { + if (!this.isRunning()) { + this.startedAt = now(); + delete this.stoppedAt; + this.startPolling(); + document.addEventListener("visibilitychange", this.visibilityDidChange); + return ActionCable.log("ConnectionMonitor started. pollInterval = " + (this.getPollInterval()) + " ms"); + } + }; + + ConnectionMonitor.prototype.stop = function() { + if (this.isRunning()) { + this.stoppedAt = now(); + this.stopPolling(); + document.removeEventListener("visibilitychange", this.visibilityDidChange); + return ActionCable.log("ConnectionMonitor stopped"); + } + }; + + ConnectionMonitor.prototype.isRunning = function() { + return (this.startedAt != null) && (this.stoppedAt == null); + }; + + ConnectionMonitor.prototype.recordPing = function() { + return this.pingedAt = now(); + }; + + ConnectionMonitor.prototype.recordConnect = function() { + this.reconnectAttempts = 0; + this.recordPing(); + delete this.disconnectedAt; + return ActionCable.log("ConnectionMonitor recorded connect"); + }; + + ConnectionMonitor.prototype.recordDisconnect = function() { + this.disconnectedAt = now(); + return ActionCable.log("ConnectionMonitor recorded disconnect"); + }; + + ConnectionMonitor.prototype.startPolling = function() { + this.stopPolling(); + return this.poll(); + }; + + ConnectionMonitor.prototype.stopPolling = function() { + return clearTimeout(this.pollTimeout); + }; + + ConnectionMonitor.prototype.poll = function() { + return this.pollTimeout = setTimeout((function(_this) { + return function() { + _this.reconnectIfStale(); + return _this.poll(); + }; + })(this), this.getPollInterval()); + }; + + ConnectionMonitor.prototype.getPollInterval = function() { + var interval, max, min, ref; + ref = this.constructor.pollInterval, min = ref.min, max = ref.max; + interval = 5 * Math.log(this.reconnectAttempts + 1); + return Math.round(clamp(interval, min, max) * 1000); + }; + + ConnectionMonitor.prototype.reconnectIfStale = function() { + if (this.connectionIsStale()) { + ActionCable.log("ConnectionMonitor detected stale connection. reconnectAttempts = " + this.reconnectAttempts + ", pollInterval = " + (this.getPollInterval()) + " ms, time disconnected = " + (secondsSince(this.disconnectedAt)) + " s, stale threshold = " + this.constructor.staleThreshold + " s"); + this.reconnectAttempts++; + if (this.disconnectedRecently()) { + return ActionCable.log("ConnectionMonitor skipping reopening recent disconnect"); + } else { + ActionCable.log("ConnectionMonitor reopening"); + return this.connection.reopen(); + } + } + }; + + ConnectionMonitor.prototype.connectionIsStale = function() { + var ref; + return secondsSince((ref = this.pingedAt) != null ? ref : this.startedAt) > this.constructor.staleThreshold; + }; + + ConnectionMonitor.prototype.disconnectedRecently = function() { + return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold; + }; + + ConnectionMonitor.prototype.visibilityDidChange = function() { + if (document.visibilityState === "visible") { + return setTimeout((function(_this) { + return function() { + if (_this.connectionIsStale() || !_this.connection.isOpen()) { + ActionCable.log("ConnectionMonitor reopening stale connection on visibilitychange. visbilityState = " + document.visibilityState); + return _this.connection.reopen(); + } + }; + })(this), 200); + } + }; + + now = function() { + return new Date().getTime(); + }; + + secondsSince = function(time) { + return (now() - time) / 1000; + }; + + clamp = function(number, min, max) { + return Math.max(min, Math.min(max, number)); + }; + + return ConnectionMonitor; + + })(); + + }).call(this); + (function() { + var i, message_types, protocols, ref, supportedProtocols, unsupportedProtocol, + slice = [].slice, + bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + + ref = ActionCable.INTERNAL, message_types = ref.message_types, protocols = ref.protocols; + + supportedProtocols = 2 <= protocols.length ? slice.call(protocols, 0, i = protocols.length - 1) : (i = 0, []), unsupportedProtocol = protocols[i++]; + + ActionCable.Connection = (function() { + Connection.reopenDelay = 500; + + function Connection(consumer) { + this.consumer = consumer; + this.open = bind(this.open, this); + this.subscriptions = this.consumer.subscriptions; + this.monitor = new ActionCable.ConnectionMonitor(this); + this.disconnected = true; + } + + Connection.prototype.send = function(data) { + if (this.isOpen()) { + this.webSocket.send(JSON.stringify(data)); + return true; + } else { + return false; + } + }; + + Connection.prototype.open = function() { + if (this.isActive()) { + ActionCable.log("Attempted to open WebSocket, but existing socket is " + (this.getState())); + return false; + } else { + ActionCable.log("Opening WebSocket, current state is " + (this.getState()) + ", subprotocols: " + protocols); + if (this.webSocket != null) { + this.uninstallEventHandlers(); + } + this.webSocket = new ActionCable.WebSocket(this.consumer.url, protocols); + this.installEventHandlers(); + this.monitor.start(); + return true; + } + }; + + Connection.prototype.close = function(arg) { + var allowReconnect, ref1; + allowReconnect = (arg != null ? arg : { + allowReconnect: true + }).allowReconnect; + if (!allowReconnect) { + this.monitor.stop(); + } + if (this.isActive()) { + return (ref1 = this.webSocket) != null ? ref1.close() : void 0; + } + }; + + Connection.prototype.reopen = function() { + var error; + ActionCable.log("Reopening WebSocket, current state is " + (this.getState())); + if (this.isActive()) { + try { + return this.close(); + } catch (error1) { + error = error1; + return ActionCable.log("Failed to reopen WebSocket", error); + } finally { + ActionCable.log("Reopening WebSocket in " + this.constructor.reopenDelay + "ms"); + setTimeout(this.open, this.constructor.reopenDelay); + } + } else { + return this.open(); + } + }; + + Connection.prototype.getProtocol = function() { + var ref1; + return (ref1 = this.webSocket) != null ? ref1.protocol : void 0; + }; + + Connection.prototype.isOpen = function() { + return this.isState("open"); + }; + + Connection.prototype.isActive = function() { + return this.isState("open", "connecting"); + }; + + Connection.prototype.isProtocolSupported = function() { + var ref1; + return ref1 = this.getProtocol(), indexOf.call(supportedProtocols, ref1) >= 0; + }; + + Connection.prototype.isState = function() { + var ref1, states; + states = 1 <= arguments.length ? slice.call(arguments, 0) : []; + return ref1 = this.getState(), indexOf.call(states, ref1) >= 0; + }; + + Connection.prototype.getState = function() { + var ref1, state, value; + for (state in WebSocket) { + value = WebSocket[state]; + if (value === ((ref1 = this.webSocket) != null ? ref1.readyState : void 0)) { + return state.toLowerCase(); + } + } + return null; + }; + + Connection.prototype.installEventHandlers = function() { + var eventName, handler; + for (eventName in this.events) { + handler = this.events[eventName].bind(this); + this.webSocket["on" + eventName] = handler; + } + }; + + Connection.prototype.uninstallEventHandlers = function() { + var eventName; + for (eventName in this.events) { + this.webSocket["on" + eventName] = function() {}; + } + }; + + Connection.prototype.events = { + message: function(event) { + var identifier, message, ref1, type; + if (!this.isProtocolSupported()) { + return; + } + ref1 = JSON.parse(event.data), identifier = ref1.identifier, message = ref1.message, type = ref1.type; + switch (type) { + case message_types.welcome: + this.monitor.recordConnect(); + return this.subscriptions.reload(); + case message_types.ping: + return this.monitor.recordPing(); + case message_types.confirmation: + return this.subscriptions.notify(identifier, "connected"); + case message_types.rejection: + return this.subscriptions.reject(identifier); + default: + return this.subscriptions.notify(identifier, "received", message); + } + }, + open: function() { + ActionCable.log("WebSocket onopen event, using '" + (this.getProtocol()) + "' subprotocol"); + this.disconnected = false; + if (!this.isProtocolSupported()) { + ActionCable.log("Protocol is unsupported. Stopping monitor and disconnecting."); + return this.close({ + allowReconnect: false + }); + } + }, + close: function(event) { + ActionCable.log("WebSocket onclose event"); + if (this.disconnected) { + return; + } + this.disconnected = true; + this.monitor.recordDisconnect(); + return this.subscriptions.notifyAll("disconnected", { + willAttemptReconnect: this.monitor.isRunning() + }); + }, + error: function() { + return ActionCable.log("WebSocket onerror event"); + } + }; + + return Connection; + + })(); + + }).call(this); + (function() { + var slice = [].slice; + + ActionCable.Subscriptions = (function() { + function Subscriptions(consumer) { + this.consumer = consumer; + this.subscriptions = []; + } + + Subscriptions.prototype.create = function(channelName, mixin) { + var channel, params, subscription; + channel = channelName; + params = typeof channel === "object" ? channel : { + channel: channel + }; + subscription = new ActionCable.Subscription(this.consumer, params, mixin); + return this.add(subscription); + }; + + Subscriptions.prototype.add = function(subscription) { + this.subscriptions.push(subscription); + this.consumer.ensureActiveConnection(); + this.notify(subscription, "initialized"); + this.sendCommand(subscription, "subscribe"); + return subscription; + }; + + Subscriptions.prototype.remove = function(subscription) { + this.forget(subscription); + if (!this.findAll(subscription.identifier).length) { + this.sendCommand(subscription, "unsubscribe"); + } + return subscription; + }; + + Subscriptions.prototype.reject = function(identifier) { + var i, len, ref, results, subscription; + ref = this.findAll(identifier); + results = []; + for (i = 0, len = ref.length; i < len; i++) { + subscription = ref[i]; + this.forget(subscription); + this.notify(subscription, "rejected"); + results.push(subscription); + } + return results; + }; + + Subscriptions.prototype.forget = function(subscription) { + var s; + this.subscriptions = (function() { + var i, len, ref, results; + ref = this.subscriptions; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + s = ref[i]; + if (s !== subscription) { + results.push(s); + } + } + return results; + }).call(this); + return subscription; + }; + + Subscriptions.prototype.findAll = function(identifier) { + var i, len, ref, results, s; + ref = this.subscriptions; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + s = ref[i]; + if (s.identifier === identifier) { + results.push(s); + } + } + return results; + }; + + Subscriptions.prototype.reload = function() { + var i, len, ref, results, subscription; + ref = this.subscriptions; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + subscription = ref[i]; + results.push(this.sendCommand(subscription, "subscribe")); + } + return results; + }; + + Subscriptions.prototype.notifyAll = function() { + var args, callbackName, i, len, ref, results, subscription; + callbackName = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; + ref = this.subscriptions; + results = []; + for (i = 0, len = ref.length; i < len; i++) { + subscription = ref[i]; + results.push(this.notify.apply(this, [subscription, callbackName].concat(slice.call(args)))); + } + return results; + }; + + Subscriptions.prototype.notify = function() { + var args, callbackName, i, len, results, subscription, subscriptions; + subscription = arguments[0], callbackName = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : []; + if (typeof subscription === "string") { + subscriptions = this.findAll(subscription); + } else { + subscriptions = [subscription]; + } + results = []; + for (i = 0, len = subscriptions.length; i < len; i++) { + subscription = subscriptions[i]; + results.push(typeof subscription[callbackName] === "function" ? subscription[callbackName].apply(subscription, args) : void 0); + } + return results; + }; + + Subscriptions.prototype.sendCommand = function(subscription, command) { + var identifier; + identifier = subscription.identifier; + return this.consumer.send({ + command: command, + identifier: identifier + }); + }; + + return Subscriptions; + + })(); + + }).call(this); + (function() { + ActionCable.Subscription = (function() { + var extend; + + function Subscription(consumer, params, mixin) { + this.consumer = consumer; + if (params == null) { + params = {}; + } + this.identifier = JSON.stringify(params); + extend(this, mixin); + } + + Subscription.prototype.perform = function(action, data) { + if (data == null) { + data = {}; + } + data.action = action; + return this.send(data); + }; + + Subscription.prototype.send = function(data) { + return this.consumer.send({ + command: "message", + identifier: this.identifier, + data: JSON.stringify(data) + }); + }; + + Subscription.prototype.unsubscribe = function() { + return this.consumer.subscriptions.remove(this); + }; + + extend = function(object, properties) { + var key, value; + if (properties != null) { + for (key in properties) { + value = properties[key]; + object[key] = value; + } + } + return object; + }; + + return Subscription; + + })(); + + }).call(this); + (function() { + ActionCable.Consumer = (function() { + function Consumer(url) { + this.url = url; + this.subscriptions = new ActionCable.Subscriptions(this); + this.connection = new ActionCable.Connection(this); + } + + Consumer.prototype.send = function(data) { + return this.connection.send(data); + }; + + Consumer.prototype.connect = function() { + return this.connection.open(); + }; + + Consumer.prototype.disconnect = function() { + return this.connection.close({ + allowReconnect: false + }); + }; + + Consumer.prototype.ensureActiveConnection = function() { + if (!this.connection.isActive()) { + return this.connection.open(); + } + }; + + return Consumer; + + })(); + + }).call(this); + }).call(this); + + if (typeof module === "object" && module.exports) { + module.exports = ActionCable; + } else if (typeof define === "function" && define.amd) { + define(ActionCable); + } +}).call(this); diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/rails/generators/channel/USAGE b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/rails/generators/channel/USAGE new file mode 100644 index 00000000..dd109fda --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/rails/generators/channel/USAGE @@ -0,0 +1,14 @@ +Description: +============ + Stubs out a new cable channel for the server (in Ruby) and client (in CoffeeScript). + Pass the channel name, either CamelCased or under_scored, and an optional list of channel actions as arguments. + + Note: Turn on the cable connection in app/assets/javascripts/cable.js after generating any channels. + +Example: +======== + rails generate channel Chat speak + + creates a Chat channel class and CoffeeScript asset: + Channel: app/channels/chat_channel.rb + Assets: app/assets/javascripts/channels/chat.coffee diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/rails/generators/channel/channel_generator.rb b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/rails/generators/channel/channel_generator.rb new file mode 100644 index 00000000..c3528370 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/rails/generators/channel/channel_generator.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Rails + module Generators + class ChannelGenerator < NamedBase + source_root File.expand_path("templates", __dir__) + + argument :actions, type: :array, default: [], banner: "method method" + + class_option :assets, type: :boolean + + check_class_collision suffix: "Channel" + + def create_channel_file + template "channel.rb", File.join("app/channels", class_path, "#{file_name}_channel.rb") + + if options[:assets] + if behavior == :invoke + template "assets/cable.js", "app/assets/javascripts/cable.js" + end + + js_template "assets/channel", File.join("app/assets/javascripts/channels", class_path, "#{file_name}") + end + + generate_application_cable_files + end + + private + def file_name + @_file_name ||= super.gsub(/_channel/i, "") + end + + # FIXME: Change these files to symlinks once RubyGems 2.5.0 is required. + def generate_application_cable_files + return if behavior != :invoke + + files = [ + "application_cable/channel.rb", + "application_cable/connection.rb" + ] + + files.each do |name| + path = File.join("app/channels/", name) + template(name, path) if !File.exist?(path) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/rails/generators/channel/templates/application_cable/channel.rb.tt b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/rails/generators/channel/templates/application_cable/channel.rb.tt new file mode 100644 index 00000000..d6726972 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/rails/generators/channel/templates/application_cable/channel.rb.tt @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/rails/generators/channel/templates/application_cable/connection.rb.tt b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/rails/generators/channel/templates/application_cable/connection.rb.tt new file mode 100644 index 00000000..0ff5442f --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/rails/generators/channel/templates/application_cable/connection.rb.tt @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/rails/generators/channel/templates/assets/cable.js.tt b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/rails/generators/channel/templates/assets/cable.js.tt new file mode 100644 index 00000000..739aa5f0 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/rails/generators/channel/templates/assets/cable.js.tt @@ -0,0 +1,13 @@ +// Action Cable provides the framework to deal with WebSockets in Rails. +// You can generate new channels where WebSocket features live using the `rails generate channel` command. +// +//= require action_cable +//= require_self +//= require_tree ./channels + +(function() { + this.App || (this.App = {}); + + App.cable = ActionCable.createConsumer(); + +}).call(this); diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/rails/generators/channel/templates/assets/channel.coffee.tt b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/rails/generators/channel/templates/assets/channel.coffee.tt new file mode 100644 index 00000000..5467811a --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/rails/generators/channel/templates/assets/channel.coffee.tt @@ -0,0 +1,14 @@ +App.<%= class_name.underscore %> = App.cable.subscriptions.create "<%= class_name %>Channel", + connected: -> + # Called when the subscription is ready for use on the server + + disconnected: -> + # Called when the subscription has been terminated by the server + + received: (data) -> + # Called when there's incoming data on the websocket for this channel +<% actions.each do |action| -%> + + <%= action %>: -> + @perform '<%= action %>' +<% end -%> diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/rails/generators/channel/templates/assets/channel.js.tt b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/rails/generators/channel/templates/assets/channel.js.tt new file mode 100644 index 00000000..ab0e68b1 --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/rails/generators/channel/templates/assets/channel.js.tt @@ -0,0 +1,18 @@ +App.<%= class_name.underscore %> = App.cable.subscriptions.create("<%= class_name %>Channel", { + connected: function() { + // Called when the subscription is ready for use on the server + }, + + disconnected: function() { + // Called when the subscription has been terminated by the server + }, + + received: function(data) { + // Called when there's incoming data on the websocket for this channel + }<%= actions.any? ? ",\n" : '' %> +<% actions.each do |action| -%> + <%=action %>: function() { + return this.perform('<%= action %>'); + }<%= action == actions[-1] ? '' : ",\n" %> +<% end -%> +}); diff --git a/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/rails/generators/channel/templates/channel.rb.tt b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/rails/generators/channel/templates/channel.rb.tt new file mode 100644 index 00000000..4bcfb2be --- /dev/null +++ b/path/ruby/2.6.0/gems/actioncable-5.2.3/lib/rails/generators/channel/templates/channel.rb.tt @@ -0,0 +1,16 @@ +<% module_namespacing do -%> +class <%= class_name %>Channel < ApplicationCable::Channel + def subscribed + # stream_from "some_channel" + end + + def unsubscribed + # Any cleanup needed when channel is unsubscribed + end +<% actions.each do |action| -%> + + def <%= action %> + end +<% end -%> +end +<% end -%> diff --git a/path/ruby/2.6.0/gems/actionmailer-5.2.3/CHANGELOG.md b/path/ruby/2.6.0/gems/actionmailer-5.2.3/CHANGELOG.md new file mode 100644 index 00000000..65309bf0 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionmailer-5.2.3/CHANGELOG.md @@ -0,0 +1,54 @@ +## Rails 5.2.3 (March 27, 2019) ## + +* No changes. + + +## Rails 5.2.2.1 (March 11, 2019) ## + +* No changes. + + +## Rails 5.2.2 (December 04, 2018) ## + +* No changes. + + +## Rails 5.2.1.1 (November 27, 2018) ## + +* No changes. + + +## Rails 5.2.1 (August 07, 2018) ## + +* Ensure mail gem is eager autoloaded when eager load is true to prevent thread deadlocks. + + *Samuel Cochran* + + +## Rails 5.2.0 (April 09, 2018) ## + +* Bring back proc with arity of 1 in `ActionMailer::Base.default` proc + since it was supported in Rails 5.0 but not deprecated. + + *Jimmy Bourassa* + +* Add `assert_enqueued_email_with` test helper. + + assert_enqueued_email_with ContactMailer, :welcome do + ContactMailer.welcome.deliver_later + end + + *Mikkel Malmberg* + +* Allow Action Mailer classes to configure their delivery job. + + class MyMailer < ApplicationMailer + self.delivery_job = MyCustomDeliveryJob + + ... + end + + *Matthew Mongeau* + + +Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/actionmailer/CHANGELOG.md) for previous changes. diff --git a/path/ruby/2.6.0/gems/actionmailer-5.2.3/MIT-LICENSE b/path/ruby/2.6.0/gems/actionmailer-5.2.3/MIT-LICENSE new file mode 100644 index 00000000..1cb3add0 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionmailer-5.2.3/MIT-LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2004-2018 David Heinemeier Hansson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/path/ruby/2.6.0/gems/actionmailer-5.2.3/README.rdoc b/path/ruby/2.6.0/gems/actionmailer-5.2.3/README.rdoc new file mode 100644 index 00000000..e7e70fb7 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionmailer-5.2.3/README.rdoc @@ -0,0 +1,175 @@ += Action Mailer -- Easy email delivery and testing + +Action Mailer is a framework for designing email service layers. These layers +are used to consolidate code for sending out forgotten passwords, welcome +wishes on signup, invoices for billing, and any other use case that requires +a written notification to either a person or another system. + +Action Mailer is in essence a wrapper around Action Controller and the +Mail gem. It provides a way to make emails using templates in the same +way that Action Controller renders views using templates. + +Additionally, an Action Mailer class can be used to process incoming email, +such as allowing a blog to accept new posts from an email (which could even +have been sent from a phone). + +== Sending emails + +The framework works by initializing any instance variables you want to be +available in the email template, followed by a call to +mail+ to deliver +the email. + +This can be as simple as: + + class Notifier < ActionMailer::Base + default from: 'system@loudthinking.com' + + def welcome(recipient) + @recipient = recipient + mail(to: recipient, + subject: "[Signed up] Welcome #{recipient}") + end + end + +The body of the email is created by using an Action View template (regular +ERB) that has the instance variables that are declared in the mailer action. + +So the corresponding body template for the method above could look like this: + + Hello there, + + Mr. <%= @recipient %> + + Thank you for signing up! + +If the recipient was given as "david@loudthinking.com", the email +generated would look like this: + + Date: Mon, 25 Jan 2010 22:48:09 +1100 + From: system@loudthinking.com + To: david@loudthinking.com + Message-ID: <4b5d84f9dd6a5_7380800b81ac29578@void.loudthinking.com.mail> + Subject: [Signed up] Welcome david@loudthinking.com + Mime-Version: 1.0 + Content-Type: text/plain; + charset="US-ASCII"; + Content-Transfer-Encoding: 7bit + + Hello there, + + Mr. david@loudthinking.com + + Thank you for signing up! + +In order to send mails, you simply call the method and then call +deliver_now+ on the return value. + +Calling the method returns a Mail Message object: + + message = Notifier.welcome("david@loudthinking.com") # => Returns a Mail::Message object + message.deliver_now # => delivers the email + +Or you can just chain the methods together like: + + Notifier.welcome("david@loudthinking.com").deliver_now # Creates the email and sends it immediately + +== Setting defaults + +It is possible to set default values that will be used in every method in your +Action Mailer class. To implement this functionality, you just call the public +class method +default+ which you get for free from ActionMailer::Base. +This method accepts a Hash as the parameter. You can use any of the headers, +email messages have, like +:from+ as the key. You can also pass in a string as +the key, like "Content-Type", but Action Mailer does this out of the box for you, +so you won't need to worry about that. Finally, it is also possible to pass in a +Proc that will get evaluated when it is needed. + +Note that every value you set with this method will get overwritten if you use the +same key in your mailer method. + +Example: + + class AuthenticationMailer < ActionMailer::Base + default from: "awesome@application.com", subject: Proc.new { "E-mail was generated at #{Time.now}" } + ..... + end + +== Receiving emails + +To receive emails, you need to implement a public instance method called ++receive+ that takes an email object as its single parameter. The Action Mailer +framework has a corresponding class method, which is also called +receive+, that +accepts a raw, unprocessed email as a string, which it then turns into the email +object and calls the receive instance method. + +Example: + + class Mailman < ActionMailer::Base + def receive(email) + page = Page.find_by(address: email.to.first) + page.emails.create( + subject: email.subject, body: email.body + ) + + if email.has_attachments? + email.attachments.each do |attachment| + page.attachments.create({ + file: attachment, description: email.subject + }) + end + end + end + end + +This Mailman can be the target for Postfix or other MTAs. In Rails, you would use +the runner in the trivial case like this: + + rails runner 'Mailman.receive(STDIN.read)' + +However, invoking Rails in the runner for each mail to be received is very +resource intensive. A single instance of Rails should be run within a daemon, if +it is going to process more than just a limited amount of email. + +== Configuration + +The Base class has the full list of configuration options. Here's an example: + + ActionMailer::Base.smtp_settings = { + address: 'smtp.yourserver.com', # default: localhost + port: '25', # default: 25 + user_name: 'user', + password: 'pass', + authentication: :plain # :plain, :login or :cram_md5 + } + + +== Download and installation + +The latest version of Action Mailer can be installed with RubyGems: + + $ gem install actionmailer + +Source code can be downloaded as part of the Rails project on GitHub: + +* https://github.com/rails/rails/tree/5-2-stable/actionmailer + + +== License + +Action Mailer is released under the MIT license: + +* https://opensource.org/licenses/MIT + + +== Support + +API documentation is at + +* http://api.rubyonrails.org + +Bug reports for the Ruby on Rails project can be filed here: + +* https://github.com/rails/rails/issues + +Feature requests should be discussed on the rails-core mailing list here: + +* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core diff --git a/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer.rb b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer.rb new file mode 100644 index 00000000..69eae65d --- /dev/null +++ b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +#-- +# Copyright (c) 2004-2018 David Heinemeier Hansson +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ + +require "abstract_controller" +require "action_mailer/version" + +# Common Active Support usage in Action Mailer +require "active_support" +require "active_support/rails" +require "active_support/core_ext/class" +require "active_support/core_ext/module/attr_internal" +require "active_support/core_ext/string/inflections" +require "active_support/lazy_load_hooks" + +module ActionMailer + extend ::ActiveSupport::Autoload + + eager_autoload do + autoload :Collector + end + + autoload :Base + autoload :DeliveryMethods + autoload :InlinePreviewInterceptor + autoload :MailHelper + autoload :Parameterized + autoload :Preview + autoload :Previews, "action_mailer/preview" + autoload :TestCase + autoload :TestHelper + autoload :MessageDelivery + autoload :DeliveryJob + + def self.eager_load! + super + + require "mail" + Mail.eager_autoload! + end +end + +autoload :Mime, "action_dispatch/http/mime_type" + +ActiveSupport.on_load(:action_view) do + ActionView::Base.default_formats ||= Mime::SET.symbols + ActionView::Template::Types.delegate_to Mime +end diff --git a/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/base.rb b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/base.rb new file mode 100644 index 00000000..3af95081 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/base.rb @@ -0,0 +1,991 @@ +# frozen_string_literal: true + +require "mail" +require "action_mailer/collector" +require "active_support/core_ext/string/inflections" +require "active_support/core_ext/hash/except" +require "active_support/core_ext/module/anonymous" + +require "action_mailer/log_subscriber" +require "action_mailer/rescuable" + +module ActionMailer + # Action Mailer allows you to send email from your application using a mailer model and views. + # + # = Mailer Models + # + # To use Action Mailer, you need to create a mailer model. + # + # $ rails generate mailer Notifier + # + # The generated model inherits from ApplicationMailer which in turn + # inherits from ActionMailer::Base. A mailer model defines methods + # used to generate an email message. In these methods, you can setup variables to be used in + # the mailer views, options on the mail itself such as the :from address, and attachments. + # + # class ApplicationMailer < ActionMailer::Base + # default from: 'from@example.com' + # layout 'mailer' + # end + # + # class NotifierMailer < ApplicationMailer + # default from: 'no-reply@example.com', + # return_path: 'system@example.com' + # + # def welcome(recipient) + # @account = recipient + # mail(to: recipient.email_address_with_name, + # bcc: ["bcc@example.com", "Order Watcher "]) + # end + # end + # + # Within the mailer method, you have access to the following methods: + # + # * attachments[]= - Allows you to add attachments to your email in an intuitive + # manner; attachments['filename.png'] = File.read('path/to/filename.png') + # + # * attachments.inline[]= - Allows you to add an inline attachment to your email + # in the same manner as attachments[]= + # + # * headers[]= - Allows you to specify any header field in your email such + # as headers['X-No-Spam'] = 'True'. Note that declaring a header multiple times + # will add many fields of the same name. Read #headers doc for more information. + # + # * headers(hash) - Allows you to specify multiple headers in your email such + # as headers({'X-No-Spam' => 'True', 'In-Reply-To' => '1234@message.id'}) + # + # * mail - Allows you to specify email to be sent. + # + # The hash passed to the mail method allows you to specify any header that a Mail::Message + # will accept (any valid email header including optional fields). + # + # The +mail+ method, if not passed a block, will inspect your views and send all the views with + # the same name as the method, so the above action would send the +welcome.text.erb+ view + # file as well as the +welcome.html.erb+ view file in a +multipart/alternative+ email. + # + # If you want to explicitly render only certain templates, pass a block: + # + # mail(to: user.email) do |format| + # format.text + # format.html + # end + # + # The block syntax is also useful in providing information specific to a part: + # + # mail(to: user.email) do |format| + # format.text(content_transfer_encoding: "base64") + # format.html + # end + # + # Or even to render a special view: + # + # mail(to: user.email) do |format| + # format.text + # format.html { render "some_other_template" } + # end + # + # = Mailer views + # + # Like Action Controller, each mailer class has a corresponding view directory in which each + # method of the class looks for a template with its name. + # + # To define a template to be used with a mailer, create an .erb file with the same + # name as the method in your mailer model. For example, in the mailer defined above, the template at + # app/views/notifier_mailer/welcome.text.erb would be used to generate the email. + # + # Variables defined in the methods of your mailer model are accessible as instance variables in their + # corresponding view. + # + # Emails by default are sent in plain text, so a sample view for our model example might look like this: + # + # Hi <%= @account.name %>, + # Thanks for joining our service! Please check back often. + # + # You can even use Action View helpers in these views. For example: + # + # You got a new note! + # <%= truncate(@note.body, length: 25) %> + # + # If you need to access the subject, from or the recipients in the view, you can do that through message object: + # + # You got a new note from <%= message.from %>! + # <%= truncate(@note.body, length: 25) %> + # + # + # = Generating URLs + # + # URLs can be generated in mailer views using url_for or named routes. Unlike controllers from + # Action Pack, the mailer instance doesn't have any context about the incoming request, so you'll need + # to provide all of the details needed to generate a URL. + # + # When using url_for you'll need to provide the :host, :controller, and :action: + # + # <%= url_for(host: "example.com", controller: "welcome", action: "greeting") %> + # + # When using named routes you only need to supply the :host: + # + # <%= users_url(host: "example.com") %> + # + # You should use the named_route_url style (which generates absolute URLs) and avoid using the + # named_route_path style (which generates relative URLs), since clients reading the mail will + # have no concept of a current URL from which to determine a relative path. + # + # It is also possible to set a default host that will be used in all mailers by setting the :host + # option as a configuration option in config/application.rb: + # + # config.action_mailer.default_url_options = { host: "example.com" } + # + # You can also define a default_url_options method on individual mailers to override these + # default settings per-mailer. + # + # By default when config.force_ssl is +true+, URLs generated for hosts will use the HTTPS protocol. + # + # = Sending mail + # + # Once a mailer action and template are defined, you can deliver your message or defer its creation and + # delivery for later: + # + # NotifierMailer.welcome(User.first).deliver_now # sends the email + # mail = NotifierMailer.welcome(User.first) # => an ActionMailer::MessageDelivery object + # mail.deliver_now # generates and sends the email now + # + # The ActionMailer::MessageDelivery class is a wrapper around a delegate that will call + # your method to generate the mail. If you want direct access to the delegator, or Mail::Message, + # you can call the message method on the ActionMailer::MessageDelivery object. + # + # NotifierMailer.welcome(User.first).message # => a Mail::Message object + # + # Action Mailer is nicely integrated with Active Job so you can generate and send emails in the background + # (example: outside of the request-response cycle, so the user doesn't have to wait on it): + # + # NotifierMailer.welcome(User.first).deliver_later # enqueue the email sending to Active Job + # + # Note that deliver_later will execute your method from the background job. + # + # You never instantiate your mailer class. Rather, you just call the method you defined on the class itself. + # All instance methods are expected to return a message object to be sent. + # + # = Multipart Emails + # + # Multipart messages can also be used implicitly because Action Mailer will automatically detect and use + # multipart templates, where each template is named after the name of the action, followed by the content + # type. Each such detected template will be added to the message, as a separate part. + # + # For example, if the following templates exist: + # * signup_notification.text.erb + # * signup_notification.html.erb + # * signup_notification.xml.builder + # * signup_notification.yml.erb + # + # Each would be rendered and added as a separate part to the message, with the corresponding content + # type. The content type for the entire message is automatically set to multipart/alternative, + # which indicates that the email contains multiple different representations of the same email + # body. The same instance variables defined in the action are passed to all email templates. + # + # Implicit template rendering is not performed if any attachments or parts have been added to the email. + # This means that you'll have to manually add each part to the email and set the content type of the email + # to multipart/alternative. + # + # = Attachments + # + # Sending attachment in emails is easy: + # + # class NotifierMailer < ApplicationMailer + # def welcome(recipient) + # attachments['free_book.pdf'] = File.read('path/to/file.pdf') + # mail(to: recipient, subject: "New account information") + # end + # end + # + # Which will (if it had both a welcome.text.erb and welcome.html.erb + # template in the view directory), send a complete multipart/mixed email with two parts, + # the first part being a multipart/alternative with the text and HTML email parts inside, + # and the second being a application/pdf with a Base64 encoded copy of the file.pdf book + # with the filename +free_book.pdf+. + # + # If you need to send attachments with no content, you need to create an empty view for it, + # or add an empty body parameter like this: + # + # class NotifierMailer < ApplicationMailer + # def welcome(recipient) + # attachments['free_book.pdf'] = File.read('path/to/file.pdf') + # mail(to: recipient, subject: "New account information", body: "") + # end + # end + # + # You can also send attachments with html template, in this case you need to add body, attachments, + # and custom content type like this: + # + # class NotifierMailer < ApplicationMailer + # def welcome(recipient) + # attachments["free_book.pdf"] = File.read("path/to/file.pdf") + # mail(to: recipient, + # subject: "New account information", + # content_type: "text/html", + # body: "Hello there") + # end + # end + # + # = Inline Attachments + # + # You can also specify that a file should be displayed inline with other HTML. This is useful + # if you want to display a corporate logo or a photo. + # + # class NotifierMailer < ApplicationMailer + # def welcome(recipient) + # attachments.inline['photo.png'] = File.read('path/to/photo.png') + # mail(to: recipient, subject: "Here is what we look like") + # end + # end + # + # And then to reference the image in the view, you create a welcome.html.erb file and + # make a call to +image_tag+ passing in the attachment you want to display and then call + # +url+ on the attachment to get the relative content id path for the image source: + # + #

Please Don't Cringe

+ # + # <%= image_tag attachments['photo.png'].url -%> + # + # As we are using Action View's +image_tag+ method, you can pass in any other options you want: + # + #

Please Don't Cringe

+ # + # <%= image_tag attachments['photo.png'].url, alt: 'Our Photo', class: 'photo' -%> + # + # = Observing and Intercepting Mails + # + # Action Mailer provides hooks into the Mail observer and interceptor methods. These allow you to + # register classes that are called during the mail delivery life cycle. + # + # An observer class must implement the :delivered_email(message) method which will be + # called once for every email sent after the email has been sent. + # + # An interceptor class must implement the :delivering_email(message) method which will be + # called before the email is sent, allowing you to make modifications to the email before it hits + # the delivery agents. Your class should make any needed modifications directly to the passed + # in Mail::Message instance. + # + # = Default Hash + # + # Action Mailer provides some intelligent defaults for your emails, these are usually specified in a + # default method inside the class definition: + # + # class NotifierMailer < ApplicationMailer + # default sender: 'system@example.com' + # end + # + # You can pass in any header value that a Mail::Message accepts. Out of the box, + # ActionMailer::Base sets the following: + # + # * mime_version: "1.0" + # * charset: "UTF-8" + # * content_type: "text/plain" + # * parts_order: [ "text/plain", "text/enriched", "text/html" ] + # + # parts_order and charset are not actually valid Mail::Message header fields, + # but Action Mailer translates them appropriately and sets the correct values. + # + # As you can pass in any header, you need to either quote the header as a string, or pass it in as + # an underscored symbol, so the following will work: + # + # class NotifierMailer < ApplicationMailer + # default 'Content-Transfer-Encoding' => '7bit', + # content_description: 'This is a description' + # end + # + # Finally, Action Mailer also supports passing Proc and Lambda objects into the default hash, + # so you can define methods that evaluate as the message is being generated: + # + # class NotifierMailer < ApplicationMailer + # default 'X-Special-Header' => Proc.new { my_method }, to: -> { @inviter.email_address } + # + # private + # def my_method + # 'some complex call' + # end + # end + # + # Note that the proc/lambda is evaluated right at the start of the mail message generation, so if you + # set something in the default hash using a proc, and then set the same thing inside of your + # mailer method, it will get overwritten by the mailer method. + # + # It is also possible to set these default options that will be used in all mailers through + # the default_options= configuration in config/application.rb: + # + # config.action_mailer.default_options = { from: "no-reply@example.org" } + # + # = Callbacks + # + # You can specify callbacks using before_action and after_action for configuring your messages. + # This may be useful, for example, when you want to add default inline attachments for all + # messages sent out by a certain mailer class: + # + # class NotifierMailer < ApplicationMailer + # before_action :add_inline_attachment! + # + # def welcome + # mail + # end + # + # private + # def add_inline_attachment! + # attachments.inline["footer.jpg"] = File.read('/path/to/filename.jpg') + # end + # end + # + # Callbacks in Action Mailer are implemented using + # AbstractController::Callbacks, so you can define and configure + # callbacks in the same manner that you would use callbacks in classes that + # inherit from ActionController::Base. + # + # Note that unless you have a specific reason to do so, you should prefer + # using before_action rather than after_action in your + # Action Mailer classes so that headers are parsed properly. + # + # = Previewing emails + # + # You can preview your email templates visually by adding a mailer preview file to the + # ActionMailer::Base.preview_path. Since most emails do something interesting + # with database data, you'll need to write some scenarios to load messages with fake data: + # + # class NotifierMailerPreview < ActionMailer::Preview + # def welcome + # NotifierMailer.welcome(User.first) + # end + # end + # + # Methods must return a Mail::Message object which can be generated by calling the mailer + # method without the additional deliver_now / deliver_later. The location of the + # mailer previews directory can be configured using the preview_path option which has a default + # of test/mailers/previews: + # + # config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews" + # + # An overview of all previews is accessible at http://localhost:3000/rails/mailers + # on a running development server instance. + # + # Previews can also be intercepted in a similar manner as deliveries can be by registering + # a preview interceptor that has a previewing_email method: + # + # class CssInlineStyler + # def self.previewing_email(message) + # # inline CSS styles + # end + # end + # + # config.action_mailer.preview_interceptors :css_inline_styler + # + # Note that interceptors need to be registered both with register_interceptor + # and register_preview_interceptor if they should operate on both sending and + # previewing emails. + # + # = Configuration options + # + # These options are specified on the class level, like + # ActionMailer::Base.raise_delivery_errors = true + # + # * default_options - You can pass this in at a class level as well as within the class itself as + # per the above section. + # + # * logger - the logger is used for generating information on the mailing run if available. + # Can be set to +nil+ for no logging. Compatible with both Ruby's own +Logger+ and Log4r loggers. + # + # * smtp_settings - Allows detailed configuration for :smtp delivery method: + # * :address - Allows you to use a remote mail server. Just change it from its default + # "localhost" setting. + # * :port - On the off chance that your mail server doesn't run on port 25, you can change it. + # * :domain - If you need to specify a HELO domain, you can do it here. + # * :user_name - If your mail server requires authentication, set the username in this setting. + # * :password - If your mail server requires authentication, set the password in this setting. + # * :authentication - If your mail server requires authentication, you need to specify the + # authentication type here. + # This is a symbol and one of :plain (will send the password Base64 encoded), :login (will + # send the password Base64 encoded) or :cram_md5 (combines a Challenge/Response mechanism to exchange + # information and a cryptographic Message Digest 5 algorithm to hash important information) + # * :enable_starttls_auto - Detects if STARTTLS is enabled in your SMTP server and starts + # to use it. Defaults to true. + # * :openssl_verify_mode - When using TLS, you can set how OpenSSL checks the certificate. This is + # really useful if you need to validate a self-signed and/or a wildcard certificate. You can use the name + # of an OpenSSL verify constant ('none' or 'peer') or directly the constant + # (OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER). + # :ssl/:tls Enables the SMTP connection to use SMTP/TLS (SMTPS: SMTP over direct TLS connection) + # + # * sendmail_settings - Allows you to override options for the :sendmail delivery method. + # * :location - The location of the sendmail executable. Defaults to /usr/sbin/sendmail. + # * :arguments - The command line arguments. Defaults to -i with -f sender@address + # added automatically before the message is sent. + # + # * file_settings - Allows you to override options for the :file delivery method. + # * :location - The directory into which emails will be written. Defaults to the application + # tmp/mails. + # + # * raise_delivery_errors - Whether or not errors should be raised if the email fails to be delivered. + # + # * delivery_method - Defines a delivery method. Possible values are :smtp (default), + # :sendmail, :test, and :file. Or you may provide a custom delivery method + # object e.g. +MyOwnDeliveryMethodClass+. See the Mail gem documentation on the interface you need to + # implement for a custom delivery agent. + # + # * perform_deliveries - Determines whether emails are actually sent from Action Mailer when you + # call .deliver on an email message or on an Action Mailer method. This is on by default but can + # be turned off to aid in functional testing. + # + # * deliveries - Keeps an array of all the emails sent out through the Action Mailer with + # delivery_method :test. Most useful for unit and functional testing. + # + # * deliver_later_queue_name - The name of the queue used with deliver_later. Defaults to +mailers+. + class Base < AbstractController::Base + include DeliveryMethods + include Rescuable + include Parameterized + include Previews + + abstract! + + include AbstractController::Rendering + + include AbstractController::Logger + include AbstractController::Helpers + include AbstractController::Translation + include AbstractController::AssetPaths + include AbstractController::Callbacks + include AbstractController::Caching + + include ActionView::Layouts + + PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [:@_action_has_layout] + + def _protected_ivars # :nodoc: + PROTECTED_IVARS + end + + helper ActionMailer::MailHelper + + class_attribute :delivery_job, default: ::ActionMailer::DeliveryJob + class_attribute :default_params, default: { + mime_version: "1.0", + charset: "UTF-8", + content_type: "text/plain", + parts_order: [ "text/plain", "text/enriched", "text/html" ] + }.freeze + + class << self + # Register one or more Observers which will be notified when mail is delivered. + def register_observers(*observers) + observers.flatten.compact.each { |observer| register_observer(observer) } + end + + # Register one or more Interceptors which will be called before mail is sent. + def register_interceptors(*interceptors) + interceptors.flatten.compact.each { |interceptor| register_interceptor(interceptor) } + end + + # Register an Observer which will be notified when mail is delivered. + # Either a class, string or symbol can be passed in as the Observer. + # If a string or symbol is passed in it will be camelized and constantized. + def register_observer(observer) + Mail.register_observer(observer_class_for(observer)) + end + + # Register an Interceptor which will be called before mail is sent. + # Either a class, string or symbol can be passed in as the Interceptor. + # If a string or symbol is passed in it will be camelized and constantized. + def register_interceptor(interceptor) + Mail.register_interceptor(observer_class_for(interceptor)) + end + + def observer_class_for(value) # :nodoc: + case value + when String, Symbol + value.to_s.camelize.constantize + else + value + end + end + private :observer_class_for + + # Returns the name of the current mailer. This method is also being used as a path for a view lookup. + # If this is an anonymous mailer, this method will return +anonymous+ instead. + def mailer_name + @mailer_name ||= anonymous? ? "anonymous" : name.underscore + end + # Allows to set the name of current mailer. + attr_writer :mailer_name + alias :controller_path :mailer_name + + # Sets the defaults through app configuration: + # + # config.action_mailer.default(from: "no-reply@example.org") + # + # Aliased by ::default_options= + def default(value = nil) + self.default_params = default_params.merge(value).freeze if value + default_params + end + # Allows to set defaults through app configuration: + # + # config.action_mailer.default_options = { from: "no-reply@example.org" } + alias :default_options= :default + + # Receives a raw email, parses it into an email object, decodes it, + # instantiates a new mailer, and passes the email object to the mailer + # object's +receive+ method. + # + # If you want your mailer to be able to process incoming messages, you'll + # need to implement a +receive+ method that accepts the raw email string + # as a parameter: + # + # class MyMailer < ActionMailer::Base + # def receive(mail) + # # ... + # end + # end + def receive(raw_mail) + ActiveSupport::Notifications.instrument("receive.action_mailer") do |payload| + mail = Mail.new(raw_mail) + set_payload_for_mail(payload, mail) + new.receive(mail) + end + end + + # Wraps an email delivery inside of ActiveSupport::Notifications instrumentation. + # + # This method is actually called by the Mail::Message object itself + # through a callback when you call :deliver on the Mail::Message, + # calling +deliver_mail+ directly and passing a Mail::Message will do + # nothing except tell the logger you sent the email. + def deliver_mail(mail) #:nodoc: + ActiveSupport::Notifications.instrument("deliver.action_mailer") do |payload| + set_payload_for_mail(payload, mail) + yield # Let Mail do the delivery actions + end + end + + private + + def set_payload_for_mail(payload, mail) + payload[:mailer] = name + payload[:message_id] = mail.message_id + payload[:subject] = mail.subject + payload[:to] = mail.to + payload[:from] = mail.from + payload[:bcc] = mail.bcc if mail.bcc.present? + payload[:cc] = mail.cc if mail.cc.present? + payload[:date] = mail.date + payload[:mail] = mail.encoded + end + + def method_missing(method_name, *args) + if action_methods.include?(method_name.to_s) + MessageDelivery.new(self, method_name, *args) + else + super + end + end + + def respond_to_missing?(method, include_all = false) + action_methods.include?(method.to_s) || super + end + end + + attr_internal :message + + def initialize + super() + @_mail_was_called = false + @_message = Mail.new + end + + def process(method_name, *args) #:nodoc: + payload = { + mailer: self.class.name, + action: method_name, + args: args + } + + ActiveSupport::Notifications.instrument("process.action_mailer", payload) do + super + @_message = NullMail.new unless @_mail_was_called + end + end + + class NullMail #:nodoc: + def body; "" end + def header; {} end + + def respond_to?(string, include_all = false) + true + end + + def method_missing(*args) + nil + end + end + + # Returns the name of the mailer object. + def mailer_name + self.class.mailer_name + end + + # Allows you to pass random and unusual headers to the new Mail::Message + # object which will add them to itself. + # + # headers['X-Special-Domain-Specific-Header'] = "SecretValue" + # + # You can also pass a hash into headers of header field names and values, + # which will then be set on the Mail::Message object: + # + # headers 'X-Special-Domain-Specific-Header' => "SecretValue", + # 'In-Reply-To' => incoming.message_id + # + # The resulting Mail::Message will have the following in its header: + # + # X-Special-Domain-Specific-Header: SecretValue + # + # Note about replacing already defined headers: + # + # * +subject+ + # * +sender+ + # * +from+ + # * +to+ + # * +cc+ + # * +bcc+ + # * +reply-to+ + # * +orig-date+ + # * +message-id+ + # * +references+ + # + # Fields can only appear once in email headers while other fields such as + # X-Anything can appear multiple times. + # + # If you want to replace any header which already exists, first set it to + # +nil+ in order to reset the value otherwise another field will be added + # for the same header. + def headers(args = nil) + if args + @_message.headers(args) + else + @_message + end + end + + # Allows you to add attachments to an email, like so: + # + # mail.attachments['filename.jpg'] = File.read('/path/to/filename.jpg') + # + # If you do this, then Mail will take the file name and work out the mime type. + # It will also set the Content-Type, Content-Disposition, Content-Transfer-Encoding + # and encode the contents of the attachment in Base64. + # + # You can also specify overrides if you want by passing a hash instead of a string: + # + # mail.attachments['filename.jpg'] = {mime_type: 'application/gzip', + # content: File.read('/path/to/filename.jpg')} + # + # If you want to use encoding other than Base64 then you will need to pass encoding + # type along with the pre-encoded content as Mail doesn't know how to decode the + # data: + # + # file_content = SpecialEncode(File.read('/path/to/filename.jpg')) + # mail.attachments['filename.jpg'] = {mime_type: 'application/gzip', + # encoding: 'SpecialEncoding', + # content: file_content } + # + # You can also search for specific attachments: + # + # # By Filename + # mail.attachments['filename.jpg'] # => Mail::Part object or nil + # + # # or by index + # mail.attachments[0] # => Mail::Part (first attachment) + # + def attachments + if @_mail_was_called + LateAttachmentsProxy.new(@_message.attachments) + else + @_message.attachments + end + end + + class LateAttachmentsProxy < SimpleDelegator + def inline; _raise_error end + def []=(_name, _content); _raise_error end + + private + def _raise_error + raise RuntimeError, "Can't add attachments after `mail` was called.\n" \ + "Make sure to use `attachments[]=` before calling `mail`." + end + end + + # The main method that creates the message and renders the email templates. There are + # two ways to call this method, with a block, or without a block. + # + # It accepts a headers hash. This hash allows you to specify + # the most used headers in an email message, these are: + # + # * +:subject+ - The subject of the message, if this is omitted, Action Mailer will + # ask the Rails I18n class for a translated +:subject+ in the scope of + # [mailer_scope, action_name] or if this is missing, will translate the + # humanized version of the +action_name+ + # * +:to+ - Who the message is destined for, can be a string of addresses, or an array + # of addresses. + # * +:from+ - Who the message is from + # * +:cc+ - Who you would like to Carbon-Copy on this email, can be a string of addresses, + # or an array of addresses. + # * +:bcc+ - Who you would like to Blind-Carbon-Copy on this email, can be a string of + # addresses, or an array of addresses. + # * +:reply_to+ - Who to set the Reply-To header of the email to. + # * +:date+ - The date to say the email was sent on. + # + # You can set default values for any of the above headers (except +:date+) + # by using the ::default class method: + # + # class Notifier < ActionMailer::Base + # default from: 'no-reply@test.lindsaar.net', + # bcc: 'email_logger@test.lindsaar.net', + # reply_to: 'bounces@test.lindsaar.net' + # end + # + # If you need other headers not listed above, you can either pass them in + # as part of the headers hash or use the headers['name'] = value + # method. + # + # When a +:return_path+ is specified as header, that value will be used as + # the 'envelope from' address for the Mail message. Setting this is useful + # when you want delivery notifications sent to a different address than the + # one in +:from+. Mail will actually use the +:return_path+ in preference + # to the +:sender+ in preference to the +:from+ field for the 'envelope + # from' value. + # + # If you do not pass a block to the +mail+ method, it will find all + # templates in the view paths using by default the mailer name and the + # method name that it is being called from, it will then create parts for + # each of these templates intelligently, making educated guesses on correct + # content type and sequence, and return a fully prepared Mail::Message + # ready to call :deliver on to send. + # + # For example: + # + # class Notifier < ActionMailer::Base + # default from: 'no-reply@test.lindsaar.net' + # + # def welcome + # mail(to: 'mikel@test.lindsaar.net') + # end + # end + # + # Will look for all templates at "app/views/notifier" with name "welcome". + # If no welcome template exists, it will raise an ActionView::MissingTemplate error. + # + # However, those can be customized: + # + # mail(template_path: 'notifications', template_name: 'another') + # + # And now it will look for all templates at "app/views/notifications" with name "another". + # + # If you do pass a block, you can render specific templates of your choice: + # + # mail(to: 'mikel@test.lindsaar.net') do |format| + # format.text + # format.html + # end + # + # You can even render plain text directly without using a template: + # + # mail(to: 'mikel@test.lindsaar.net') do |format| + # format.text { render plain: "Hello Mikel!" } + # format.html { render html: "

Hello Mikel!

".html_safe } + # end + # + # Which will render a +multipart/alternative+ email with +text/plain+ and + # +text/html+ parts. + # + # The block syntax also allows you to customize the part headers if desired: + # + # mail(to: 'mikel@test.lindsaar.net') do |format| + # format.text(content_transfer_encoding: "base64") + # format.html + # end + # + def mail(headers = {}, &block) + return message if @_mail_was_called && headers.blank? && !block + + # At the beginning, do not consider class default for content_type + content_type = headers[:content_type] + + headers = apply_defaults(headers) + + # Apply charset at the beginning so all fields are properly quoted + message.charset = charset = headers[:charset] + + # Set configure delivery behavior + wrap_delivery_behavior!(headers[:delivery_method], headers[:delivery_method_options]) + + assign_headers_to_message(message, headers) + + # Render the templates and blocks + responses = collect_responses(headers, &block) + @_mail_was_called = true + + create_parts_from_responses(message, responses) + + # Setup content type, reapply charset and handle parts order + message.content_type = set_content_type(message, content_type, headers[:content_type]) + message.charset = charset + + if message.multipart? + message.body.set_sort_order(headers[:parts_order]) + message.body.sort_parts! + end + + message + end + + private + + # Used by #mail to set the content type of the message. + # + # It will use the given +user_content_type+, or multipart if the mail + # message has any attachments. If the attachments are inline, the content + # type will be "multipart/related", otherwise "multipart/mixed". + # + # If there is no content type passed in via headers, and there are no + # attachments, or the message is multipart, then the default content type is + # used. + def set_content_type(m, user_content_type, class_default) # :doc: + params = m.content_type_parameters || {} + case + when user_content_type.present? + user_content_type + when m.has_attachments? + if m.attachments.detect(&:inline?) + ["multipart", "related", params] + else + ["multipart", "mixed", params] + end + when m.multipart? + ["multipart", "alternative", params] + else + m.content_type || class_default + end + end + + # Translates the +subject+ using Rails I18n class under [mailer_scope, action_name] scope. + # If it does not find a translation for the +subject+ under the specified scope it will default to a + # humanized version of the action_name. + # If the subject has interpolations, you can pass them through the +interpolations+ parameter. + def default_i18n_subject(interpolations = {}) # :doc: + mailer_scope = self.class.mailer_name.tr("/", ".") + I18n.t(:subject, interpolations.merge(scope: [mailer_scope, action_name], default: action_name.humanize)) + end + + # Emails do not support relative path links. + def self.supports_path? # :doc: + false + end + + def apply_defaults(headers) + default_values = self.class.default.map do |key, value| + [ + key, + compute_default(value) + ] + end.to_h + + headers_with_defaults = headers.reverse_merge(default_values) + headers_with_defaults[:subject] ||= default_i18n_subject + headers_with_defaults + end + + def compute_default(value) + return value unless value.is_a?(Proc) + + if value.arity == 1 + instance_exec(self, &value) + else + instance_exec(&value) + end + end + + def assign_headers_to_message(message, headers) + assignable = headers.except(:parts_order, :content_type, :body, :template_name, + :template_path, :delivery_method, :delivery_method_options) + assignable.each { |k, v| message[k] = v } + end + + def collect_responses(headers) + if block_given? + collector = ActionMailer::Collector.new(lookup_context) { render(action_name) } + yield(collector) + collector.responses + elsif headers[:body] + collect_responses_from_text(headers) + else + collect_responses_from_templates(headers) + end + end + + def collect_responses_from_text(headers) + [{ + body: headers.delete(:body), + content_type: headers[:content_type] || "text/plain" + }] + end + + def collect_responses_from_templates(headers) + templates_path = headers[:template_path] || self.class.mailer_name + templates_name = headers[:template_name] || action_name + + each_template(Array(templates_path), templates_name).map do |template| + self.formats = template.formats + { + body: render(template: template), + content_type: template.type.to_s + } + end + end + + def each_template(paths, name, &block) + templates = lookup_context.find_all(name, paths) + if templates.empty? + raise ActionView::MissingTemplate.new(paths, name, paths, false, "mailer") + else + templates.uniq(&:formats).each(&block) + end + end + + def create_parts_from_responses(m, responses) + if responses.size == 1 && !m.has_attachments? + responses[0].each { |k, v| m[k] = v } + elsif responses.size > 1 && m.has_attachments? + container = Mail::Part.new + container.content_type = "multipart/alternative" + responses.each { |r| insert_part(container, r, m.charset) } + m.add_part(container) + else + responses.each { |r| insert_part(m, r, m.charset) } + end + end + + def insert_part(container, response, charset) + response[:charset] ||= charset + part = Mail::Part.new(response) + container.add_part(part) + end + + # This and #instrument_name is for caching instrument + def instrument_payload(key) + { + mailer: mailer_name, + key: key + } + end + + def instrument_name + "action_mailer".freeze + end + + ActiveSupport.run_load_hooks(:action_mailer, self) + end +end diff --git a/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/collector.rb b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/collector.rb new file mode 100644 index 00000000..888410fa --- /dev/null +++ b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/collector.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require "abstract_controller/collector" +require "active_support/core_ext/hash/reverse_merge" +require "active_support/core_ext/array/extract_options" + +module ActionMailer + class Collector + include AbstractController::Collector + attr_reader :responses + + def initialize(context, &block) + @context = context + @responses = [] + @default_render = block + end + + def any(*args, &block) + options = args.extract_options! + raise ArgumentError, "You have to supply at least one format" if args.empty? + args.each { |type| send(type, options.dup, &block) } + end + alias :all :any + + def custom(mime, options = {}) + options.reverse_merge!(content_type: mime.to_s) + @context.formats = [mime.to_sym] + options[:body] = block_given? ? yield : @default_render.call + @responses << options + end + end +end diff --git a/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/delivery_job.rb b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/delivery_job.rb new file mode 100644 index 00000000..40f26d8a --- /dev/null +++ b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/delivery_job.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require "active_job" + +module ActionMailer + # The ActionMailer::DeliveryJob class is used when you + # want to send emails outside of the request-response cycle. + # + # Exceptions are rescued and handled by the mailer class. + class DeliveryJob < ActiveJob::Base # :nodoc: + queue_as { ActionMailer::Base.deliver_later_queue_name } + + rescue_from StandardError, with: :handle_exception_with_mailer_class + + def perform(mailer, mail_method, delivery_method, *args) #:nodoc: + mailer.constantize.public_send(mail_method, *args).send(delivery_method) + end + + private + # "Deserialize" the mailer class name by hand in case another argument + # (like a Global ID reference) raised DeserializationError. + def mailer_class + if mailer = Array(@serialized_arguments).first || Array(arguments).first + mailer.constantize + end + end + + def handle_exception_with_mailer_class(exception) + if klass = mailer_class + klass.handle_exception exception + else + raise exception + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/delivery_methods.rb b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/delivery_methods.rb new file mode 100644 index 00000000..5cd62307 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/delivery_methods.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require "tmpdir" + +module ActionMailer + # This module handles everything related to mail delivery, from registering + # new delivery methods to configuring the mail object to be sent. + module DeliveryMethods + extend ActiveSupport::Concern + + included do + # Do not make this inheritable, because we always want it to propagate + cattr_accessor :raise_delivery_errors, default: true + cattr_accessor :perform_deliveries, default: true + cattr_accessor :deliver_later_queue_name, default: :mailers + + class_attribute :delivery_methods, default: {}.freeze + class_attribute :delivery_method, default: :smtp + + add_delivery_method :smtp, Mail::SMTP, + address: "localhost", + port: 25, + domain: "localhost.localdomain", + user_name: nil, + password: nil, + authentication: nil, + enable_starttls_auto: true + + add_delivery_method :file, Mail::FileDelivery, + location: defined?(Rails.root) ? "#{Rails.root}/tmp/mails" : "#{Dir.tmpdir}/mails" + + add_delivery_method :sendmail, Mail::Sendmail, + location: "/usr/sbin/sendmail", + arguments: "-i" + + add_delivery_method :test, Mail::TestMailer + end + + # Helpers for creating and wrapping delivery behavior, used by DeliveryMethods. + module ClassMethods + # Provides a list of emails that have been delivered by Mail::TestMailer + delegate :deliveries, :deliveries=, to: Mail::TestMailer + + # Adds a new delivery method through the given class using the given + # symbol as alias and the default options supplied. + # + # add_delivery_method :sendmail, Mail::Sendmail, + # location: '/usr/sbin/sendmail', + # arguments: '-i' + def add_delivery_method(symbol, klass, default_options = {}) + class_attribute(:"#{symbol}_settings") unless respond_to?(:"#{symbol}_settings") + send(:"#{symbol}_settings=", default_options) + self.delivery_methods = delivery_methods.merge(symbol.to_sym => klass).freeze + end + + def wrap_delivery_behavior(mail, method = nil, options = nil) # :nodoc: + method ||= delivery_method + mail.delivery_handler = self + + case method + when NilClass + raise "Delivery method cannot be nil" + when Symbol + if klass = delivery_methods[method] + mail.delivery_method(klass, (send(:"#{method}_settings") || {}).merge(options || {})) + else + raise "Invalid delivery method #{method.inspect}" + end + else + mail.delivery_method(method) + end + + mail.perform_deliveries = perform_deliveries + mail.raise_delivery_errors = raise_delivery_errors + end + end + + def wrap_delivery_behavior!(*args) # :nodoc: + self.class.wrap_delivery_behavior(message, *args) + end + end +end diff --git a/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/gem_version.rb b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/gem_version.rb new file mode 100644 index 00000000..62de8125 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/gem_version.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActionMailer + # Returns the version of the currently loaded Action Mailer as a Gem::Version. + def self.gem_version + Gem::Version.new VERSION::STRING + end + + module VERSION + MAJOR = 5 + MINOR = 2 + TINY = 3 + PRE = nil + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") + end +end diff --git a/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/inline_preview_interceptor.rb b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/inline_preview_interceptor.rb new file mode 100644 index 00000000..8a12f805 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/inline_preview_interceptor.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require "base64" + +module ActionMailer + # Implements a mailer preview interceptor that converts image tag src attributes + # that use inline cid: style URLs to data: style URLs so that they are visible + # when previewing an HTML email in a web browser. + # + # This interceptor is enabled by default. To disable it, delete it from the + # ActionMailer::Base.preview_interceptors array: + # + # ActionMailer::Base.preview_interceptors.delete(ActionMailer::InlinePreviewInterceptor) + # + class InlinePreviewInterceptor + PATTERN = /src=(?:"cid:[^"]+"|'cid:[^']+')/i + + include Base64 + + def self.previewing_email(message) #:nodoc: + new(message).transform! + end + + def initialize(message) #:nodoc: + @message = message + end + + def transform! #:nodoc: + return message if html_part.blank? + + html_part.body = html_part.decoded.gsub(PATTERN) do |match| + if part = find_part(match[9..-2]) + %[src="#{data_url(part)}"] + else + match + end + end + + message + end + + private + def message + @message + end + + def html_part + @html_part ||= message.html_part + end + + def data_url(part) + "data:#{part.mime_type};base64,#{strict_encode64(part.body.raw_source)}" + end + + def find_part(cid) + message.all_parts.find { |p| p.attachment? && p.cid == cid } + end + end +end diff --git a/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/log_subscriber.rb b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/log_subscriber.rb new file mode 100644 index 00000000..87cfbfff --- /dev/null +++ b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/log_subscriber.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require "active_support/log_subscriber" + +module ActionMailer + # Implements the ActiveSupport::LogSubscriber for logging notifications when + # email is delivered or received. + class LogSubscriber < ActiveSupport::LogSubscriber + # An email was delivered. + def deliver(event) + info do + recipients = Array(event.payload[:to]).join(", ") + "Sent mail to #{recipients} (#{event.duration.round(1)}ms)" + end + + debug { event.payload[:mail] } + end + + # An email was received. + def receive(event) + info { "Received mail (#{event.duration.round(1)}ms)" } + debug { event.payload[:mail] } + end + + # An email was generated. + def process(event) + debug do + mailer = event.payload[:mailer] + action = event.payload[:action] + "#{mailer}##{action}: processed outbound mail in #{event.duration.round(1)}ms" + end + end + + # Use the logger configured for ActionMailer::Base. + def logger + ActionMailer::Base.logger + end + end +end + +ActionMailer::LogSubscriber.attach_to :action_mailer diff --git a/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/mail_helper.rb b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/mail_helper.rb new file mode 100644 index 00000000..e7bed41f --- /dev/null +++ b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/mail_helper.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +module ActionMailer + # Provides helper methods for ActionMailer::Base that can be used for easily + # formatting messages, accessing mailer or message instances, and the + # attachments list. + module MailHelper + # Take the text and format it, indented two spaces for each line, and + # wrapped at 72 columns: + # + # text = <<-TEXT + # This is + # the paragraph. + # + # * item1 * item2 + # TEXT + # + # block_format text + # # => " This is the paragraph.\n\n * item1\n * item2\n" + def block_format(text) + formatted = text.split(/\n\r?\n/).collect { |paragraph| + format_paragraph(paragraph) + }.join("\n\n") + + # Make list points stand on their own line + formatted.gsub!(/[ ]*([*]+) ([^*]*)/) { " #{$1} #{$2.strip}\n" } + formatted.gsub!(/[ ]*([#]+) ([^#]*)/) { " #{$1} #{$2.strip}\n" } + + formatted + end + + # Access the mailer instance. + def mailer + @_controller + end + + # Access the message instance. + def message + @_message + end + + # Access the message attachments list. + def attachments + mailer.attachments + end + + # Returns +text+ wrapped at +len+ columns and indented +indent+ spaces. + # By default column length +len+ equals 72 characters and indent + # +indent+ equal two spaces. + # + # my_text = 'Here is a sample text with more than 40 characters' + # + # format_paragraph(my_text, 25, 4) + # # => " Here is a sample text with\n more than 40 characters" + def format_paragraph(text, len = 72, indent = 2) + sentences = [[]] + + text.split.each do |word| + if sentences.first.present? && (sentences.last + [word]).join(" ").length > len + sentences << [word] + else + sentences.last << word + end + end + + indentation = " " * indent + sentences.map! { |sentence| + "#{indentation}#{sentence.join(' ')}" + }.join "\n" + end + end +end diff --git a/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/message_delivery.rb b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/message_delivery.rb new file mode 100644 index 00000000..2377aeb9 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/message_delivery.rb @@ -0,0 +1,144 @@ +# frozen_string_literal: true + +require "delegate" + +module ActionMailer + # The ActionMailer::MessageDelivery class is used by + # ActionMailer::Base when creating a new mailer. + # MessageDelivery is a wrapper (+Delegator+ subclass) around a lazy + # created Mail::Message. You can get direct access to the + # Mail::Message, deliver the email or schedule the email to be sent + # through Active Job. + # + # Notifier.welcome(User.first) # an ActionMailer::MessageDelivery object + # Notifier.welcome(User.first).deliver_now # sends the email + # Notifier.welcome(User.first).deliver_later # enqueue email delivery as a job through Active Job + # Notifier.welcome(User.first).message # a Mail::Message object + class MessageDelivery < Delegator + def initialize(mailer_class, action, *args) #:nodoc: + @mailer_class, @action, @args = mailer_class, action, args + + # The mail is only processed if we try to call any methods on it. + # Typical usage will leave it unloaded and call deliver_later. + @processed_mailer = nil + @mail_message = nil + end + + # Method calls are delegated to the Mail::Message that's ready to deliver. + def __getobj__ #:nodoc: + @mail_message ||= processed_mailer.message + end + + # Unused except for delegator internals (dup, marshaling). + def __setobj__(mail_message) #:nodoc: + @mail_message = mail_message + end + + # Returns the resulting Mail::Message + def message + __getobj__ + end + + # Was the delegate loaded, causing the mailer action to be processed? + def processed? + @processed_mailer || @mail_message + end + + # Enqueues the email to be delivered through Active Job. When the + # job runs it will send the email using +deliver_now!+. That means + # that the message will be sent bypassing checking +perform_deliveries+ + # and +raise_delivery_errors+, so use with caution. + # + # Notifier.welcome(User.first).deliver_later! + # Notifier.welcome(User.first).deliver_later!(wait: 1.hour) + # Notifier.welcome(User.first).deliver_later!(wait_until: 10.hours.from_now) + # + # Options: + # + # * :wait - Enqueue the email to be delivered with a delay + # * :wait_until - Enqueue the email to be delivered at (after) a specific date / time + # * :queue - Enqueue the email on the specified queue + # + # By default, the email will be enqueued using ActionMailer::DeliveryJob. Each + # ActionMailer::Base class can specify the job to use by setting the class variable + # +delivery_job+. + # + # class AccountRegistrationMailer < ApplicationMailer + # self.delivery_job = RegistrationDeliveryJob + # end + def deliver_later!(options = {}) + enqueue_delivery :deliver_now!, options + end + + # Enqueues the email to be delivered through Active Job. When the + # job runs it will send the email using +deliver_now+. + # + # Notifier.welcome(User.first).deliver_later + # Notifier.welcome(User.first).deliver_later(wait: 1.hour) + # Notifier.welcome(User.first).deliver_later(wait_until: 10.hours.from_now) + # + # Options: + # + # * :wait - Enqueue the email to be delivered with a delay. + # * :wait_until - Enqueue the email to be delivered at (after) a specific date / time. + # * :queue - Enqueue the email on the specified queue. + # + # By default, the email will be enqueued using ActionMailer::DeliveryJob. Each + # ActionMailer::Base class can specify the job to use by setting the class variable + # +delivery_job+. + # + # class AccountRegistrationMailer < ApplicationMailer + # self.delivery_job = RegistrationDeliveryJob + # end + def deliver_later(options = {}) + enqueue_delivery :deliver_now, options + end + + # Delivers an email without checking +perform_deliveries+ and +raise_delivery_errors+, + # so use with caution. + # + # Notifier.welcome(User.first).deliver_now! + # + def deliver_now! + processed_mailer.handle_exceptions do + message.deliver! + end + end + + # Delivers an email: + # + # Notifier.welcome(User.first).deliver_now + # + def deliver_now + processed_mailer.handle_exceptions do + message.deliver + end + end + + private + # Returns the processed Mailer instance. We keep this instance + # on hand so we can delegate exception handling to it. + def processed_mailer + @processed_mailer ||= @mailer_class.new.tap do |mailer| + mailer.process @action, *@args + end + end + + def enqueue_delivery(delivery_method, options = {}) + if processed? + ::Kernel.raise "You've accessed the message before asking to " \ + "deliver it later, so you may have made local changes that would " \ + "be silently lost if we enqueued a job to deliver it. Why? Only " \ + "the mailer method *arguments* are passed with the delivery job! " \ + "Do not access the message in any way if you mean to deliver it " \ + "later. Workarounds: 1. don't touch the message before calling " \ + "#deliver_later, 2. only touch the message *within your mailer " \ + "method*, or 3. use a custom Active Job instead of #deliver_later." + else + args = @mailer_class.name, @action.to_s, delivery_method.to_s, *@args + job = @mailer_class.delivery_job + job.set(options).perform_later(*args) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/parameterized.rb b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/parameterized.rb new file mode 100644 index 00000000..5e768e71 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/parameterized.rb @@ -0,0 +1,154 @@ +# frozen_string_literal: true + +module ActionMailer + # Provides the option to parameterize mailers in order to share instance variable + # setup, processing, and common headers. + # + # Consider this example that does not use parameterization: + # + # class InvitationsMailer < ApplicationMailer + # def account_invitation(inviter, invitee) + # @account = inviter.account + # @inviter = inviter + # @invitee = invitee + # + # subject = "#{@inviter.name} invited you to their Basecamp (#{@account.name})" + # + # mail \ + # subject: subject, + # to: invitee.email_address, + # from: common_address(inviter), + # reply_to: inviter.email_address_with_name + # end + # + # def project_invitation(project, inviter, invitee) + # @account = inviter.account + # @project = project + # @inviter = inviter + # @invitee = invitee + # @summarizer = ProjectInvitationSummarizer.new(@project.bucket) + # + # subject = "#{@inviter.name.familiar} added you to a project in Basecamp (#{@account.name})" + # + # mail \ + # subject: subject, + # to: invitee.email_address, + # from: common_address(inviter), + # reply_to: inviter.email_address_with_name + # end + # + # def bulk_project_invitation(projects, inviter, invitee) + # @account = inviter.account + # @projects = projects.sort_by(&:name) + # @inviter = inviter + # @invitee = invitee + # + # subject = "#{@inviter.name.familiar} added you to some new stuff in Basecamp (#{@account.name})" + # + # mail \ + # subject: subject, + # to: invitee.email_address, + # from: common_address(inviter), + # reply_to: inviter.email_address_with_name + # end + # end + # + # InvitationsMailer.account_invitation(person_a, person_b).deliver_later + # + # Using parameterized mailers, this can be rewritten as: + # + # class InvitationsMailer < ApplicationMailer + # before_action { @inviter, @invitee = params[:inviter], params[:invitee] } + # before_action { @account = params[:inviter].account } + # + # default to: -> { @invitee.email_address }, + # from: -> { common_address(@inviter) }, + # reply_to: -> { @inviter.email_address_with_name } + # + # def account_invitation + # mail subject: "#{@inviter.name} invited you to their Basecamp (#{@account.name})" + # end + # + # def project_invitation + # @project = params[:project] + # @summarizer = ProjectInvitationSummarizer.new(@project.bucket) + # + # mail subject: "#{@inviter.name.familiar} added you to a project in Basecamp (#{@account.name})" + # end + # + # def bulk_project_invitation + # @projects = params[:projects].sort_by(&:name) + # + # mail subject: "#{@inviter.name.familiar} added you to some new stuff in Basecamp (#{@account.name})" + # end + # end + # + # InvitationsMailer.with(inviter: person_a, invitee: person_b).account_invitation.deliver_later + module Parameterized + extend ActiveSupport::Concern + + included do + attr_accessor :params + end + + module ClassMethods + # Provide the parameters to the mailer in order to use them in the instance methods and callbacks. + # + # InvitationsMailer.with(inviter: person_a, invitee: person_b).account_invitation.deliver_later + # + # See Parameterized documentation for full example. + def with(params) + ActionMailer::Parameterized::Mailer.new(self, params) + end + end + + class Mailer # :nodoc: + def initialize(mailer, params) + @mailer, @params = mailer, params + end + + private + def method_missing(method_name, *args) + if @mailer.action_methods.include?(method_name.to_s) + ActionMailer::Parameterized::MessageDelivery.new(@mailer, method_name, @params, *args) + else + super + end + end + + def respond_to_missing?(method, include_all = false) + @mailer.respond_to?(method, include_all) + end + end + + class MessageDelivery < ActionMailer::MessageDelivery # :nodoc: + def initialize(mailer_class, action, params, *args) + super(mailer_class, action, *args) + @params = params + end + + private + def processed_mailer + @processed_mailer ||= @mailer_class.new.tap do |mailer| + mailer.params = @params + mailer.process @action, *@args + end + end + + def enqueue_delivery(delivery_method, options = {}) + if processed? + super + else + args = @mailer_class.name, @action.to_s, delivery_method.to_s, @params, *@args + ActionMailer::Parameterized::DeliveryJob.set(options).perform_later(*args) + end + end + end + + class DeliveryJob < ActionMailer::DeliveryJob # :nodoc: + def perform(mailer, mail_method, delivery_method, params, *args) + mailer.constantize.with(params).public_send(mail_method, *args).send(delivery_method) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/preview.rb b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/preview.rb new file mode 100644 index 00000000..0aea84fd --- /dev/null +++ b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/preview.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +require "active_support/descendants_tracker" + +module ActionMailer + module Previews #:nodoc: + extend ActiveSupport::Concern + + included do + # Set the location of mailer previews through app configuration: + # + # config.action_mailer.preview_path = "#{Rails.root}/lib/mailer_previews" + # + mattr_accessor :preview_path, instance_writer: false + + # Enable or disable mailer previews through app configuration: + # + # config.action_mailer.show_previews = true + # + # Defaults to +true+ for development environment + # + mattr_accessor :show_previews, instance_writer: false + + # :nodoc: + mattr_accessor :preview_interceptors, instance_writer: false, default: [ActionMailer::InlinePreviewInterceptor] + end + + module ClassMethods + # Register one or more Interceptors which will be called before mail is previewed. + def register_preview_interceptors(*interceptors) + interceptors.flatten.compact.each { |interceptor| register_preview_interceptor(interceptor) } + end + + # Register an Interceptor which will be called before mail is previewed. + # Either a class or a string can be passed in as the Interceptor. If a + # string is passed in it will be constantized. + def register_preview_interceptor(interceptor) + preview_interceptor = \ + case interceptor + when String, Symbol + interceptor.to_s.camelize.constantize + else + interceptor + end + + unless preview_interceptors.include?(preview_interceptor) + preview_interceptors << preview_interceptor + end + end + end + end + + class Preview + extend ActiveSupport::DescendantsTracker + + attr_reader :params + + def initialize(params = {}) + @params = params + end + + class << self + # Returns all mailer preview classes. + def all + load_previews if descendants.empty? + descendants + end + + # Returns the mail object for the given email name. The registered preview + # interceptors will be informed so that they can transform the message + # as they would if the mail was actually being delivered. + def call(email, params = {}) + preview = new(params) + message = preview.public_send(email) + inform_preview_interceptors(message) + message + end + + # Returns all of the available email previews. + def emails + public_instance_methods(false).map(&:to_s).sort + end + + # Returns +true+ if the email exists. + def email_exists?(email) + emails.include?(email) + end + + # Returns +true+ if the preview exists. + def exists?(preview) + all.any? { |p| p.preview_name == preview } + end + + # Find a mailer preview by its underscored class name. + def find(preview) + all.find { |p| p.preview_name == preview } + end + + # Returns the underscored name of the mailer preview without the suffix. + def preview_name + name.sub(/Preview$/, "").underscore + end + + private + def load_previews + if preview_path + Dir["#{preview_path}/**/*_preview.rb"].sort.each { |file| require_dependency file } + end + end + + def preview_path + Base.preview_path + end + + def show_previews + Base.show_previews + end + + def inform_preview_interceptors(message) + Base.preview_interceptors.each do |interceptor| + interceptor.previewing_email(message) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/railtie.rb b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/railtie.rb new file mode 100644 index 00000000..69578471 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/railtie.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require "active_job/railtie" +require "action_mailer" +require "rails" +require "abstract_controller/railties/routes_helpers" + +module ActionMailer + class Railtie < Rails::Railtie # :nodoc: + config.action_mailer = ActiveSupport::OrderedOptions.new + config.eager_load_namespaces << ActionMailer + + initializer "action_mailer.logger" do + ActiveSupport.on_load(:action_mailer) { self.logger ||= Rails.logger } + end + + initializer "action_mailer.set_configs" do |app| + paths = app.config.paths + options = app.config.action_mailer + + if app.config.force_ssl + options.default_url_options ||= {} + options.default_url_options[:protocol] ||= "https" + end + + options.assets_dir ||= paths["public"].first + options.javascripts_dir ||= paths["public/javascripts"].first + options.stylesheets_dir ||= paths["public/stylesheets"].first + options.show_previews = Rails.env.development? if options.show_previews.nil? + options.cache_store ||= Rails.cache + + if options.show_previews + options.preview_path ||= defined?(Rails.root) ? "#{Rails.root}/test/mailers/previews" : nil + end + + # make sure readers methods get compiled + options.asset_host ||= app.config.asset_host + options.relative_url_root ||= app.config.relative_url_root + + ActiveSupport.on_load(:action_mailer) do + include AbstractController::UrlFor + extend ::AbstractController::Railties::RoutesHelpers.with(app.routes, false) + include app.routes.mounted_helpers + + register_interceptors(options.delete(:interceptors)) + register_preview_interceptors(options.delete(:preview_interceptors)) + register_observers(options.delete(:observers)) + + options.each { |k, v| send("#{k}=", v) } + end + + ActiveSupport.on_load(:action_dispatch_integration_test) { include ActionMailer::TestCase::ClearTestDeliveries } + end + + initializer "action_mailer.compile_config_methods" do + ActiveSupport.on_load(:action_mailer) do + config.compile_methods! if config.respond_to?(:compile_methods!) + end + end + + initializer "action_mailer.eager_load_actions" do + ActiveSupport.on_load(:after_initialize) do + ActionMailer::Base.descendants.each(&:action_methods) if config.eager_load + end + end + + config.after_initialize do |app| + options = app.config.action_mailer + + if options.show_previews + app.routes.prepend do + get "/rails/mailers" => "rails/mailers#index", internal: true + get "/rails/mailers/*path" => "rails/mailers#preview", internal: true + end + + if options.preview_path + ActiveSupport::Dependencies.autoload_paths << options.preview_path + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/rescuable.rb b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/rescuable.rb new file mode 100644 index 00000000..5b567eb5 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/rescuable.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module ActionMailer #:nodoc: + # Provides +rescue_from+ for mailers. Wraps mailer action processing, + # mail job processing, and mail delivery. + module Rescuable + extend ActiveSupport::Concern + include ActiveSupport::Rescuable + + class_methods do + def handle_exception(exception) #:nodoc: + rescue_with_handler(exception) || raise(exception) + end + end + + def handle_exceptions #:nodoc: + yield + rescue => exception + rescue_with_handler(exception) || raise + end + + private + def process(*) + handle_exceptions do + super + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/test_case.rb b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/test_case.rb new file mode 100644 index 00000000..ee5a8648 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/test_case.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +require "active_support/test_case" +require "rails-dom-testing" + +module ActionMailer + class NonInferrableMailerError < ::StandardError + def initialize(name) + super "Unable to determine the mailer to test from #{name}. " \ + "You'll need to specify it using tests YourMailer in your " \ + "test case definition" + end + end + + class TestCase < ActiveSupport::TestCase + module ClearTestDeliveries + extend ActiveSupport::Concern + + included do + setup :clear_test_deliveries + teardown :clear_test_deliveries + end + + private + + def clear_test_deliveries + if ActionMailer::Base.delivery_method == :test + ActionMailer::Base.deliveries.clear + end + end + end + + module Behavior + extend ActiveSupport::Concern + + include ActiveSupport::Testing::ConstantLookup + include TestHelper + include Rails::Dom::Testing::Assertions::SelectorAssertions + include Rails::Dom::Testing::Assertions::DomAssertions + + included do + class_attribute :_mailer_class + setup :initialize_test_deliveries + setup :set_expected_mail + teardown :restore_test_deliveries + ActiveSupport.run_load_hooks(:action_mailer_test_case, self) + end + + module ClassMethods + def tests(mailer) + case mailer + when String, Symbol + self._mailer_class = mailer.to_s.camelize.constantize + when Module + self._mailer_class = mailer + else + raise NonInferrableMailerError.new(mailer) + end + end + + def mailer_class + if mailer = _mailer_class + mailer + else + tests determine_default_mailer(name) + end + end + + def determine_default_mailer(name) + mailer = determine_constant_from_test_name(name) do |constant| + Class === constant && constant < ActionMailer::Base + end + raise NonInferrableMailerError.new(name) if mailer.nil? + mailer + end + end + + private + + def initialize_test_deliveries + set_delivery_method :test + @old_perform_deliveries = ActionMailer::Base.perform_deliveries + ActionMailer::Base.perform_deliveries = true + ActionMailer::Base.deliveries.clear + end + + def restore_test_deliveries + restore_delivery_method + ActionMailer::Base.perform_deliveries = @old_perform_deliveries + end + + def set_delivery_method(method) + @old_delivery_method = ActionMailer::Base.delivery_method + ActionMailer::Base.delivery_method = method + end + + def restore_delivery_method + ActionMailer::Base.deliveries.clear + ActionMailer::Base.delivery_method = @old_delivery_method + end + + def set_expected_mail + @expected = Mail.new + @expected.content_type ["text", "plain", { "charset" => charset }] + @expected.mime_version = "1.0" + end + + def charset + "UTF-8" + end + + def encode(subject) + Mail::Encodings.q_value_encode(subject, charset) + end + + def read_fixture(action) + IO.readlines(File.join(Rails.root, "test", "fixtures", self.class.mailer_class.name.underscore, action)) + end + end + + include Behavior + end +end diff --git a/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/test_helper.rb b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/test_helper.rb new file mode 100644 index 00000000..041b9d0b --- /dev/null +++ b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/test_helper.rb @@ -0,0 +1,144 @@ +# frozen_string_literal: true + +require "active_job" + +module ActionMailer + # Provides helper methods for testing Action Mailer, including #assert_emails + # and #assert_no_emails. + module TestHelper + include ActiveJob::TestHelper + + # Asserts that the number of emails sent matches the given number. + # + # def test_emails + # assert_emails 0 + # ContactMailer.welcome.deliver_now + # assert_emails 1 + # ContactMailer.welcome.deliver_now + # assert_emails 2 + # end + # + # If a block is passed, that block should cause the specified number of + # emails to be sent. + # + # def test_emails_again + # assert_emails 1 do + # ContactMailer.welcome.deliver_now + # end + # + # assert_emails 2 do + # ContactMailer.welcome.deliver_now + # ContactMailer.welcome.deliver_now + # end + # end + def assert_emails(number) + if block_given? + original_count = ActionMailer::Base.deliveries.size + yield + new_count = ActionMailer::Base.deliveries.size + assert_equal number, new_count - original_count, "#{number} emails expected, but #{new_count - original_count} were sent" + else + assert_equal number, ActionMailer::Base.deliveries.size + end + end + + # Asserts that no emails have been sent. + # + # def test_emails + # assert_no_emails + # ContactMailer.welcome.deliver_now + # assert_emails 1 + # end + # + # If a block is passed, that block should not cause any emails to be sent. + # + # def test_emails_again + # assert_no_emails do + # # No emails should be sent from this block + # end + # end + # + # Note: This assertion is simply a shortcut for: + # + # assert_emails 0, &block + def assert_no_emails(&block) + assert_emails 0, &block + end + + # Asserts that the number of emails enqueued for later delivery matches + # the given number. + # + # def test_emails + # assert_enqueued_emails 0 + # ContactMailer.welcome.deliver_later + # assert_enqueued_emails 1 + # ContactMailer.welcome.deliver_later + # assert_enqueued_emails 2 + # end + # + # If a block is passed, that block should cause the specified number of + # emails to be enqueued. + # + # def test_emails_again + # assert_enqueued_emails 1 do + # ContactMailer.welcome.deliver_later + # end + # + # assert_enqueued_emails 2 do + # ContactMailer.welcome.deliver_later + # ContactMailer.welcome.deliver_later + # end + # end + def assert_enqueued_emails(number, &block) + assert_enqueued_jobs number, only: [ ActionMailer::DeliveryJob, ActionMailer::Parameterized::DeliveryJob ], &block + end + + # Asserts that block should cause the specified email + # to be enqueued. + # + # def test_email_in_block + # assert_enqueued_email_with ContactMailer, :welcome do + # ContactMailer.welcome.deliver_later + # end + # end + # + # If +args+ is provided as a Hash, a parameterized email is matched. + # + # def test_parameterized_email + # assert_enqueued_email_with ContactMailer, :welcome, + # args: {email: 'user@example.com} do + # ContactMailer.with(email: 'user@example.com').welcome.deliver_later + # end + # end + def assert_enqueued_email_with(mailer, method, args: nil, queue: "mailers", &block) + if args.is_a? Hash + job = ActionMailer::Parameterized::DeliveryJob + args = [mailer.to_s, method.to_s, "deliver_now", args] + else + job = ActionMailer::DeliveryJob + args = [mailer.to_s, method.to_s, "deliver_now", *args] + end + + assert_enqueued_with(job: job, args: args, queue: queue, &block) + end + + # Asserts that no emails are enqueued for later delivery. + # + # def test_no_emails + # assert_no_enqueued_emails + # ContactMailer.welcome.deliver_later + # assert_enqueued_emails 1 + # end + # + # If a block is provided, it should not cause any emails to be enqueued. + # + # def test_no_emails + # assert_no_enqueued_emails do + # # No emails should be enqueued from this block + # end + # end + def assert_no_enqueued_emails(&block) + assert_no_enqueued_jobs only: [ ActionMailer::DeliveryJob, ActionMailer::Parameterized::DeliveryJob ], &block + end + end +end diff --git a/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/version.rb b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/version.rb new file mode 100644 index 00000000..4549d6eb --- /dev/null +++ b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/action_mailer/version.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require_relative "gem_version" + +module ActionMailer + # Returns the version of the currently loaded Action Mailer as a + # Gem::Version. + def self.version + gem_version + end +end diff --git a/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/rails/generators/mailer/USAGE b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/rails/generators/mailer/USAGE new file mode 100644 index 00000000..2b0a0781 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/rails/generators/mailer/USAGE @@ -0,0 +1,17 @@ +Description: +============ + Stubs out a new mailer and its views. Passes the mailer name, either + CamelCased or under_scored, and an optional list of emails as arguments. + + This generates a mailer class in app/mailers and invokes your template + engine and test framework generators. + +Example: +======== + rails generate mailer Notifications signup forgot_password invoice + + creates a Notifications mailer class, views, and test: + Mailer: app/mailers/notifications_mailer.rb + Views: app/views/notifications_mailer/signup.text.erb [...] + Test: test/mailers/notifications_mailer_test.rb + diff --git a/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/rails/generators/mailer/mailer_generator.rb b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/rails/generators/mailer/mailer_generator.rb new file mode 100644 index 00000000..97eac30d --- /dev/null +++ b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/rails/generators/mailer/mailer_generator.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module Rails + module Generators + class MailerGenerator < NamedBase + source_root File.expand_path("templates", __dir__) + + argument :actions, type: :array, default: [], banner: "method method" + + check_class_collision suffix: "Mailer" + + def create_mailer_file + template "mailer.rb", File.join("app/mailers", class_path, "#{file_name}_mailer.rb") + + in_root do + if behavior == :invoke && !File.exist?(application_mailer_file_name) + template "application_mailer.rb", application_mailer_file_name + end + end + end + + hook_for :template_engine, :test_framework + + private + def file_name # :doc: + @_file_name ||= super.gsub(/_mailer/i, "") + end + + def application_mailer_file_name + @_application_mailer_file_name ||= if mountable_engine? + "app/mailers/#{namespaced_path}/application_mailer.rb" + else + "app/mailers/application_mailer.rb" + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/rails/generators/mailer/templates/application_mailer.rb.tt b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/rails/generators/mailer/templates/application_mailer.rb.tt new file mode 100644 index 00000000..00fb9bd4 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/rails/generators/mailer/templates/application_mailer.rb.tt @@ -0,0 +1,6 @@ +<% module_namespacing do -%> +class ApplicationMailer < ActionMailer::Base + default from: 'from@example.com' + layout 'mailer' +end +<% end %> diff --git a/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/rails/generators/mailer/templates/mailer.rb.tt b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/rails/generators/mailer/templates/mailer.rb.tt new file mode 100644 index 00000000..348d3147 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionmailer-5.2.3/lib/rails/generators/mailer/templates/mailer.rb.tt @@ -0,0 +1,17 @@ +<% module_namespacing do -%> +class <%= class_name %>Mailer < ApplicationMailer +<% actions.each do |action| -%> + + # Subject can be set in your I18n file at config/locales/en.yml + # with the following lookup: + # + # en.<%= file_path.tr("/",".") %>_mailer.<%= action %>.subject + # + def <%= action %> + @greeting = "Hi" + + mail to: "to@example.org" + end +<% end -%> +end +<% end -%> diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/CHANGELOG.md b/path/ruby/2.6.0/gems/actionpack-5.2.3/CHANGELOG.md new file mode 100644 index 00000000..bd33f949 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/CHANGELOG.md @@ -0,0 +1,429 @@ +## Rails 5.2.3 (March 27, 2019) ## + +* Allow using combine the Cache Control `public` and `no-cache` headers. + + Before this change, even if `public` was specified for Cache Control header, + it was excluded when `no-cache` was included. This fixed to keep `public` + header as is. + + Fixes #34780. + + *Yuji Yaginuma* + +* Allow `nil` params for `ActionController::TestCase`. + + *Ryo Nakamura* + + +## Rails 5.2.2.1 (March 11, 2019) ## + +* No changes. + + +## Rails 5.2.2 (December 04, 2018) ## + +* Reset Capybara sessions if failed system test screenshot raising an exception. + + Reset Capybara sessions if `take_failed_screenshot` raise exception + in system test `after_teardown`. + + *Maxim Perepelitsa* + +* Use request object for context if there's no controller + + There is no controller instance when using a redirect route or a + mounted rack application so pass the request object as the context + when resolving dynamic CSP sources in this scenario. + + Fixes #34200. + + *Andrew White* + +* Apply mapping to symbols returned from dynamic CSP sources + + Previously if a dynamic source returned a symbol such as :self it + would be converted to a string implicity, e.g: + + policy.default_src -> { :self } + + would generate the header: + + Content-Security-Policy: default-src self + + and now it generates: + + Content-Security-Policy: default-src 'self' + + *Andrew White* + +* Fix `rails routes -c` for controller name consists of multiple word. + + *Yoshiyuki Kinjo* + +* Call the `#redirect_to` block in controller context. + + *Steven Peckins* + + +## Rails 5.2.1.1 (November 27, 2018) ## + +* No changes. + + +## Rails 5.2.1 (August 07, 2018) ## + +* Prevent `?null=` being passed on JSON encoded test requests. + + `RequestEncoder#encode_params` won't attempt to parse params if + there are none. + + So call like this will no longer append a `?null=` query param. + + get foos_url, as: :json + + *Alireza Bashiri* + +* Ensure `ActionController::Parameters#transform_values` and + `ActionController::Parameters#transform_values!` converts hashes into + parameters. + + *Kevin Sjöberg* + +* Fix strong parameters `permit!` with nested arrays. + + Given: + ``` + params = ActionController::Parameters.new(nested_arrays: [[{ x: 2, y: 3 }, { x: 21, y: 42 }]]) + params.permit! + ``` + + `params[:nested_arrays][0][0].permitted?` will now return `true` instead of `false`. + + *Steve Hull* + +* Reset `RAW_POST_DATA` and `CONTENT_LENGTH` request environment between test requests in + `ActionController::TestCase` subclasses. + + *Eugene Kenny* + +* Output only one Content-Security-Policy nonce header value per request. + + Fixes #32597. + + *Andrey Novikov*, *Andrew White* + +* Only disable GPUs for headless Chrome on Windows. + + It is not necessary anymore for Linux and macOS machines. + + https://bugs.chromium.org/p/chromium/issues/detail?id=737678#c1 + + *Stefan Wrobel* + +* Fix system tests transactions not closed between examples. + + *Sergey Tarasov* + + +## Rails 5.2.0 (April 09, 2018) ## + +* Check exclude before flagging cookies as secure. + + *Catherine Khuu* + +* Always yield a CSP policy instance from `content_security_policy` + + This allows a controller action to enable the policy individually + for a controller and/or specific actions. + + *Andrew White* + +* Add the ability to disable the global CSP in a controller, e.g: + + class LegacyPagesController < ApplicationController + content_security_policy false, only: :index + end + + *Andrew White* + +* Add alias method `to_hash` to `to_h` for `cookies`. + Add alias method `to_h` to `to_hash` for `session`. + + *Igor Kasyanchuk* + +* Update the default HSTS max-age value to 31536000 seconds (1 year) + to meet the minimum max-age requirement for https://hstspreload.org/. + + *Grant Bourque* + +* Add support for automatic nonce generation for Rails UJS. + + Because the UJS library creates a script tag to process responses it + normally requires the script-src attribute of the content security + policy to include 'unsafe-inline'. + + To work around this we generate a per-request nonce value that is + embedded in a meta tag in a similar fashion to how CSRF protection + embeds its token in a meta tag. The UJS library can then read the + nonce value and set it on the dynamically generated script tag to + enable it to execute without needing 'unsafe-inline' enabled. + + Nonce generation isn't 100% safe - if your script tag is including + user generated content in someway then it may be possible to exploit + an XSS vulnerability which can take advantage of the nonce. It is + however an improvement on a blanket permission for inline scripts. + + It is also possible to use the nonce within your own script tags by + using `nonce: true` to set the nonce value on the tag, e.g + + <%= javascript_tag nonce: true do %> + alert('Hello, World!'); + <% end %> + + Fixes #31689. + + *Andrew White* + +* Matches behavior of `Hash#each` in `ActionController::Parameters#each`. + + *Dominic Cleal* + +* Add `Referrer-Policy` header to default headers set. + + *Guillermo Iguaran* + +* Changed the system tests to set Puma as default server only when the + user haven't specified manually another server. + + *Guillermo Iguaran* + +* Add secure `X-Download-Options` and `X-Permitted-Cross-Domain-Policies` to + default headers set. + + *Guillermo Iguaran* + +* Add headless firefox support to System Tests. + + *bogdanvlviv* + +* Changed the default system test screenshot output from `inline` to `simple`. + + `inline` works well for iTerm2 but not everyone uses iTerm2. Some terminals like + Terminal.app ignore the `inline` and output the path to the file since it can't + render the image. Other terminals, like those on Ubuntu, cannot handle the image + inline, but also don't handle it gracefully and instead of outputting the file + path, it dumps binary into the terminal. + + Commit 9d6e28 fixes this by changing the default for screenshot to be `simple`. + + *Eileen M. Uchitelle* + +* Register most popular audio/video/font mime types supported by modern browsers. + + *Guillermo Iguaran* + +* Fix optimized url helpers when using relative url root. + + Fixes #31220. + + *Andrew White* + +* Add DSL for configuring Content-Security-Policy header. + + The DSL allows you to configure a global Content-Security-Policy + header and then override within a controller. For more information + about the Content-Security-Policy header see MDN: + + https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy + + Example global policy: + + # config/initializers/content_security_policy.rb + Rails.application.config.content_security_policy do |p| + p.default_src :self, :https + p.font_src :self, :https, :data + p.img_src :self, :https, :data + p.object_src :none + p.script_src :self, :https + p.style_src :self, :https, :unsafe_inline + end + + Example controller overrides: + + # Override policy inline + class PostsController < ApplicationController + content_security_policy do |p| + p.upgrade_insecure_requests true + end + end + + # Using literal values + class PostsController < ApplicationController + content_security_policy do |p| + p.base_uri "https://www.example.com" + end + end + + # Using mixed static and dynamic values + class PostsController < ApplicationController + content_security_policy do |p| + p.base_uri :self, -> { "https://#{current_user.domain}.example.com" } + end + end + + Allows you to also only report content violations for migrating + legacy content using the `content_security_policy_report_only` + configuration attribute, e.g; + + # config/initializers/content_security_policy.rb + Rails.application.config.content_security_policy_report_only = true + + # controller override + class PostsController < ApplicationController + content_security_policy_report_only only: :index + end + + Note that this feature does not validate the header for performance + reasons since the header is calculated at runtime. + + *Andrew White* + +* Make `assert_recognizes` to traverse mounted engines. + + *Yuichiro Kaneko* + +* Remove deprecated `ActionController::ParamsParser::ParseError`. + + *Rafael Mendonça França* + +* Add `:allow_other_host` option to `redirect_back` method. + + When `allow_other_host` is set to `false`, the `redirect_back` will not allow redirecting from a + different host. `allow_other_host` is `true` by default. + + *Tim Masliuchenko* + +* Add headless chrome support to System Tests. + + *Yuji Yaginuma* + +* Add ability to enable Early Hints for HTTP/2 + + If supported by the server, and enabled in Puma this allows H2 Early Hints to be used. + + The `javascript_include_tag` and the `stylesheet_link_tag` automatically add Early Hints if requested. + + *Eileen M. Uchitelle*, *Aaron Patterson* + +* Simplify cookies middleware with key rotation support + + Use the `rotate` method for both `MessageEncryptor` and + `MessageVerifier` to add key rotation support for encrypted and + signed cookies. This also helps simplify support for legacy cookie + security. + + *Michael J Coyne* + +* Use Capybara registered `:puma` server config. + + The Capybara registered `:puma` server ensures the puma server is run in process so + connection sharing and open request detection work correctly by default. + + *Thomas Walpole* + +* Cookies `:expires` option supports `ActiveSupport::Duration` object. + + cookies[:user_name] = { value: "assain", expires: 1.hour } + cookies[:key] = { value: "a yummy cookie", expires: 6.months } + + Pull Request: #30121 + + *Assain Jaleel* + +* Enforce signed/encrypted cookie expiry server side. + + Rails can thwart attacks by malicious clients that don't honor a cookie's expiry. + + It does so by stashing the expiry within the written cookie and relying on the + signing/encrypting to vouch that it hasn't been tampered with. Then on a + server-side read, the expiry is verified and any expired cookie is discarded. + + Pull Request: #30121 + + *Assain Jaleel* + +* Make `take_failed_screenshot` work within engine. + + Fixes #30405. + + *Yuji Yaginuma* + +* Deprecate `ActionDispatch::TestResponse` response aliases. + + `#success?`, `#missing?` & `#error?` are not supported by the actual + `ActionDispatch::Response` object and can produce false-positives. Instead, + use the response helpers provided by `Rack::Response`. + + *Trevor Wistaff* + +* Protect from forgery by default + + Rather than protecting from forgery in the generated `ApplicationController`, + add it to `ActionController::Base` depending on + `config.action_controller.default_protect_from_forgery`. This configuration + defaults to false to support older versions which have removed it from their + `ApplicationController`, but is set to true for Rails 5.2. + + *Lisa Ugray* + +* Fallback `ActionController::Parameters#to_s` to `Hash#to_s`. + + *Kir Shatrov* + +* `driven_by` now registers poltergeist and capybara-webkit. + + If poltergeist or capybara-webkit are set as drivers is set for System Tests, + `driven_by` will register the driver and set additional options passed via + the `:options` parameter. + + Refer to the respective driver's documentation to see what options can be passed. + + *Mario Chavez* + +* AEAD encrypted cookies and sessions with GCM. + + Encrypted cookies now use AES-GCM which couples authentication and + encryption in one faster step and produces shorter ciphertexts. Cookies + encrypted using AES in CBC HMAC mode will be seamlessly upgraded when + this new mode is enabled via the + `action_dispatch.use_authenticated_cookie_encryption` configuration value. + + *Michael J Coyne* + +* Change the cache key format for fragments to make it easier to debug key churn. The new format is: + + views/template/action.html.erb:7a1156131a6928cb0026877f8b749ac9/projects/123 + ^template path ^template tree digest ^class ^id + + *DHH* + +* Add support for recyclable cache keys with fragment caching. This uses the new versioned entries in the + `ActiveSupport::Cache` stores and relies on the fact that Active Record has split `#cache_key` and `#cache_version` + to support it. + + *DHH* + +* Add `action_controller_api` and `action_controller_base` load hooks to be called in `ActiveSupport.on_load` + + `ActionController::Base` and `ActionController::API` have differing implementations. This means that + the one umbrella hook `action_controller` is not able to address certain situations where a method + may not exist in a certain implementation. + + This is fixed by adding two new hooks so you can target `ActionController::Base` vs `ActionController::API` + + Fixes #27013. + + *Julian Nadeau* + + +Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/actionpack/CHANGELOG.md) for previous changes. diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/MIT-LICENSE b/path/ruby/2.6.0/gems/actionpack-5.2.3/MIT-LICENSE new file mode 100644 index 00000000..1cb3add0 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/MIT-LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2004-2018 David Heinemeier Hansson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/README.rdoc b/path/ruby/2.6.0/gems/actionpack-5.2.3/README.rdoc new file mode 100644 index 00000000..f31b1c10 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/README.rdoc @@ -0,0 +1,57 @@ += Action Pack -- From request to response + +Action Pack is a framework for handling and responding to web requests. It +provides mechanisms for *routing* (mapping request URLs to actions), defining +*controllers* that implement actions, and generating responses by rendering +*views*, which are templates of various formats. In short, Action Pack +provides the view and controller layers in the MVC paradigm. + +It consists of several modules: + +* Action Dispatch, which parses information about the web request, handles + routing as defined by the user, and does advanced processing related to HTTP + such as MIME-type negotiation, decoding parameters in POST, PATCH, or PUT bodies, + handling HTTP caching logic, cookies and sessions. + +* Action Controller, which provides a base controller class that can be + subclassed to implement filters and actions to handle requests. The result + of an action is typically content generated from views. + +With the Ruby on Rails framework, users only directly interface with the +Action Controller module. Necessary Action Dispatch functionality is activated +by default and Action View rendering is implicitly triggered by Action +Controller. However, these modules are designed to function on their own and +can be used outside of Rails. + + +== Download and installation + +The latest version of Action Pack can be installed with RubyGems: + + $ gem install actionpack + +Source code can be downloaded as part of the Rails project on GitHub: + +* https://github.com/rails/rails/tree/5-2-stable/actionpack + + +== License + +Action Pack is released under the MIT license: + +* https://opensource.org/licenses/MIT + + +== Support + +API documentation is at: + +* http://api.rubyonrails.org + +Bug reports for the Ruby on Rails project can be filed here: + +* https://github.com/rails/rails/issues + +Feature requests should be discussed on the rails-core mailing list here: + +* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller.rb new file mode 100644 index 00000000..3a989311 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require "action_pack" +require "active_support/rails" +require "active_support/i18n" + +module AbstractController + extend ActiveSupport::Autoload + + autoload :ActionNotFound, "abstract_controller/base" + autoload :Base + autoload :Caching + autoload :Callbacks + autoload :Collector + autoload :DoubleRenderError, "abstract_controller/rendering" + autoload :Helpers + autoload :Logger + autoload :Rendering + autoload :Translation + autoload :AssetPaths + autoload :UrlFor + + def self.eager_load! + super + AbstractController::Caching.eager_load! + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/asset_paths.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/asset_paths.rb new file mode 100644 index 00000000..d6ee84b8 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/asset_paths.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module AbstractController + module AssetPaths #:nodoc: + extend ActiveSupport::Concern + + included do + config_accessor :asset_host, :assets_dir, :javascripts_dir, + :stylesheets_dir, :default_asset_host_protocol, :relative_url_root + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/base.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/base.rb new file mode 100644 index 00000000..a312af67 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/base.rb @@ -0,0 +1,265 @@ +# frozen_string_literal: true + +require "abstract_controller/error" +require "active_support/configurable" +require "active_support/descendants_tracker" +require "active_support/core_ext/module/anonymous" +require "active_support/core_ext/module/attr_internal" + +module AbstractController + # Raised when a non-existing controller action is triggered. + class ActionNotFound < StandardError + end + + # AbstractController::Base is a low-level API. Nobody should be + # using it directly, and subclasses (like ActionController::Base) are + # expected to provide their own +render+ method, since rendering means + # different things depending on the context. + class Base + ## + # Returns the body of the HTTP response sent by the controller. + attr_internal :response_body + + ## + # Returns the name of the action this controller is processing. + attr_internal :action_name + + ## + # Returns the formats that can be processed by the controller. + attr_internal :formats + + include ActiveSupport::Configurable + extend ActiveSupport::DescendantsTracker + + class << self + attr_reader :abstract + alias_method :abstract?, :abstract + + # Define a controller as abstract. See internal_methods for more + # details. + def abstract! + @abstract = true + end + + def inherited(klass) # :nodoc: + # Define the abstract ivar on subclasses so that we don't get + # uninitialized ivar warnings + unless klass.instance_variable_defined?(:@abstract) + klass.instance_variable_set(:@abstract, false) + end + super + end + + # A list of all internal methods for a controller. This finds the first + # abstract superclass of a controller, and gets a list of all public + # instance methods on that abstract class. Public instance methods of + # a controller would normally be considered action methods, so methods + # declared on abstract classes are being removed. + # (ActionController::Metal and ActionController::Base are defined as abstract) + def internal_methods + controller = self + + controller = controller.superclass until controller.abstract? + controller.public_instance_methods(true) + end + + # A list of method names that should be considered actions. This + # includes all public instance methods on a controller, less + # any internal methods (see internal_methods), adding back in + # any methods that are internal, but still exist on the class + # itself. + # + # ==== Returns + # * Set - A set of all methods that should be considered actions. + def action_methods + @action_methods ||= begin + # All public instance methods of this class, including ancestors + methods = (public_instance_methods(true) - + # Except for public instance methods of Base and its ancestors + internal_methods + + # Be sure to include shadowed public instance methods of this class + public_instance_methods(false)).uniq.map(&:to_s) + + methods.to_set + end + end + + # action_methods are cached and there is sometimes a need to refresh + # them. ::clear_action_methods! allows you to do that, so next time + # you run action_methods, they will be recalculated. + def clear_action_methods! + @action_methods = nil + end + + # Returns the full controller name, underscored, without the ending Controller. + # + # class MyApp::MyPostsController < AbstractController::Base + # + # end + # + # MyApp::MyPostsController.controller_path # => "my_app/my_posts" + # + # ==== Returns + # * String + def controller_path + @controller_path ||= name.sub(/Controller$/, "".freeze).underscore unless anonymous? + end + + # Refresh the cached action_methods when a new action_method is added. + def method_added(name) + super + clear_action_methods! + end + end + + abstract! + + # Calls the action going through the entire action dispatch stack. + # + # The actual method that is called is determined by calling + # #method_for_action. If no method can handle the action, then an + # AbstractController::ActionNotFound error is raised. + # + # ==== Returns + # * self + def process(action, *args) + @_action_name = action.to_s + + unless action_name = _find_action_name(@_action_name) + raise ActionNotFound, "The action '#{action}' could not be found for #{self.class.name}" + end + + @_response_body = nil + + process_action(action_name, *args) + end + + # Delegates to the class' ::controller_path + def controller_path + self.class.controller_path + end + + # Delegates to the class' ::action_methods + def action_methods + self.class.action_methods + end + + # Returns true if a method for the action is available and + # can be dispatched, false otherwise. + # + # Notice that action_methods.include?("foo") may return + # false and available_action?("foo") returns true because + # this method considers actions that are also available + # through other means, for example, implicit render ones. + # + # ==== Parameters + # * action_name - The name of an action to be tested + def available_action?(action_name) + _find_action_name(action_name) + end + + # Tests if a response body is set. Used to determine if the + # +process_action+ callback needs to be terminated in + # +AbstractController::Callbacks+. + def performed? + response_body + end + + # Returns true if the given controller is capable of rendering + # a path. A subclass of +AbstractController::Base+ + # may return false. An Email controller for example does not + # support paths, only full URLs. + def self.supports_path? + true + end + + private + + # Returns true if the name can be considered an action because + # it has a method defined in the controller. + # + # ==== Parameters + # * name - The name of an action to be tested + def action_method?(name) + self.class.action_methods.include?(name) + end + + # Call the action. Override this in a subclass to modify the + # behavior around processing an action. This, and not #process, + # is the intended way to override action dispatching. + # + # Notice that the first argument is the method to be dispatched + # which is *not* necessarily the same as the action name. + def process_action(method_name, *args) + send_action(method_name, *args) + end + + # Actually call the method associated with the action. Override + # this method if you wish to change how action methods are called, + # not to add additional behavior around it. For example, you would + # override #send_action if you want to inject arguments into the + # method. + alias send_action send + + # If the action name was not found, but a method called "action_missing" + # was found, #method_for_action will return "_handle_action_missing". + # This method calls #action_missing with the current action name. + def _handle_action_missing(*args) + action_missing(@_action_name, *args) + end + + # Takes an action name and returns the name of the method that will + # handle the action. + # + # It checks if the action name is valid and returns false otherwise. + # + # See method_for_action for more information. + # + # ==== Parameters + # * action_name - An action name to find a method name for + # + # ==== Returns + # * string - The name of the method that handles the action + # * false - No valid method name could be found. + # Raise +AbstractController::ActionNotFound+. + def _find_action_name(action_name) + _valid_action_name?(action_name) && method_for_action(action_name) + end + + # Takes an action name and returns the name of the method that will + # handle the action. In normal cases, this method returns the same + # name as it receives. By default, if #method_for_action receives + # a name that is not an action, it will look for an #action_missing + # method and return "_handle_action_missing" if one is found. + # + # Subclasses may override this method to add additional conditions + # that should be considered an action. For instance, an HTTP controller + # with a template matching the action name is considered to exist. + # + # If you override this method to handle additional cases, you may + # also provide a method (like +_handle_method_missing+) to handle + # the case. + # + # If none of these conditions are true, and +method_for_action+ + # returns +nil+, an +AbstractController::ActionNotFound+ exception will be raised. + # + # ==== Parameters + # * action_name - An action name to find a method name for + # + # ==== Returns + # * string - The name of the method that handles the action + # * nil - No method name could be found. + def method_for_action(action_name) + if action_method?(action_name) + action_name + elsif respond_to?(:action_missing, true) + "_handle_action_missing" + end + end + + # Checks if the action name is valid and returns false otherwise. + def _valid_action_name?(action_name) + !action_name.to_s.include? File::SEPARATOR + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/caching.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/caching.rb new file mode 100644 index 00000000..ce6b757c --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/caching.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +module AbstractController + module Caching + extend ActiveSupport::Concern + extend ActiveSupport::Autoload + + eager_autoload do + autoload :Fragments + end + + module ConfigMethods + def cache_store + config.cache_store + end + + def cache_store=(store) + config.cache_store = ActiveSupport::Cache.lookup_store(store) + end + + private + def cache_configured? + perform_caching && cache_store + end + end + + include ConfigMethods + include AbstractController::Caching::Fragments + + included do + extend ConfigMethods + + config_accessor :default_static_extension + self.default_static_extension ||= ".html" + + config_accessor :perform_caching + self.perform_caching = true if perform_caching.nil? + + config_accessor :enable_fragment_cache_logging + self.enable_fragment_cache_logging = false + + class_attribute :_view_cache_dependencies, default: [] + helper_method :view_cache_dependencies if respond_to?(:helper_method) + end + + module ClassMethods + def view_cache_dependency(&dependency) + self._view_cache_dependencies += [dependency] + end + end + + def view_cache_dependencies + self.class._view_cache_dependencies.map { |dep| instance_exec(&dep) }.compact + end + + private + # Convenience accessor. + def cache(key, options = {}, &block) # :doc: + if cache_configured? + cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block) + else + yield + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/caching/fragments.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/caching/fragments.rb new file mode 100644 index 00000000..f99b0830 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/caching/fragments.rb @@ -0,0 +1,166 @@ +# frozen_string_literal: true + +module AbstractController + module Caching + # Fragment caching is used for caching various blocks within + # views without caching the entire action as a whole. This is + # useful when certain elements of an action change frequently or + # depend on complicated state while other parts rarely change or + # can be shared amongst multiple parties. The caching is done using + # the +cache+ helper available in the Action View. See + # ActionView::Helpers::CacheHelper for more information. + # + # While it's strongly recommended that you use key-based cache + # expiration (see links in CacheHelper for more information), + # it is also possible to manually expire caches. For example: + # + # expire_fragment('name_of_cache') + module Fragments + extend ActiveSupport::Concern + + included do + if respond_to?(:class_attribute) + class_attribute :fragment_cache_keys + else + mattr_writer :fragment_cache_keys + end + + self.fragment_cache_keys = [] + + if respond_to?(:helper_method) + helper_method :fragment_cache_key + helper_method :combined_fragment_cache_key + end + end + + module ClassMethods + # Allows you to specify controller-wide key prefixes for + # cache fragments. Pass either a constant +value+, or a block + # which computes a value each time a cache key is generated. + # + # For example, you may want to prefix all fragment cache keys + # with a global version identifier, so you can easily + # invalidate all caches. + # + # class ApplicationController + # fragment_cache_key "v1" + # end + # + # When it's time to invalidate all fragments, simply change + # the string constant. Or, progressively roll out the cache + # invalidation using a computed value: + # + # class ApplicationController + # fragment_cache_key do + # @account.id.odd? ? "v1" : "v2" + # end + # end + def fragment_cache_key(value = nil, &key) + self.fragment_cache_keys += [key || -> { value }] + end + end + + # Given a key (as described in +expire_fragment+), returns + # a key suitable for use in reading, writing, or expiring a + # cached fragment. All keys begin with views/, + # followed by any controller-wide key prefix values, ending + # with the specified +key+ value. The key is expanded using + # ActiveSupport::Cache.expand_cache_key. + def fragment_cache_key(key) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Calling fragment_cache_key directly is deprecated and will be removed in Rails 6.0. + All fragment accessors now use the combined_fragment_cache_key method that retains the key as an array, + such that the caching stores can interrogate the parts for cache versions used in + recyclable cache keys. + MSG + + head = self.class.fragment_cache_keys.map { |k| instance_exec(&k) } + tail = key.is_a?(Hash) ? url_for(key).split("://").last : key + ActiveSupport::Cache.expand_cache_key([*head, *tail], :views) + end + + # Given a key (as described in +expire_fragment+), returns + # a key array suitable for use in reading, writing, or expiring a + # cached fragment. All keys begin with :views, + # followed by ENV["RAILS_CACHE_ID"] or ENV["RAILS_APP_VERSION"] if set, + # followed by any controller-wide key prefix values, ending + # with the specified +key+ value. + def combined_fragment_cache_key(key) + head = self.class.fragment_cache_keys.map { |k| instance_exec(&k) } + tail = key.is_a?(Hash) ? url_for(key).split("://").last : key + [ :views, (ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]), *head, *tail ].compact + end + + # Writes +content+ to the location signified by + # +key+ (see +expire_fragment+ for acceptable formats). + def write_fragment(key, content, options = nil) + return content unless cache_configured? + + key = combined_fragment_cache_key(key) + instrument_fragment_cache :write_fragment, key do + content = content.to_str + cache_store.write(key, content, options) + end + content + end + + # Reads a cached fragment from the location signified by +key+ + # (see +expire_fragment+ for acceptable formats). + def read_fragment(key, options = nil) + return unless cache_configured? + + key = combined_fragment_cache_key(key) + instrument_fragment_cache :read_fragment, key do + result = cache_store.read(key, options) + result.respond_to?(:html_safe) ? result.html_safe : result + end + end + + # Check if a cached fragment from the location signified by + # +key+ exists (see +expire_fragment+ for acceptable formats). + def fragment_exist?(key, options = nil) + return unless cache_configured? + key = combined_fragment_cache_key(key) + + instrument_fragment_cache :exist_fragment?, key do + cache_store.exist?(key, options) + end + end + + # Removes fragments from the cache. + # + # +key+ can take one of three forms: + # + # * String - This would normally take the form of a path, like + # pages/45/notes. + # * Hash - Treated as an implicit call to +url_for+, like + # { controller: 'pages', action: 'notes', id: 45} + # * Regexp - Will remove any fragment that matches, so + # %r{pages/\d*/notes} might remove all notes. Make sure you + # don't use anchors in the regex (^ or $) because + # the actual filename matched looks like + # ./cache/filename/path.cache. Note: Regexp expiration is + # only supported on caches that can iterate over all keys (unlike + # memcached). + # + # +options+ is passed through to the cache store's +delete+ + # method (or delete_matched, for Regexp keys). + def expire_fragment(key, options = nil) + return unless cache_configured? + key = combined_fragment_cache_key(key) unless key.is_a?(Regexp) + + instrument_fragment_cache :expire_fragment, key do + if key.is_a?(Regexp) + cache_store.delete_matched(key, options) + else + cache_store.delete(key, options) + end + end + end + + def instrument_fragment_cache(name, key) # :nodoc: + ActiveSupport::Notifications.instrument("#{name}.#{instrument_name}", instrument_payload(key)) { yield } + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/callbacks.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/callbacks.rb new file mode 100644 index 00000000..146d17cf --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/callbacks.rb @@ -0,0 +1,212 @@ +# frozen_string_literal: true + +module AbstractController + # = Abstract Controller Callbacks + # + # Abstract Controller provides hooks during the life cycle of a controller action. + # Callbacks allow you to trigger logic during this cycle. Available callbacks are: + # + # * after_action + # * append_after_action + # * append_around_action + # * append_before_action + # * around_action + # * before_action + # * prepend_after_action + # * prepend_around_action + # * prepend_before_action + # * skip_after_action + # * skip_around_action + # * skip_before_action + # + # NOTE: Calling the same callback multiple times will overwrite previous callback definitions. + # + module Callbacks + extend ActiveSupport::Concern + + # Uses ActiveSupport::Callbacks as the base functionality. For + # more details on the whole callback system, read the documentation + # for ActiveSupport::Callbacks. + include ActiveSupport::Callbacks + + included do + define_callbacks :process_action, + terminator: ->(controller, result_lambda) { result_lambda.call; controller.performed? }, + skip_after_callbacks_if_terminated: true + end + + # Override AbstractController::Base#process_action to run the + # process_action callbacks around the normal behavior. + def process_action(*args) + run_callbacks(:process_action) do + super + end + end + + module ClassMethods + # If +:only+ or +:except+ are used, convert the options into the + # +:if+ and +:unless+ options of ActiveSupport::Callbacks. + # + # The basic idea is that :only => :index gets converted to + # :if => proc {|c| c.action_name == "index" }. + # + # Note that :only has priority over :if in case they + # are used together. + # + # only: :index, if: -> { true } # the :if option will be ignored. + # + # Note that :if has priority over :except in case they + # are used together. + # + # except: :index, if: -> { true } # the :except option will be ignored. + # + # ==== Options + # * only - The callback should be run only for this action. + # * except - The callback should be run for all actions except this action. + def _normalize_callback_options(options) + _normalize_callback_option(options, :only, :if) + _normalize_callback_option(options, :except, :unless) + end + + def _normalize_callback_option(options, from, to) # :nodoc: + if from = options[from] + _from = Array(from).map(&:to_s).to_set + from = proc { |c| _from.include? c.action_name } + options[to] = Array(options[to]).unshift(from) + end + end + + # Take callback names and an optional callback proc, normalize them, + # then call the block with each callback. This allows us to abstract + # the normalization across several methods that use it. + # + # ==== Parameters + # * callbacks - An array of callbacks, with an optional + # options hash as the last parameter. + # * block - A proc that should be added to the callbacks. + # + # ==== Block Parameters + # * name - The callback to be added. + # * options - A hash of options to be used when adding the callback. + def _insert_callbacks(callbacks, block = nil) + options = callbacks.extract_options! + _normalize_callback_options(options) + callbacks.push(block) if block + callbacks.each do |callback| + yield callback, options + end + end + + ## + # :method: before_action + # + # :call-seq: before_action(names, block) + # + # Append a callback before actions. See _insert_callbacks for parameter details. + + ## + # :method: prepend_before_action + # + # :call-seq: prepend_before_action(names, block) + # + # Prepend a callback before actions. See _insert_callbacks for parameter details. + + ## + # :method: skip_before_action + # + # :call-seq: skip_before_action(names) + # + # Skip a callback before actions. See _insert_callbacks for parameter details. + + ## + # :method: append_before_action + # + # :call-seq: append_before_action(names, block) + # + # Append a callback before actions. See _insert_callbacks for parameter details. + + ## + # :method: after_action + # + # :call-seq: after_action(names, block) + # + # Append a callback after actions. See _insert_callbacks for parameter details. + + ## + # :method: prepend_after_action + # + # :call-seq: prepend_after_action(names, block) + # + # Prepend a callback after actions. See _insert_callbacks for parameter details. + + ## + # :method: skip_after_action + # + # :call-seq: skip_after_action(names) + # + # Skip a callback after actions. See _insert_callbacks for parameter details. + + ## + # :method: append_after_action + # + # :call-seq: append_after_action(names, block) + # + # Append a callback after actions. See _insert_callbacks for parameter details. + + ## + # :method: around_action + # + # :call-seq: around_action(names, block) + # + # Append a callback around actions. See _insert_callbacks for parameter details. + + ## + # :method: prepend_around_action + # + # :call-seq: prepend_around_action(names, block) + # + # Prepend a callback around actions. See _insert_callbacks for parameter details. + + ## + # :method: skip_around_action + # + # :call-seq: skip_around_action(names) + # + # Skip a callback around actions. See _insert_callbacks for parameter details. + + ## + # :method: append_around_action + # + # :call-seq: append_around_action(names, block) + # + # Append a callback around actions. See _insert_callbacks for parameter details. + + # set up before_action, prepend_before_action, skip_before_action, etc. + # for each of before, after, and around. + [:before, :after, :around].each do |callback| + define_method "#{callback}_action" do |*names, &blk| + _insert_callbacks(names, blk) do |name, options| + set_callback(:process_action, callback, name, options) + end + end + + define_method "prepend_#{callback}_action" do |*names, &blk| + _insert_callbacks(names, blk) do |name, options| + set_callback(:process_action, callback, name, options.merge(prepend: true)) + end + end + + # Skip a before, after or around callback. See _insert_callbacks + # for details on the allowed parameters. + define_method "skip_#{callback}_action" do |*names| + _insert_callbacks(names) do |name, options| + skip_callback(:process_action, callback, name, options) + end + end + + # *_action is the same as append_*_action + alias_method :"append_#{callback}_action", :"#{callback}_action" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/collector.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/collector.rb new file mode 100644 index 00000000..297ec5ca --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/collector.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require "action_dispatch/http/mime_type" + +module AbstractController + module Collector + def self.generate_method_for_mime(mime) + sym = mime.is_a?(Symbol) ? mime : mime.to_sym + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{sym}(*args, &block) + custom(Mime[:#{sym}], *args, &block) + end + RUBY + end + + Mime::SET.each do |mime| + generate_method_for_mime(mime) + end + + Mime::Type.register_callback do |mime| + generate_method_for_mime(mime) unless instance_methods.include?(mime.to_sym) + end + + private + + def method_missing(symbol, &block) + unless mime_constant = Mime[symbol] + raise NoMethodError, "To respond to a custom format, register it as a MIME type first: " \ + "http://guides.rubyonrails.org/action_controller_overview.html#restful-downloads. " \ + "If you meant to respond to a variant like :tablet or :phone, not a custom format, " \ + "be sure to nest your variant response within a format response: " \ + "format.html { |html| html.tablet { ... } }" + end + + if Mime::SET.include?(mime_constant) + AbstractController::Collector.generate_method_for_mime(mime_constant) + send(symbol, &block) + else + super + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/error.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/error.rb new file mode 100644 index 00000000..89a54f07 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/error.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +module AbstractController + class Error < StandardError #:nodoc: + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/helpers.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/helpers.rb new file mode 100644 index 00000000..35b462bc --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/helpers.rb @@ -0,0 +1,194 @@ +# frozen_string_literal: true + +require "active_support/dependencies" + +module AbstractController + module Helpers + extend ActiveSupport::Concern + + included do + class_attribute :_helpers, default: Module.new + class_attribute :_helper_methods, default: Array.new + end + + class MissingHelperError < LoadError + def initialize(error, path) + @error = error + @path = "helpers/#{path}.rb" + set_backtrace error.backtrace + + if error.path =~ /^#{path}(\.rb)?$/ + super("Missing helper file helpers/%s.rb" % path) + else + raise error + end + end + end + + module ClassMethods + # When a class is inherited, wrap its helper module in a new module. + # This ensures that the parent class's module can be changed + # independently of the child class's. + def inherited(klass) + helpers = _helpers + klass._helpers = Module.new { include helpers } + klass.class_eval { default_helper_module! } unless klass.anonymous? + super + end + + # Declare a controller method as a helper. For example, the following + # makes the +current_user+ and +logged_in?+ controller methods available + # to the view: + # class ApplicationController < ActionController::Base + # helper_method :current_user, :logged_in? + # + # def current_user + # @current_user ||= User.find_by(id: session[:user]) + # end + # + # def logged_in? + # current_user != nil + # end + # end + # + # In a view: + # <% if logged_in? -%>Welcome, <%= current_user.name %><% end -%> + # + # ==== Parameters + # * method[, method] - A name or names of a method on the controller + # to be made available on the view. + def helper_method(*meths) + meths.flatten! + self._helper_methods += meths + + meths.each do |meth| + _helpers.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1 + def #{meth}(*args, &blk) # def current_user(*args, &blk) + controller.send(%(#{meth}), *args, &blk) # controller.send(:current_user, *args, &blk) + end # end + ruby_eval + end + end + + # The +helper+ class method can take a series of helper module names, a block, or both. + # + # ==== Options + # * *args - Module, Symbol, String + # * block - A block defining helper methods + # + # When the argument is a module it will be included directly in the template class. + # helper FooHelper # => includes FooHelper + # + # When the argument is a string or symbol, the method will provide the "_helper" suffix, require the file + # and include the module in the template class. The second form illustrates how to include custom helpers + # when working with namespaced controllers, or other cases where the file containing the helper definition is not + # in one of Rails' standard load paths: + # helper :foo # => requires 'foo_helper' and includes FooHelper + # helper 'resources/foo' # => requires 'resources/foo_helper' and includes Resources::FooHelper + # + # Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available + # to the template. + # + # # One line + # helper { def hello() "Hello, world!" end } + # + # # Multi-line + # helper do + # def foo(bar) + # "#{bar} is the very best" + # end + # end + # + # Finally, all the above styles can be mixed together, and the +helper+ method can be invoked with a mix of + # +symbols+, +strings+, +modules+ and blocks. + # + # helper(:three, BlindHelper) { def mice() 'mice' end } + # + def helper(*args, &block) + modules_for_helpers(args).each do |mod| + add_template_helper(mod) + end + + _helpers.module_eval(&block) if block_given? + end + + # Clears up all existing helpers in this class, only keeping the helper + # with the same name as this class. + def clear_helpers + inherited_helper_methods = _helper_methods + self._helpers = Module.new + self._helper_methods = Array.new + + inherited_helper_methods.each { |meth| helper_method meth } + default_helper_module! unless anonymous? + end + + # Returns a list of modules, normalized from the acceptable kinds of + # helpers with the following behavior: + # + # String or Symbol:: :FooBar or "FooBar" becomes "foo_bar_helper", + # and "foo_bar_helper.rb" is loaded using require_dependency. + # + # Module:: No further processing + # + # After loading the appropriate files, the corresponding modules + # are returned. + # + # ==== Parameters + # * args - An array of helpers + # + # ==== Returns + # * Array - A normalized list of modules for the list of + # helpers provided. + def modules_for_helpers(args) + args.flatten.map! do |arg| + case arg + when String, Symbol + file_name = "#{arg.to_s.underscore}_helper" + begin + require_dependency(file_name) + rescue LoadError => e + raise AbstractController::Helpers::MissingHelperError.new(e, file_name) + end + + mod_name = file_name.camelize + begin + mod_name.constantize + rescue LoadError + # dependencies.rb gives a similar error message but its wording is + # not as clear because it mentions autoloading. To the user all it + # matters is that a helper module couldn't be loaded, autoloading + # is an internal mechanism that should not leak. + raise NameError, "Couldn't find #{mod_name}, expected it to be defined in helpers/#{file_name}.rb" + end + when Module + arg + else + raise ArgumentError, "helper must be a String, Symbol, or Module" + end + end + end + + private + # Makes all the (instance) methods in the helper module available to templates + # rendered through this controller. + # + # ==== Parameters + # * module - The module to include into the current helper module + # for the class + def add_template_helper(mod) + _helpers.module_eval { include mod } + end + + def default_helper_module! + module_name = name.sub(/Controller$/, "".freeze) + module_path = module_name.underscore + helper module_path + rescue LoadError => e + raise e unless e.is_missing? "helpers/#{module_path}_helper" + rescue NameError => e + raise e unless e.missing_name? "#{module_name}Helper" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/logger.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/logger.rb new file mode 100644 index 00000000..8d0acc1b --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/logger.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "active_support/benchmarkable" + +module AbstractController + module Logger #:nodoc: + extend ActiveSupport::Concern + + included do + config_accessor :logger + include ActiveSupport::Benchmarkable + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/railties/routes_helpers.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/railties/routes_helpers.rb new file mode 100644 index 00000000..b6e5631a --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/railties/routes_helpers.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module AbstractController + module Railties + module RoutesHelpers + def self.with(routes, include_path_helpers = true) + Module.new do + define_method(:inherited) do |klass| + super(klass) + if namespace = klass.parents.detect { |m| m.respond_to?(:railtie_routes_url_helpers) } + klass.include(namespace.railtie_routes_url_helpers(include_path_helpers)) + else + klass.include(routes.url_helpers(include_path_helpers)) + end + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/rendering.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/rendering.rb new file mode 100644 index 00000000..8ba2b255 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/rendering.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true + +require "abstract_controller/error" +require "action_view" +require "action_view/view_paths" +require "set" + +module AbstractController + class DoubleRenderError < Error + DEFAULT_MESSAGE = "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirect_to(...) and return\"." + + def initialize(message = nil) + super(message || DEFAULT_MESSAGE) + end + end + + module Rendering + extend ActiveSupport::Concern + include ActionView::ViewPaths + + # Normalizes arguments, options and then delegates render_to_body and + # sticks the result in self.response_body. + def render(*args, &block) + options = _normalize_render(*args, &block) + rendered_body = render_to_body(options) + if options[:html] + _set_html_content_type + else + _set_rendered_content_type rendered_format + end + self.response_body = rendered_body + end + + # Raw rendering of a template to a string. + # + # It is similar to render, except that it does not + # set the +response_body+ and it should be guaranteed + # to always return a string. + # + # If a component extends the semantics of +response_body+ + # (as ActionController extends it to be anything that + # responds to the method each), this method needs to be + # overridden in order to still return a string. + def render_to_string(*args, &block) + options = _normalize_render(*args, &block) + render_to_body(options) + end + + # Performs the actual template rendering. + def render_to_body(options = {}) + end + + # Returns Content-Type of rendered content. + def rendered_format + Mime[:text] + end + + DEFAULT_PROTECTED_INSTANCE_VARIABLES = Set.new %i( + @_action_name @_response_body @_formats @_prefixes + ) + + # This method should return a hash with assigns. + # You can overwrite this configuration per controller. + def view_assigns + protected_vars = _protected_ivars + variables = instance_variables + + variables.reject! { |s| protected_vars.include? s } + variables.each_with_object({}) { |name, hash| + hash[name.slice(1, name.length)] = instance_variable_get(name) + } + end + + private + # Normalize args by converting render "foo" to + # render :action => "foo" and render "foo/bar" to + # render :file => "foo/bar". + def _normalize_args(action = nil, options = {}) # :doc: + if action.respond_to?(:permitted?) + if action.permitted? + action + else + raise ArgumentError, "render parameters are not permitted" + end + elsif action.is_a?(Hash) + action + else + options + end + end + + # Normalize options. + def _normalize_options(options) # :doc: + options + end + + # Process extra options. + def _process_options(options) # :doc: + options + end + + # Process the rendered format. + def _process_format(format) # :nodoc: + end + + def _process_variant(options) + end + + def _set_html_content_type # :nodoc: + end + + def _set_rendered_content_type(format) # :nodoc: + end + + # Normalize args and options. + def _normalize_render(*args, &block) # :nodoc: + options = _normalize_args(*args, &block) + _process_variant(options) + _normalize_options(options) + options + end + + def _protected_ivars # :nodoc: + DEFAULT_PROTECTED_INSTANCE_VARIABLES + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/translation.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/translation.rb new file mode 100644 index 00000000..666e154e --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/translation.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module AbstractController + module Translation + # Delegates to I18n.translate. Also aliased as t. + # + # When the given key starts with a period, it will be scoped by the current + # controller and action. So if you call translate(".foo") from + # PeopleController#index, it will convert the call to + # I18n.translate("people.index.foo"). This makes it less repetitive + # to translate many keys within the same controller / action and gives you a + # simple framework for scoping them consistently. + def translate(key, options = {}) + if key.to_s.first == "." + path = controller_path.tr("/", ".") + defaults = [:"#{path}#{key}"] + defaults << options[:default] if options[:default] + options[:default] = defaults.flatten + key = "#{path}.#{action_name}#{key}" + end + I18n.translate(key, options) + end + alias :t :translate + + # Delegates to I18n.localize. Also aliased as l. + def localize(*args) + I18n.localize(*args) + end + alias :l :localize + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/url_for.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/url_for.rb new file mode 100644 index 00000000..bd74c27d --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/abstract_controller/url_for.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module AbstractController + # Includes +url_for+ into the host class (e.g. an abstract controller or mailer). The class + # has to provide a +RouteSet+ by implementing the _routes methods. Otherwise, an + # exception will be raised. + # + # Note that this module is completely decoupled from HTTP - the only requirement is a valid + # _routes implementation. + module UrlFor + extend ActiveSupport::Concern + include ActionDispatch::Routing::UrlFor + + def _routes + raise "In order to use #url_for, you must include routing helpers explicitly. " \ + "For instance, `include Rails.application.routes.url_helpers`." + end + + module ClassMethods + def _routes + nil + end + + def action_methods + @action_methods ||= begin + if _routes + super - _routes.named_routes.helper_names + else + super + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller.rb new file mode 100644 index 00000000..f43784f9 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require "active_support/rails" +require "abstract_controller" +require "action_dispatch" +require "action_controller/metal/live" +require "action_controller/metal/strong_parameters" + +module ActionController + extend ActiveSupport::Autoload + + autoload :API + autoload :Base + autoload :Metal + autoload :Middleware + autoload :Renderer + autoload :FormBuilder + + eager_autoload do + autoload :Caching + end + + autoload_under "metal" do + autoload :ConditionalGet + autoload :ContentSecurityPolicy + autoload :Cookies + autoload :DataStreaming + autoload :EtagWithTemplateDigest + autoload :EtagWithFlash + autoload :Flash + autoload :ForceSSL + autoload :Head + autoload :Helpers + autoload :HttpAuthentication + autoload :BasicImplicitRender + autoload :ImplicitRender + autoload :Instrumentation + autoload :MimeResponds + autoload :ParamsWrapper + autoload :Redirecting + autoload :Renderers + autoload :Rendering + autoload :RequestForgeryProtection + autoload :Rescue + autoload :Streaming + autoload :StrongParameters + autoload :ParameterEncoding + autoload :Testing + autoload :UrlFor + end + + autoload_under "api" do + autoload :ApiRendering + end + + autoload :TestCase, "action_controller/test_case" + autoload :TemplateAssertions, "action_controller/test_case" +end + +# Common Active Support usage in Action Controller +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/load_error" +require "active_support/core_ext/module/attr_internal" +require "active_support/core_ext/name_error" +require "active_support/core_ext/uri" +require "active_support/inflector" diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/api.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/api.rb new file mode 100644 index 00000000..b192e496 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/api.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: true + +require "action_view" +require "action_controller" +require "action_controller/log_subscriber" + +module ActionController + # API Controller is a lightweight version of ActionController::Base, + # created for applications that don't require all functionalities that a complete + # \Rails controller provides, allowing you to create controllers with just the + # features that you need for API only applications. + # + # An API Controller is different from a normal controller in the sense that + # by default it doesn't include a number of features that are usually required + # by browser access only: layouts and templates rendering, cookies, sessions, + # flash, assets, and so on. This makes the entire controller stack thinner, + # suitable for API applications. It doesn't mean you won't have such + # features if you need them: they're all available for you to include in + # your application, they're just not part of the default API controller stack. + # + # Normally, +ApplicationController+ is the only controller that inherits from + # ActionController::API. All other controllers in turn inherit from + # +ApplicationController+. + # + # A sample controller could look like this: + # + # class PostsController < ApplicationController + # def index + # posts = Post.all + # render json: posts + # end + # end + # + # Request, response, and parameters objects all work the exact same way as + # ActionController::Base. + # + # == Renders + # + # The default API Controller stack includes all renderers, which means you + # can use render :json and brothers freely in your controllers. Keep + # in mind that templates are not going to be rendered, so you need to ensure + # your controller is calling either render or redirect_to in + # all actions, otherwise it will return 204 No Content. + # + # def show + # post = Post.find(params[:id]) + # render json: post + # end + # + # == Redirects + # + # Redirects are used to move from one action to another. You can use the + # redirect_to method in your controllers in the same way as in + # ActionController::Base. For example: + # + # def create + # redirect_to root_url and return if not_authorized? + # # do stuff here + # end + # + # == Adding New Behavior + # + # In some scenarios you may want to add back some functionality provided by + # ActionController::Base that is not present by default in + # ActionController::API, for instance MimeResponds. This + # module gives you the respond_to method. Adding it is quite simple, + # you just need to include the module in a specific controller or in + # +ApplicationController+ in case you want it available in your entire + # application: + # + # class ApplicationController < ActionController::API + # include ActionController::MimeResponds + # end + # + # class PostsController < ApplicationController + # def index + # posts = Post.all + # + # respond_to do |format| + # format.json { render json: posts } + # format.xml { render xml: posts } + # end + # end + # end + # + # Make sure to check the modules included in ActionController::Base + # if you want to use any other functionality that is not provided + # by ActionController::API out of the box. + class API < Metal + abstract! + + # Shortcut helper that returns all the ActionController::API modules except + # the ones passed as arguments: + # + # class MyAPIBaseController < ActionController::Metal + # ActionController::API.without_modules(:ForceSSL, :UrlFor).each do |left| + # include left + # end + # end + # + # This gives better control over what you want to exclude and makes it easier + # to create an API controller class, instead of listing the modules required + # manually. + def self.without_modules(*modules) + modules = modules.map do |m| + m.is_a?(Symbol) ? ActionController.const_get(m) : m + end + + MODULES - modules + end + + MODULES = [ + AbstractController::Rendering, + + UrlFor, + Redirecting, + ApiRendering, + Renderers::All, + ConditionalGet, + BasicImplicitRender, + StrongParameters, + + ForceSSL, + DataStreaming, + + # Before callbacks should also be executed as early as possible, so + # also include them at the bottom. + AbstractController::Callbacks, + + # Append rescue at the bottom to wrap as much as possible. + Rescue, + + # Add instrumentations hooks at the bottom, to ensure they instrument + # all the methods properly. + Instrumentation, + + # Params wrapper should come before instrumentation so they are + # properly showed in logs + ParamsWrapper + ] + + MODULES.each do |mod| + include mod + end + + ActiveSupport.run_load_hooks(:action_controller_api, self) + ActiveSupport.run_load_hooks(:action_controller, self) + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/api/api_rendering.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/api/api_rendering.rb new file mode 100644 index 00000000..aca52653 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/api/api_rendering.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module ActionController + module ApiRendering + extend ActiveSupport::Concern + + included do + include Rendering + end + + def render_to_body(options = {}) + _process_options(options) + super + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/base.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/base.rb new file mode 100644 index 00000000..204a3d40 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/base.rb @@ -0,0 +1,276 @@ +# frozen_string_literal: true + +require "action_view" +require "action_controller/log_subscriber" +require "action_controller/metal/params_wrapper" + +module ActionController + # Action Controllers are the core of a web request in \Rails. They are made up of one or more actions that are executed + # on request and then either it renders a template or redirects to another action. An action is defined as a public method + # on the controller, which will automatically be made accessible to the web-server through \Rails Routes. + # + # By default, only the ApplicationController in a \Rails application inherits from ActionController::Base. All other + # controllers inherit from ApplicationController. This gives you one class to configure things such as + # request forgery protection and filtering of sensitive request parameters. + # + # A sample controller could look like this: + # + # class PostsController < ApplicationController + # def index + # @posts = Post.all + # end + # + # def create + # @post = Post.create params[:post] + # redirect_to posts_path + # end + # end + # + # Actions, by default, render a template in the app/views directory corresponding to the name of the controller and action + # after executing code in the action. For example, the +index+ action of the PostsController would render the + # template app/views/posts/index.html.erb by default after populating the @posts instance variable. + # + # Unlike index, the create action will not render a template. After performing its main purpose (creating a + # new post), it initiates a redirect instead. This redirect works by returning an external + # 302 Moved HTTP response that takes the user to the index action. + # + # These two methods represent the two basic action archetypes used in Action Controllers: Get-and-show and do-and-redirect. + # Most actions are variations on these themes. + # + # == Requests + # + # For every request, the router determines the value of the +controller+ and +action+ keys. These determine which controller + # and action are called. The remaining request parameters, the session (if one is available), and the full request with + # all the HTTP headers are made available to the action through accessor methods. Then the action is performed. + # + # The full request object is available via the request accessor and is primarily used to query for HTTP headers: + # + # def server_ip + # location = request.env["REMOTE_ADDR"] + # render plain: "This server hosted at #{location}" + # end + # + # == Parameters + # + # All request parameters, whether they come from a query string in the URL or form data submitted through a POST request are + # available through the params method which returns a hash. For example, an action that was performed through + # /posts?category=All&limit=5 will include { "category" => "All", "limit" => "5" } in params. + # + # It's also possible to construct multi-dimensional parameter hashes by specifying keys using brackets, such as: + # + # + # + # + # A request coming from a form holding these inputs will include { "post" => { "name" => "david", "address" => "hyacintvej" } }. + # If the address input had been named post[address][street], the params would have included + # { "post" => { "address" => { "street" => "hyacintvej" } } }. There's no limit to the depth of the nesting. + # + # == Sessions + # + # Sessions allow you to store objects in between requests. This is useful for objects that are not yet ready to be persisted, + # such as a Signup object constructed in a multi-paged process, or objects that don't change much and are needed all the time, such + # as a User object for a system that requires login. The session should not be used, however, as a cache for objects where it's likely + # they could be changed unknowingly. It's usually too much work to keep it all synchronized -- something databases already excel at. + # + # You can place objects in the session by using the session method, which accesses a hash: + # + # session[:person] = Person.authenticate(user_name, password) + # + # You can retrieve it again through the same hash: + # + # Hello #{session[:person]} + # + # For removing objects from the session, you can either assign a single key to +nil+: + # + # # removes :person from session + # session[:person] = nil + # + # or you can remove the entire session with +reset_session+. + # + # Sessions are stored by default in a browser cookie that's cryptographically signed, but unencrypted. + # This prevents the user from tampering with the session but also allows them to see its contents. + # + # Do not put secret information in cookie-based sessions! + # + # == Responses + # + # Each action results in a response, which holds the headers and document to be sent to the user's browser. The actual response + # object is generated automatically through the use of renders and redirects and requires no user intervention. + # + # == Renders + # + # Action Controller sends content to the user by using one of five rendering methods. The most versatile and common is the rendering + # of a template. Included in the Action Pack is the Action View, which enables rendering of ERB templates. It's automatically configured. + # The controller passes objects to the view by assigning instance variables: + # + # def show + # @post = Post.find(params[:id]) + # end + # + # Which are then automatically available to the view: + # + # Title: <%= @post.title %> + # + # You don't have to rely on the automated rendering. For example, actions that could result in the rendering of different templates + # will use the manual rendering methods: + # + # def search + # @results = Search.find(params[:query]) + # case @results.count + # when 0 then render action: "no_results" + # when 1 then render action: "show" + # when 2..10 then render action: "show_many" + # end + # end + # + # Read more about writing ERB and Builder templates in ActionView::Base. + # + # == Redirects + # + # Redirects are used to move from one action to another. For example, after a create action, which stores a blog entry to the + # database, we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're + # going to reuse (and redirect to) a show action that we'll assume has already been created. The code might look like this: + # + # def create + # @entry = Entry.new(params[:entry]) + # if @entry.save + # # The entry was saved correctly, redirect to show + # redirect_to action: 'show', id: @entry.id + # else + # # things didn't go so well, do something else + # end + # end + # + # In this case, after saving our new entry to the database, the user is redirected to the show method, which is then executed. + # Note that this is an external HTTP-level redirection which will cause the browser to make a second request (a GET to the show action), + # and not some internal re-routing which calls both "create" and then "show" within one request. + # + # Learn more about redirect_to and what options you have in ActionController::Redirecting. + # + # == Calling multiple redirects or renders + # + # An action may contain only a single render or a single redirect. Attempting to try to do either again will result in a DoubleRenderError: + # + # def do_something + # redirect_to action: "elsewhere" + # render action: "overthere" # raises DoubleRenderError + # end + # + # If you need to redirect on the condition of something, then be sure to add "and return" to halt execution. + # + # def do_something + # redirect_to(action: "elsewhere") and return if monkeys.nil? + # render action: "overthere" # won't be called if monkeys is nil + # end + # + class Base < Metal + abstract! + + # We document the request and response methods here because albeit they are + # implemented in ActionController::Metal, the type of the returned objects + # is unknown at that level. + + ## + # :method: request + # + # Returns an ActionDispatch::Request instance that represents the + # current request. + + ## + # :method: response + # + # Returns an ActionDispatch::Response that represents the current + # response. + + # Shortcut helper that returns all the modules included in + # ActionController::Base except the ones passed as arguments: + # + # class MyBaseController < ActionController::Metal + # ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left| + # include left + # end + # end + # + # This gives better control over what you want to exclude and makes it + # easier to create a bare controller class, instead of listing the modules + # required manually. + def self.without_modules(*modules) + modules = modules.map do |m| + m.is_a?(Symbol) ? ActionController.const_get(m) : m + end + + MODULES - modules + end + + MODULES = [ + AbstractController::Rendering, + AbstractController::Translation, + AbstractController::AssetPaths, + + Helpers, + UrlFor, + Redirecting, + ActionView::Layouts, + Rendering, + Renderers::All, + ConditionalGet, + EtagWithTemplateDigest, + EtagWithFlash, + Caching, + MimeResponds, + ImplicitRender, + StrongParameters, + ParameterEncoding, + Cookies, + Flash, + FormBuilder, + RequestForgeryProtection, + ContentSecurityPolicy, + ForceSSL, + Streaming, + DataStreaming, + HttpAuthentication::Basic::ControllerMethods, + HttpAuthentication::Digest::ControllerMethods, + HttpAuthentication::Token::ControllerMethods, + + # Before callbacks should also be executed as early as possible, so + # also include them at the bottom. + AbstractController::Callbacks, + + # Append rescue at the bottom to wrap as much as possible. + Rescue, + + # Add instrumentations hooks at the bottom, to ensure they instrument + # all the methods properly. + Instrumentation, + + # Params wrapper should come before instrumentation so they are + # properly showed in logs + ParamsWrapper + ] + + MODULES.each do |mod| + include mod + end + setup_renderer! + + # Define some internal variables that should not be propagated to the view. + PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + %i( + @_params @_response @_request @_config @_url_options @_action_has_layout @_view_context_class + @_view_renderer @_lookup_context @_routes @_view_runtime @_db_runtime @_helper_proxy + ) + + def _protected_ivars # :nodoc: + PROTECTED_IVARS + end + + def self.make_response!(request) + ActionDispatch::Response.create.tap do |res| + res.request = request + end + end + + ActiveSupport.run_load_hooks(:action_controller_base, self) + ActiveSupport.run_load_hooks(:action_controller, self) + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/caching.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/caching.rb new file mode 100644 index 00000000..97775d1d --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/caching.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module ActionController + # \Caching is a cheap way of speeding up slow applications by keeping the result of + # calculations, renderings, and database calls around for subsequent requests. + # + # You can read more about each approach by clicking the modules below. + # + # Note: To turn off all caching provided by Action Controller, set + # config.action_controller.perform_caching = false + # + # == \Caching stores + # + # All the caching stores from ActiveSupport::Cache are available to be used as backends + # for Action Controller caching. + # + # Configuration examples (FileStore is the default): + # + # config.action_controller.cache_store = :memory_store + # config.action_controller.cache_store = :file_store, '/path/to/cache/directory' + # config.action_controller.cache_store = :mem_cache_store, 'localhost' + # config.action_controller.cache_store = :mem_cache_store, Memcached::Rails.new('localhost:11211') + # config.action_controller.cache_store = MyOwnStore.new('parameter') + module Caching + extend ActiveSupport::Autoload + extend ActiveSupport::Concern + + included do + include AbstractController::Caching + end + + private + + def instrument_payload(key) + { + controller: controller_name, + action: action_name, + key: key + } + end + + def instrument_name + "action_controller".freeze + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/form_builder.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/form_builder.rb new file mode 100644 index 00000000..09d2ac18 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/form_builder.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module ActionController + # Override the default form builder for all views rendered by this + # controller and any of its descendants. Accepts a subclass of + # +ActionView::Helpers::FormBuilder+. + # + # For example, given a form builder: + # + # class AdminFormBuilder < ActionView::Helpers::FormBuilder + # def special_field(name) + # end + # end + # + # The controller specifies a form builder as its default: + # + # class AdminAreaController < ApplicationController + # default_form_builder AdminFormBuilder + # end + # + # Then in the view any form using +form_for+ will be an instance of the + # specified form builder: + # + # <%= form_for(@instance) do |builder| %> + # <%= builder.special_field(:name) %> + # <% end %> + module FormBuilder + extend ActiveSupport::Concern + + included do + class_attribute :_default_form_builder, instance_accessor: false + end + + module ClassMethods + # Set the form builder to be used as the default for all forms + # in the views rendered by this controller and its subclasses. + # + # ==== Parameters + # * builder - Default form builder, an instance of +ActionView::Helpers::FormBuilder+ + def default_form_builder(builder) + self._default_form_builder = builder + end + end + + # Default form builder for the controller + def default_form_builder + self.class._default_form_builder + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/log_subscriber.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/log_subscriber.rb new file mode 100644 index 00000000..14f41eb5 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/log_subscriber.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +module ActionController + class LogSubscriber < ActiveSupport::LogSubscriber + INTERNAL_PARAMS = %w(controller action format _method only_path) + + def start_processing(event) + return unless logger.info? + + payload = event.payload + params = payload[:params].except(*INTERNAL_PARAMS) + format = payload[:format] + format = format.to_s.upcase if format.is_a?(Symbol) + + info "Processing by #{payload[:controller]}##{payload[:action]} as #{format}" + info " Parameters: #{params.inspect}" unless params.empty? + end + + def process_action(event) + info do + payload = event.payload + additions = ActionController::Base.log_process_action(payload) + + status = payload[:status] + if status.nil? && payload[:exception].present? + exception_class_name = payload[:exception].first + status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name) + end + message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms".dup + message << " (#{additions.join(" | ".freeze)})" unless additions.empty? + message << "\n\n" if defined?(Rails.env) && Rails.env.development? + + message + end + end + + def halted_callback(event) + info { "Filter chain halted as #{event.payload[:filter].inspect} rendered or redirected" } + end + + def send_file(event) + info { "Sent file #{event.payload[:path]} (#{event.duration.round(1)}ms)" } + end + + def redirect_to(event) + info { "Redirected to #{event.payload[:location]}" } + end + + def send_data(event) + info { "Sent data #{event.payload[:filename]} (#{event.duration.round(1)}ms)" } + end + + def unpermitted_parameters(event) + debug do + unpermitted_keys = event.payload[:keys] + "Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{unpermitted_keys.map { |e| ":#{e}" }.join(", ")}" + end + end + + %w(write_fragment read_fragment exist_fragment? + expire_fragment expire_page write_page).each do |method| + class_eval <<-METHOD, __FILE__, __LINE__ + 1 + def #{method}(event) + return unless logger.info? && ActionController::Base.enable_fragment_cache_logging + key = ActiveSupport::Cache.expand_cache_key(event.payload[:key] || event.payload[:path]) + human_name = #{method.to_s.humanize.inspect} + info("\#{human_name} \#{key} (\#{event.duration.round(1)}ms)") + end + METHOD + end + + def logger + ActionController::Base.logger + end + end +end + +ActionController::LogSubscriber.attach_to :action_controller diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal.rb new file mode 100644 index 00000000..f875aa5e --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal.rb @@ -0,0 +1,256 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array/extract_options" +require "action_dispatch/middleware/stack" +require "action_dispatch/http/request" +require "action_dispatch/http/response" + +module ActionController + # Extend ActionDispatch middleware stack to make it aware of options + # allowing the following syntax in controllers: + # + # class PostsController < ApplicationController + # use AuthenticationMiddleware, except: [:index, :show] + # end + # + class MiddlewareStack < ActionDispatch::MiddlewareStack #:nodoc: + class Middleware < ActionDispatch::MiddlewareStack::Middleware #:nodoc: + def initialize(klass, args, actions, strategy, block) + @actions = actions + @strategy = strategy + super(klass, args, block) + end + + def valid?(action) + @strategy.call @actions, action + end + end + + def build(action, app = Proc.new) + action = action.to_s + + middlewares.reverse.inject(app) do |a, middleware| + middleware.valid?(action) ? middleware.build(a) : a + end + end + + private + + INCLUDE = ->(list, action) { list.include? action } + EXCLUDE = ->(list, action) { !list.include? action } + NULL = ->(list, action) { true } + + def build_middleware(klass, args, block) + options = args.extract_options! + only = Array(options.delete(:only)).map(&:to_s) + except = Array(options.delete(:except)).map(&:to_s) + args << options unless options.empty? + + strategy = NULL + list = nil + + if only.any? + strategy = INCLUDE + list = only + elsif except.any? + strategy = EXCLUDE + list = except + end + + Middleware.new(klass, args, list, strategy, block) + end + end + + # ActionController::Metal is the simplest possible controller, providing a + # valid Rack interface without the additional niceties provided by + # ActionController::Base. + # + # A sample metal controller might look like this: + # + # class HelloController < ActionController::Metal + # def index + # self.response_body = "Hello World!" + # end + # end + # + # And then to route requests to your metal controller, you would add + # something like this to config/routes.rb: + # + # get 'hello', to: HelloController.action(:index) + # + # The +action+ method returns a valid Rack application for the \Rails + # router to dispatch to. + # + # == Rendering Helpers + # + # ActionController::Metal by default provides no utilities for rendering + # views, partials, or other responses aside from explicitly calling of + # response_body=, content_type=, and status=. To + # add the render helpers you're used to having in a normal controller, you + # can do the following: + # + # class HelloController < ActionController::Metal + # include AbstractController::Rendering + # include ActionView::Layouts + # append_view_path "#{Rails.root}/app/views" + # + # def index + # render "hello/index" + # end + # end + # + # == Redirection Helpers + # + # To add redirection helpers to your metal controller, do the following: + # + # class HelloController < ActionController::Metal + # include ActionController::Redirecting + # include Rails.application.routes.url_helpers + # + # def index + # redirect_to root_url + # end + # end + # + # == Other Helpers + # + # You can refer to the modules included in ActionController::Base to see + # other features you can bring into your metal controller. + # + class Metal < AbstractController::Base + abstract! + + # Returns the last part of the controller's name, underscored, without the ending + # Controller. For instance, PostsController returns posts. + # Namespaces are left out, so Admin::PostsController returns posts as well. + # + # ==== Returns + # * string + def self.controller_name + @controller_name ||= name.demodulize.sub(/Controller$/, "").underscore + end + + def self.make_response!(request) + ActionDispatch::Response.new.tap do |res| + res.request = request + end + end + + def self.binary_params_for?(action) # :nodoc: + false + end + + # Delegates to the class' controller_name. + def controller_name + self.class.controller_name + end + + attr_internal :response, :request + delegate :session, to: "@_request" + delegate :headers, :status=, :location=, :content_type=, + :status, :location, :content_type, to: "@_response" + + def initialize + @_request = nil + @_response = nil + @_routes = nil + super + end + + def params + @_params ||= request.parameters + end + + def params=(val) + @_params = val + end + + alias :response_code :status # :nodoc: + + # Basic url_for that can be overridden for more robust functionality. + def url_for(string) + string + end + + def response_body=(body) + body = [body] unless body.nil? || body.respond_to?(:each) + response.reset_body! + return unless body + response.body = body + super + end + + # Tests if render or redirect has already happened. + def performed? + response_body || response.committed? + end + + def dispatch(name, request, response) #:nodoc: + set_request!(request) + set_response!(response) + process(name) + request.commit_flash + to_a + end + + def set_response!(response) # :nodoc: + @_response = response + end + + def set_request!(request) #:nodoc: + @_request = request + @_request.controller_instance = self + end + + def to_a #:nodoc: + response.to_a + end + + def reset_session + @_request.reset_session + end + + class_attribute :middleware_stack, default: ActionController::MiddlewareStack.new + + def self.inherited(base) # :nodoc: + base.middleware_stack = middleware_stack.dup + super + end + + # Pushes the given Rack middleware and its arguments to the bottom of the + # middleware stack. + def self.use(*args, &block) + middleware_stack.use(*args, &block) + end + + # Alias for +middleware_stack+. + def self.middleware + middleware_stack + end + + # Returns a Rack endpoint for the given action name. + def self.action(name) + app = lambda { |env| + req = ActionDispatch::Request.new(env) + res = make_response! req + new.dispatch(name, req, res) + } + + if middleware_stack.any? + middleware_stack.build(name, app) + else + app + end + end + + # Direct dispatch to the controller. Instantiates the controller, then + # executes the action named +name+. + def self.dispatch(name, req, res) + if middleware_stack.any? + middleware_stack.build(name) { |env| new.dispatch(name, req, res) }.call req.env + else + new.dispatch(name, req, res) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/basic_implicit_render.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/basic_implicit_render.rb new file mode 100644 index 00000000..2dc990f3 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/basic_implicit_render.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module ActionController + module BasicImplicitRender # :nodoc: + def send_action(method, *args) + super.tap { default_render unless performed? } + end + + def default_render(*args) + head :no_content + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/conditional_get.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/conditional_get.rb new file mode 100644 index 00000000..06b6a95f --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/conditional_get.rb @@ -0,0 +1,274 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/keys" + +module ActionController + module ConditionalGet + extend ActiveSupport::Concern + + include Head + + included do + class_attribute :etaggers, default: [] + end + + module ClassMethods + # Allows you to consider additional controller-wide information when generating an ETag. + # For example, if you serve pages tailored depending on who's logged in at the moment, you + # may want to add the current user id to be part of the ETag to prevent unauthorized displaying + # of cached pages. + # + # class InvoicesController < ApplicationController + # etag { current_user.try :id } + # + # def show + # # Etag will differ even for the same invoice when it's viewed by a different current_user + # @invoice = Invoice.find(params[:id]) + # fresh_when(@invoice) + # end + # end + def etag(&etagger) + self.etaggers += [etagger] + end + end + + # Sets the +etag+, +last_modified+, or both on the response and renders a + # 304 Not Modified response if the request is already fresh. + # + # === Parameters: + # + # * :etag Sets a "weak" ETag validator on the response. See the + # +:weak_etag+ option. + # * :weak_etag Sets a "weak" ETag validator on the response. + # Requests that set If-None-Match header may return a 304 Not Modified + # response if it matches the ETag exactly. A weak ETag indicates semantic + # equivalence, not byte-for-byte equality, so they're good for caching + # HTML pages in browser caches. They can't be used for responses that + # must be byte-identical, like serving Range requests within a PDF file. + # * :strong_etag Sets a "strong" ETag validator on the response. + # Requests that set If-None-Match header may return a 304 Not Modified + # response if it matches the ETag exactly. A strong ETag implies exact + # equality: the response must match byte for byte. This is necessary for + # doing Range requests within a large video or PDF file, for example, or + # for compatibility with some CDNs that don't support weak ETags. + # * :last_modified Sets a "weak" last-update validator on the + # response. Subsequent requests that set If-Modified-Since may return a + # 304 Not Modified response if last_modified <= If-Modified-Since. + # * :public By default the Cache-Control header is private, set this to + # +true+ if you want your application to be cacheable by other devices (proxy caches). + # * :template By default, the template digest for the current + # controller/action is included in ETags. If the action renders a + # different template, you can include its digest instead. If the action + # doesn't render a template at all, you can pass template: false + # to skip any attempt to check for a template digest. + # + # === Example: + # + # def show + # @article = Article.find(params[:id]) + # fresh_when(etag: @article, last_modified: @article.updated_at, public: true) + # end + # + # This will render the show template if the request isn't sending a matching ETag or + # If-Modified-Since header and just a 304 Not Modified response if there's a match. + # + # You can also just pass a record. In this case +last_modified+ will be set + # by calling +updated_at+ and +etag+ by passing the object itself. + # + # def show + # @article = Article.find(params[:id]) + # fresh_when(@article) + # end + # + # You can also pass an object that responds to +maximum+, such as a + # collection of active records. In this case +last_modified+ will be set by + # calling maximum(:updated_at) on the collection (the timestamp of the + # most recently updated record) and the +etag+ by passing the object itself. + # + # def index + # @articles = Article.all + # fresh_when(@articles) + # end + # + # When passing a record or a collection, you can still set the public header: + # + # def show + # @article = Article.find(params[:id]) + # fresh_when(@article, public: true) + # end + # + # When rendering a different template than the default controller/action + # style, you can indicate which digest to include in the ETag: + # + # before_action { fresh_when @article, template: 'widgets/show' } + # + def fresh_when(object = nil, etag: nil, weak_etag: nil, strong_etag: nil, last_modified: nil, public: false, template: nil) + weak_etag ||= etag || object unless strong_etag + last_modified ||= object.try(:updated_at) || object.try(:maximum, :updated_at) + + if strong_etag + response.strong_etag = combine_etags strong_etag, + last_modified: last_modified, public: public, template: template + elsif weak_etag || template + response.weak_etag = combine_etags weak_etag, + last_modified: last_modified, public: public, template: template + end + + response.last_modified = last_modified if last_modified + response.cache_control[:public] = true if public + + head :not_modified if request.fresh?(response) + end + + # Sets the +etag+ and/or +last_modified+ on the response and checks it against + # the client request. If the request doesn't match the options provided, the + # request is considered stale and should be generated from scratch. Otherwise, + # it's fresh and we don't need to generate anything and a reply of 304 Not Modified is sent. + # + # === Parameters: + # + # * :etag Sets a "weak" ETag validator on the response. See the + # +:weak_etag+ option. + # * :weak_etag Sets a "weak" ETag validator on the response. + # Requests that set If-None-Match header may return a 304 Not Modified + # response if it matches the ETag exactly. A weak ETag indicates semantic + # equivalence, not byte-for-byte equality, so they're good for caching + # HTML pages in browser caches. They can't be used for responses that + # must be byte-identical, like serving Range requests within a PDF file. + # * :strong_etag Sets a "strong" ETag validator on the response. + # Requests that set If-None-Match header may return a 304 Not Modified + # response if it matches the ETag exactly. A strong ETag implies exact + # equality: the response must match byte for byte. This is necessary for + # doing Range requests within a large video or PDF file, for example, or + # for compatibility with some CDNs that don't support weak ETags. + # * :last_modified Sets a "weak" last-update validator on the + # response. Subsequent requests that set If-Modified-Since may return a + # 304 Not Modified response if last_modified <= If-Modified-Since. + # * :public By default the Cache-Control header is private, set this to + # +true+ if you want your application to be cacheable by other devices (proxy caches). + # * :template By default, the template digest for the current + # controller/action is included in ETags. If the action renders a + # different template, you can include its digest instead. If the action + # doesn't render a template at all, you can pass template: false + # to skip any attempt to check for a template digest. + # + # === Example: + # + # def show + # @article = Article.find(params[:id]) + # + # if stale?(etag: @article, last_modified: @article.updated_at) + # @statistics = @article.really_expensive_call + # respond_to do |format| + # # all the supported formats + # end + # end + # end + # + # You can also just pass a record. In this case +last_modified+ will be set + # by calling +updated_at+ and +etag+ by passing the object itself. + # + # def show + # @article = Article.find(params[:id]) + # + # if stale?(@article) + # @statistics = @article.really_expensive_call + # respond_to do |format| + # # all the supported formats + # end + # end + # end + # + # You can also pass an object that responds to +maximum+, such as a + # collection of active records. In this case +last_modified+ will be set by + # calling +maximum(:updated_at)+ on the collection (the timestamp of the + # most recently updated record) and the +etag+ by passing the object itself. + # + # def index + # @articles = Article.all + # + # if stale?(@articles) + # @statistics = @articles.really_expensive_call + # respond_to do |format| + # # all the supported formats + # end + # end + # end + # + # When passing a record or a collection, you can still set the public header: + # + # def show + # @article = Article.find(params[:id]) + # + # if stale?(@article, public: true) + # @statistics = @article.really_expensive_call + # respond_to do |format| + # # all the supported formats + # end + # end + # end + # + # When rendering a different template than the default controller/action + # style, you can indicate which digest to include in the ETag: + # + # def show + # super if stale? @article, template: 'widgets/show' + # end + # + def stale?(object = nil, **freshness_kwargs) + fresh_when(object, **freshness_kwargs) + !request.fresh?(response) + end + + # Sets an HTTP 1.1 Cache-Control header. Defaults to issuing a +private+ + # instruction, so that intermediate caches must not cache the response. + # + # expires_in 20.minutes + # expires_in 3.hours, public: true + # expires_in 3.hours, public: true, must_revalidate: true + # + # This method will overwrite an existing Cache-Control header. + # See https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities. + # + # The method will also ensure an HTTP Date header for client compatibility. + def expires_in(seconds, options = {}) + response.cache_control.merge!( + max_age: seconds, + public: options.delete(:public), + must_revalidate: options.delete(:must_revalidate) + ) + options.delete(:private) + + response.cache_control[:extras] = options.map { |k, v| "#{k}=#{v}" } + response.date = Time.now unless response.date? + end + + # Sets an HTTP 1.1 Cache-Control header of no-cache. This means the + # resource will be marked as stale, so clients must always revalidate. + # Intermediate/browser caches may still store the asset. + def expires_now + response.cache_control.replace(no_cache: true) + end + + # Cache or yield the block. The cache is supposed to never expire. + # + # You can use this method when you have an HTTP response that never changes, + # and the browser and proxies should cache it indefinitely. + # + # * +public+: By default, HTTP responses are private, cached only on the + # user's web browser. To allow proxies to cache the response, set +true+ to + # indicate that they can serve the cached response to all users. + def http_cache_forever(public: false) + expires_in 100.years, public: public + + yield if stale?(etag: request.fullpath, + last_modified: Time.new(2011, 1, 1).utc, + public: public) + end + + private + def combine_etags(validator, options) + [validator, *etaggers.map { |etagger| instance_exec(options, &etagger) }].compact + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/content_security_policy.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/content_security_policy.rb new file mode 100644 index 00000000..b8fab4eb --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/content_security_policy.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module ActionController #:nodoc: + module ContentSecurityPolicy + # TODO: Documentation + extend ActiveSupport::Concern + + include AbstractController::Helpers + include AbstractController::Callbacks + + included do + helper_method :content_security_policy? + helper_method :content_security_policy_nonce + end + + module ClassMethods + def content_security_policy(enabled = true, **options, &block) + before_action(options) do + if block_given? + policy = current_content_security_policy + yield policy + request.content_security_policy = policy + end + + unless enabled + request.content_security_policy = nil + end + end + end + + def content_security_policy_report_only(report_only = true, **options) + before_action(options) do + request.content_security_policy_report_only = report_only + end + end + end + + private + + def content_security_policy? + request.content_security_policy + end + + def content_security_policy_nonce + request.content_security_policy_nonce + end + + def current_content_security_policy + request.content_security_policy.try(:clone) || ActionDispatch::ContentSecurityPolicy.new + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/cookies.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/cookies.rb new file mode 100644 index 00000000..ff469666 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/cookies.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module ActionController #:nodoc: + module Cookies + extend ActiveSupport::Concern + + included do + helper_method :cookies if defined?(helper_method) + end + + private + def cookies + request.cookie_jar + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/data_streaming.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/data_streaming.rb new file mode 100644 index 00000000..5a82ccf6 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/data_streaming.rb @@ -0,0 +1,152 @@ +# frozen_string_literal: true + +require "action_controller/metal/exceptions" + +module ActionController #:nodoc: + # Methods for sending arbitrary data and for streaming files to the browser, + # instead of rendering. + module DataStreaming + extend ActiveSupport::Concern + + include ActionController::Rendering + + DEFAULT_SEND_FILE_TYPE = "application/octet-stream".freeze #:nodoc: + DEFAULT_SEND_FILE_DISPOSITION = "attachment".freeze #:nodoc: + + private + # Sends the file. This uses a server-appropriate method (such as X-Sendfile) + # via the Rack::Sendfile middleware. The header to use is set via + # +config.action_dispatch.x_sendfile_header+. + # Your server can also configure this for you by setting the X-Sendfile-Type header. + # + # Be careful to sanitize the path parameter if it is coming from a web + # page. send_file(params[:path]) allows a malicious user to + # download any file on your server. + # + # Options: + # * :filename - suggests a filename for the browser to use. + # Defaults to File.basename(path). + # * :type - specifies an HTTP content type. + # You can specify either a string or a symbol for a registered type with Mime::Type.register, for example :json. + # If omitted, the type will be inferred from the file extension specified in :filename. + # If no content type is registered for the extension, the default type 'application/octet-stream' will be used. + # * :disposition - specifies whether the file will be shown inline or downloaded. + # Valid values are 'inline' and 'attachment' (default). + # * :status - specifies the status code to send with the response. Defaults to 200. + # * :url_based_filename - set to +true+ if you want the browser to guess the filename from + # the URL, which is necessary for i18n filenames on certain browsers + # (setting :filename overrides this option). + # + # The default Content-Type and Content-Disposition headers are + # set to download arbitrary binary files in as many browsers as + # possible. IE versions 4, 5, 5.5, and 6 are all known to have + # a variety of quirks (especially when downloading over SSL). + # + # Simple download: + # + # send_file '/path/to.zip' + # + # Show a JPEG in the browser: + # + # send_file '/path/to.jpeg', type: 'image/jpeg', disposition: 'inline' + # + # Show a 404 page in the browser: + # + # send_file '/path/to/404.html', type: 'text/html; charset=utf-8', status: 404 + # + # Read about the other Content-* HTTP headers if you'd like to + # provide the user with more information (such as Content-Description) in + # https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11. + # + # Also be aware that the document may be cached by proxies and browsers. + # The Pragma and Cache-Control headers declare how the file may be cached + # by intermediaries. They default to require clients to validate with + # the server before releasing cached responses. See + # https://www.mnot.net/cache_docs/ for an overview of web caching and + # https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9 + # for the Cache-Control header spec. + def send_file(path, options = {}) #:doc: + raise MissingFile, "Cannot read file #{path}" unless File.file?(path) && File.readable?(path) + + options[:filename] ||= File.basename(path) unless options[:url_based_filename] + send_file_headers! options + + self.status = options[:status] || 200 + self.content_type = options[:content_type] if options.key?(:content_type) + response.send_file path + end + + # Sends the given binary data to the browser. This method is similar to + # render plain: data, but also allows you to specify whether + # the browser should display the response as a file attachment (i.e. in a + # download dialog) or as inline data. You may also set the content type, + # the file name, and other things. + # + # Options: + # * :filename - suggests a filename for the browser to use. + # * :type - specifies an HTTP content type. Defaults to 'application/octet-stream'. + # You can specify either a string or a symbol for a registered type with Mime::Type.register, for example :json. + # If omitted, type will be inferred from the file extension specified in :filename. + # If no content type is registered for the extension, the default type 'application/octet-stream' will be used. + # * :disposition - specifies whether the file will be shown inline or downloaded. + # Valid values are 'inline' and 'attachment' (default). + # * :status - specifies the status code to send with the response. Defaults to 200. + # + # Generic data download: + # + # send_data buffer + # + # Download a dynamically-generated tarball: + # + # send_data generate_tgz('dir'), filename: 'dir.tgz' + # + # Display an image Active Record in the browser: + # + # send_data image.data, type: image.content_type, disposition: 'inline' + # + # See +send_file+ for more information on HTTP Content-* headers and caching. + def send_data(data, options = {}) #:doc: + send_file_headers! options + render options.slice(:status, :content_type).merge(body: data) + end + + def send_file_headers!(options) + type_provided = options.has_key?(:type) + + content_type = options.fetch(:type, DEFAULT_SEND_FILE_TYPE) + self.content_type = content_type + response.sending_file = true + + raise ArgumentError, ":type option required" if content_type.nil? + + if content_type.is_a?(Symbol) + extension = Mime[content_type] + raise ArgumentError, "Unknown MIME type #{options[:type]}" unless extension + self.content_type = extension + else + if !type_provided && options[:filename] + # If type wasn't provided, try guessing from file extension. + content_type = Mime::Type.lookup_by_extension(File.extname(options[:filename]).downcase.delete(".")) || content_type + end + self.content_type = content_type + end + + disposition = options.fetch(:disposition, DEFAULT_SEND_FILE_DISPOSITION) + unless disposition.nil? + disposition = disposition.to_s + disposition += %(; filename="#{options[:filename]}") if options[:filename] + headers["Content-Disposition"] = disposition + end + + headers["Content-Transfer-Encoding"] = "binary" + + # Fix a problem with IE 6.0 on opening downloaded files: + # If Cache-Control: no-cache is set (which Rails does by default), + # IE removes the file it just downloaded from its cache immediately + # after it displays the "open/save" dialog, which means that if you + # hit "open" the file isn't there anymore when the application that + # is called for handling the download is run, so let's workaround that + response.cache_control[:public] ||= false + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/etag_with_flash.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/etag_with_flash.rb new file mode 100644 index 00000000..38899e2f --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/etag_with_flash.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module ActionController + # When you're using the flash, it's generally used as a conditional on the view. + # This means the content of the view depends on the flash. Which in turn means + # that the ETag for a response should be computed with the content of the flash + # in mind. This does that by including the content of the flash as a component + # in the ETag that's generated for a response. + module EtagWithFlash + extend ActiveSupport::Concern + + include ActionController::ConditionalGet + + included do + etag { flash unless flash.empty? } + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/etag_with_template_digest.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/etag_with_template_digest.rb new file mode 100644 index 00000000..640c7553 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/etag_with_template_digest.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module ActionController + # When our views change, they should bubble up into HTTP cache freshness + # and bust browser caches. So the template digest for the current action + # is automatically included in the ETag. + # + # Enabled by default for apps that use Action View. Disable by setting + # + # config.action_controller.etag_with_template_digest = false + # + # Override the template to digest by passing +:template+ to +fresh_when+ + # and +stale?+ calls. For example: + # + # # We're going to render widgets/show, not posts/show + # fresh_when @post, template: 'widgets/show' + # + # # We're not going to render a template, so omit it from the ETag. + # fresh_when @post, template: false + # + module EtagWithTemplateDigest + extend ActiveSupport::Concern + + include ActionController::ConditionalGet + + included do + class_attribute :etag_with_template_digest, default: true + + ActiveSupport.on_load :action_view, yield: true do + etag do |options| + determine_template_etag(options) if etag_with_template_digest + end + end + end + + private + def determine_template_etag(options) + if template = pick_template_for_etag(options) + lookup_and_digest_template(template) + end + end + + # Pick the template digest to include in the ETag. If the +:template+ option + # is present, use the named template. If +:template+ is +nil+ or absent, use + # the default controller/action template. If +:template+ is false, omit the + # template digest from the ETag. + def pick_template_for_etag(options) + unless options[:template] == false + options[:template] || "#{controller_path}/#{action_name}" + end + end + + def lookup_and_digest_template(template) + ActionView::Digestor.digest name: template, finder: lookup_context + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/exceptions.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/exceptions.rb new file mode 100644 index 00000000..a65857d6 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/exceptions.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module ActionController + class ActionControllerError < StandardError #:nodoc: + end + + class BadRequest < ActionControllerError #:nodoc: + def initialize(msg = nil) + super(msg) + set_backtrace $!.backtrace if $! + end + end + + class RenderError < ActionControllerError #:nodoc: + end + + class RoutingError < ActionControllerError #:nodoc: + attr_reader :failures + def initialize(message, failures = []) + super(message) + @failures = failures + end + end + + class ActionController::UrlGenerationError < ActionControllerError #:nodoc: + end + + class MethodNotAllowed < ActionControllerError #:nodoc: + def initialize(*allowed_methods) + super("Only #{allowed_methods.to_sentence(locale: :en)} requests are allowed.") + end + end + + class NotImplemented < MethodNotAllowed #:nodoc: + end + + class MissingFile < ActionControllerError #:nodoc: + end + + class SessionOverflowError < ActionControllerError #:nodoc: + DEFAULT_MESSAGE = "Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data." + + def initialize(message = nil) + super(message || DEFAULT_MESSAGE) + end + end + + class UnknownHttpMethod < ActionControllerError #:nodoc: + end + + class UnknownFormat < ActionControllerError #:nodoc: + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/flash.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/flash.rb new file mode 100644 index 00000000..5115c2fa --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/flash.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module ActionController #:nodoc: + module Flash + extend ActiveSupport::Concern + + included do + class_attribute :_flash_types, instance_accessor: false, default: [] + + delegate :flash, to: :request + add_flash_types(:alert, :notice) + end + + module ClassMethods + # Creates new flash types. You can pass as many types as you want to create + # flash types other than the default alert and notice in + # your controllers and views. For instance: + # + # # in application_controller.rb + # class ApplicationController < ActionController::Base + # add_flash_types :warning + # end + # + # # in your controller + # redirect_to user_path(@user), warning: "Incomplete profile" + # + # # in your view + # <%= warning %> + # + # This method will automatically define a new method for each of the given + # names, and it will be available in your views. + def add_flash_types(*types) + types.each do |type| + next if _flash_types.include?(type) + + define_method(type) do + request.flash[type] + end + helper_method type + + self._flash_types += [type] + end + end + end + + private + def redirect_to(options = {}, response_status_and_flash = {}) #:doc: + self.class._flash_types.each do |flash_type| + if type = response_status_and_flash.delete(flash_type) + flash[flash_type] = type + end + end + + if other_flashes = response_status_and_flash.delete(:flash) + flash.update(other_flashes) + end + + super(options, response_status_and_flash) + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/force_ssl.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/force_ssl.rb new file mode 100644 index 00000000..7de500d1 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/force_ssl.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/except" +require "active_support/core_ext/hash/slice" + +module ActionController + # This module provides a method which will redirect the browser to use the secured HTTPS + # protocol. This will ensure that users' sensitive information will be + # transferred safely over the internet. You _should_ always force the browser + # to use HTTPS when you're transferring sensitive information such as + # user authentication, account information, or credit card information. + # + # Note that if you are really concerned about your application security, + # you might consider using +config.force_ssl+ in your config file instead. + # That will ensure all the data is transferred via HTTPS, and will + # prevent the user from getting their session hijacked when accessing the + # site over unsecured HTTP protocol. + module ForceSSL + extend ActiveSupport::Concern + include AbstractController::Callbacks + + ACTION_OPTIONS = [:only, :except, :if, :unless] + URL_OPTIONS = [:protocol, :host, :domain, :subdomain, :port, :path] + REDIRECT_OPTIONS = [:status, :flash, :alert, :notice] + + module ClassMethods + # Force the request to this particular controller or specified actions to be + # through the HTTPS protocol. + # + # If you need to disable this for any reason (e.g. development) then you can use + # an +:if+ or +:unless+ condition. + # + # class AccountsController < ApplicationController + # force_ssl if: :ssl_configured? + # + # def ssl_configured? + # !Rails.env.development? + # end + # end + # + # ==== URL Options + # You can pass any of the following options to affect the redirect URL + # * host - Redirect to a different host name + # * subdomain - Redirect to a different subdomain + # * domain - Redirect to a different domain + # * port - Redirect to a non-standard port + # * path - Redirect to a different path + # + # ==== Redirect Options + # You can pass any of the following options to affect the redirect status and response + # * status - Redirect with a custom status (default is 301 Moved Permanently) + # * flash - Set a flash message when redirecting + # * alert - Set an alert message when redirecting + # * notice - Set a notice message when redirecting + # + # ==== Action Options + # You can pass any of the following options to affect the before_action callback + # * only - The callback should be run only for this action + # * except - The callback should be run for all actions except this action + # * if - A symbol naming an instance method or a proc; the + # callback will be called only when it returns a true value. + # * unless - A symbol naming an instance method or a proc; the + # callback will be called only when it returns a false value. + def force_ssl(options = {}) + action_options = options.slice(*ACTION_OPTIONS) + redirect_options = options.except(*ACTION_OPTIONS) + before_action(action_options) do + force_ssl_redirect(redirect_options) + end + end + end + + # Redirect the existing request to use the HTTPS protocol. + # + # ==== Parameters + # * host_or_options - Either a host name or any of the URL and + # redirect options available to the force_ssl method. + def force_ssl_redirect(host_or_options = nil) + unless request.ssl? + options = { + protocol: "https://", + host: request.host, + path: request.fullpath, + status: :moved_permanently + } + + if host_or_options.is_a?(Hash) + options.merge!(host_or_options) + elsif host_or_options + options[:host] = host_or_options + end + + secure_url = ActionDispatch::Http::URL.url_for(options.slice(*URL_OPTIONS)) + flash.keep if respond_to?(:flash) && request.respond_to?(:flash) + redirect_to secure_url, options.slice(*REDIRECT_OPTIONS) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/head.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/head.rb new file mode 100644 index 00000000..bac9bc5e --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/head.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module ActionController + module Head + # Returns a response that has no content (merely headers). The options + # argument is interpreted to be a hash of header names and values. + # This allows you to easily return a response that consists only of + # significant headers: + # + # head :created, location: person_path(@person) + # + # head :created, location: @person + # + # It can also be used to return exceptional conditions: + # + # return head(:method_not_allowed) unless request.post? + # return head(:bad_request) unless valid_request? + # render + # + # See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list of valid +status+ symbols. + def head(status, options = {}) + if status.is_a?(Hash) + raise ArgumentError, "#{status.inspect} is not a valid value for `status`." + end + + status ||= :ok + + location = options.delete(:location) + content_type = options.delete(:content_type) + + options.each do |key, value| + headers[key.to_s.dasherize.split("-").each { |v| v[0] = v[0].chr.upcase }.join("-")] = value.to_s + end + + self.status = status + self.location = url_for(location) if location + + self.response_body = "" + + if include_content?(response_code) + self.content_type = content_type || (Mime[formats.first] if formats) + response.charset = false + end + + true + end + + private + def include_content?(status) + case status + when 100..199 + false + when 204, 205, 304 + false + else + true + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/helpers.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/helpers.rb new file mode 100644 index 00000000..22c84e44 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/helpers.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +module ActionController + # The \Rails framework provides a large number of helpers for working with assets, dates, forms, + # numbers and model objects, to name a few. These helpers are available to all templates + # by default. + # + # In addition to using the standard template helpers provided, creating custom helpers to + # extract complicated logic or reusable functionality is strongly encouraged. By default, each controller + # will include all helpers. These helpers are only accessible on the controller through #helpers + # + # In previous versions of \Rails the controller will include a helper which + # matches the name of the controller, e.g., MyController will automatically + # include MyHelper. To return old behavior set +config.action_controller.include_all_helpers+ to +false+. + # + # Additional helpers can be specified using the +helper+ class method in ActionController::Base or any + # controller which inherits from it. + # + # The +to_s+ method from the \Time class can be wrapped in a helper method to display a custom message if + # a \Time object is blank: + # + # module FormattedTimeHelper + # def format_time(time, format=:long, blank_message=" ") + # time.blank? ? blank_message : time.to_s(format) + # end + # end + # + # FormattedTimeHelper can now be included in a controller, using the +helper+ class method: + # + # class EventsController < ActionController::Base + # helper FormattedTimeHelper + # def index + # @events = Event.all + # end + # end + # + # Then, in any view rendered by EventController, the format_time method can be called: + # + # <% @events.each do |event| -%> + #

+ # <%= format_time(event.time, :short, "N/A") %> | <%= event.name %> + #

+ # <% end -%> + # + # Finally, assuming we have two event instances, one which has a time and one which does not, + # the output might look like this: + # + # 23 Aug 11:30 | Carolina Railhawks Soccer Match + # N/A | Carolina Railhawks Training Workshop + # + module Helpers + extend ActiveSupport::Concern + + class << self; attr_accessor :helpers_path; end + include AbstractController::Helpers + + included do + class_attribute :helpers_path, default: [] + class_attribute :include_all_helpers, default: true + end + + module ClassMethods + # Declares helper accessors for controller attributes. For example, the + # following adds new +name+ and name= instance methods to a + # controller and makes them available to the view: + # attr_accessor :name + # helper_attr :name + # + # ==== Parameters + # * attrs - Names of attributes to be converted into helpers. + def helper_attr(*attrs) + attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") } + end + + # Provides a proxy to access helper methods from outside the view. + def helpers + @helper_proxy ||= begin + proxy = ActionView::Base.new + proxy.config = config.inheritable_copy + proxy.extend(_helpers) + end + end + + # Overwrite modules_for_helpers to accept :all as argument, which loads + # all helpers in helpers_path. + # + # ==== Parameters + # * args - A list of helpers + # + # ==== Returns + # * array - A normalized list of modules for the list of helpers provided. + def modules_for_helpers(args) + args += all_application_helpers if args.delete(:all) + super(args) + end + + # Returns a list of helper names in a given path. + # + # ActionController::Base.all_helpers_from_path 'app/helpers' + # # => ["application", "chart", "rubygems"] + def all_helpers_from_path(path) + helpers = Array(path).flat_map do |_path| + extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/ + names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1'.freeze) } + names.sort! + end + helpers.uniq! + helpers + end + + private + # Extract helper names from files in app/helpers/**/*_helper.rb + def all_application_helpers + all_helpers_from_path(helpers_path) + end + end + + # Provides a proxy to access helper methods from outside the view. + def helpers + @_helper_proxy ||= view_context + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/http_authentication.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/http_authentication.rb new file mode 100644 index 00000000..01676f32 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/http_authentication.rb @@ -0,0 +1,519 @@ +# frozen_string_literal: true + +require "base64" +require "active_support/security_utils" + +module ActionController + # Makes it dead easy to do HTTP Basic, Digest and Token authentication. + module HttpAuthentication + # Makes it dead easy to do HTTP \Basic authentication. + # + # === Simple \Basic example + # + # class PostsController < ApplicationController + # http_basic_authenticate_with name: "dhh", password: "secret", except: :index + # + # def index + # render plain: "Everyone can see me!" + # end + # + # def edit + # render plain: "I'm only accessible if you know the password" + # end + # end + # + # === Advanced \Basic example + # + # Here is a more advanced \Basic example where only Atom feeds and the XML API is protected by HTTP authentication, + # the regular HTML interface is protected by a session approach: + # + # class ApplicationController < ActionController::Base + # before_action :set_account, :authenticate + # + # private + # def set_account + # @account = Account.find_by(url_name: request.subdomains.first) + # end + # + # def authenticate + # case request.format + # when Mime[:xml], Mime[:atom] + # if user = authenticate_with_http_basic { |u, p| @account.users.authenticate(u, p) } + # @current_user = user + # else + # request_http_basic_authentication + # end + # else + # if session_authenticated? + # @current_user = @account.users.find(session[:authenticated][:user_id]) + # else + # redirect_to(login_url) and return false + # end + # end + # end + # end + # + # In your integration tests, you can do something like this: + # + # def test_access_granted_from_xml + # @request.env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(users(:dhh).name, users(:dhh).password) + # get "/notes/1.xml" + # + # assert_equal 200, status + # end + module Basic + extend self + + module ControllerMethods + extend ActiveSupport::Concern + + module ClassMethods + def http_basic_authenticate_with(options = {}) + before_action(options.except(:name, :password, :realm)) do + authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password| + # This comparison uses & so that it doesn't short circuit and + # uses `secure_compare` so that length information + # isn't leaked. + ActiveSupport::SecurityUtils.secure_compare(name, options[:name]) & + ActiveSupport::SecurityUtils.secure_compare(password, options[:password]) + end + end + end + end + + def authenticate_or_request_with_http_basic(realm = "Application", message = nil, &login_procedure) + authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm, message) + end + + def authenticate_with_http_basic(&login_procedure) + HttpAuthentication::Basic.authenticate(request, &login_procedure) + end + + def request_http_basic_authentication(realm = "Application", message = nil) + HttpAuthentication::Basic.authentication_request(self, realm, message) + end + end + + def authenticate(request, &login_procedure) + if has_basic_credentials?(request) + login_procedure.call(*user_name_and_password(request)) + end + end + + def has_basic_credentials?(request) + request.authorization.present? && (auth_scheme(request).downcase == "basic") + end + + def user_name_and_password(request) + decode_credentials(request).split(":", 2) + end + + def decode_credentials(request) + ::Base64.decode64(auth_param(request) || "") + end + + def auth_scheme(request) + request.authorization.to_s.split(" ", 2).first + end + + def auth_param(request) + request.authorization.to_s.split(" ", 2).second + end + + def encode_credentials(user_name, password) + "Basic #{::Base64.strict_encode64("#{user_name}:#{password}")}" + end + + def authentication_request(controller, realm, message) + message ||= "HTTP Basic: Access denied.\n" + controller.headers["WWW-Authenticate"] = %(Basic realm="#{realm.tr('"'.freeze, "".freeze)}") + controller.status = 401 + controller.response_body = message + end + end + + # Makes it dead easy to do HTTP \Digest authentication. + # + # === Simple \Digest example + # + # require 'digest/md5' + # class PostsController < ApplicationController + # REALM = "SuperSecret" + # USERS = {"dhh" => "secret", #plain text password + # "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password + # + # before_action :authenticate, except: [:index] + # + # def index + # render plain: "Everyone can see me!" + # end + # + # def edit + # render plain: "I'm only accessible if you know the password" + # end + # + # private + # def authenticate + # authenticate_or_request_with_http_digest(REALM) do |username| + # USERS[username] + # end + # end + # end + # + # === Notes + # + # The +authenticate_or_request_with_http_digest+ block must return the user's password + # or the ha1 digest hash so the framework can appropriately hash to check the user's + # credentials. Returning +nil+ will cause authentication to fail. + # + # Storing the ha1 hash: MD5(username:realm:password), is better than storing a plain password. If + # the password file or database is compromised, the attacker would be able to use the ha1 hash to + # authenticate as the user at this +realm+, but would not have the user's password to try using at + # other sites. + # + # In rare instances, web servers or front proxies strip authorization headers before + # they reach your application. You can debug this situation by logging all environment + # variables, and check for HTTP_AUTHORIZATION, amongst others. + module Digest + extend self + + module ControllerMethods + def authenticate_or_request_with_http_digest(realm = "Application", message = nil, &password_procedure) + authenticate_with_http_digest(realm, &password_procedure) || request_http_digest_authentication(realm, message) + end + + # Authenticate with HTTP Digest, returns true or false + def authenticate_with_http_digest(realm = "Application", &password_procedure) + HttpAuthentication::Digest.authenticate(request, realm, &password_procedure) + end + + # Render output including the HTTP Digest authentication header + def request_http_digest_authentication(realm = "Application", message = nil) + HttpAuthentication::Digest.authentication_request(self, realm, message) + end + end + + # Returns false on a valid response, true otherwise + def authenticate(request, realm, &password_procedure) + request.authorization && validate_digest_response(request, realm, &password_procedure) + end + + # Returns false unless the request credentials response value matches the expected value. + # First try the password as a ha1 digest password. If this fails, then try it as a plain + # text password. + def validate_digest_response(request, realm, &password_procedure) + secret_key = secret_token(request) + credentials = decode_credentials_header(request) + valid_nonce = validate_nonce(secret_key, request, credentials[:nonce]) + + if valid_nonce && realm == credentials[:realm] && opaque(secret_key) == credentials[:opaque] + password = password_procedure.call(credentials[:username]) + return false unless password + + method = request.get_header("rack.methodoverride.original_method") || request.get_header("REQUEST_METHOD") + uri = credentials[:uri] + + [true, false].any? do |trailing_question_mark| + [true, false].any? do |password_is_ha1| + _uri = trailing_question_mark ? uri + "?" : uri + expected = expected_response(method, _uri, credentials, password, password_is_ha1) + expected == credentials[:response] + end + end + end + end + + # Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+ + # Optional parameter +password_is_ha1+ is set to +true+ by default, since best practice is to store ha1 digest instead + # of a plain-text password. + def expected_response(http_method, uri, credentials, password, password_is_ha1 = true) + ha1 = password_is_ha1 ? password : ha1(credentials, password) + ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase, uri].join(":")) + ::Digest::MD5.hexdigest([ha1, credentials[:nonce], credentials[:nc], credentials[:cnonce], credentials[:qop], ha2].join(":")) + end + + def ha1(credentials, password) + ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(":")) + end + + def encode_credentials(http_method, credentials, password, password_is_ha1) + credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password, password_is_ha1) + "Digest " + credentials.sort_by { |x| x[0].to_s }.map { |v| "#{v[0]}='#{v[1]}'" }.join(", ") + end + + def decode_credentials_header(request) + decode_credentials(request.authorization) + end + + def decode_credentials(header) + ActiveSupport::HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/, "").split(",").map do |pair| + key, value = pair.split("=", 2) + [key.strip, value.to_s.gsub(/^"|"$/, "").delete("'")] + end] + end + + def authentication_header(controller, realm) + secret_key = secret_token(controller.request) + nonce = self.nonce(secret_key) + opaque = opaque(secret_key) + controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce}", opaque="#{opaque}") + end + + def authentication_request(controller, realm, message = nil) + message ||= "HTTP Digest: Access denied.\n" + authentication_header(controller, realm) + controller.status = 401 + controller.response_body = message + end + + def secret_token(request) + key_generator = request.key_generator + http_auth_salt = request.http_auth_salt + key_generator.generate_key(http_auth_salt) + end + + # Uses an MD5 digest based on time to generate a value to be used only once. + # + # A server-specified data string which should be uniquely generated each time a 401 response is made. + # It is recommended that this string be base64 or hexadecimal data. + # Specifically, since the string is passed in the header lines as a quoted string, the double-quote character is not allowed. + # + # The contents of the nonce are implementation dependent. + # The quality of the implementation depends on a good choice. + # A nonce might, for example, be constructed as the base 64 encoding of + # + # time-stamp H(time-stamp ":" ETag ":" private-key) + # + # where time-stamp is a server-generated time or other non-repeating value, + # ETag is the value of the HTTP ETag header associated with the requested entity, + # and private-key is data known only to the server. + # With a nonce of this form a server would recalculate the hash portion after receiving the client authentication header and + # reject the request if it did not match the nonce from that header or + # if the time-stamp value is not recent enough. In this way the server can limit the time of the nonce's validity. + # The inclusion of the ETag prevents a replay request for an updated version of the resource. + # (Note: including the IP address of the client in the nonce would appear to offer the server the ability + # to limit the reuse of the nonce to the same client that originally got it. + # However, that would break proxy farms, where requests from a single user often go through different proxies in the farm. + # Also, IP address spoofing is not that hard.) + # + # An implementation might choose not to accept a previously used nonce or a previously used digest, in order to + # protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for + # POST, PUT, or PATCH requests and a time-stamp for GET requests. For more details on the issues involved see Section 4 + # of this document. + # + # The nonce is opaque to the client. Composed of Time, and hash of Time with secret + # key from the Rails session secret generated upon creation of project. Ensures + # the time cannot be modified by client. + def nonce(secret_key, time = Time.now) + t = time.to_i + hashed = [t, secret_key] + digest = ::Digest::MD5.hexdigest(hashed.join(":")) + ::Base64.strict_encode64("#{t}:#{digest}") + end + + # Might want a shorter timeout depending on whether the request + # is a PATCH, PUT, or POST, and if the client is a browser or web service. + # Can be much shorter if the Stale directive is implemented. This would + # allow a user to use new nonce without prompting the user again for their + # username and password. + def validate_nonce(secret_key, request, value, seconds_to_timeout = 5 * 60) + return false if value.nil? + t = ::Base64.decode64(value).split(":").first.to_i + nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout + end + + # Opaque based on digest of secret key + def opaque(secret_key) + ::Digest::MD5.hexdigest(secret_key) + end + end + + # Makes it dead easy to do HTTP Token authentication. + # + # Simple Token example: + # + # class PostsController < ApplicationController + # TOKEN = "secret" + # + # before_action :authenticate, except: [ :index ] + # + # def index + # render plain: "Everyone can see me!" + # end + # + # def edit + # render plain: "I'm only accessible if you know the password" + # end + # + # private + # def authenticate + # authenticate_or_request_with_http_token do |token, options| + # # Compare the tokens in a time-constant manner, to mitigate + # # timing attacks. + # ActiveSupport::SecurityUtils.secure_compare(token, TOKEN) + # end + # end + # end + # + # + # Here is a more advanced Token example where only Atom feeds and the XML API is protected by HTTP token authentication, + # the regular HTML interface is protected by a session approach: + # + # class ApplicationController < ActionController::Base + # before_action :set_account, :authenticate + # + # private + # def set_account + # @account = Account.find_by(url_name: request.subdomains.first) + # end + # + # def authenticate + # case request.format + # when Mime[:xml], Mime[:atom] + # if user = authenticate_with_http_token { |t, o| @account.users.authenticate(t, o) } + # @current_user = user + # else + # request_http_token_authentication + # end + # else + # if session_authenticated? + # @current_user = @account.users.find(session[:authenticated][:user_id]) + # else + # redirect_to(login_url) and return false + # end + # end + # end + # end + # + # + # In your integration tests, you can do something like this: + # + # def test_access_granted_from_xml + # get( + # "/notes/1.xml", nil, + # 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Token.encode_credentials(users(:dhh).token) + # ) + # + # assert_equal 200, status + # end + # + # + # On shared hosts, Apache sometimes doesn't pass authentication headers to + # FCGI instances. If your environment matches this description and you cannot + # authenticate, try this rule in your Apache setup: + # + # RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L] + module Token + TOKEN_KEY = "token=" + TOKEN_REGEX = /^(Token|Bearer)\s+/ + AUTHN_PAIR_DELIMITERS = /(?:,|;|\t+)/ + extend self + + module ControllerMethods + def authenticate_or_request_with_http_token(realm = "Application", message = nil, &login_procedure) + authenticate_with_http_token(&login_procedure) || request_http_token_authentication(realm, message) + end + + def authenticate_with_http_token(&login_procedure) + Token.authenticate(self, &login_procedure) + end + + def request_http_token_authentication(realm = "Application", message = nil) + Token.authentication_request(self, realm, message) + end + end + + # If token Authorization header is present, call the login + # procedure with the present token and options. + # + # [controller] + # ActionController::Base instance for the current request. + # + # [login_procedure] + # Proc to call if a token is present. The Proc should take two arguments: + # + # authenticate(controller) { |token, options| ... } + # + # Returns the return value of login_procedure if a + # token is found. Returns nil if no token is found. + + def authenticate(controller, &login_procedure) + token, options = token_and_options(controller.request) + unless token.blank? + login_procedure.call(token, options) + end + end + + # Parses the token and options out of the token Authorization header. + # The value for the Authorization header is expected to have the prefix + # "Token" or "Bearer". If the header looks like this: + # Authorization: Token token="abc", nonce="def" + # Then the returned token is "abc", and the options are + # {nonce: "def"} + # + # request - ActionDispatch::Request instance with the current headers. + # + # Returns an +Array+ of [String, Hash] if a token is present. + # Returns +nil+ if no token is found. + def token_and_options(request) + authorization_request = request.authorization.to_s + if authorization_request[TOKEN_REGEX] + params = token_params_from authorization_request + [params.shift[1], Hash[params].with_indifferent_access] + end + end + + def token_params_from(auth) + rewrite_param_values params_array_from raw_params auth + end + + # Takes raw_params and turns it into an array of parameters + def params_array_from(raw_params) + raw_params.map { |param| param.split %r/=(.+)?/ } + end + + # This removes the " characters wrapping the value. + def rewrite_param_values(array_params) + array_params.each { |param| (param[1] || "".dup).gsub! %r/^"|"$/, "" } + end + + # This method takes an authorization body and splits up the key-value + # pairs by the standardized :, ;, or \t + # delimiters defined in +AUTHN_PAIR_DELIMITERS+. + def raw_params(auth) + _raw_params = auth.sub(TOKEN_REGEX, "").split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/) + + if !(_raw_params.first =~ %r{\A#{TOKEN_KEY}}) + _raw_params[0] = "#{TOKEN_KEY}#{_raw_params.first}" + end + + _raw_params + end + + # Encodes the given token and options into an Authorization header value. + # + # token - String token. + # options - optional Hash of the options. + # + # Returns String. + def encode_credentials(token, options = {}) + values = ["#{TOKEN_KEY}#{token.to_s.inspect}"] + options.map do |key, value| + "#{key}=#{value.to_s.inspect}" + end + "Token #{values * ", "}" + end + + # Sets a WWW-Authenticate header to let the client know a token is desired. + # + # controller - ActionController::Base instance for the outgoing response. + # realm - String realm to use in the header. + # + # Returns nothing. + def authentication_request(controller, realm, message = nil) + message ||= "HTTP Token: Access denied.\n" + controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"'.freeze, "".freeze)}") + controller.__send__ :render, plain: message, status: :unauthorized + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/implicit_render.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/implicit_render.rb new file mode 100644 index 00000000..ac0c127c --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/implicit_render.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +module ActionController + # Handles implicit rendering for a controller action that does not + # explicitly respond with +render+, +respond_to+, +redirect+, or +head+. + # + # For API controllers, the implicit response is always 204 No Content. + # + # For all other controllers, we use these heuristics to decide whether to + # render a template, raise an error for a missing template, or respond with + # 204 No Content: + # + # First, if we DO find a template, it's rendered. Template lookup accounts + # for the action name, locales, format, variant, template handlers, and more + # (see +render+ for details). + # + # Second, if we DON'T find a template but the controller action does have + # templates for other formats, variants, etc., then we trust that you meant + # to provide a template for this response, too, and we raise + # ActionController::UnknownFormat with an explanation. + # + # Third, if we DON'T find a template AND the request is a page load in a web + # browser (technically, a non-XHR GET request for an HTML response) where + # you reasonably expect to have rendered a template, then we raise + # ActionView::UnknownFormat with an explanation. + # + # Finally, if we DON'T find a template AND the request isn't a browser page + # load, then we implicitly respond with 204 No Content. + module ImplicitRender + # :stopdoc: + include BasicImplicitRender + + def default_render(*args) + if template_exists?(action_name.to_s, _prefixes, variants: request.variant) + render(*args) + elsif any_templates?(action_name.to_s, _prefixes) + message = "#{self.class.name}\##{action_name} is missing a template " \ + "for this request format and variant.\n" \ + "\nrequest.formats: #{request.formats.map(&:to_s).inspect}" \ + "\nrequest.variant: #{request.variant.inspect}" + + raise ActionController::UnknownFormat, message + elsif interactive_browser_request? + message = "#{self.class.name}\##{action_name} is missing a template " \ + "for this request format and variant.\n\n" \ + "request.formats: #{request.formats.map(&:to_s).inspect}\n" \ + "request.variant: #{request.variant.inspect}\n\n" \ + "NOTE! For XHR/Ajax or API requests, this action would normally " \ + "respond with 204 No Content: an empty white screen. Since you're " \ + "loading it in a web browser, we assume that you expected to " \ + "actually render a template, not nothing, so we're showing an " \ + "error to be extra-clear. If you expect 204 No Content, carry on. " \ + "That's what you'll get from an XHR or API request. Give it a shot." + + raise ActionController::UnknownFormat, message + else + logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger + super + end + end + + def method_for_action(action_name) + super || if template_exists?(action_name.to_s, _prefixes) + "default_render" + end + end + + private + def interactive_browser_request? + request.get? && request.format == Mime[:html] && !request.xhr? + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/instrumentation.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/instrumentation.rb new file mode 100644 index 00000000..be944962 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/instrumentation.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +require "benchmark" +require "abstract_controller/logger" + +module ActionController + # Adds instrumentation to several ends in ActionController::Base. It also provides + # some hooks related with process_action. This allows an ORM like Active Record + # and/or DataMapper to plug in ActionController and show related information. + # + # Check ActiveRecord::Railties::ControllerRuntime for an example. + module Instrumentation + extend ActiveSupport::Concern + + include AbstractController::Logger + + attr_internal :view_runtime + + def process_action(*args) + raw_payload = { + controller: self.class.name, + action: action_name, + params: request.filtered_parameters, + headers: request.headers, + format: request.format.ref, + method: request.request_method, + path: request.fullpath + } + + ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup) + + ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload| + begin + result = super + payload[:status] = response.status + result + ensure + append_info_to_payload(payload) + end + end + end + + def render(*args) + render_output = nil + self.view_runtime = cleanup_view_runtime do + Benchmark.ms { render_output = super } + end + render_output + end + + def send_file(path, options = {}) + ActiveSupport::Notifications.instrument("send_file.action_controller", + options.merge(path: path)) do + super + end + end + + def send_data(data, options = {}) + ActiveSupport::Notifications.instrument("send_data.action_controller", options) do + super + end + end + + def redirect_to(*args) + ActiveSupport::Notifications.instrument("redirect_to.action_controller") do |payload| + result = super + payload[:status] = response.status + payload[:location] = response.filtered_location + result + end + end + + private + + # A hook invoked every time a before callback is halted. + def halted_callback_hook(filter) + ActiveSupport::Notifications.instrument("halted_callback.action_controller", filter: filter) + end + + # A hook which allows you to clean up any time, wrongly taken into account in + # views, like database querying time. + # + # def cleanup_view_runtime + # super - time_taken_in_something_expensive + # end + def cleanup_view_runtime # :doc: + yield + end + + # Every time after an action is processed, this method is invoked + # with the payload, so you can add more information. + def append_info_to_payload(payload) # :doc: + payload[:view_runtime] = view_runtime + end + + module ClassMethods + # A hook which allows other frameworks to log what happened during + # controller process action. This method should return an array + # with the messages to be added. + def log_process_action(payload) #:nodoc: + messages, view_runtime = [], payload[:view_runtime] + messages << ("Views: %.1fms" % view_runtime.to_f) if view_runtime + messages + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/live.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/live.rb new file mode 100644 index 00000000..2f4c8fb8 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/live.rb @@ -0,0 +1,312 @@ +# frozen_string_literal: true + +require "action_dispatch/http/response" +require "delegate" +require "active_support/json" + +module ActionController + # Mix this module into your controller, and all actions in that controller + # will be able to stream data to the client as it's written. + # + # class MyController < ActionController::Base + # include ActionController::Live + # + # def stream + # response.headers['Content-Type'] = 'text/event-stream' + # 100.times { + # response.stream.write "hello world\n" + # sleep 1 + # } + # ensure + # response.stream.close + # end + # end + # + # There are a few caveats with this module. You *cannot* write headers after the + # response has been committed (Response#committed? will return truthy). + # Calling +write+ or +close+ on the response stream will cause the response + # object to be committed. Make sure all headers are set before calling write + # or close on your stream. + # + # You *must* call close on your stream when you're finished, otherwise the + # socket may be left open forever. + # + # The final caveat is that your actions are executed in a separate thread than + # the main thread. Make sure your actions are thread safe, and this shouldn't + # be a problem (don't share state across threads, etc). + module Live + extend ActiveSupport::Concern + + module ClassMethods + def make_response!(request) + if request.get_header("HTTP_VERSION") == "HTTP/1.0" + super + else + Live::Response.new.tap do |res| + res.request = request + end + end + end + end + + # This class provides the ability to write an SSE (Server Sent Event) + # to an IO stream. The class is initialized with a stream and can be used + # to either write a JSON string or an object which can be converted to JSON. + # + # Writing an object will convert it into standard SSE format with whatever + # options you have configured. You may choose to set the following options: + # + # 1) Event. If specified, an event with this name will be dispatched on + # the browser. + # 2) Retry. The reconnection time in milliseconds used when attempting + # to send the event. + # 3) Id. If the connection dies while sending an SSE to the browser, then + # the server will receive a +Last-Event-ID+ header with value equal to +id+. + # + # After setting an option in the constructor of the SSE object, all future + # SSEs sent across the stream will use those options unless overridden. + # + # Example Usage: + # + # class MyController < ActionController::Base + # include ActionController::Live + # + # def index + # response.headers['Content-Type'] = 'text/event-stream' + # sse = SSE.new(response.stream, retry: 300, event: "event-name") + # sse.write({ name: 'John'}) + # sse.write({ name: 'John'}, id: 10) + # sse.write({ name: 'John'}, id: 10, event: "other-event") + # sse.write({ name: 'John'}, id: 10, event: "other-event", retry: 500) + # ensure + # sse.close + # end + # end + # + # Note: SSEs are not currently supported by IE. However, they are supported + # by Chrome, Firefox, Opera, and Safari. + class SSE + WHITELISTED_OPTIONS = %w( retry event id ) + + def initialize(stream, options = {}) + @stream = stream + @options = options + end + + def close + @stream.close + end + + def write(object, options = {}) + case object + when String + perform_write(object, options) + else + perform_write(ActiveSupport::JSON.encode(object), options) + end + end + + private + + def perform_write(json, options) + current_options = @options.merge(options).stringify_keys + + WHITELISTED_OPTIONS.each do |option_name| + if (option_value = current_options[option_name]) + @stream.write "#{option_name}: #{option_value}\n" + end + end + + message = json.gsub("\n".freeze, "\ndata: ".freeze) + @stream.write "data: #{message}\n\n" + end + end + + class ClientDisconnected < RuntimeError + end + + class Buffer < ActionDispatch::Response::Buffer #:nodoc: + include MonitorMixin + + # Ignore that the client has disconnected. + # + # If this value is `true`, calling `write` after the client + # disconnects will result in the written content being silently + # discarded. If this value is `false` (the default), a + # ClientDisconnected exception will be raised. + attr_accessor :ignore_disconnect + + def initialize(response) + @error_callback = lambda { true } + @cv = new_cond + @aborted = false + @ignore_disconnect = false + super(response, SizedQueue.new(10)) + end + + def write(string) + unless @response.committed? + @response.set_header "Cache-Control", "no-cache" + @response.delete_header "Content-Length" + end + + super + + unless connected? + @buf.clear + + unless @ignore_disconnect + # Raise ClientDisconnected, which is a RuntimeError (not an + # IOError), because that's more appropriate for something beyond + # the developer's control. + raise ClientDisconnected, "client disconnected" + end + end + end + + # Write a 'close' event to the buffer; the producer/writing thread + # uses this to notify us that it's finished supplying content. + # + # See also #abort. + def close + synchronize do + super + @buf.push nil + @cv.broadcast + end + end + + # Inform the producer/writing thread that the client has + # disconnected; the reading thread is no longer interested in + # anything that's being written. + # + # See also #close. + def abort + synchronize do + @aborted = true + @buf.clear + end + end + + # Is the client still connected and waiting for content? + # + # The result of calling `write` when this is `false` is determined + # by `ignore_disconnect`. + def connected? + !@aborted + end + + def on_error(&block) + @error_callback = block + end + + def call_on_error + @error_callback.call + end + + private + + def each_chunk(&block) + loop do + str = nil + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + str = @buf.pop + end + break unless str + yield str + end + end + end + + class Response < ActionDispatch::Response #:nodoc: all + private + + def before_committed + super + jar = request.cookie_jar + # The response can be committed multiple times + jar.write self unless committed? + end + + def build_buffer(response, body) + buf = Live::Buffer.new response + body.each { |part| buf.write part } + buf + end + end + + def process(name) + t1 = Thread.current + locals = t1.keys.map { |key| [key, t1[key]] } + + error = nil + # This processes the action in a child thread. It lets us return the + # response code and headers back up the Rack stack, and still process + # the body in parallel with sending data to the client. + new_controller_thread { + ActiveSupport::Dependencies.interlock.running do + t2 = Thread.current + + # Since we're processing the view in a different thread, copy the + # thread locals from the main thread to the child thread. :'( + locals.each { |k, v| t2[k] = v } + + begin + super(name) + rescue => e + if @_response.committed? + begin + @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html + @_response.stream.call_on_error + rescue => exception + log_error(exception) + ensure + log_error(e) + @_response.stream.close + end + else + error = e + end + ensure + @_response.commit! + end + end + } + + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @_response.await_commit + end + + raise error if error + end + + # Spawn a new thread to serve up the controller in. This is to get + # around the fact that Rack isn't based around IOs and we need to use + # a thread to stream data from the response bodies. Nobody should call + # this method except in Rails internals. Seriously! + def new_controller_thread # :nodoc: + Thread.new { + t2 = Thread.current + t2.abort_on_exception = true + yield + } + end + + def log_error(exception) + logger = ActionController::Base.logger + return unless logger + + logger.fatal do + message = "\n#{exception.class} (#{exception.message}):\n".dup + message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) + message << " " << exception.backtrace.join("\n ") + "#{message}\n\n" + end + end + + def response_body=(body) + super + response.close if response + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/mime_responds.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/mime_responds.rb new file mode 100644 index 00000000..2233b934 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/mime_responds.rb @@ -0,0 +1,313 @@ +# frozen_string_literal: true + +require "abstract_controller/collector" + +module ActionController #:nodoc: + module MimeResponds + # Without web-service support, an action which collects the data for displaying a list of people + # might look something like this: + # + # def index + # @people = Person.all + # end + # + # That action implicitly responds to all formats, but formats can also be whitelisted: + # + # def index + # @people = Person.all + # respond_to :html, :js + # end + # + # Here's the same action, with web-service support baked in: + # + # def index + # @people = Person.all + # + # respond_to do |format| + # format.html + # format.js + # format.xml { render xml: @people } + # end + # end + # + # What that says is, "if the client wants HTML or JS in response to this action, just respond as we + # would have before, but if the client wants XML, return them the list of people in XML format." + # (Rails determines the desired response format from the HTTP Accept header submitted by the client.) + # + # Supposing you have an action that adds a new person, optionally creating their company + # (by name) if it does not already exist, without web-services, it might look like this: + # + # def create + # @company = Company.find_or_create_by(name: params[:company][:name]) + # @person = @company.people.create(params[:person]) + # + # redirect_to(person_list_url) + # end + # + # Here's the same action, with web-service support baked in: + # + # def create + # company = params[:person].delete(:company) + # @company = Company.find_or_create_by(name: company[:name]) + # @person = @company.people.create(params[:person]) + # + # respond_to do |format| + # format.html { redirect_to(person_list_url) } + # format.js + # format.xml { render xml: @person.to_xml(include: @company) } + # end + # end + # + # If the client wants HTML, we just redirect them back to the person list. If they want JavaScript, + # then it is an Ajax request and we render the JavaScript template associated with this action. + # Lastly, if the client wants XML, we render the created person as XML, but with a twist: we also + # include the person's company in the rendered XML, so you get something like this: + # + # + # ... + # ... + # + # ... + # ... + # ... + # + # + # + # Note, however, the extra bit at the top of that action: + # + # company = params[:person].delete(:company) + # @company = Company.find_or_create_by(name: company[:name]) + # + # This is because the incoming XML document (if a web-service request is in process) can only contain a + # single root-node. So, we have to rearrange things so that the request looks like this (url-encoded): + # + # person[name]=...&person[company][name]=...&... + # + # And, like this (xml-encoded): + # + # + # ... + # + # ... + # + # + # + # In other words, we make the request so that it operates on a single entity's person. Then, in the action, + # we extract the company data from the request, find or create the company, and then create the new person + # with the remaining data. + # + # Note that you can define your own XML parameter parser which would allow you to describe multiple entities + # in a single request (i.e., by wrapping them all in a single root node), but if you just go with the flow + # and accept Rails' defaults, life will be much easier. + # + # If you need to use a MIME type which isn't supported by default, you can register your own handlers in + # +config/initializers/mime_types.rb+ as follows. + # + # Mime::Type.register "image/jpg", :jpg + # + # Respond to also allows you to specify a common block for different formats by using +any+: + # + # def index + # @people = Person.all + # + # respond_to do |format| + # format.html + # format.any(:xml, :json) { render request.format.to_sym => @people } + # end + # end + # + # In the example above, if the format is xml, it will render: + # + # render xml: @people + # + # Or if the format is json: + # + # render json: @people + # + # Formats can have different variants. + # + # The request variant is a specialization of the request format, like :tablet, + # :phone, or :desktop. + # + # We often want to render different html/json/xml templates for phones, + # tablets, and desktop browsers. Variants make it easy. + # + # You can set the variant in a +before_action+: + # + # request.variant = :tablet if request.user_agent =~ /iPad/ + # + # Respond to variants in the action just like you respond to formats: + # + # respond_to do |format| + # format.html do |variant| + # variant.tablet # renders app/views/projects/show.html+tablet.erb + # variant.phone { extra_setup; render ... } + # variant.none { special_setup } # executed only if there is no variant set + # end + # end + # + # Provide separate templates for each format and variant: + # + # app/views/projects/show.html.erb + # app/views/projects/show.html+tablet.erb + # app/views/projects/show.html+phone.erb + # + # When you're not sharing any code within the format, you can simplify defining variants + # using the inline syntax: + # + # respond_to do |format| + # format.js { render "trash" } + # format.html.phone { redirect_to progress_path } + # format.html.none { render "trash" } + # end + # + # Variants also support common +any+/+all+ block that formats have. + # + # It works for both inline: + # + # respond_to do |format| + # format.html.any { render html: "any" } + # format.html.phone { render html: "phone" } + # end + # + # and block syntax: + # + # respond_to do |format| + # format.html do |variant| + # variant.any(:tablet, :phablet){ render html: "any" } + # variant.phone { render html: "phone" } + # end + # end + # + # You can also set an array of variants: + # + # request.variant = [:tablet, :phone] + # + # This will work similarly to formats and MIME types negotiation. If there + # is no +:tablet+ variant declared, the +:phone+ variant will be used: + # + # respond_to do |format| + # format.html.none + # format.html.phone # this gets rendered + # end + def respond_to(*mimes) + raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given? + + collector = Collector.new(mimes, request.variant) + yield collector if block_given? + + if format = collector.negotiate_format(request) + _process_format(format) + _set_rendered_content_type format + response = collector.response + response.call if response + else + raise ActionController::UnknownFormat + end + end + + # A container for responses available from the current controller for + # requests for different mime-types sent to a particular action. + # + # The public controller methods +respond_to+ may be called with a block + # that is used to define responses to different mime-types, e.g. + # for +respond_to+ : + # + # respond_to do |format| + # format.html + # format.xml { render xml: @people } + # end + # + # In this usage, the argument passed to the block (+format+ above) is an + # instance of the ActionController::MimeResponds::Collector class. This + # object serves as a container in which available responses can be stored by + # calling any of the dynamically generated, mime-type-specific methods such + # as +html+, +xml+ etc on the Collector. Each response is represented by a + # corresponding block if present. + # + # A subsequent call to #negotiate_format(request) will enable the Collector + # to determine which specific mime-type it should respond with for the current + # request, with this response then being accessible by calling #response. + class Collector + include AbstractController::Collector + attr_accessor :format + + def initialize(mimes, variant = nil) + @responses = {} + @variant = variant + + mimes.each { |mime| @responses[Mime[mime]] = nil } + end + + def any(*args, &block) + if args.any? + args.each { |type| send(type, &block) } + else + custom(Mime::ALL, &block) + end + end + alias :all :any + + def custom(mime_type, &block) + mime_type = Mime::Type.lookup(mime_type.to_s) unless mime_type.is_a?(Mime::Type) + @responses[mime_type] ||= if block_given? + block + else + VariantCollector.new(@variant) + end + end + + def response + response = @responses.fetch(format, @responses[Mime::ALL]) + if response.is_a?(VariantCollector) # `format.html.phone` - variant inline syntax + response.variant + elsif response.nil? || response.arity == 0 # `format.html` - just a format, call its block + response + else # `format.html{ |variant| variant.phone }` - variant block syntax + variant_collector = VariantCollector.new(@variant) + response.call(variant_collector) # call format block with variants collector + variant_collector.variant + end + end + + def negotiate_format(request) + @format = request.negotiate_mime(@responses.keys) + end + + class VariantCollector #:nodoc: + def initialize(variant = nil) + @variant = variant + @variants = {} + end + + def any(*args, &block) + if block_given? + if args.any? && args.none? { |a| a == @variant } + args.each { |v| @variants[v] = block } + else + @variants[:any] = block + end + end + end + alias :all :any + + def method_missing(name, *args, &block) + @variants[name] = block if block_given? + end + + def variant + if @variant.empty? + @variants[:none] || @variants[:any] + else + @variants[variant_key] + end + end + + private + def variant_key + @variant.find { |variant| @variants.key?(variant) } || :any + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/parameter_encoding.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/parameter_encoding.rb new file mode 100644 index 00000000..7a45732d --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/parameter_encoding.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module ActionController + # Specify binary encoding for parameters for a given action. + module ParameterEncoding + extend ActiveSupport::Concern + + module ClassMethods + def inherited(klass) # :nodoc: + super + klass.setup_param_encode + end + + def setup_param_encode # :nodoc: + @_parameter_encodings = {} + end + + def binary_params_for?(action) # :nodoc: + @_parameter_encodings[action.to_s] + end + + # Specify that a given action's parameters should all be encoded as + # ASCII-8BIT (it "skips" the encoding default of UTF-8). + # + # For example, a controller would use it like this: + # + # class RepositoryController < ActionController::Base + # skip_parameter_encoding :show + # + # def show + # @repo = Repository.find_by_filesystem_path params[:file_path] + # + # # `repo_name` is guaranteed to be UTF-8, but was ASCII-8BIT, so + # # tag it as such + # @repo_name = params[:repo_name].force_encoding 'UTF-8' + # end + # + # def index + # @repositories = Repository.all + # end + # end + # + # The show action in the above controller would have all parameter values + # encoded as ASCII-8BIT. This is useful in the case where an application + # must handle data but encoding of the data is unknown, like file system data. + def skip_parameter_encoding(action) + @_parameter_encodings[action.to_s] = true + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/params_wrapper.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/params_wrapper.rb new file mode 100644 index 00000000..a678377d --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/params_wrapper.rb @@ -0,0 +1,293 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/slice" +require "active_support/core_ext/hash/except" +require "active_support/core_ext/module/anonymous" +require "action_dispatch/http/mime_type" + +module ActionController + # Wraps the parameters hash into a nested hash. This will allow clients to + # submit requests without having to specify any root elements. + # + # This functionality is enabled in +config/initializers/wrap_parameters.rb+ + # and can be customized. + # + # You could also turn it on per controller by setting the format array to + # a non-empty array: + # + # class UsersController < ApplicationController + # wrap_parameters format: [:json, :xml, :url_encoded_form, :multipart_form] + # end + # + # If you enable +ParamsWrapper+ for +:json+ format, instead of having to + # send JSON parameters like this: + # + # {"user": {"name": "Konata"}} + # + # You can send parameters like this: + # + # {"name": "Konata"} + # + # And it will be wrapped into a nested hash with the key name matching the + # controller's name. For example, if you're posting to +UsersController+, + # your new +params+ hash will look like this: + # + # {"name" => "Konata", "user" => {"name" => "Konata"}} + # + # You can also specify the key in which the parameters should be wrapped to, + # and also the list of attributes it should wrap by using either +:include+ or + # +:exclude+ options like this: + # + # class UsersController < ApplicationController + # wrap_parameters :person, include: [:username, :password] + # end + # + # On Active Record models with no +:include+ or +:exclude+ option set, + # it will only wrap the parameters returned by the class method + # attribute_names. + # + # If you're going to pass the parameters to an +ActiveModel+ object (such as + # User.new(params[:user])), you might consider passing the model class to + # the method instead. The +ParamsWrapper+ will actually try to determine the + # list of attribute names from the model and only wrap those attributes: + # + # class UsersController < ApplicationController + # wrap_parameters Person + # end + # + # You still could pass +:include+ and +:exclude+ to set the list of attributes + # you want to wrap. + # + # By default, if you don't specify the key in which the parameters would be + # wrapped to, +ParamsWrapper+ will actually try to determine if there's + # a model related to it or not. This controller, for example: + # + # class Admin::UsersController < ApplicationController + # end + # + # will try to check if Admin::User or +User+ model exists, and use it to + # determine the wrapper key respectively. If both models don't exist, + # it will then fallback to use +user+ as the key. + module ParamsWrapper + extend ActiveSupport::Concern + + EXCLUDE_PARAMETERS = %w(authenticity_token _method utf8) + + require "mutex_m" + + class Options < Struct.new(:name, :format, :include, :exclude, :klass, :model) # :nodoc: + include Mutex_m + + def self.from_hash(hash) + name = hash[:name] + format = Array(hash[:format]) + include = hash[:include] && Array(hash[:include]).collect(&:to_s) + exclude = hash[:exclude] && Array(hash[:exclude]).collect(&:to_s) + new name, format, include, exclude, nil, nil + end + + def initialize(name, format, include, exclude, klass, model) # :nodoc: + super + @include_set = include + @name_set = name + end + + def model + super || synchronize { super || self.model = _default_wrap_model } + end + + def include + return super if @include_set + + m = model + synchronize do + return super if @include_set + + @include_set = true + + unless super || exclude + if m.respond_to?(:attribute_names) && m.attribute_names.any? + if m.respond_to?(:stored_attributes) && !m.stored_attributes.empty? + self.include = m.attribute_names + m.stored_attributes.values.flatten.map(&:to_s) + else + self.include = m.attribute_names + end + + if m.respond_to?(:nested_attributes_options) && m.nested_attributes_options.keys.any? + self.include += m.nested_attributes_options.keys.map do |key| + key.to_s.concat("_attributes") + end + end + + self.include + end + end + end + end + + def name + return super if @name_set + + m = model + synchronize do + return super if @name_set + + @name_set = true + + unless super || klass.anonymous? + self.name = m ? m.to_s.demodulize.underscore : + klass.controller_name.singularize + end + end + end + + private + # Determine the wrapper model from the controller's name. By convention, + # this could be done by trying to find the defined model that has the + # same singular name as the controller. For example, +UsersController+ + # will try to find if the +User+ model exists. + # + # This method also does namespace lookup. Foo::Bar::UsersController will + # try to find Foo::Bar::User, Foo::User and finally User. + def _default_wrap_model + return nil if klass.anonymous? + model_name = klass.name.sub(/Controller$/, "").classify + + begin + if model_klass = model_name.safe_constantize + model_klass + else + namespaces = model_name.split("::") + namespaces.delete_at(-2) + break if namespaces.last == model_name + model_name = namespaces.join("::") + end + end until model_klass + + model_klass + end + end + + included do + class_attribute :_wrapper_options, default: Options.from_hash(format: []) + end + + module ClassMethods + def _set_wrapper_options(options) + self._wrapper_options = Options.from_hash(options) + end + + # Sets the name of the wrapper key, or the model which +ParamsWrapper+ + # would use to determine the attribute names from. + # + # ==== Examples + # wrap_parameters format: :xml + # # enables the parameter wrapper for XML format + # + # wrap_parameters :person + # # wraps parameters into +params[:person]+ hash + # + # wrap_parameters Person + # # wraps parameters by determining the wrapper key from Person class + # (+person+, in this case) and the list of attribute names + # + # wrap_parameters include: [:username, :title] + # # wraps only +:username+ and +:title+ attributes from parameters. + # + # wrap_parameters false + # # disables parameters wrapping for this controller altogether. + # + # ==== Options + # * :format - The list of formats in which the parameters wrapper + # will be enabled. + # * :include - The list of attribute names which parameters wrapper + # will wrap into a nested hash. + # * :exclude - The list of attribute names which parameters wrapper + # will exclude from a nested hash. + def wrap_parameters(name_or_model_or_options, options = {}) + model = nil + + case name_or_model_or_options + when Hash + options = name_or_model_or_options + when false + options = options.merge(format: []) + when Symbol, String + options = options.merge(name: name_or_model_or_options) + else + model = name_or_model_or_options + end + + opts = Options.from_hash _wrapper_options.to_h.slice(:format).merge(options) + opts.model = model + opts.klass = self + + self._wrapper_options = opts + end + + # Sets the default wrapper key or model which will be used to determine + # wrapper key and attribute names. Called automatically when the + # module is inherited. + def inherited(klass) + if klass._wrapper_options.format.any? + params = klass._wrapper_options.dup + params.klass = klass + klass._wrapper_options = params + end + super + end + end + + # Performs parameters wrapping upon the request. Called automatically + # by the metal call stack. + def process_action(*args) + if _wrapper_enabled? + wrapped_hash = _wrap_parameters request.request_parameters + wrapped_keys = request.request_parameters.keys + wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys) + + # This will make the wrapped hash accessible from controller and view. + request.parameters.merge! wrapped_hash + request.request_parameters.merge! wrapped_hash + + # This will display the wrapped hash in the log file. + request.filtered_parameters.merge! wrapped_filtered_hash + end + super + end + + private + + # Returns the wrapper key which will be used to store wrapped parameters. + def _wrapper_key + _wrapper_options.name + end + + # Returns the list of enabled formats. + def _wrapper_formats + _wrapper_options.format + end + + # Returns the list of parameters which will be selected for wrapped. + def _wrap_parameters(parameters) + { _wrapper_key => _extract_parameters(parameters) } + end + + def _extract_parameters(parameters) + if include_only = _wrapper_options.include + parameters.slice(*include_only) + else + exclude = _wrapper_options.exclude || [] + parameters.except(*(exclude + EXCLUDE_PARAMETERS)) + end + end + + # Checks if we should perform parameters wrapping. + def _wrapper_enabled? + return false unless request.has_content_type? + + ref = request.content_mime_type.ref + _wrapper_formats.include?(ref) && _wrapper_key && !request.parameters.key?(_wrapper_key) + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/redirecting.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/redirecting.rb new file mode 100644 index 00000000..2804a06a --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/redirecting.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +module ActionController + module Redirecting + extend ActiveSupport::Concern + + include AbstractController::Logger + include ActionController::UrlFor + + # Redirects the browser to the target specified in +options+. This parameter can be any one of: + # + # * Hash - The URL will be generated by calling url_for with the +options+. + # * Record - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record. + # * String starting with protocol:// (like http://) or a protocol relative reference (like //) - Is passed straight through as the target for redirection. + # * String not containing a protocol - The current protocol and host is prepended to the string. + # * Proc - A block that will be executed in the controller's context. Should return any option accepted by +redirect_to+. + # + # === Examples: + # + # redirect_to action: "show", id: 5 + # redirect_to @post + # redirect_to "http://www.rubyonrails.org" + # redirect_to "/images/screenshot.jpg" + # redirect_to posts_url + # redirect_to proc { edit_post_url(@post) } + # + # The redirection happens as a 302 Found header unless otherwise specified using the :status option: + # + # redirect_to post_url(@post), status: :found + # redirect_to action: 'atom', status: :moved_permanently + # redirect_to post_url(@post), status: 301 + # redirect_to action: 'atom', status: 302 + # + # The status code can either be a standard {HTTP Status code}[https://www.iana.org/assignments/http-status-codes] as an + # integer, or a symbol representing the downcased, underscored and symbolized description. + # Note that the status code must be a 3xx HTTP code, or redirection will not occur. + # + # If you are using XHR requests other than GET or POST and redirecting after the + # request then some browsers will follow the redirect using the original request + # method. This may lead to undesirable behavior such as a double DELETE. To work + # around this you can return a 303 See Other status code which will be + # followed using a GET request. + # + # redirect_to posts_url, status: :see_other + # redirect_to action: 'index', status: 303 + # + # It is also possible to assign a flash message as part of the redirection. There are two special accessors for the commonly used flash names + # +alert+ and +notice+ as well as a general purpose +flash+ bucket. + # + # redirect_to post_url(@post), alert: "Watch it, mister!" + # redirect_to post_url(@post), status: :found, notice: "Pay attention to the road" + # redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id } + # redirect_to({ action: 'atom' }, alert: "Something serious happened") + # + # Statements after +redirect_to+ in our controller get executed, so +redirect_to+ doesn't stop the execution of the function. + # To terminate the execution of the function immediately after the +redirect_to+, use return. + # redirect_to post_url(@post) and return + def redirect_to(options = {}, response_status = {}) + raise ActionControllerError.new("Cannot redirect to nil!") unless options + raise AbstractController::DoubleRenderError if response_body + + self.status = _extract_redirect_to_status(options, response_status) + self.location = _compute_redirect_to_location(request, options) + self.response_body = "You are being redirected." + end + + # Redirects the browser to the page that issued the request (the referrer) + # if possible, otherwise redirects to the provided default fallback + # location. + # + # The referrer information is pulled from the HTTP +Referer+ (sic) header on + # the request. This is an optional header and its presence on the request is + # subject to browser security settings and user preferences. If the request + # is missing this header, the fallback_location will be used. + # + # redirect_back fallback_location: { action: "show", id: 5 } + # redirect_back fallback_location: @post + # redirect_back fallback_location: "http://www.rubyonrails.org" + # redirect_back fallback_location: "/images/screenshot.jpg" + # redirect_back fallback_location: posts_url + # redirect_back fallback_location: proc { edit_post_url(@post) } + # redirect_back fallback_location: '/', allow_other_host: false + # + # ==== Options + # * :fallback_location - The default fallback location that will be used on missing +Referer+ header. + # * :allow_other_host - Allow or disallow redirection to the host that is different to the current host, defaults to true. + # + # All other options that can be passed to redirect_to are accepted as + # options and the behavior is identical. + def redirect_back(fallback_location:, allow_other_host: true, **args) + referer = request.headers["Referer"] + redirect_to_referer = referer && (allow_other_host || _url_host_allowed?(referer)) + redirect_to redirect_to_referer ? referer : fallback_location, **args + end + + def _compute_redirect_to_location(request, options) #:nodoc: + case options + # The scheme name consist of a letter followed by any combination of + # letters, digits, and the plus ("+"), period ("."), or hyphen ("-") + # characters; and is terminated by a colon (":"). + # See https://tools.ietf.org/html/rfc3986#section-3.1 + # The protocol relative scheme starts with a double slash "//". + when /\A([a-z][a-z\d\-+\.]*:|\/\/).*/i + options + when String + request.protocol + request.host_with_port + options + when Proc + _compute_redirect_to_location request, instance_eval(&options) + else + url_for(options) + end.delete("\0\r\n") + end + module_function :_compute_redirect_to_location + public :_compute_redirect_to_location + + private + def _extract_redirect_to_status(options, response_status) + if options.is_a?(Hash) && options.key?(:status) + Rack::Utils.status_code(options.delete(:status)) + elsif response_status.key?(:status) + Rack::Utils.status_code(response_status[:status]) + else + 302 + end + end + + def _url_host_allowed?(url) + URI(url.to_s).host == request.host + rescue ArgumentError, URI::Error + false + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/renderers.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/renderers.rb new file mode 100644 index 00000000..b81d3ef5 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/renderers.rb @@ -0,0 +1,181 @@ +# frozen_string_literal: true + +require "set" + +module ActionController + # See Renderers.add + def self.add_renderer(key, &block) + Renderers.add(key, &block) + end + + # See Renderers.remove + def self.remove_renderer(key) + Renderers.remove(key) + end + + # See Responder#api_behavior + class MissingRenderer < LoadError + def initialize(format) + super "No renderer defined for format: #{format}" + end + end + + module Renderers + extend ActiveSupport::Concern + + # A Set containing renderer names that correspond to available renderer procs. + # Default values are :json, :js, :xml. + RENDERERS = Set.new + + included do + class_attribute :_renderers, default: Set.new.freeze + end + + # Used in ActionController::Base + # and ActionController::API to include all + # renderers by default. + module All + extend ActiveSupport::Concern + include Renderers + + included do + self._renderers = RENDERERS + end + end + + # Adds a new renderer to call within controller actions. + # A renderer is invoked by passing its name as an option to + # AbstractController::Rendering#render. To create a renderer + # pass it a name and a block. The block takes two arguments, the first + # is the value paired with its key and the second is the remaining + # hash of options passed to +render+. + # + # Create a csv renderer: + # + # ActionController::Renderers.add :csv do |obj, options| + # filename = options[:filename] || 'data' + # str = obj.respond_to?(:to_csv) ? obj.to_csv : obj.to_s + # send_data str, type: Mime[:csv], + # disposition: "attachment; filename=#{filename}.csv" + # end + # + # Note that we used Mime[:csv] for the csv mime type as it comes with Rails. + # For a custom renderer, you'll need to register a mime type with + # Mime::Type.register. + # + # To use the csv renderer in a controller action: + # + # def show + # @csvable = Csvable.find(params[:id]) + # respond_to do |format| + # format.html + # format.csv { render csv: @csvable, filename: @csvable.name } + # end + # end + def self.add(key, &block) + define_method(_render_with_renderer_method_name(key), &block) + RENDERERS << key.to_sym + end + + # This method is the opposite of add method. + # + # To remove a csv renderer: + # + # ActionController::Renderers.remove(:csv) + def self.remove(key) + RENDERERS.delete(key.to_sym) + method_name = _render_with_renderer_method_name(key) + remove_possible_method(method_name) + end + + def self._render_with_renderer_method_name(key) + "_render_with_renderer_#{key}" + end + + module ClassMethods + # Adds, by name, a renderer or renderers to the +_renderers+ available + # to call within controller actions. + # + # It is useful when rendering from an ActionController::Metal controller or + # otherwise to add an available renderer proc to a specific controller. + # + # Both ActionController::Base and ActionController::API + # include ActionController::Renderers::All, making all renderers + # available in the controller. See Renderers::RENDERERS and Renderers.add. + # + # Since ActionController::Metal controllers cannot render, the controller + # must include AbstractController::Rendering, ActionController::Rendering, + # and ActionController::Renderers, and have at least one renderer. + # + # Rather than including ActionController::Renderers::All and including all renderers, + # you may specify which renderers to include by passing the renderer name or names to + # +use_renderers+. For example, a controller that includes only the :json renderer + # (+_render_with_renderer_json+) might look like: + # + # class MetalRenderingController < ActionController::Metal + # include AbstractController::Rendering + # include ActionController::Rendering + # include ActionController::Renderers + # + # use_renderers :json + # + # def show + # render json: record + # end + # end + # + # You must specify a +use_renderer+, else the +controller.renderer+ and + # +controller._renderers+ will be nil, and the action will fail. + def use_renderers(*args) + renderers = _renderers + args + self._renderers = renderers.freeze + end + alias use_renderer use_renderers + end + + # Called by +render+ in AbstractController::Rendering + # which sets the return value as the +response_body+. + # + # If no renderer is found, +super+ returns control to + # ActionView::Rendering.render_to_body, if present. + def render_to_body(options) + _render_to_body_with_renderer(options) || super + end + + def _render_to_body_with_renderer(options) + _renderers.each do |name| + if options.key?(name) + _process_options(options) + method_name = Renderers._render_with_renderer_method_name(name) + return send(method_name, options.delete(name), options) + end + end + nil + end + + add :json do |json, options| + json = json.to_json(options) unless json.kind_of?(String) + + if options[:callback].present? + if content_type.nil? || content_type == Mime[:json] + self.content_type = Mime[:js] + end + + "/**/#{options[:callback]}(#{json})" + else + self.content_type ||= Mime[:json] + json + end + end + + add :js do |js, options| + self.content_type ||= Mime[:js] + js.respond_to?(:to_js) ? js.to_js(options) : js + end + + add :xml do |xml, options| + self.content_type ||= Mime[:xml] + xml.respond_to?(:to_xml) ? xml.to_xml(options) : xml + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/rendering.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/rendering.rb new file mode 100644 index 00000000..6d181e64 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/rendering.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +module ActionController + module Rendering + extend ActiveSupport::Concern + + RENDER_FORMATS_IN_PRIORITY = [:body, :plain, :html] + + module ClassMethods + # Documentation at ActionController::Renderer#render + delegate :render, to: :renderer + + # Returns a renderer instance (inherited from ActionController::Renderer) + # for the controller. + attr_reader :renderer + + def setup_renderer! # :nodoc: + @renderer = Renderer.for(self) + end + + def inherited(klass) + klass.setup_renderer! + super + end + end + + # Before processing, set the request formats in current controller formats. + def process_action(*) #:nodoc: + self.formats = request.formats.map(&:ref).compact + super + end + + # Check for double render errors and set the content_type after rendering. + def render(*args) #:nodoc: + raise ::AbstractController::DoubleRenderError if response_body + super + end + + # Overwrite render_to_string because body can now be set to a Rack body. + def render_to_string(*) + result = super + if result.respond_to?(:each) + string = "".dup + result.each { |r| string << r } + string + else + result + end + end + + def render_to_body(options = {}) + super || _render_in_priorities(options) || " " + end + + private + + def _process_variant(options) + if defined?(request) && !request.nil? && request.variant.present? + options[:variant] = request.variant + end + end + + def _render_in_priorities(options) + RENDER_FORMATS_IN_PRIORITY.each do |format| + return options[format] if options.key?(format) + end + + nil + end + + def _set_html_content_type + self.content_type = Mime[:html].to_s + end + + def _set_rendered_content_type(format) + if format && !response.content_type + self.content_type = format.to_s + end + end + + # Normalize arguments by catching blocks and setting them on :update. + def _normalize_args(action = nil, options = {}, &blk) + options = super + options[:update] = blk if block_given? + options + end + + # Normalize both text and status options. + def _normalize_options(options) + _normalize_text(options) + + if options[:html] + options[:html] = ERB::Util.html_escape(options[:html]) + end + + if options[:status] + options[:status] = Rack::Utils.status_code(options[:status]) + end + + super + end + + def _normalize_text(options) + RENDER_FORMATS_IN_PRIORITY.each do |format| + if options.key?(format) && options[format].respond_to?(:to_text) + options[format] = options[format].to_text + end + end + end + + # Process controller specific options, as status, content-type and location. + def _process_options(options) + status, content_type, location = options.values_at(:status, :content_type, :location) + + self.status = status if status + self.content_type = content_type if content_type + headers["Location"] = url_for(location) if location + + super + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/request_forgery_protection.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/request_forgery_protection.rb new file mode 100644 index 00000000..4e097f30 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/request_forgery_protection.rb @@ -0,0 +1,445 @@ +# frozen_string_literal: true + +require "rack/session/abstract/id" +require "action_controller/metal/exceptions" +require "active_support/security_utils" +require "active_support/core_ext/string/strip" + +module ActionController #:nodoc: + class InvalidAuthenticityToken < ActionControllerError #:nodoc: + end + + class InvalidCrossOriginRequest < ActionControllerError #:nodoc: + end + + # Controller actions are protected from Cross-Site Request Forgery (CSRF) attacks + # by including a token in the rendered HTML for your application. This token is + # stored as a random string in the session, to which an attacker does not have + # access. When a request reaches your application, \Rails verifies the received + # token with the token in the session. All requests are checked except GET requests + # as these should be idempotent. Keep in mind that all session-oriented requests + # should be CSRF protected, including JavaScript and HTML requests. + # + # Since HTML and JavaScript requests are typically made from the browser, we + # need to ensure to verify request authenticity for the web browser. We can + # use session-oriented authentication for these types of requests, by using + # the protect_from_forgery method in our controllers. + # + # GET requests are not protected since they don't have side effects like writing + # to the database and don't leak sensitive information. JavaScript requests are + # an exception: a third-party site can use a + # + # The first two characters (">) are required in case the exception happens + # while rendering attributes for a given tag. You can check the real cause + # for the exception in your logger. + # + # == Web server support + # + # Not all web servers support streaming out-of-the-box. You need to check + # the instructions for each of them. + # + # ==== Unicorn + # + # Unicorn supports streaming but it needs to be configured. For this, you + # need to create a config file as follow: + # + # # unicorn.config.rb + # listen 3000, tcp_nopush: false + # + # And use it on initialization: + # + # unicorn_rails --config-file unicorn.config.rb + # + # You may also want to configure other parameters like :tcp_nodelay. + # Please check its documentation for more information: https://bogomips.org/unicorn/Unicorn/Configurator.html#method-i-listen + # + # If you are using Unicorn with NGINX, you may need to tweak NGINX. + # Streaming should work out of the box on Rainbows. + # + # ==== Passenger + # + # To be described. + # + module Streaming + extend ActiveSupport::Concern + + private + + # Set proper cache control and transfer encoding when streaming + def _process_options(options) + super + if options[:stream] + if request.version == "HTTP/1.0" + options.delete(:stream) + else + headers["Cache-Control"] ||= "no-cache" + headers["Transfer-Encoding"] = "chunked" + headers.delete("Content-Length") + end + end + end + + # Call render_body if we are streaming instead of usual +render+. + def _render_template(options) + if options.delete(:stream) + Rack::Chunked::Body.new view_renderer.render_body(view_context, options) + else + super + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/strong_parameters.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/strong_parameters.rb new file mode 100644 index 00000000..510cb353 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/strong_parameters.rb @@ -0,0 +1,1086 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/indifferent_access" +require "active_support/core_ext/hash/transform_values" +require "active_support/core_ext/array/wrap" +require "active_support/core_ext/string/filters" +require "active_support/core_ext/object/to_query" +require "active_support/rescuable" +require "action_dispatch/http/upload" +require "rack/test" +require "stringio" +require "set" +require "yaml" + +module ActionController + # Raised when a required parameter is missing. + # + # params = ActionController::Parameters.new(a: {}) + # params.fetch(:b) + # # => ActionController::ParameterMissing: param is missing or the value is empty: b + # params.require(:a) + # # => ActionController::ParameterMissing: param is missing or the value is empty: a + class ParameterMissing < KeyError + attr_reader :param # :nodoc: + + def initialize(param) # :nodoc: + @param = param + super("param is missing or the value is empty: #{param}") + end + end + + # Raised when a supplied parameter is not expected and + # ActionController::Parameters.action_on_unpermitted_parameters + # is set to :raise. + # + # params = ActionController::Parameters.new(a: "123", b: "456") + # params.permit(:c) + # # => ActionController::UnpermittedParameters: found unpermitted parameters: :a, :b + class UnpermittedParameters < IndexError + attr_reader :params # :nodoc: + + def initialize(params) # :nodoc: + @params = params + super("found unpermitted parameter#{'s' if params.size > 1 }: #{params.map { |e| ":#{e}" }.join(", ")}") + end + end + + # Raised when a Parameters instance is not marked as permitted and + # an operation to transform it to hash is called. + # + # params = ActionController::Parameters.new(a: "123", b: "456") + # params.to_h + # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash + class UnfilteredParameters < ArgumentError + def initialize # :nodoc: + super("unable to convert unpermitted parameters to hash") + end + end + + # == Action Controller \Parameters + # + # Allows you to choose which attributes should be whitelisted for mass updating + # and thus prevent accidentally exposing that which shouldn't be exposed. + # Provides two methods for this purpose: #require and #permit. The former is + # used to mark parameters as required. The latter is used to set the parameter + # as permitted and limit which attributes should be allowed for mass updating. + # + # params = ActionController::Parameters.new({ + # person: { + # name: "Francesco", + # age: 22, + # role: "admin" + # } + # }) + # + # permitted = params.require(:person).permit(:name, :age) + # permitted # => "Francesco", "age"=>22} permitted: true> + # permitted.permitted? # => true + # + # Person.first.update!(permitted) + # # => # + # + # It provides two options that controls the top-level behavior of new instances: + # + # * +permit_all_parameters+ - If it's +true+, all the parameters will be + # permitted by default. The default is +false+. + # * +action_on_unpermitted_parameters+ - Allow to control the behavior when parameters + # that are not explicitly permitted are found. The values can be +false+ to just filter them + # out, :log to additionally write a message on the logger, or :raise to raise + # ActionController::UnpermittedParameters exception. The default value is :log + # in test and development environments, +false+ otherwise. + # + # Examples: + # + # params = ActionController::Parameters.new + # params.permitted? # => false + # + # ActionController::Parameters.permit_all_parameters = true + # + # params = ActionController::Parameters.new + # params.permitted? # => true + # + # params = ActionController::Parameters.new(a: "123", b: "456") + # params.permit(:c) + # # => + # + # ActionController::Parameters.action_on_unpermitted_parameters = :raise + # + # params = ActionController::Parameters.new(a: "123", b: "456") + # params.permit(:c) + # # => ActionController::UnpermittedParameters: found unpermitted keys: a, b + # + # Please note that these options *are not thread-safe*. In a multi-threaded + # environment they should only be set once at boot-time and never mutated at + # runtime. + # + # You can fetch values of ActionController::Parameters using either + # :key or "key". + # + # params = ActionController::Parameters.new(key: "value") + # params[:key] # => "value" + # params["key"] # => "value" + class Parameters + cattr_accessor :permit_all_parameters, instance_accessor: false, default: false + + cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false + + ## + # :method: as_json + # + # :call-seq: + # as_json(options=nil) + # + # Returns a hash that can be used as the JSON representation for the parameters. + + ## + # :method: empty? + # + # :call-seq: + # empty?() + # + # Returns true if the parameters have no key/value pairs. + + ## + # :method: has_key? + # + # :call-seq: + # has_key?(key) + # + # Returns true if the given key is present in the parameters. + + ## + # :method: has_value? + # + # :call-seq: + # has_value?(value) + # + # Returns true if the given value is present for some key in the parameters. + + ## + # :method: include? + # + # :call-seq: + # include?(key) + # + # Returns true if the given key is present in the parameters. + + ## + # :method: key? + # + # :call-seq: + # key?(key) + # + # Returns true if the given key is present in the parameters. + + ## + # :method: keys + # + # :call-seq: + # keys() + # + # Returns a new array of the keys of the parameters. + + ## + # :method: to_s + # + # :call-seq: + # to_s() + # + # Returns the content of the parameters as a string. + + ## + # :method: value? + # + # :call-seq: + # value?(value) + # + # Returns true if the given value is present for some key in the parameters. + + ## + # :method: values + # + # :call-seq: + # values() + # + # Returns a new array of the values of the parameters. + delegate :keys, :key?, :has_key?, :values, :has_value?, :value?, :empty?, :include?, + :as_json, :to_s, to: :@parameters + + # By default, never raise an UnpermittedParameters exception if these + # params are present. The default includes both 'controller' and 'action' + # because they are added by Rails and should be of no concern. One way + # to change these is to specify `always_permitted_parameters` in your + # config. For instance: + # + # config.always_permitted_parameters = %w( controller action format ) + cattr_accessor :always_permitted_parameters, default: %w( controller action ) + + # Returns a new instance of ActionController::Parameters. + # Also, sets the +permitted+ attribute to the default value of + # ActionController::Parameters.permit_all_parameters. + # + # class Person < ActiveRecord::Base + # end + # + # params = ActionController::Parameters.new(name: "Francesco") + # params.permitted? # => false + # Person.new(params) # => ActiveModel::ForbiddenAttributesError + # + # ActionController::Parameters.permit_all_parameters = true + # + # params = ActionController::Parameters.new(name: "Francesco") + # params.permitted? # => true + # Person.new(params) # => # + def initialize(parameters = {}) + @parameters = parameters.with_indifferent_access + @permitted = self.class.permit_all_parameters + end + + # Returns true if another +Parameters+ object contains the same content and + # permitted flag. + def ==(other) + if other.respond_to?(:permitted?) + permitted? == other.permitted? && parameters == other.parameters + else + @parameters == other + end + end + + # Returns a safe ActiveSupport::HashWithIndifferentAccess + # representation of the parameters with all unpermitted keys removed. + # + # params = ActionController::Parameters.new({ + # name: "Senjougahara Hitagi", + # oddity: "Heavy stone crab" + # }) + # params.to_h + # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash + # + # safe_params = params.permit(:name) + # safe_params.to_h # => {"name"=>"Senjougahara Hitagi"} + def to_h + if permitted? + convert_parameters_to_hashes(@parameters, :to_h) + else + raise UnfilteredParameters + end + end + + # Returns a safe Hash representation of the parameters + # with all unpermitted keys removed. + # + # params = ActionController::Parameters.new({ + # name: "Senjougahara Hitagi", + # oddity: "Heavy stone crab" + # }) + # params.to_hash + # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash + # + # safe_params = params.permit(:name) + # safe_params.to_hash # => {"name"=>"Senjougahara Hitagi"} + def to_hash + to_h.to_hash + end + + # Returns a string representation of the receiver suitable for use as a URL + # query string: + # + # params = ActionController::Parameters.new({ + # name: "David", + # nationality: "Danish" + # }) + # params.to_query + # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash + # + # safe_params = params.permit(:name, :nationality) + # safe_params.to_query + # # => "name=David&nationality=Danish" + # + # An optional namespace can be passed to enclose key names: + # + # params = ActionController::Parameters.new({ + # name: "David", + # nationality: "Danish" + # }) + # safe_params = params.permit(:name, :nationality) + # safe_params.to_query("user") + # # => "user%5Bname%5D=David&user%5Bnationality%5D=Danish" + # + # The string pairs "key=value" that conform the query string + # are sorted lexicographically in ascending order. + # + # This method is also aliased as +to_param+. + def to_query(*args) + to_h.to_query(*args) + end + alias_method :to_param, :to_query + + # Returns an unsafe, unfiltered + # ActiveSupport::HashWithIndifferentAccess representation of the + # parameters. + # + # params = ActionController::Parameters.new({ + # name: "Senjougahara Hitagi", + # oddity: "Heavy stone crab" + # }) + # params.to_unsafe_h + # # => {"name"=>"Senjougahara Hitagi", "oddity" => "Heavy stone crab"} + def to_unsafe_h + convert_parameters_to_hashes(@parameters, :to_unsafe_h) + end + alias_method :to_unsafe_hash, :to_unsafe_h + + # Convert all hashes in values into parameters, then yield each pair in + # the same way as Hash#each_pair. + def each_pair(&block) + @parameters.each_pair do |key, value| + yield [key, convert_hashes_to_parameters(key, value)] + end + end + alias_method :each, :each_pair + + # Attribute that keeps track of converted arrays, if any, to avoid double + # looping in the common use case permit + mass-assignment. Defined in a + # method to instantiate it only if needed. + # + # Testing membership still loops, but it's going to be faster than our own + # loop that converts values. Also, we are not going to build a new array + # object per fetch. + def converted_arrays + @converted_arrays ||= Set.new + end + + # Returns +true+ if the parameter is permitted, +false+ otherwise. + # + # params = ActionController::Parameters.new + # params.permitted? # => false + # params.permit! + # params.permitted? # => true + def permitted? + @permitted + end + + # Sets the +permitted+ attribute to +true+. This can be used to pass + # mass assignment. Returns +self+. + # + # class Person < ActiveRecord::Base + # end + # + # params = ActionController::Parameters.new(name: "Francesco") + # params.permitted? # => false + # Person.new(params) # => ActiveModel::ForbiddenAttributesError + # params.permit! + # params.permitted? # => true + # Person.new(params) # => # + def permit! + each_pair do |key, value| + Array.wrap(value).flatten.each do |v| + v.permit! if v.respond_to? :permit! + end + end + + @permitted = true + self + end + + # This method accepts both a single key and an array of keys. + # + # When passed a single key, if it exists and its associated value is + # either present or the singleton +false+, returns said value: + # + # ActionController::Parameters.new(person: { name: "Francesco" }).require(:person) + # # => "Francesco"} permitted: false> + # + # Otherwise raises ActionController::ParameterMissing: + # + # ActionController::Parameters.new.require(:person) + # # ActionController::ParameterMissing: param is missing or the value is empty: person + # + # ActionController::Parameters.new(person: nil).require(:person) + # # ActionController::ParameterMissing: param is missing or the value is empty: person + # + # ActionController::Parameters.new(person: "\t").require(:person) + # # ActionController::ParameterMissing: param is missing or the value is empty: person + # + # ActionController::Parameters.new(person: {}).require(:person) + # # ActionController::ParameterMissing: param is missing or the value is empty: person + # + # When given an array of keys, the method tries to require each one of them + # in order. If it succeeds, an array with the respective return values is + # returned: + # + # params = ActionController::Parameters.new(user: { ... }, profile: { ... }) + # user_params, profile_params = params.require([:user, :profile]) + # + # Otherwise, the method re-raises the first exception found: + # + # params = ActionController::Parameters.new(user: {}, profile: {}) + # user_params, profile_params = params.require([:user, :profile]) + # # ActionController::ParameterMissing: param is missing or the value is empty: user + # + # Technically this method can be used to fetch terminal values: + # + # # CAREFUL + # params = ActionController::Parameters.new(person: { name: "Finn" }) + # name = params.require(:person).require(:name) # CAREFUL + # + # but take into account that at some point those ones have to be permitted: + # + # def person_params + # params.require(:person).permit(:name).tap do |person_params| + # person_params.require(:name) # SAFER + # end + # end + # + # for example. + def require(key) + return key.map { |k| require(k) } if key.is_a?(Array) + value = self[key] + if value.present? || value == false + value + else + raise ParameterMissing.new(key) + end + end + + # Alias of #require. + alias :required :require + + # Returns a new ActionController::Parameters instance that + # includes only the given +filters+ and sets the +permitted+ attribute + # for the object to +true+. This is useful for limiting which attributes + # should be allowed for mass updating. + # + # params = ActionController::Parameters.new(user: { name: "Francesco", age: 22, role: "admin" }) + # permitted = params.require(:user).permit(:name, :age) + # permitted.permitted? # => true + # permitted.has_key?(:name) # => true + # permitted.has_key?(:age) # => true + # permitted.has_key?(:role) # => false + # + # Only permitted scalars pass the filter. For example, given + # + # params.permit(:name) + # + # +:name+ passes if it is a key of +params+ whose associated value is of type + # +String+, +Symbol+, +NilClass+, +Numeric+, +TrueClass+, +FalseClass+, + # +Date+, +Time+, +DateTime+, +StringIO+, +IO+, + # +ActionDispatch::Http::UploadedFile+ or +Rack::Test::UploadedFile+. + # Otherwise, the key +:name+ is filtered out. + # + # You may declare that the parameter should be an array of permitted scalars + # by mapping it to an empty array: + # + # params = ActionController::Parameters.new(tags: ["rails", "parameters"]) + # params.permit(tags: []) + # + # Sometimes it is not possible or convenient to declare the valid keys of + # a hash parameter or its internal structure. Just map to an empty hash: + # + # params.permit(preferences: {}) + # + # Be careful because this opens the door to arbitrary input. In this + # case, +permit+ ensures values in the returned structure are permitted + # scalars and filters out anything else. + # + # You can also use +permit+ on nested parameters, like: + # + # params = ActionController::Parameters.new({ + # person: { + # name: "Francesco", + # age: 22, + # pets: [{ + # name: "Purplish", + # category: "dogs" + # }] + # } + # }) + # + # permitted = params.permit(person: [ :name, { pets: :name } ]) + # permitted.permitted? # => true + # permitted[:person][:name] # => "Francesco" + # permitted[:person][:age] # => nil + # permitted[:person][:pets][0][:name] # => "Purplish" + # permitted[:person][:pets][0][:category] # => nil + # + # Note that if you use +permit+ in a key that points to a hash, + # it won't allow all the hash. You also need to specify which + # attributes inside the hash should be whitelisted. + # + # params = ActionController::Parameters.new({ + # person: { + # contact: { + # email: "none@test.com", + # phone: "555-1234" + # } + # } + # }) + # + # params.require(:person).permit(:contact) + # # => + # + # params.require(:person).permit(contact: :phone) + # # => "555-1234"} permitted: true>} permitted: true> + # + # params.require(:person).permit(contact: [ :email, :phone ]) + # # => "none@test.com", "phone"=>"555-1234"} permitted: true>} permitted: true> + def permit(*filters) + params = self.class.new + + filters.flatten.each do |filter| + case filter + when Symbol, String + permitted_scalar_filter(params, filter) + when Hash + hash_filter(params, filter) + end + end + + unpermitted_parameters!(params) if self.class.action_on_unpermitted_parameters + + params.permit! + end + + # Returns a parameter for the given +key+. If not found, + # returns +nil+. + # + # params = ActionController::Parameters.new(person: { name: "Francesco" }) + # params[:person] # => "Francesco"} permitted: false> + # params[:none] # => nil + def [](key) + convert_hashes_to_parameters(key, @parameters[key]) + end + + # Assigns a value to a given +key+. The given key may still get filtered out + # when +permit+ is called. + def []=(key, value) + @parameters[key] = value + end + + # Returns a parameter for the given +key+. If the +key+ + # can't be found, there are several options: With no other arguments, + # it will raise an ActionController::ParameterMissing error; + # if a second argument is given, then that is returned (converted to an + # instance of ActionController::Parameters if possible); if a block + # is given, then that will be run and its result returned. + # + # params = ActionController::Parameters.new(person: { name: "Francesco" }) + # params.fetch(:person) # => "Francesco"} permitted: false> + # params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none + # params.fetch(:none, {}) # => + # params.fetch(:none, "Francesco") # => "Francesco" + # params.fetch(:none) { "Francesco" } # => "Francesco" + def fetch(key, *args) + convert_value_to_parameters( + @parameters.fetch(key) { + if block_given? + yield + else + args.fetch(0) { raise ActionController::ParameterMissing.new(key) } + end + } + ) + end + + if Hash.method_defined?(:dig) + # Extracts the nested parameter from the given +keys+ by calling +dig+ + # at each step. Returns +nil+ if any intermediate step is +nil+. + # + # params = ActionController::Parameters.new(foo: { bar: { baz: 1 } }) + # params.dig(:foo, :bar, :baz) # => 1 + # params.dig(:foo, :zot, :xyz) # => nil + # + # params2 = ActionController::Parameters.new(foo: [10, 11, 12]) + # params2.dig(:foo, 1) # => 11 + def dig(*keys) + convert_hashes_to_parameters(keys.first, @parameters[keys.first]) + @parameters.dig(*keys) + end + end + + # Returns a new ActionController::Parameters instance that + # includes only the given +keys+. If the given +keys+ + # don't exist, returns an empty hash. + # + # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) + # params.slice(:a, :b) # => 1, "b"=>2} permitted: false> + # params.slice(:d) # => + def slice(*keys) + new_instance_with_inherited_permitted_status(@parameters.slice(*keys)) + end + + # Returns current ActionController::Parameters instance which + # contains only the given +keys+. + def slice!(*keys) + @parameters.slice!(*keys) + self + end + + # Returns a new ActionController::Parameters instance that + # filters out the given +keys+. + # + # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) + # params.except(:a, :b) # => 3} permitted: false> + # params.except(:d) # => 1, "b"=>2, "c"=>3} permitted: false> + def except(*keys) + new_instance_with_inherited_permitted_status(@parameters.except(*keys)) + end + + # Removes and returns the key/value pairs matching the given keys. + # + # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) + # params.extract!(:a, :b) # => 1, "b"=>2} permitted: false> + # params # => 3} permitted: false> + def extract!(*keys) + new_instance_with_inherited_permitted_status(@parameters.extract!(*keys)) + end + + # Returns a new ActionController::Parameters with the results of + # running +block+ once for every value. The keys are unchanged. + # + # params = ActionController::Parameters.new(a: 1, b: 2, c: 3) + # params.transform_values { |x| x * 2 } + # # => 2, "b"=>4, "c"=>6} permitted: false> + def transform_values + return to_enum(:transform_values) unless block_given? + new_instance_with_inherited_permitted_status( + @parameters.transform_values { |v| yield convert_value_to_parameters(v) } + ) + end + + # Performs values transformation and returns the altered + # ActionController::Parameters instance. + def transform_values! + return to_enum(:transform_values!) unless block_given? + @parameters.transform_values! { |v| yield convert_value_to_parameters(v) } + self + end + + # Returns a new ActionController::Parameters instance with the + # results of running +block+ once for every key. The values are unchanged. + def transform_keys(&block) + if block + new_instance_with_inherited_permitted_status( + @parameters.transform_keys(&block) + ) + else + @parameters.transform_keys + end + end + + # Performs keys transformation and returns the altered + # ActionController::Parameters instance. + def transform_keys!(&block) + @parameters.transform_keys!(&block) + self + end + + # Deletes a key-value pair from +Parameters+ and returns the value. If + # +key+ is not found, returns +nil+ (or, with optional code block, yields + # +key+ and returns the result). Cf. +#extract!+, which returns the + # corresponding +ActionController::Parameters+ object. + def delete(key, &block) + convert_value_to_parameters(@parameters.delete(key, &block)) + end + + # Returns a new instance of ActionController::Parameters with only + # items that the block evaluates to true. + def select(&block) + new_instance_with_inherited_permitted_status(@parameters.select(&block)) + end + + # Equivalent to Hash#keep_if, but returns +nil+ if no changes were made. + def select!(&block) + @parameters.select!(&block) + self + end + alias_method :keep_if, :select! + + # Returns a new instance of ActionController::Parameters with items + # that the block evaluates to true removed. + def reject(&block) + new_instance_with_inherited_permitted_status(@parameters.reject(&block)) + end + + # Removes items that the block evaluates to true and returns self. + def reject!(&block) + @parameters.reject!(&block) + self + end + alias_method :delete_if, :reject! + + # Returns values that were assigned to the given +keys+. Note that all the + # +Hash+ objects will be converted to ActionController::Parameters. + def values_at(*keys) + convert_value_to_parameters(@parameters.values_at(*keys)) + end + + # Returns a new ActionController::Parameters with all keys from + # +other_hash+ merged into current hash. + def merge(other_hash) + new_instance_with_inherited_permitted_status( + @parameters.merge(other_hash.to_h) + ) + end + + # Returns current ActionController::Parameters instance with + # +other_hash+ merged into current hash. + def merge!(other_hash) + @parameters.merge!(other_hash.to_h) + self + end + + # Returns a new ActionController::Parameters with all keys from + # current hash merged into +other_hash+. + def reverse_merge(other_hash) + new_instance_with_inherited_permitted_status( + other_hash.to_h.merge(@parameters) + ) + end + alias_method :with_defaults, :reverse_merge + + # Returns current ActionController::Parameters instance with + # current hash merged into +other_hash+. + def reverse_merge!(other_hash) + @parameters.merge!(other_hash.to_h) { |key, left, right| left } + self + end + alias_method :with_defaults!, :reverse_merge! + + # This is required by ActiveModel attribute assignment, so that user can + # pass +Parameters+ to a mass assignment methods in a model. It should not + # matter as we are using +HashWithIndifferentAccess+ internally. + def stringify_keys # :nodoc: + dup + end + + def inspect + "<#{self.class} #{@parameters} permitted: #{@permitted}>" + end + + def self.hook_into_yaml_loading # :nodoc: + # Wire up YAML format compatibility with Rails 4.2 and Psych 2.0.8 and 2.0.9+. + # Makes the YAML parser call `init_with` when it encounters the keys below + # instead of trying its own parsing routines. + YAML.load_tags["!ruby/hash-with-ivars:ActionController::Parameters"] = name + YAML.load_tags["!ruby/hash:ActionController::Parameters"] = name + end + hook_into_yaml_loading + + def init_with(coder) # :nodoc: + case coder.tag + when "!ruby/hash:ActionController::Parameters" + # YAML 2.0.8's format where hash instance variables weren't stored. + @parameters = coder.map.with_indifferent_access + @permitted = false + when "!ruby/hash-with-ivars:ActionController::Parameters" + # YAML 2.0.9's Hash subclass format where keys and values + # were stored under an elements hash and `permitted` within an ivars hash. + @parameters = coder.map["elements"].with_indifferent_access + @permitted = coder.map["ivars"][:@permitted] + when "!ruby/object:ActionController::Parameters" + # YAML's Object format. Only needed because of the format + # backwardscompability above, otherwise equivalent to YAML's initialization. + @parameters, @permitted = coder.map["parameters"], coder.map["permitted"] + end + end + + # Returns duplicate of object including all parameters. + def deep_dup + self.class.new(@parameters.deep_dup).tap do |duplicate| + duplicate.permitted = @permitted + end + end + + protected + attr_reader :parameters + + def permitted=(new_permitted) + @permitted = new_permitted + end + + def fields_for_style? + @parameters.all? { |k, v| k =~ /\A-?\d+\z/ && (v.is_a?(Hash) || v.is_a?(Parameters)) } + end + + private + def new_instance_with_inherited_permitted_status(hash) + self.class.new(hash).tap do |new_instance| + new_instance.permitted = @permitted + end + end + + def convert_parameters_to_hashes(value, using) + case value + when Array + value.map { |v| convert_parameters_to_hashes(v, using) } + when Hash + value.transform_values do |v| + convert_parameters_to_hashes(v, using) + end.with_indifferent_access + when Parameters + value.send(using) + else + value + end + end + + def convert_hashes_to_parameters(key, value) + converted = convert_value_to_parameters(value) + @parameters[key] = converted unless converted.equal?(value) + converted + end + + def convert_value_to_parameters(value) + case value + when Array + return value if converted_arrays.member?(value) + converted = value.map { |_| convert_value_to_parameters(_) } + converted_arrays << converted + converted + when Hash + self.class.new(value) + else + value + end + end + + def each_element(object) + case object + when Array + object.grep(Parameters).map { |el| yield el }.compact + when Parameters + if object.fields_for_style? + hash = object.class.new + object.each { |k, v| hash[k] = yield v } + hash + else + yield object + end + end + end + + def unpermitted_parameters!(params) + unpermitted_keys = unpermitted_keys(params) + if unpermitted_keys.any? + case self.class.action_on_unpermitted_parameters + when :log + name = "unpermitted_parameters.action_controller" + ActiveSupport::Notifications.instrument(name, keys: unpermitted_keys) + when :raise + raise ActionController::UnpermittedParameters.new(unpermitted_keys) + end + end + end + + def unpermitted_keys(params) + keys - params.keys - always_permitted_parameters + end + + # + # --- Filtering ---------------------------------------------------------- + # + + # This is a white list of permitted scalar types that includes the ones + # supported in XML and JSON requests. + # + # This list is in particular used to filter ordinary requests, String goes + # as first element to quickly short-circuit the common case. + # + # If you modify this collection please update the API of +permit+ above. + PERMITTED_SCALAR_TYPES = [ + String, + Symbol, + NilClass, + Numeric, + TrueClass, + FalseClass, + Date, + Time, + # DateTimes are Dates, we document the type but avoid the redundant check. + StringIO, + IO, + ActionDispatch::Http::UploadedFile, + Rack::Test::UploadedFile, + ] + + def permitted_scalar?(value) + PERMITTED_SCALAR_TYPES.any? { |type| value.is_a?(type) } + end + + def permitted_scalar_filter(params, key) + if has_key?(key) && permitted_scalar?(self[key]) + params[key] = self[key] + end + + keys.grep(/\A#{Regexp.escape(key)}\(\d+[if]?\)\z/) do |k| + if permitted_scalar?(self[k]) + params[k] = self[k] + end + end + end + + def array_of_permitted_scalars?(value) + if value.is_a?(Array) && value.all? { |element| permitted_scalar?(element) } + yield value + end + end + + def non_scalar?(value) + value.is_a?(Array) || value.is_a?(Parameters) + end + + EMPTY_ARRAY = [] + EMPTY_HASH = {} + def hash_filter(params, filter) + filter = filter.with_indifferent_access + + # Slicing filters out non-declared keys. + slice(*filter.keys).each do |key, value| + next unless value + next unless has_key? key + + if filter[key] == EMPTY_ARRAY + # Declaration { comment_ids: [] }. + array_of_permitted_scalars?(self[key]) do |val| + params[key] = val + end + elsif filter[key] == EMPTY_HASH + # Declaration { preferences: {} }. + if value.is_a?(Parameters) + params[key] = permit_any_in_parameters(value) + end + elsif non_scalar?(value) + # Declaration { user: :name } or { user: [:name, :age, { address: ... }] }. + params[key] = each_element(value) do |element| + element.permit(*Array.wrap(filter[key])) + end + end + end + end + + def permit_any_in_parameters(params) + self.class.new.tap do |sanitized| + params.each do |key, value| + case value + when ->(v) { permitted_scalar?(v) } + sanitized[key] = value + when Array + sanitized[key] = permit_any_in_array(value) + when Parameters + sanitized[key] = permit_any_in_parameters(value) + else + # Filter this one out. + end + end + end + end + + def permit_any_in_array(array) + [].tap do |sanitized| + array.each do |element| + case element + when ->(e) { permitted_scalar?(e) } + sanitized << element + when Parameters + sanitized << permit_any_in_parameters(element) + else + # Filter this one out. + end + end + end + end + + def initialize_copy(source) + super + @parameters = @parameters.dup + end + end + + # == Strong \Parameters + # + # It provides an interface for protecting attributes from end-user + # assignment. This makes Action Controller parameters forbidden + # to be used in Active Model mass assignment until they have been + # whitelisted. + # + # In addition, parameters can be marked as required and flow through a + # predefined raise/rescue flow to end up as a 400 Bad Request with no + # effort. + # + # class PeopleController < ActionController::Base + # # Using "Person.create(params[:person])" would raise an + # # ActiveModel::ForbiddenAttributesError exception because it'd + # # be using mass assignment without an explicit permit step. + # # This is the recommended form: + # def create + # Person.create(person_params) + # end + # + # # This will pass with flying colors as long as there's a person key in the + # # parameters, otherwise it'll raise an ActionController::ParameterMissing + # # exception, which will get caught by ActionController::Base and turned + # # into a 400 Bad Request reply. + # def update + # redirect_to current_account.people.find(params[:id]).tap { |person| + # person.update!(person_params) + # } + # end + # + # private + # # Using a private method to encapsulate the permissible parameters is + # # a good pattern since you'll be able to reuse the same permit + # # list between create and update. Also, you can specialize this method + # # with per-user checking of permissible attributes. + # def person_params + # params.require(:person).permit(:name, :age) + # end + # end + # + # In order to use accepts_nested_attributes_for with Strong \Parameters, you + # will need to specify which nested attributes should be whitelisted. You might want + # to allow +:id+ and +:_destroy+, see ActiveRecord::NestedAttributes for more information. + # + # class Person + # has_many :pets + # accepts_nested_attributes_for :pets + # end + # + # class PeopleController < ActionController::Base + # def create + # Person.create(person_params) + # end + # + # ... + # + # private + # + # def person_params + # # It's mandatory to specify the nested attributes that should be whitelisted. + # # If you use `permit` with just the key that points to the nested attributes hash, + # # it will return an empty hash. + # params.require(:person).permit(:name, :age, pets_attributes: [ :id, :name, :category ]) + # end + # end + # + # See ActionController::Parameters.require and ActionController::Parameters.permit + # for more information. + module StrongParameters + extend ActiveSupport::Concern + include ActiveSupport::Rescuable + + # Returns a new ActionController::Parameters object that + # has been instantiated with the request.parameters. + def params + @_params ||= Parameters.new(request.parameters) + end + + # Assigns the given +value+ to the +params+ hash. If +value+ + # is a Hash, this will create an ActionController::Parameters + # object that has been instantiated with the given +value+ hash. + def params=(value) + @_params = value.is_a?(Hash) ? Parameters.new(value) : value + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/testing.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/testing.rb new file mode 100644 index 00000000..6e8a9504 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/testing.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module ActionController + module Testing + extend ActiveSupport::Concern + + # Behavior specific to functional tests + module Functional # :nodoc: + def recycle! + @_url_options = nil + self.formats = nil + self.params = nil + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/url_for.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/url_for.rb new file mode 100644 index 00000000..84dbb59a --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/metal/url_for.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module ActionController + # Includes +url_for+ into the host class. The class has to provide a +RouteSet+ by implementing + # the _routes method. Otherwise, an exception will be raised. + # + # In addition to AbstractController::UrlFor, this module accesses the HTTP layer to define + # URL options like the +host+. In order to do so, this module requires the host class + # to implement +env+ which needs to be Rack-compatible and +request+ + # which is either an instance of +ActionDispatch::Request+ or an object + # that responds to the +host+, +optional_port+, +protocol+ and + # +symbolized_path_parameter+ methods. + # + # class RootUrl + # include ActionController::UrlFor + # include Rails.application.routes.url_helpers + # + # delegate :env, :request, to: :controller + # + # def initialize(controller) + # @controller = controller + # @url = root_path # named route from the application. + # end + # end + module UrlFor + extend ActiveSupport::Concern + + include AbstractController::UrlFor + + def url_options + @_url_options ||= { + host: request.host, + port: request.optional_port, + protocol: request.protocol, + _recall: request.path_parameters + }.merge!(super).freeze + + if (same_origin = _routes.equal?(request.routes)) || + (script_name = request.engine_script_name(_routes)) || + (original_script_name = request.original_script_name) + + options = @_url_options.dup + if original_script_name + options[:original_script_name] = original_script_name + else + if same_origin + options[:script_name] = request.script_name.empty? ? "".freeze : request.script_name.dup + else + options[:script_name] = script_name + end + end + options.freeze + else + @_url_options + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/railtie.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/railtie.rb new file mode 100644 index 00000000..7d42f5d9 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/railtie.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require "rails" +require "action_controller" +require "action_dispatch/railtie" +require "abstract_controller/railties/routes_helpers" +require "action_controller/railties/helpers" +require "action_view/railtie" + +module ActionController + class Railtie < Rails::Railtie #:nodoc: + config.action_controller = ActiveSupport::OrderedOptions.new + + config.eager_load_namespaces << ActionController + + initializer "action_controller.assets_config", group: :all do |app| + app.config.action_controller.assets_dir ||= app.config.paths["public"].first + end + + initializer "action_controller.set_helpers_path" do |app| + ActionController::Helpers.helpers_path = app.helpers_paths + end + + initializer "action_controller.parameters_config" do |app| + options = app.config.action_controller + + ActiveSupport.on_load(:action_controller, run_once: true) do + ActionController::Parameters.permit_all_parameters = options.delete(:permit_all_parameters) { false } + if app.config.action_controller[:always_permitted_parameters] + ActionController::Parameters.always_permitted_parameters = + app.config.action_controller.delete(:always_permitted_parameters) + end + ActionController::Parameters.action_on_unpermitted_parameters = options.delete(:action_on_unpermitted_parameters) do + (Rails.env.test? || Rails.env.development?) ? :log : false + end + end + end + + initializer "action_controller.set_configs" do |app| + paths = app.config.paths + options = app.config.action_controller + + options.logger ||= Rails.logger + options.cache_store ||= Rails.cache + + options.javascripts_dir ||= paths["public/javascripts"].first + options.stylesheets_dir ||= paths["public/stylesheets"].first + + # Ensure readers methods get compiled. + options.asset_host ||= app.config.asset_host + options.relative_url_root ||= app.config.relative_url_root + + ActiveSupport.on_load(:action_controller) do + include app.routes.mounted_helpers + extend ::AbstractController::Railties::RoutesHelpers.with(app.routes) + extend ::ActionController::Railties::Helpers + + options.each do |k, v| + k = "#{k}=" + if respond_to?(k) + send(k, v) + elsif !Base.respond_to?(k) + raise "Invalid option key: #{k}" + end + end + end + end + + initializer "action_controller.compile_config_methods" do + ActiveSupport.on_load(:action_controller) do + config.compile_methods! if config.respond_to?(:compile_methods!) + end + end + + initializer "action_controller.request_forgery_protection" do |app| + ActiveSupport.on_load(:action_controller_base) do + if app.config.action_controller.default_protect_from_forgery + protect_from_forgery with: :exception + end + end + end + + initializer "action_controller.eager_load_actions" do + ActiveSupport.on_load(:after_initialize) do + ActionController::Metal.descendants.each(&:action_methods) if config.eager_load + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/railties/helpers.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/railties/helpers.rb new file mode 100644 index 00000000..fa746fa9 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/railties/helpers.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module ActionController + module Railties + module Helpers + def inherited(klass) + super + return unless klass.respond_to?(:helpers_path=) + + if namespace = klass.parents.detect { |m| m.respond_to?(:railtie_helpers_paths) } + paths = namespace.railtie_helpers_paths + else + paths = ActionController::Helpers.helpers_path + end + + klass.helpers_path = paths + + if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers + klass.helper :all + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/renderer.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/renderer.rb new file mode 100644 index 00000000..49c5b782 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/renderer.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/keys" + +module ActionController + # ActionController::Renderer allows you to render arbitrary templates + # without requirement of being in controller actions. + # + # You get a concrete renderer class by invoking ActionController::Base#renderer. + # For example: + # + # ApplicationController.renderer + # + # It allows you to call method #render directly. + # + # ApplicationController.renderer.render template: '...' + # + # You can use this shortcut in a controller, instead of the previous example: + # + # ApplicationController.render template: '...' + # + # #render allows you to use the same options that you can use when rendering in a controller. + # For example: + # + # FooController.render :action, locals: { ... }, assigns: { ... } + # + # The template will be rendered in a Rack environment which is accessible through + # ActionController::Renderer#env. You can set it up in two ways: + # + # * by changing renderer defaults, like + # + # ApplicationController.renderer.defaults # => hash with default Rack environment + # + # * by initializing an instance of renderer by passing it a custom environment. + # + # ApplicationController.renderer.new(method: 'post', https: true) + # + class Renderer + attr_reader :defaults, :controller + + DEFAULTS = { + http_host: "example.org", + https: false, + method: "get", + script_name: "", + input: "" + }.freeze + + # Create a new renderer instance for a specific controller class. + def self.for(controller, env = {}, defaults = DEFAULTS.dup) + new(controller, env, defaults) + end + + # Create a new renderer for the same controller but with a new env. + def new(env = {}) + self.class.new controller, env, defaults + end + + # Create a new renderer for the same controller but with new defaults. + def with_defaults(defaults) + self.class.new controller, @env, self.defaults.merge(defaults) + end + + # Accepts a custom Rack environment to render templates in. + # It will be merged with the default Rack environment defined by + # +ActionController::Renderer::DEFAULTS+. + def initialize(controller, env, defaults) + @controller = controller + @defaults = defaults + @env = normalize_keys defaults.merge(env) + end + + # Render templates with any options from ActionController::Base#render_to_string. + def render(*args) + raise "missing controller" unless controller + + request = ActionDispatch::Request.new @env + request.routes = controller._routes + + instance = controller.new + instance.set_request! request + instance.set_response! controller.make_response!(request) + instance.render_to_string(*args) + end + + private + def normalize_keys(env) + new_env = {} + env.each_pair { |k, v| new_env[rack_key_for(k)] = rack_value_for(k, v) } + new_env["rack.url_scheme"] = new_env["HTTPS"] == "on" ? "https" : "http" + new_env + end + + RACK_KEY_TRANSLATION = { + http_host: "HTTP_HOST", + https: "HTTPS", + method: "REQUEST_METHOD", + script_name: "SCRIPT_NAME", + input: "rack.input" + } + + IDENTITY = ->(_) { _ } + + RACK_VALUE_TRANSLATION = { + https: ->(v) { v ? "on" : "off" }, + method: ->(v) { v.upcase }, + } + + def rack_key_for(key) + RACK_KEY_TRANSLATION.fetch(key, key.to_s) + end + + def rack_value_for(key, value) + RACK_VALUE_TRANSLATION.fetch(key, IDENTITY).call value + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/template_assertions.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/template_assertions.rb new file mode 100644 index 00000000..dd83c1a2 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/template_assertions.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module ActionController + module TemplateAssertions + def assert_template(options = {}, message = nil) + raise NoMethodError, + "assert_template has been extracted to a gem. To continue using it, + add `gem 'rails-controller-testing'` to your Gemfile." + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/test_case.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/test_case.rb new file mode 100644 index 00000000..a643484d --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_controller/test_case.rb @@ -0,0 +1,629 @@ +# frozen_string_literal: true + +require "rack/session/abstract/id" +require "active_support/core_ext/hash/conversions" +require "active_support/core_ext/object/to_query" +require "active_support/core_ext/module/anonymous" +require "active_support/core_ext/module/redefine_method" +require "active_support/core_ext/hash/keys" +require "active_support/testing/constant_lookup" +require "action_controller/template_assertions" +require "rails-dom-testing" + +module ActionController + class Metal + include Testing::Functional + end + + module Live + # Disable controller / rendering threads in tests. User tests can access + # the database on the main thread, so they could open a txn, then the + # controller thread will open a new connection and try to access data + # that's only visible to the main thread's txn. This is the problem in #23483. + silence_redefinition_of_method :new_controller_thread + def new_controller_thread # :nodoc: + yield + end + end + + # ActionController::TestCase will be deprecated and moved to a gem in Rails 5.1. + # Please use ActionDispatch::IntegrationTest going forward. + class TestRequest < ActionDispatch::TestRequest #:nodoc: + DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup + DEFAULT_ENV.delete "PATH_INFO" + + def self.new_session + TestSession.new + end + + attr_reader :controller_class + + # Create a new test request with default `env` values. + def self.create(controller_class) + env = {} + env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application + env["rack.request.cookie_hash"] = {}.with_indifferent_access + new(default_env.merge(env), new_session, controller_class) + end + + def self.default_env + DEFAULT_ENV + end + private_class_method :default_env + + def initialize(env, session, controller_class) + super(env) + + self.session = session + self.session_options = TestSession::DEFAULT_OPTIONS.dup + @controller_class = controller_class + @custom_param_parsers = { + xml: lambda { |raw_post| Hash.from_xml(raw_post)["hash"] } + } + end + + def query_string=(string) + set_header Rack::QUERY_STRING, string + end + + def content_type=(type) + set_header "CONTENT_TYPE", type + end + + def assign_parameters(routes, controller_path, action, parameters, generated_path, query_string_keys) + non_path_parameters = {} + path_parameters = {} + + parameters.each do |key, value| + if query_string_keys.include?(key) + non_path_parameters[key] = value + else + if value.is_a?(Array) + value = value.map(&:to_param) + else + value = value.to_param + end + + path_parameters[key] = value + end + end + + if get? + if query_string.blank? + self.query_string = non_path_parameters.to_query + end + else + if ENCODER.should_multipart?(non_path_parameters) + self.content_type = ENCODER.content_type + data = ENCODER.build_multipart non_path_parameters + else + fetch_header("CONTENT_TYPE") do |k| + set_header k, "application/x-www-form-urlencoded" + end + + case content_mime_type.to_sym + when nil + raise "Unknown Content-Type: #{content_type}" + when :json + data = ActiveSupport::JSON.encode(non_path_parameters) + when :xml + data = non_path_parameters.to_xml + when :url_encoded_form + data = non_path_parameters.to_query + else + @custom_param_parsers[content_mime_type.symbol] = ->(_) { non_path_parameters } + data = non_path_parameters.to_query + end + end + + data_stream = StringIO.new(data) + set_header "CONTENT_LENGTH", data_stream.length.to_s + set_header "rack.input", data_stream + end + + fetch_header("PATH_INFO") do |k| + set_header k, generated_path + end + path_parameters[:controller] = controller_path + path_parameters[:action] = action + + self.path_parameters = path_parameters + end + + ENCODER = Class.new do + include Rack::Test::Utils + + def should_multipart?(params) + # FIXME: lifted from Rack-Test. We should push this separation upstream. + multipart = false + query = lambda { |value| + case value + when Array + value.each(&query) + when Hash + value.values.each(&query) + when Rack::Test::UploadedFile + multipart = true + end + } + params.values.each(&query) + multipart + end + + public :build_multipart + + def content_type + "multipart/form-data; boundary=#{Rack::Test::MULTIPART_BOUNDARY}" + end + end.new + + private + + def params_parsers + super.merge @custom_param_parsers + end + end + + class LiveTestResponse < Live::Response + # Was the response successful? + alias_method :success?, :successful? + + # Was the URL not found? + alias_method :missing?, :not_found? + + # Was there a server-side error? + alias_method :error?, :server_error? + end + + # Methods #destroy and #load! are overridden to avoid calling methods on the + # @store object, which does not exist for the TestSession class. + class TestSession < Rack::Session::Abstract::SessionHash #:nodoc: + DEFAULT_OPTIONS = Rack::Session::Abstract::Persisted::DEFAULT_OPTIONS + + def initialize(session = {}) + super(nil, nil) + @id = SecureRandom.hex(16) + @data = stringify_keys(session) + @loaded = true + end + + def exists? + true + end + + def keys + @data.keys + end + + def values + @data.values + end + + def destroy + clear + end + + def fetch(key, *args, &block) + @data.fetch(key.to_s, *args, &block) + end + + private + + def load! + @id + end + end + + # Superclass for ActionController functional tests. Functional tests allow you to + # test a single controller action per test method. + # + # == Use integration style controller tests over functional style controller tests. + # + # Rails discourages the use of functional tests in favor of integration tests + # (use ActionDispatch::IntegrationTest). + # + # New Rails applications no longer generate functional style controller tests and they should + # only be used for backward compatibility. Integration style controller tests perform actual + # requests, whereas functional style controller tests merely simulate a request. Besides, + # integration tests are as fast as functional tests and provide lot of helpers such as +as+, + # +parsed_body+ for effective testing of controller actions including even API endpoints. + # + # == Basic example + # + # Functional tests are written as follows: + # 1. First, one uses the +get+, +post+, +patch+, +put+, +delete+ or +head+ method to simulate + # an HTTP request. + # 2. Then, one asserts whether the current state is as expected. "State" can be anything: + # the controller's HTTP response, the database contents, etc. + # + # For example: + # + # class BooksControllerTest < ActionController::TestCase + # def test_create + # # Simulate a POST response with the given HTTP parameters. + # post(:create, params: { book: { title: "Love Hina" }}) + # + # # Asserts that the controller tried to redirect us to + # # the created book's URI. + # assert_response :found + # + # # Asserts that the controller really put the book in the database. + # assert_not_nil Book.find_by(title: "Love Hina") + # end + # end + # + # You can also send a real document in the simulated HTTP request. + # + # def test_create + # json = {book: { title: "Love Hina" }}.to_json + # post :create, body: json + # end + # + # == Special instance variables + # + # ActionController::TestCase will also automatically provide the following instance + # variables for use in the tests: + # + # @controller:: + # The controller instance that will be tested. + # @request:: + # An ActionController::TestRequest, representing the current HTTP + # request. You can modify this object before sending the HTTP request. For example, + # you might want to set some session properties before sending a GET request. + # @response:: + # An ActionDispatch::TestResponse object, representing the response + # of the last HTTP response. In the above example, @response becomes valid + # after calling +post+. If the various assert methods are not sufficient, then you + # may use this object to inspect the HTTP response in detail. + # + # (Earlier versions of \Rails required each functional test to subclass + # Test::Unit::TestCase and define @controller, @request, @response in +setup+.) + # + # == Controller is automatically inferred + # + # ActionController::TestCase will automatically infer the controller under test + # from the test class name. If the controller cannot be inferred from the test + # class name, you can explicitly set it with +tests+. + # + # class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase + # tests WidgetController + # end + # + # == \Testing controller internals + # + # In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions + # can be used against. These collections are: + # + # * session: Objects being saved in the session. + # * flash: The flash objects currently in the session. + # * cookies: \Cookies being sent to the user on this request. + # + # These collections can be used just like any other hash: + # + # assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave" + # assert flash.empty? # makes sure that there's nothing in the flash + # + # On top of the collections, you have the complete URL that a given action redirected to available in redirect_to_url. + # + # For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another + # action call which can then be asserted against. + # + # == Manipulating session and cookie variables + # + # Sometimes you need to set up the session and cookie variables for a test. + # To do this just assign a value to the session or cookie collection: + # + # session[:key] = "value" + # cookies[:key] = "value" + # + # To clear the cookies for a test just clear the cookie collection: + # + # cookies.clear + # + # == \Testing named routes + # + # If you're using named routes, they can be easily tested using the original named routes' methods straight in the test case. + # + # assert_redirected_to page_url(title: 'foo') + class TestCase < ActiveSupport::TestCase + module Behavior + extend ActiveSupport::Concern + include ActionDispatch::TestProcess + include ActiveSupport::Testing::ConstantLookup + include Rails::Dom::Testing::Assertions + + attr_reader :response, :request + + module ClassMethods + # Sets the controller class name. Useful if the name can't be inferred from test class. + # Normalizes +controller_class+ before using. + # + # tests WidgetController + # tests :widget + # tests 'widget' + def tests(controller_class) + case controller_class + when String, Symbol + self.controller_class = "#{controller_class.to_s.camelize}Controller".constantize + when Class + self.controller_class = controller_class + else + raise ArgumentError, "controller class must be a String, Symbol, or Class" + end + end + + def controller_class=(new_class) + self._controller_class = new_class + end + + def controller_class + if current_controller_class = _controller_class + current_controller_class + else + self.controller_class = determine_default_controller_class(name) + end + end + + def determine_default_controller_class(name) + determine_constant_from_test_name(name) do |constant| + Class === constant && constant < ActionController::Metal + end + end + end + + # Simulate a GET request with the given parameters. + # + # - +action+: The controller action to call. + # - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+. + # - +body+: The request body with a string that is appropriately encoded + # (application/x-www-form-urlencoded or multipart/form-data). + # - +session+: A hash of parameters to store in the session. This may be +nil+. + # - +flash+: A hash of parameters to store in the flash. This may be +nil+. + # + # You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with + # +post+, +patch+, +put+, +delete+, and +head+. + # Example sending parameters, session and setting a flash message: + # + # get :show, + # params: { id: 7 }, + # session: { user_id: 1 }, + # flash: { notice: 'This is flash message' } + # + # Note that the request method is not verified. The different methods are + # available to make the tests more expressive. + def get(action, **args) + res = process(action, method: "GET", **args) + cookies.update res.cookies + res + end + + # Simulate a POST request with the given parameters and set/volley the response. + # See +get+ for more details. + def post(action, **args) + process(action, method: "POST", **args) + end + + # Simulate a PATCH request with the given parameters and set/volley the response. + # See +get+ for more details. + def patch(action, **args) + process(action, method: "PATCH", **args) + end + + # Simulate a PUT request with the given parameters and set/volley the response. + # See +get+ for more details. + def put(action, **args) + process(action, method: "PUT", **args) + end + + # Simulate a DELETE request with the given parameters and set/volley the response. + # See +get+ for more details. + def delete(action, **args) + process(action, method: "DELETE", **args) + end + + # Simulate a HEAD request with the given parameters and set/volley the response. + # See +get+ for more details. + def head(action, **args) + process(action, method: "HEAD", **args) + end + + # Simulate an HTTP request to +action+ by specifying request method, + # parameters and set/volley the response. + # + # - +action+: The controller action to call. + # - +method+: Request method used to send the HTTP request. Possible values + # are +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, +HEAD+. Defaults to +GET+. Can be a symbol. + # - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+. + # - +body+: The request body with a string that is appropriately encoded + # (application/x-www-form-urlencoded or multipart/form-data). + # - +session+: A hash of parameters to store in the session. This may be +nil+. + # - +flash+: A hash of parameters to store in the flash. This may be +nil+. + # - +format+: Request format. Defaults to +nil+. Can be string or symbol. + # - +as+: Content type. Defaults to +nil+. Must be a symbol that corresponds + # to a mime type. + # + # Example calling +create+ action and sending two params: + # + # process :create, + # method: 'POST', + # params: { + # user: { name: 'Gaurish Sharma', email: 'user@example.com' } + # }, + # session: { user_id: 1 }, + # flash: { notice: 'This is flash message' } + # + # To simulate +GET+, +POST+, +PATCH+, +PUT+, +DELETE+ and +HEAD+ requests + # prefer using #get, #post, #patch, #put, #delete and #head methods + # respectively which will make tests more expressive. + # + # Note that the request method is not verified. + def process(action, method: "GET", params: nil, session: nil, body: nil, flash: {}, format: nil, xhr: false, as: nil) + check_required_ivars + + http_method = method.to_s.upcase + + @html_document = nil + + cookies.update(@request.cookies) + cookies.update_cookies_from_jar + @request.set_header "HTTP_COOKIE", cookies.to_header + @request.delete_header "action_dispatch.cookies" + + @request = TestRequest.new scrub_env!(@request.env), @request.session, @controller.class + @response = build_response @response_klass + @response.request = @request + @controller.recycle! + + if body + @request.set_header "RAW_POST_DATA", body + end + + @request.set_header "REQUEST_METHOD", http_method + + if as + @request.content_type = Mime[as].to_s + format ||= as + end + + parameters = (params || {}).symbolize_keys + + if format + parameters[:format] = format + end + + generated_extras = @routes.generate_extras(parameters.merge(controller: controller_class_name, action: action.to_s)) + generated_path = generated_path(generated_extras) + query_string_keys = query_parameter_names(generated_extras) + + @request.assign_parameters(@routes, controller_class_name, action.to_s, parameters, generated_path, query_string_keys) + + @request.session.update(session) if session + @request.flash.update(flash || {}) + + if xhr + @request.set_header "HTTP_X_REQUESTED_WITH", "XMLHttpRequest" + @request.fetch_header("HTTP_ACCEPT") do |k| + @request.set_header k, [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ") + end + end + + @request.fetch_header("SCRIPT_NAME") do |k| + @request.set_header k, @controller.config.relative_url_root + end + + begin + @controller.recycle! + @controller.dispatch(action, @request, @response) + ensure + @request = @controller.request + @response = @controller.response + + if @request.have_cookie_jar? + unless @request.cookie_jar.committed? + @request.cookie_jar.write(@response) + cookies.update(@request.cookie_jar.instance_variable_get(:@cookies)) + end + end + @response.prepare! + + if flash_value = @request.flash.to_session_value + @request.session["flash"] = flash_value + else + @request.session.delete("flash") + end + + if xhr + @request.delete_header "HTTP_X_REQUESTED_WITH" + @request.delete_header "HTTP_ACCEPT" + end + @request.query_string = "" + + @response.sent! + end + + @response + end + + def controller_class_name + @controller.class.anonymous? ? "anonymous" : @controller.class.controller_path + end + + def generated_path(generated_extras) + generated_extras[0] + end + + def query_parameter_names(generated_extras) + generated_extras[1] + [:controller, :action] + end + + def setup_controller_request_and_response + @controller = nil unless defined? @controller + + @response_klass = ActionDispatch::TestResponse + + if klass = self.class.controller_class + if klass < ActionController::Live + @response_klass = LiveTestResponse + end + unless @controller + begin + @controller = klass.new + rescue + warn "could not construct controller #{klass}" if $VERBOSE + end + end + end + + @request = TestRequest.create(@controller.class) + @response = build_response @response_klass + @response.request = @request + + if @controller + @controller.request = @request + @controller.params = {} + end + end + + def build_response(klass) + klass.create + end + + included do + include ActionController::TemplateAssertions + include ActionDispatch::Assertions + class_attribute :_controller_class + setup :setup_controller_request_and_response + ActiveSupport.run_load_hooks(:action_controller_test_case, self) + end + + private + + def scrub_env!(env) + env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ } + env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ } + env.delete "action_dispatch.request.query_parameters" + env.delete "action_dispatch.request.request_parameters" + env["rack.input"] = StringIO.new + env.delete "CONTENT_LENGTH" + env.delete "RAW_POST_DATA" + env + end + + def document_root_element + html_document.root + end + + def check_required_ivars + # Sanity check for required instance variables so we can give an + # understandable error message. + [:@routes, :@controller, :@request, :@response].each do |iv_name| + if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil? + raise "#{iv_name} is nil: make sure you set it in your test's setup method." + end + end + end + end + + include Behavior + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch.rb new file mode 100644 index 00000000..0822cdc0 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +#-- +# Copyright (c) 2004-2018 David Heinemeier Hansson +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ + +require "active_support" +require "active_support/rails" +require "active_support/core_ext/module/attribute_accessors" + +require "action_pack" +require "rack" + +module Rack + autoload :Test, "rack/test" +end + +module ActionDispatch + extend ActiveSupport::Autoload + + class IllegalStateError < StandardError + end + + eager_autoload do + autoload_under "http" do + autoload :ContentSecurityPolicy + autoload :Request + autoload :Response + end + end + + autoload_under "middleware" do + autoload :RequestId + autoload :Callbacks + autoload :Cookies + autoload :DebugExceptions + autoload :DebugLocks + autoload :ExceptionWrapper + autoload :Executor + autoload :Flash + autoload :PublicExceptions + autoload :Reloader + autoload :RemoteIp + autoload :ShowExceptions + autoload :SSL + autoload :Static + end + + autoload :Journey + autoload :MiddlewareStack, "action_dispatch/middleware/stack" + autoload :Routing + + module Http + extend ActiveSupport::Autoload + + autoload :Cache + autoload :Headers + autoload :MimeNegotiation + autoload :Parameters + autoload :ParameterFilter + autoload :Upload + autoload :UploadedFile, "action_dispatch/http/upload" + autoload :URL + end + + module Session + autoload :AbstractStore, "action_dispatch/middleware/session/abstract_store" + autoload :CookieStore, "action_dispatch/middleware/session/cookie_store" + autoload :MemCacheStore, "action_dispatch/middleware/session/mem_cache_store" + autoload :CacheStore, "action_dispatch/middleware/session/cache_store" + end + + mattr_accessor :test_app + + autoload_under "testing" do + autoload :Assertions + autoload :Integration + autoload :IntegrationTest, "action_dispatch/testing/integration" + autoload :TestProcess + autoload :TestRequest + autoload :TestResponse + autoload :AssertionResponse + end + + autoload :SystemTestCase, "action_dispatch/system_test_case" +end + +autoload :Mime, "action_dispatch/http/mime_type" + +ActiveSupport.on_load(:action_view) do + ActionView::Base.default_formats ||= Mime::SET.symbols + ActionView::Template::Types.delegate_to Mime +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/cache.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/cache.rb new file mode 100644 index 00000000..179d7e6c --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/cache.rb @@ -0,0 +1,222 @@ +# frozen_string_literal: true + +module ActionDispatch + module Http + module Cache + module Request + HTTP_IF_MODIFIED_SINCE = "HTTP_IF_MODIFIED_SINCE".freeze + HTTP_IF_NONE_MATCH = "HTTP_IF_NONE_MATCH".freeze + + def if_modified_since + if since = get_header(HTTP_IF_MODIFIED_SINCE) + Time.rfc2822(since) rescue nil + end + end + + def if_none_match + get_header HTTP_IF_NONE_MATCH + end + + def if_none_match_etags + if_none_match ? if_none_match.split(/\s*,\s*/) : [] + end + + def not_modified?(modified_at) + if_modified_since && modified_at && if_modified_since >= modified_at + end + + def etag_matches?(etag) + if etag + validators = if_none_match_etags + validators.include?(etag) || validators.include?("*") + end + end + + # Check response freshness (Last-Modified and ETag) against request + # If-Modified-Since and If-None-Match conditions. If both headers are + # supplied, both must match, or the request is not considered fresh. + def fresh?(response) + last_modified = if_modified_since + etag = if_none_match + + return false unless last_modified || etag + + success = true + success &&= not_modified?(response.last_modified) if last_modified + success &&= etag_matches?(response.etag) if etag + success + end + end + + module Response + attr_reader :cache_control + + def last_modified + if last = get_header(LAST_MODIFIED) + Time.httpdate(last) + end + end + + def last_modified? + has_header? LAST_MODIFIED + end + + def last_modified=(utc_time) + set_header LAST_MODIFIED, utc_time.httpdate + end + + def date + if date_header = get_header(DATE) + Time.httpdate(date_header) + end + end + + def date? + has_header? DATE + end + + def date=(utc_time) + set_header DATE, utc_time.httpdate + end + + # This method sets a weak ETag validator on the response so browsers + # and proxies may cache the response, keyed on the ETag. On subsequent + # requests, the If-None-Match header is set to the cached ETag. If it + # matches the current ETag, we can return a 304 Not Modified response + # with no body, letting the browser or proxy know that their cache is + # current. Big savings in request time and network bandwidth. + # + # Weak ETags are considered to be semantically equivalent but not + # byte-for-byte identical. This is perfect for browser caching of HTML + # pages where we don't care about exact equality, just what the user + # is viewing. + # + # Strong ETags are considered byte-for-byte identical. They allow a + # browser or proxy cache to support Range requests, useful for paging + # through a PDF file or scrubbing through a video. Some CDNs only + # support strong ETags and will ignore weak ETags entirely. + # + # Weak ETags are what we almost always need, so they're the default. + # Check out #strong_etag= to provide a strong ETag validator. + def etag=(weak_validators) + self.weak_etag = weak_validators + end + + def weak_etag=(weak_validators) + set_header "ETag", generate_weak_etag(weak_validators) + end + + def strong_etag=(strong_validators) + set_header "ETag", generate_strong_etag(strong_validators) + end + + def etag?; etag; end + + # True if an ETag is set and it's a weak validator (preceded with W/) + def weak_etag? + etag? && etag.starts_with?('W/"') + end + + # True if an ETag is set and it isn't a weak validator (not preceded with W/) + def strong_etag? + etag? && !weak_etag? + end + + private + + DATE = "Date".freeze + LAST_MODIFIED = "Last-Modified".freeze + SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public private must-revalidate]) + + def generate_weak_etag(validators) + "W/#{generate_strong_etag(validators)}" + end + + def generate_strong_etag(validators) + %("#{ActiveSupport::Digest.hexdigest(ActiveSupport::Cache.expand_cache_key(validators))}") + end + + def cache_control_segments + if cache_control = _cache_control + cache_control.delete(" ").split(",") + else + [] + end + end + + def cache_control_headers + cache_control = {} + + cache_control_segments.each do |segment| + directive, argument = segment.split("=", 2) + + if SPECIAL_KEYS.include? directive + key = directive.tr("-", "_") + cache_control[key.to_sym] = argument || true + else + cache_control[:extras] ||= [] + cache_control[:extras] << segment + end + end + + cache_control + end + + def prepare_cache_control! + @cache_control = cache_control_headers + end + + DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate".freeze + NO_CACHE = "no-cache".freeze + PUBLIC = "public".freeze + PRIVATE = "private".freeze + MUST_REVALIDATE = "must-revalidate".freeze + + def handle_conditional_get! + # Normally default cache control setting is handled by ETag + # middleware. But, if an etag is already set, the middleware + # defaults to `no-cache` unless a default `Cache-Control` value is + # previously set. So, set a default one here. + if (etag? || last_modified?) && !self._cache_control + self._cache_control = DEFAULT_CACHE_CONTROL + end + end + + def merge_and_normalize_cache_control!(cache_control) + control = {} + cc_headers = cache_control_headers + if extras = cc_headers.delete(:extras) + cache_control[:extras] ||= [] + cache_control[:extras] += extras + cache_control[:extras].uniq! + end + + control.merge! cc_headers + control.merge! cache_control + + if control.empty? + # Let middleware handle default behavior + elsif control[:no_cache] + options = [] + options << PUBLIC if control[:public] + options << NO_CACHE + options.concat(control[:extras]) if control[:extras] + + self._cache_control = options.join(", ") + else + extras = control[:extras] + max_age = control[:max_age] + + options = [] + options << "max-age=#{max_age.to_i}" if max_age + options << (control[:public] ? PUBLIC : PRIVATE) + options << MUST_REVALIDATE if control[:must_revalidate] + options.concat(extras) if extras + + self._cache_control = options.join(", ") + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/content_security_policy.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/content_security_policy.rb new file mode 100644 index 00000000..6f9fb11a --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/content_security_policy.rb @@ -0,0 +1,272 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/deep_dup" + +module ActionDispatch #:nodoc: + class ContentSecurityPolicy + class Middleware + CONTENT_TYPE = "Content-Type".freeze + POLICY = "Content-Security-Policy".freeze + POLICY_REPORT_ONLY = "Content-Security-Policy-Report-Only".freeze + + def initialize(app) + @app = app + end + + def call(env) + request = ActionDispatch::Request.new env + _, headers, _ = response = @app.call(env) + + return response unless html_response?(headers) + return response if policy_present?(headers) + + if policy = request.content_security_policy + nonce = request.content_security_policy_nonce + context = request.controller_instance || request + headers[header_name(request)] = policy.build(context, nonce) + end + + response + end + + private + + def html_response?(headers) + if content_type = headers[CONTENT_TYPE] + content_type =~ /html/ + end + end + + def header_name(request) + if request.content_security_policy_report_only + POLICY_REPORT_ONLY + else + POLICY + end + end + + def policy_present?(headers) + headers[POLICY] || headers[POLICY_REPORT_ONLY] + end + end + + module Request + POLICY = "action_dispatch.content_security_policy".freeze + POLICY_REPORT_ONLY = "action_dispatch.content_security_policy_report_only".freeze + NONCE_GENERATOR = "action_dispatch.content_security_policy_nonce_generator".freeze + NONCE = "action_dispatch.content_security_policy_nonce".freeze + + def content_security_policy + get_header(POLICY) + end + + def content_security_policy=(policy) + set_header(POLICY, policy) + end + + def content_security_policy_report_only + get_header(POLICY_REPORT_ONLY) + end + + def content_security_policy_report_only=(value) + set_header(POLICY_REPORT_ONLY, value) + end + + def content_security_policy_nonce_generator + get_header(NONCE_GENERATOR) + end + + def content_security_policy_nonce_generator=(generator) + set_header(NONCE_GENERATOR, generator) + end + + def content_security_policy_nonce + if content_security_policy_nonce_generator + if nonce = get_header(NONCE) + nonce + else + set_header(NONCE, generate_content_security_policy_nonce) + end + end + end + + private + + def generate_content_security_policy_nonce + content_security_policy_nonce_generator.call(self) + end + end + + MAPPINGS = { + self: "'self'", + unsafe_eval: "'unsafe-eval'", + unsafe_inline: "'unsafe-inline'", + none: "'none'", + http: "http:", + https: "https:", + data: "data:", + mediastream: "mediastream:", + blob: "blob:", + filesystem: "filesystem:", + report_sample: "'report-sample'", + strict_dynamic: "'strict-dynamic'", + ws: "ws:", + wss: "wss:" + }.freeze + + DIRECTIVES = { + base_uri: "base-uri", + child_src: "child-src", + connect_src: "connect-src", + default_src: "default-src", + font_src: "font-src", + form_action: "form-action", + frame_ancestors: "frame-ancestors", + frame_src: "frame-src", + img_src: "img-src", + manifest_src: "manifest-src", + media_src: "media-src", + object_src: "object-src", + script_src: "script-src", + style_src: "style-src", + worker_src: "worker-src" + }.freeze + + NONCE_DIRECTIVES = %w[script-src].freeze + + private_constant :MAPPINGS, :DIRECTIVES, :NONCE_DIRECTIVES + + attr_reader :directives + + def initialize + @directives = {} + yield self if block_given? + end + + def initialize_copy(other) + @directives = other.directives.deep_dup + end + + DIRECTIVES.each do |name, directive| + define_method(name) do |*sources| + if sources.first + @directives[directive] = apply_mappings(sources) + else + @directives.delete(directive) + end + end + end + + def block_all_mixed_content(enabled = true) + if enabled + @directives["block-all-mixed-content"] = true + else + @directives.delete("block-all-mixed-content") + end + end + + def plugin_types(*types) + if types.first + @directives["plugin-types"] = types + else + @directives.delete("plugin-types") + end + end + + def report_uri(uri) + @directives["report-uri"] = [uri] + end + + def require_sri_for(*types) + if types.first + @directives["require-sri-for"] = types + else + @directives.delete("require-sri-for") + end + end + + def sandbox(*values) + if values.empty? + @directives["sandbox"] = true + elsif values.first + @directives["sandbox"] = values + else + @directives.delete("sandbox") + end + end + + def upgrade_insecure_requests(enabled = true) + if enabled + @directives["upgrade-insecure-requests"] = true + else + @directives.delete("upgrade-insecure-requests") + end + end + + def build(context = nil, nonce = nil) + build_directives(context, nonce).compact.join("; ") + end + + private + def apply_mappings(sources) + sources.map do |source| + case source + when Symbol + apply_mapping(source) + when String, Proc + source + else + raise ArgumentError, "Invalid content security policy source: #{source.inspect}" + end + end + end + + def apply_mapping(source) + MAPPINGS.fetch(source) do + raise ArgumentError, "Unknown content security policy source mapping: #{source.inspect}" + end + end + + def build_directives(context, nonce) + @directives.map do |directive, sources| + if sources.is_a?(Array) + if nonce && nonce_directive?(directive) + "#{directive} #{build_directive(sources, context).join(' ')} 'nonce-#{nonce}'" + else + "#{directive} #{build_directive(sources, context).join(' ')}" + end + elsif sources + directive + else + nil + end + end + end + + def build_directive(sources, context) + sources.map { |source| resolve_source(source, context) } + end + + def resolve_source(source, context) + case source + when String + source + when Symbol + source.to_s + when Proc + if context.nil? + raise RuntimeError, "Missing context for the dynamic content security policy source: #{source.inspect}" + else + resolved = context.instance_exec(&source) + resolved.is_a?(Symbol) ? apply_mapping(resolved) : resolved + end + else + raise RuntimeError, "Unexpected content security policy source: #{source.inspect}" + end + end + + def nonce_directive?(directive) + NONCE_DIRECTIVES.include?(directive) + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/filter_parameters.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/filter_parameters.rb new file mode 100644 index 00000000..ec86b8bc --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/filter_parameters.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require "action_dispatch/http/parameter_filter" + +module ActionDispatch + module Http + # Allows you to specify sensitive parameters which will be replaced from + # the request log by looking in the query string of the request and all + # sub-hashes of the params hash to filter. Filtering only certain sub-keys + # from a hash is possible by using the dot notation: 'credit_card.number'. + # If a block is given, each key and value of the params hash and all + # sub-hashes is passed to it, where the value or the key can be replaced using + # String#replace or similar method. + # + # env["action_dispatch.parameter_filter"] = [:password] + # => replaces the value to all keys matching /password/i with "[FILTERED]" + # + # env["action_dispatch.parameter_filter"] = [:foo, "bar"] + # => replaces the value to all keys matching /foo|bar/i with "[FILTERED]" + # + # env["action_dispatch.parameter_filter"] = [ "credit_card.code" ] + # => replaces { credit_card: {code: "xxxx"} } with "[FILTERED]", does not + # change { file: { code: "xxxx"} } + # + # env["action_dispatch.parameter_filter"] = -> (k, v) do + # v.reverse! if k =~ /secret/i + # end + # => reverses the value to all keys matching /secret/i + module FilterParameters + ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc: + NULL_PARAM_FILTER = ParameterFilter.new # :nodoc: + NULL_ENV_FILTER = ParameterFilter.new ENV_MATCH # :nodoc: + + def initialize + super + @filtered_parameters = nil + @filtered_env = nil + @filtered_path = nil + end + + # Returns a hash of parameters with all sensitive data replaced. + def filtered_parameters + @filtered_parameters ||= parameter_filter.filter(parameters) + end + + # Returns a hash of request.env with all sensitive data replaced. + def filtered_env + @filtered_env ||= env_filter.filter(@env) + end + + # Reconstructs a path with all sensitive GET parameters replaced. + def filtered_path + @filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}" + end + + private + + def parameter_filter # :doc: + parameter_filter_for fetch_header("action_dispatch.parameter_filter") { + return NULL_PARAM_FILTER + } + end + + def env_filter # :doc: + user_key = fetch_header("action_dispatch.parameter_filter") { + return NULL_ENV_FILTER + } + parameter_filter_for(Array(user_key) + ENV_MATCH) + end + + def parameter_filter_for(filters) # :doc: + ParameterFilter.new(filters) + end + + KV_RE = "[^&;=]+" + PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})} + def filtered_query_string # :doc: + query_string.gsub(PAIR_RE) do |_| + parameter_filter.filter($1 => $2).first.join("=") + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/filter_redirect.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/filter_redirect.rb new file mode 100644 index 00000000..25394fe5 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/filter_redirect.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module ActionDispatch + module Http + module FilterRedirect + FILTERED = "[FILTERED]".freeze # :nodoc: + + def filtered_location # :nodoc: + if location_filter_match? + FILTERED + else + location + end + end + + private + + def location_filters + if request + request.get_header("action_dispatch.redirect_filter") || [] + else + [] + end + end + + def location_filter_match? + location_filters.any? do |filter| + if String === filter + location.include?(filter) + elsif Regexp === filter + location =~ filter + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/headers.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/headers.rb new file mode 100644 index 00000000..c3c2a9d8 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/headers.rb @@ -0,0 +1,132 @@ +# frozen_string_literal: true + +module ActionDispatch + module Http + # Provides access to the request's HTTP headers from the environment. + # + # env = { "CONTENT_TYPE" => "text/plain", "HTTP_USER_AGENT" => "curl/7.43.0" } + # headers = ActionDispatch::Http::Headers.from_hash(env) + # headers["Content-Type"] # => "text/plain" + # headers["User-Agent"] # => "curl/7.43.0" + # + # Also note that when headers are mapped to CGI-like variables by the Rack + # server, both dashes and underscores are converted to underscores. This + # ambiguity cannot be resolved at this stage anymore. Both underscores and + # dashes have to be interpreted as if they were originally sent as dashes. + # + # # GET / HTTP/1.1 + # # ... + # # User-Agent: curl/7.43.0 + # # X_Custom_Header: token + # + # headers["X_Custom_Header"] # => nil + # headers["X-Custom-Header"] # => "token" + class Headers + CGI_VARIABLES = Set.new(%W[ + AUTH_TYPE + CONTENT_LENGTH + CONTENT_TYPE + GATEWAY_INTERFACE + HTTPS + PATH_INFO + PATH_TRANSLATED + QUERY_STRING + REMOTE_ADDR + REMOTE_HOST + REMOTE_IDENT + REMOTE_USER + REQUEST_METHOD + SCRIPT_NAME + SERVER_NAME + SERVER_PORT + SERVER_PROTOCOL + SERVER_SOFTWARE + ]).freeze + + HTTP_HEADER = /\A[A-Za-z0-9-]+\z/ + + include Enumerable + + def self.from_hash(hash) + new ActionDispatch::Request.new hash + end + + def initialize(request) # :nodoc: + @req = request + end + + # Returns the value for the given key mapped to @env. + def [](key) + @req.get_header env_name(key) + end + + # Sets the given value for the key mapped to @env. + def []=(key, value) + @req.set_header env_name(key), value + end + + # Add a value to a multivalued header like Vary or Accept-Encoding. + def add(key, value) + @req.add_header env_name(key), value + end + + def key?(key) + @req.has_header? env_name(key) + end + alias :include? :key? + + DEFAULT = Object.new # :nodoc: + + # Returns the value for the given key mapped to @env. + # + # If the key is not found and an optional code block is not provided, + # raises a KeyError exception. + # + # If the code block is provided, then it will be run and + # its result returned. + def fetch(key, default = DEFAULT) + @req.fetch_header(env_name(key)) do + return default unless default == DEFAULT + return yield if block_given? + raise KeyError, key + end + end + + def each(&block) + @req.each_header(&block) + end + + # Returns a new Http::Headers instance containing the contents of + # headers_or_env and the original instance. + def merge(headers_or_env) + headers = @req.dup.headers + headers.merge!(headers_or_env) + headers + end + + # Adds the contents of headers_or_env to original instance + # entries; duplicate keys are overwritten with the values from + # headers_or_env. + def merge!(headers_or_env) + headers_or_env.each do |key, value| + @req.set_header env_name(key), value + end + end + + def env; @req.env.dup; end + + private + + # Converts an HTTP header name to an environment variable name if it is + # not contained within the headers hash. + def env_name(key) + key = key.to_s + if key =~ HTTP_HEADER + key = key.upcase.tr("-", "_") + key = "HTTP_" + key unless CGI_VARIABLES.include?(key) + end + key + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/mime_negotiation.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/mime_negotiation.rb new file mode 100644 index 00000000..ada52adf --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/mime_negotiation.rb @@ -0,0 +1,175 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attribute_accessors" + +module ActionDispatch + module Http + module MimeNegotiation + extend ActiveSupport::Concern + + included do + mattr_accessor :ignore_accept_header, default: false + end + + # The MIME type of the HTTP request, such as Mime[:xml]. + def content_mime_type + fetch_header("action_dispatch.request.content_type") do |k| + v = if get_header("CONTENT_TYPE") =~ /^([^,\;]*)/ + Mime::Type.lookup($1.strip.downcase) + else + nil + end + set_header k, v + end + end + + def content_type + content_mime_type && content_mime_type.to_s + end + + def has_content_type? # :nodoc: + get_header "CONTENT_TYPE" + end + + # Returns the accepted MIME type for the request. + def accepts + fetch_header("action_dispatch.request.accepts") do |k| + header = get_header("HTTP_ACCEPT").to_s.strip + + v = if header.empty? + [content_mime_type] + else + Mime::Type.parse(header) + end + set_header k, v + end + end + + # Returns the MIME type for the \format used in the request. + # + # GET /posts/5.xml | request.format => Mime[:xml] + # GET /posts/5.xhtml | request.format => Mime[:html] + # GET /posts/5 | request.format => Mime[:html] or Mime[:js], or request.accepts.first + # + def format(view_path = []) + formats.first || Mime::NullType.instance + end + + def formats + fetch_header("action_dispatch.request.formats") do |k| + params_readable = begin + parameters[:format] + rescue ActionController::BadRequest + false + end + + v = if params_readable + Array(Mime[parameters[:format]]) + elsif use_accept_header && valid_accept_header + accepts + elsif extension_format = format_from_path_extension + [extension_format] + elsif xhr? + [Mime[:js]] + else + [Mime[:html]] + end + + v = v.select do |format| + format.symbol || format.ref == "*/*" + end + + set_header k, v + end + end + + # Sets the \variant for template. + def variant=(variant) + variant = Array(variant) + + if variant.all? { |v| v.is_a?(Symbol) } + @variant = ActiveSupport::ArrayInquirer.new(variant) + else + raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols. " \ + "For security reasons, never directly set the variant to a user-provided value, " \ + "like params[:variant].to_sym. Check user-provided value against a whitelist first, " \ + "then set the variant: request.variant = :tablet if params[:variant] == 'tablet'" + end + end + + def variant + @variant ||= ActiveSupport::ArrayInquirer.new + end + + # Sets the \format by string extension, which can be used to force custom formats + # that are not controlled by the extension. + # + # class ApplicationController < ActionController::Base + # before_action :adjust_format_for_iphone + # + # private + # def adjust_format_for_iphone + # request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/] + # end + # end + def format=(extension) + parameters[:format] = extension.to_s + set_header "action_dispatch.request.formats", [Mime::Type.lookup_by_extension(parameters[:format])] + end + + # Sets the \formats by string extensions. This differs from #format= by allowing you + # to set multiple, ordered formats, which is useful when you want to have a fallback. + # + # In this example, the :iphone format will be used if it's available, otherwise it'll fallback + # to the :html format. + # + # class ApplicationController < ActionController::Base + # before_action :adjust_format_for_iphone_with_html_fallback + # + # private + # def adjust_format_for_iphone_with_html_fallback + # request.formats = [ :iphone, :html ] if request.env["HTTP_USER_AGENT"][/iPhone/] + # end + # end + def formats=(extensions) + parameters[:format] = extensions.first.to_s + set_header "action_dispatch.request.formats", extensions.collect { |extension| + Mime::Type.lookup_by_extension(extension) + } + end + + # Returns the first MIME type that matches the provided array of MIME types. + def negotiate_mime(order) + formats.each do |priority| + if priority == Mime::ALL + return order.first + elsif order.include?(priority) + return priority + end + end + + order.include?(Mime::ALL) ? format : nil + end + + private + + BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/ + + def valid_accept_header # :doc: + (xhr? && (accept.present? || content_mime_type)) || + (accept.present? && accept !~ BROWSER_LIKE_ACCEPTS) + end + + def use_accept_header # :doc: + !self.class.ignore_accept_header + end + + def format_from_path_extension # :doc: + path = get_header("action_dispatch.original_path") || get_header("PATH_INFO") + if match = path && path.match(/\.(\w+)\z/) + Mime[match.captures.first] + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/mime_type.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/mime_type.rb new file mode 100644 index 00000000..d2b21068 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/mime_type.rb @@ -0,0 +1,342 @@ +# frozen_string_literal: true + +# -*- frozen-string-literal: true -*- + +require "singleton" +require "active_support/core_ext/string/starts_ends_with" + +module Mime + class Mimes + include Enumerable + + def initialize + @mimes = [] + @symbols = nil + end + + def each + @mimes.each { |x| yield x } + end + + def <<(type) + @mimes << type + @symbols = nil + end + + def delete_if + @mimes.delete_if { |x| yield x }.tap { @symbols = nil } + end + + def symbols + @symbols ||= map(&:to_sym) + end + end + + SET = Mimes.new + EXTENSION_LOOKUP = {} + LOOKUP = {} + + class << self + def [](type) + return type if type.is_a?(Type) + Type.lookup_by_extension(type) + end + + def fetch(type) + return type if type.is_a?(Type) + EXTENSION_LOOKUP.fetch(type.to_s) { |k| yield k } + end + end + + # Encapsulates the notion of a MIME type. Can be used at render time, for example, with: + # + # class PostsController < ActionController::Base + # def show + # @post = Post.find(params[:id]) + # + # respond_to do |format| + # format.html + # format.ics { render body: @post.to_ics, mime_type: Mime::Type.lookup("text/calendar") } + # format.xml { render xml: @post } + # end + # end + # end + class Type + attr_reader :symbol + + @register_callbacks = [] + + # A simple helper class used in parsing the accept header. + class AcceptItem #:nodoc: + attr_accessor :index, :name, :q + alias :to_s :name + + def initialize(index, name, q = nil) + @index = index + @name = name + q ||= 0.0 if @name == "*/*".freeze # Default wildcard match to end of list. + @q = ((q || 1.0).to_f * 100).to_i + end + + def <=>(item) + result = item.q <=> @q + result = @index <=> item.index if result == 0 + result + end + end + + class AcceptList #:nodoc: + def self.sort!(list) + list.sort! + + text_xml_idx = find_item_by_name list, "text/xml" + app_xml_idx = find_item_by_name list, Mime[:xml].to_s + + # Take care of the broken text/xml entry by renaming or deleting it. + if text_xml_idx && app_xml_idx + app_xml = list[app_xml_idx] + text_xml = list[text_xml_idx] + + app_xml.q = [text_xml.q, app_xml.q].max # Set the q value to the max of the two. + if app_xml_idx > text_xml_idx # Make sure app_xml is ahead of text_xml in the list. + list[app_xml_idx], list[text_xml_idx] = text_xml, app_xml + app_xml_idx, text_xml_idx = text_xml_idx, app_xml_idx + end + list.delete_at(text_xml_idx) # Delete text_xml from the list. + elsif text_xml_idx + list[text_xml_idx].name = Mime[:xml].to_s + end + + # Look for more specific XML-based types and sort them ahead of app/xml. + if app_xml_idx + app_xml = list[app_xml_idx] + idx = app_xml_idx + + while idx < list.length + type = list[idx] + break if type.q < app_xml.q + + if type.name.ends_with? "+xml" + list[app_xml_idx], list[idx] = list[idx], app_xml + app_xml_idx = idx + end + idx += 1 + end + end + + list.map! { |i| Mime::Type.lookup(i.name) }.uniq! + list + end + + def self.find_item_by_name(array, name) + array.index { |item| item.name == name } + end + end + + class << self + TRAILING_STAR_REGEXP = /^(text|application)\/\*/ + PARAMETER_SEPARATOR_REGEXP = /;\s*\w+="?\w+"?/ + + def register_callback(&block) + @register_callbacks << block + end + + def lookup(string) + LOOKUP[string] || Type.new(string) + end + + def lookup_by_extension(extension) + EXTENSION_LOOKUP[extension.to_s] + end + + # Registers an alias that's not used on MIME type lookup, but can be referenced directly. Especially useful for + # rendering different HTML versions depending on the user agent, like an iPhone. + def register_alias(string, symbol, extension_synonyms = []) + register(string, symbol, [], extension_synonyms, true) + end + + def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], skip_lookup = false) + new_mime = Type.new(string, symbol, mime_type_synonyms) + + SET << new_mime + + ([string] + mime_type_synonyms).each { |str| LOOKUP[str] = new_mime } unless skip_lookup + ([symbol] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext.to_s] = new_mime } + + @register_callbacks.each do |callback| + callback.call(new_mime) + end + new_mime + end + + def parse(accept_header) + if !accept_header.include?(",") + accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first + parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)].compact + else + list, index = [], 0 + accept_header.split(",").each do |header| + params, q = header.split(PARAMETER_SEPARATOR_REGEXP) + + next unless params + params.strip! + next if params.empty? + + params = parse_trailing_star(params) || [params] + + params.each do |m| + list << AcceptItem.new(index, m.to_s, q) + index += 1 + end + end + AcceptList.sort! list + end + end + + def parse_trailing_star(accept_header) + parse_data_with_trailing_star($1) if accept_header =~ TRAILING_STAR_REGEXP + end + + # For an input of 'text', returns [Mime[:json], Mime[:xml], Mime[:ics], + # Mime[:html], Mime[:css], Mime[:csv], Mime[:js], Mime[:yaml], Mime[:text]. + # + # For an input of 'application', returns [Mime[:html], Mime[:js], + # Mime[:xml], Mime[:yaml], Mime[:atom], Mime[:json], Mime[:rss], Mime[:url_encoded_form]. + def parse_data_with_trailing_star(type) + Mime::SET.select { |m| m =~ type } + end + + # This method is opposite of register method. + # + # To unregister a MIME type: + # + # Mime::Type.unregister(:mobile) + def unregister(symbol) + symbol = symbol.downcase + if mime = Mime[symbol] + SET.delete_if { |v| v.eql?(mime) } + LOOKUP.delete_if { |_, v| v.eql?(mime) } + EXTENSION_LOOKUP.delete_if { |_, v| v.eql?(mime) } + end + end + end + + attr_reader :hash + + def initialize(string, symbol = nil, synonyms = []) + @symbol, @synonyms = symbol, synonyms + @string = string + @hash = [@string, @synonyms, @symbol].hash + end + + def to_s + @string + end + + def to_str + to_s + end + + def to_sym + @symbol + end + + def ref + symbol || to_s + end + + def ===(list) + if list.is_a?(Array) + (@synonyms + [ self ]).any? { |synonym| list.include?(synonym) } + else + super + end + end + + def ==(mime_type) + return false unless mime_type + (@synonyms + [ self ]).any? do |synonym| + synonym.to_s == mime_type.to_s || synonym.to_sym == mime_type.to_sym + end + end + + def eql?(other) + super || (self.class == other.class && + @string == other.string && + @synonyms == other.synonyms && + @symbol == other.symbol) + end + + def =~(mime_type) + return false unless mime_type + regexp = Regexp.new(Regexp.quote(mime_type.to_s)) + @synonyms.any? { |synonym| synonym.to_s =~ regexp } || @string =~ regexp + end + + def html? + symbol == :html || @string =~ /html/ + end + + def all?; false; end + + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected + + attr_reader :string, :synonyms + + private + + def to_ary; end + def to_a; end + + def method_missing(method, *args) + if method.to_s.ends_with? "?" + method[0..-2].downcase.to_sym == to_sym + else + super + end + end + + def respond_to_missing?(method, include_private = false) + (method.to_s.ends_with? "?") || super + end + end + + class AllType < Type + include Singleton + + def initialize + super "*/*", :all + end + + def all?; true; end + def html?; true; end + end + + # ALL isn't a real MIME type, so we don't register it for lookup with the + # other concrete types. It's a wildcard match that we use for `respond_to` + # negotiation internals. + ALL = AllType.instance + + class NullType + include Singleton + + def nil? + true + end + + def ref; end + + private + def respond_to_missing?(method, _) + method.to_s.ends_with? "?" + end + + def method_missing(method, *args) + false if method.to_s.ends_with? "?" + end + end +end + +require "action_dispatch/http/mime_types" diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/mime_types.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/mime_types.rb new file mode 100644 index 00000000..342e6de3 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/mime_types.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +# Build list of Mime types for HTTP responses +# https://www.iana.org/assignments/media-types/ + +Mime::Type.register "text/html", :html, %w( application/xhtml+xml ), %w( xhtml ) +Mime::Type.register "text/plain", :text, [], %w(txt) +Mime::Type.register "text/javascript", :js, %w( application/javascript application/x-javascript ) +Mime::Type.register "text/css", :css +Mime::Type.register "text/calendar", :ics +Mime::Type.register "text/csv", :csv +Mime::Type.register "text/vcard", :vcf +Mime::Type.register "text/vtt", :vtt, %w(vtt) + +Mime::Type.register "image/png", :png, [], %w(png) +Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe pjpeg) +Mime::Type.register "image/gif", :gif, [], %w(gif) +Mime::Type.register "image/bmp", :bmp, [], %w(bmp) +Mime::Type.register "image/tiff", :tiff, [], %w(tif tiff) +Mime::Type.register "image/svg+xml", :svg + +Mime::Type.register "video/mpeg", :mpeg, [], %w(mpg mpeg mpe) + +Mime::Type.register "audio/mpeg", :mp3, [], %w(mp1 mp2 mp3) +Mime::Type.register "audio/ogg", :ogg, [], %w(oga ogg spx opus) +Mime::Type.register "audio/aac", :m4a, %w( audio/mp4 ), %w(m4a mpg4 aac) + +Mime::Type.register "video/webm", :webm, [], %w(webm) +Mime::Type.register "video/mp4", :mp4, [], %w(mp4 m4v) + +Mime::Type.register "font/otf", :otf, [], %w(otf) +Mime::Type.register "font/ttf", :ttf, [], %w(ttf) +Mime::Type.register "font/woff", :woff, [], %w(woff) +Mime::Type.register "font/woff2", :woff2, [], %w(woff2) + +Mime::Type.register "application/xml", :xml, %w( text/xml application/x-xml ) +Mime::Type.register "application/rss+xml", :rss +Mime::Type.register "application/atom+xml", :atom +Mime::Type.register "application/x-yaml", :yaml, %w( text/yaml ), %w(yml yaml) + +Mime::Type.register "multipart/form-data", :multipart_form +Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form + +# https://www.ietf.org/rfc/rfc4627.txt +# http://www.json.org/JSONRequest.html +Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest ) + +Mime::Type.register "application/pdf", :pdf, [], %w(pdf) +Mime::Type.register "application/zip", :zip, [], %w(zip) +Mime::Type.register "application/gzip", :gzip, %w(application/x-gzip), %w(gz) diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/parameter_filter.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/parameter_filter.rb new file mode 100644 index 00000000..1d589648 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/parameter_filter.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/duplicable" + +module ActionDispatch + module Http + class ParameterFilter + FILTERED = "[FILTERED]".freeze # :nodoc: + + def initialize(filters = []) + @filters = filters + end + + def filter(params) + compiled_filter.call(params) + end + + private + + def compiled_filter + @compiled_filter ||= CompiledFilter.compile(@filters) + end + + class CompiledFilter # :nodoc: + def self.compile(filters) + return lambda { |params| params.dup } if filters.empty? + + strings, regexps, blocks = [], [], [] + + filters.each do |item| + case item + when Proc + blocks << item + when Regexp + regexps << item + else + strings << Regexp.escape(item.to_s) + end + end + + deep_regexps, regexps = regexps.partition { |r| r.to_s.include?("\\.".freeze) } + deep_strings, strings = strings.partition { |s| s.include?("\\.".freeze) } + + regexps << Regexp.new(strings.join("|".freeze), true) unless strings.empty? + deep_regexps << Regexp.new(deep_strings.join("|".freeze), true) unless deep_strings.empty? + + new regexps, deep_regexps, blocks + end + + attr_reader :regexps, :deep_regexps, :blocks + + def initialize(regexps, deep_regexps, blocks) + @regexps = regexps + @deep_regexps = deep_regexps.any? ? deep_regexps : nil + @blocks = blocks + end + + def call(original_params, parents = []) + filtered_params = original_params.class.new + + original_params.each do |key, value| + parents.push(key) if deep_regexps + if regexps.any? { |r| key =~ r } + value = FILTERED + elsif deep_regexps && (joined = parents.join(".")) && deep_regexps.any? { |r| joined =~ r } + value = FILTERED + elsif value.is_a?(Hash) + value = call(value, parents) + elsif value.is_a?(Array) + value = value.map { |v| v.is_a?(Hash) ? call(v, parents) : v } + elsif blocks.any? + key = key.dup if key.duplicable? + value = value.dup if value.duplicable? + blocks.each { |b| b.call(key, value) } + end + parents.pop if deep_regexps + + filtered_params[key] = value + end + + filtered_params + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/parameters.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/parameters.rb new file mode 100644 index 00000000..8d7431fd --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/parameters.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +module ActionDispatch + module Http + module Parameters + extend ActiveSupport::Concern + + PARAMETERS_KEY = "action_dispatch.request.path_parameters" + + DEFAULT_PARSERS = { + Mime[:json].symbol => -> (raw_post) { + data = ActiveSupport::JSON.decode(raw_post) + data.is_a?(Hash) ? data : { _json: data } + } + } + + # Raised when raw data from the request cannot be parsed by the parser + # defined for request's content MIME type. + class ParseError < StandardError + def initialize + super($!.message) + end + end + + included do + class << self + # Returns the parameter parsers. + attr_reader :parameter_parsers + end + + self.parameter_parsers = DEFAULT_PARSERS + end + + module ClassMethods + # Configure the parameter parser for a given MIME type. + # + # It accepts a hash where the key is the symbol of the MIME type + # and the value is a proc. + # + # original_parsers = ActionDispatch::Request.parameter_parsers + # xml_parser = -> (raw_post) { Hash.from_xml(raw_post) || {} } + # new_parsers = original_parsers.merge(xml: xml_parser) + # ActionDispatch::Request.parameter_parsers = new_parsers + def parameter_parsers=(parsers) + @parameter_parsers = parsers.transform_keys { |key| key.respond_to?(:symbol) ? key.symbol : key } + end + end + + # Returns both GET and POST \parameters in a single hash. + def parameters + params = get_header("action_dispatch.request.parameters") + return params if params + + params = begin + request_parameters.merge(query_parameters) + rescue EOFError + query_parameters.dup + end + params.merge!(path_parameters) + params = set_binary_encoding(params, params[:controller], params[:action]) + set_header("action_dispatch.request.parameters", params) + params + end + alias :params :parameters + + def path_parameters=(parameters) #:nodoc: + delete_header("action_dispatch.request.parameters") + + parameters = set_binary_encoding(parameters, parameters[:controller], parameters[:action]) + # If any of the path parameters has an invalid encoding then + # raise since it's likely to trigger errors further on. + Request::Utils.check_param_encoding(parameters) + + set_header PARAMETERS_KEY, parameters + rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e + raise ActionController::BadRequest.new("Invalid path parameters: #{e.message}") + end + + # Returns a hash with the \parameters used to form the \path of the request. + # Returned hash keys are strings: + # + # {'action' => 'my_action', 'controller' => 'my_controller'} + def path_parameters + get_header(PARAMETERS_KEY) || set_header(PARAMETERS_KEY, {}) + end + + private + + def set_binary_encoding(params, controller, action) + return params unless controller && controller.valid_encoding? + + if binary_params_for?(controller, action) + ActionDispatch::Request::Utils.each_param_value(params) do |param| + param.force_encoding ::Encoding::ASCII_8BIT + end + end + params + end + + def binary_params_for?(controller, action) + controller_class_for(controller).binary_params_for?(action) + rescue NameError + false + end + + def parse_formatted_parameters(parsers) + return yield if content_length.zero? || content_mime_type.nil? + + strategy = parsers.fetch(content_mime_type.symbol) { return yield } + + begin + strategy.call(raw_post) + rescue # JSON or Ruby code block errors. + my_logger = logger || ActiveSupport::Logger.new($stderr) + my_logger.debug "Error occurred while parsing request parameters.\nContents:\n\n#{raw_post}" + + raise ParseError + end + end + + def params_parsers + ActionDispatch::Request.parameter_parsers + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/rack_cache.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/rack_cache.rb new file mode 100644 index 00000000..3e2d01ae --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/rack_cache.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require "rack/cache" +require "rack/cache/context" +require "active_support/cache" + +module ActionDispatch + class RailsMetaStore < Rack::Cache::MetaStore + def self.resolve(uri) + new + end + + def initialize(store = Rails.cache) + @store = store + end + + def read(key) + if data = @store.read(key) + Marshal.load(data) + else + [] + end + end + + def write(key, value) + @store.write(key, Marshal.dump(value)) + end + + ::Rack::Cache::MetaStore::RAILS = self + end + + class RailsEntityStore < Rack::Cache::EntityStore + def self.resolve(uri) + new + end + + def initialize(store = Rails.cache) + @store = store + end + + def exist?(key) + @store.exist?(key) + end + + def open(key) + @store.read(key) + end + + def read(key) + body = open(key) + body.join if body + end + + def write(body) + buf = [] + key, size = slurp(body) { |part| buf << part } + @store.write(key, buf) + [key, size] + end + + ::Rack::Cache::EntityStore::RAILS = self + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/request.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/request.rb new file mode 100644 index 00000000..3838b84a --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/request.rb @@ -0,0 +1,430 @@ +# frozen_string_literal: true + +require "stringio" + +require "active_support/inflector" +require "action_dispatch/http/headers" +require "action_controller/metal/exceptions" +require "rack/request" +require "action_dispatch/http/cache" +require "action_dispatch/http/mime_negotiation" +require "action_dispatch/http/parameters" +require "action_dispatch/http/filter_parameters" +require "action_dispatch/http/upload" +require "action_dispatch/http/url" +require "active_support/core_ext/array/conversions" + +module ActionDispatch + class Request + include Rack::Request::Helpers + include ActionDispatch::Http::Cache::Request + include ActionDispatch::Http::MimeNegotiation + include ActionDispatch::Http::Parameters + include ActionDispatch::Http::FilterParameters + include ActionDispatch::Http::URL + include ActionDispatch::ContentSecurityPolicy::Request + include Rack::Request::Env + + autoload :Session, "action_dispatch/request/session" + autoload :Utils, "action_dispatch/request/utils" + + LOCALHOST = Regexp.union [/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, /^::1$/, /^0:0:0:0:0:0:0:1(%.*)?$/] + + ENV_METHODS = %w[ AUTH_TYPE GATEWAY_INTERFACE + PATH_TRANSLATED REMOTE_HOST + REMOTE_IDENT REMOTE_USER REMOTE_ADDR + SERVER_NAME SERVER_PROTOCOL + ORIGINAL_SCRIPT_NAME + + HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING + HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM + HTTP_NEGOTIATE HTTP_PRAGMA HTTP_CLIENT_IP + HTTP_X_FORWARDED_FOR HTTP_ORIGIN HTTP_VERSION + HTTP_X_CSRF_TOKEN HTTP_X_REQUEST_ID HTTP_X_FORWARDED_HOST + SERVER_ADDR + ].freeze + + ENV_METHODS.each do |env| + class_eval <<-METHOD, __FILE__, __LINE__ + 1 + def #{env.sub(/^HTTP_/n, '').downcase} # def accept_charset + get_header "#{env}".freeze # get_header "HTTP_ACCEPT_CHARSET".freeze + end # end + METHOD + end + + def self.empty + new({}) + end + + def initialize(env) + super + @method = nil + @request_method = nil + @remote_ip = nil + @original_fullpath = nil + @fullpath = nil + @ip = nil + end + + def commit_cookie_jar! # :nodoc: + end + + PASS_NOT_FOUND = Class.new { # :nodoc: + def self.action(_); self; end + def self.call(_); [404, { "X-Cascade" => "pass" }, []]; end + def self.binary_params_for?(action); false; end + } + + def controller_class + params = path_parameters + params[:action] ||= "index" + controller_class_for(params[:controller]) + end + + def controller_class_for(name) + if name + controller_param = name.underscore + const_name = "#{controller_param.camelize}Controller" + ActiveSupport::Dependencies.constantize(const_name) + else + PASS_NOT_FOUND + end + end + + # Returns true if the request has a header matching the given key parameter. + # + # request.key? :ip_spoofing_check # => true + def key?(key) + has_header? key + end + + # List of HTTP request methods from the following RFCs: + # Hypertext Transfer Protocol -- HTTP/1.1 (https://www.ietf.org/rfc/rfc2616.txt) + # HTTP Extensions for Distributed Authoring -- WEBDAV (https://www.ietf.org/rfc/rfc2518.txt) + # Versioning Extensions to WebDAV (https://www.ietf.org/rfc/rfc3253.txt) + # Ordered Collections Protocol (WebDAV) (https://www.ietf.org/rfc/rfc3648.txt) + # Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol (https://www.ietf.org/rfc/rfc3744.txt) + # Web Distributed Authoring and Versioning (WebDAV) SEARCH (https://www.ietf.org/rfc/rfc5323.txt) + # Calendar Extensions to WebDAV (https://www.ietf.org/rfc/rfc4791.txt) + # PATCH Method for HTTP (https://www.ietf.org/rfc/rfc5789.txt) + RFC2616 = %w(OPTIONS GET HEAD POST PUT DELETE TRACE CONNECT) + RFC2518 = %w(PROPFIND PROPPATCH MKCOL COPY MOVE LOCK UNLOCK) + RFC3253 = %w(VERSION-CONTROL REPORT CHECKOUT CHECKIN UNCHECKOUT MKWORKSPACE UPDATE LABEL MERGE BASELINE-CONTROL MKACTIVITY) + RFC3648 = %w(ORDERPATCH) + RFC3744 = %w(ACL) + RFC5323 = %w(SEARCH) + RFC4791 = %w(MKCALENDAR) + RFC5789 = %w(PATCH) + + HTTP_METHODS = RFC2616 + RFC2518 + RFC3253 + RFC3648 + RFC3744 + RFC5323 + RFC4791 + RFC5789 + + HTTP_METHOD_LOOKUP = {} + + # Populate the HTTP method lookup cache. + HTTP_METHODS.each { |method| + HTTP_METHOD_LOOKUP[method] = method.underscore.to_sym + } + + # Returns the HTTP \method that the application should see. + # In the case where the \method was overridden by a middleware + # (for instance, if a HEAD request was converted to a GET, + # or if a _method parameter was used to determine the \method + # the application should use), this \method returns the overridden + # value, not the original. + def request_method + @request_method ||= check_method(super) + end + + def routes # :nodoc: + get_header("action_dispatch.routes".freeze) + end + + def routes=(routes) # :nodoc: + set_header("action_dispatch.routes".freeze, routes) + end + + def engine_script_name(_routes) # :nodoc: + get_header(_routes.env_key) + end + + def engine_script_name=(name) # :nodoc: + set_header(routes.env_key, name.dup) + end + + def request_method=(request_method) #:nodoc: + if check_method(request_method) + @request_method = set_header("REQUEST_METHOD", request_method) + end + end + + def controller_instance # :nodoc: + get_header("action_controller.instance".freeze) + end + + def controller_instance=(controller) # :nodoc: + set_header("action_controller.instance".freeze, controller) + end + + def http_auth_salt + get_header "action_dispatch.http_auth_salt" + end + + def show_exceptions? # :nodoc: + # We're treating `nil` as "unset", and we want the default setting to be + # `true`. This logic should be extracted to `env_config` and calculated + # once. + !(get_header("action_dispatch.show_exceptions".freeze) == false) + end + + # Returns a symbol form of the #request_method. + def request_method_symbol + HTTP_METHOD_LOOKUP[request_method] + end + + # Returns the original value of the environment's REQUEST_METHOD, + # even if it was overridden by middleware. See #request_method for + # more information. + def method + @method ||= check_method(get_header("rack.methodoverride.original_method") || get_header("REQUEST_METHOD")) + end + + # Returns a symbol form of the #method. + def method_symbol + HTTP_METHOD_LOOKUP[method] + end + + # Provides access to the request's HTTP headers, for example: + # + # request.headers["Content-Type"] # => "text/plain" + def headers + @headers ||= Http::Headers.new(self) + end + + # Early Hints is an HTTP/2 status code that indicates hints to help a client start + # making preparations for processing the final response. + # + # If the env contains +rack.early_hints+ then the server accepts HTTP2 push for Link headers. + # + # The +send_early_hints+ method accepts a hash of links as follows: + # + # send_early_hints("Link" => "; rel=preload; as=style\n; rel=preload") + # + # If you are using +javascript_include_tag+ or +stylesheet_link_tag+ the + # Early Hints headers are included by default if supported. + def send_early_hints(links) + return unless env["rack.early_hints"] + + env["rack.early_hints"].call(links) + end + + # Returns a +String+ with the last requested path including their params. + # + # # get '/foo' + # request.original_fullpath # => '/foo' + # + # # get '/foo?bar' + # request.original_fullpath # => '/foo?bar' + def original_fullpath + @original_fullpath ||= (get_header("ORIGINAL_FULLPATH") || fullpath) + end + + # Returns the +String+ full path including params of the last URL requested. + # + # # get "/articles" + # request.fullpath # => "/articles" + # + # # get "/articles?page=2" + # request.fullpath # => "/articles?page=2" + def fullpath + @fullpath ||= super + end + + # Returns the original request URL as a +String+. + # + # # get "/articles?page=2" + # request.original_url # => "http://www.example.com/articles?page=2" + def original_url + base_url + original_fullpath + end + + # The +String+ MIME type of the request. + # + # # get "/articles" + # request.media_type # => "application/x-www-form-urlencoded" + def media_type + content_mime_type.to_s + end + + # Returns the content length of the request as an integer. + def content_length + super.to_i + end + + # Returns true if the "X-Requested-With" header contains "XMLHttpRequest" + # (case-insensitive), which may need to be manually added depending on the + # choice of JavaScript libraries and frameworks. + def xml_http_request? + get_header("HTTP_X_REQUESTED_WITH") =~ /XMLHttpRequest/i + end + alias :xhr? :xml_http_request? + + # Returns the IP address of client as a +String+. + def ip + @ip ||= super + end + + # Returns the IP address of client as a +String+, + # usually set by the RemoteIp middleware. + def remote_ip + @remote_ip ||= (get_header("action_dispatch.remote_ip") || ip).to_s + end + + def remote_ip=(remote_ip) + set_header "action_dispatch.remote_ip".freeze, remote_ip + end + + ACTION_DISPATCH_REQUEST_ID = "action_dispatch.request_id".freeze # :nodoc: + + # Returns the unique request id, which is based on either the X-Request-Id header that can + # be generated by a firewall, load balancer, or web server or by the RequestId middleware + # (which sets the action_dispatch.request_id environment variable). + # + # This unique ID is useful for tracing a request from end-to-end as part of logging or debugging. + # This relies on the Rack variable set by the ActionDispatch::RequestId middleware. + def request_id + get_header ACTION_DISPATCH_REQUEST_ID + end + + def request_id=(id) # :nodoc: + set_header ACTION_DISPATCH_REQUEST_ID, id + end + + alias_method :uuid, :request_id + + # Returns the lowercase name of the HTTP server software. + def server_software + (get_header("SERVER_SOFTWARE") && /^([a-zA-Z]+)/ =~ get_header("SERVER_SOFTWARE")) ? $1.downcase : nil + end + + # Read the request \body. This is useful for web services that need to + # work with raw requests directly. + def raw_post + unless has_header? "RAW_POST_DATA" + raw_post_body = body + set_header("RAW_POST_DATA", raw_post_body.read(content_length)) + raw_post_body.rewind if raw_post_body.respond_to?(:rewind) + end + get_header "RAW_POST_DATA" + end + + # The request body is an IO input stream. If the RAW_POST_DATA environment + # variable is already set, wrap it in a StringIO. + def body + if raw_post = get_header("RAW_POST_DATA") + raw_post = raw_post.dup.force_encoding(Encoding::BINARY) + StringIO.new(raw_post) + else + body_stream + end + end + + # Determine whether the request body contains form-data by checking + # the request Content-Type for one of the media-types: + # "application/x-www-form-urlencoded" or "multipart/form-data". The + # list of form-data media types can be modified through the + # +FORM_DATA_MEDIA_TYPES+ array. + # + # A request body is not assumed to contain form-data when no + # Content-Type header is provided and the request_method is POST. + def form_data? + FORM_DATA_MEDIA_TYPES.include?(media_type) + end + + def body_stream #:nodoc: + get_header("rack.input") + end + + # TODO This should be broken apart into AD::Request::Session and probably + # be included by the session middleware. + def reset_session + if session && session.respond_to?(:destroy) + session.destroy + else + self.session = {} + end + end + + def session=(session) #:nodoc: + Session.set self, session + end + + def session_options=(options) + Session::Options.set self, options + end + + # Override Rack's GET method to support indifferent access. + def GET + fetch_header("action_dispatch.request.query_parameters") do |k| + rack_query_params = super || {} + # Check for non UTF-8 parameter values, which would cause errors later + Request::Utils.check_param_encoding(rack_query_params) + set_header k, Request::Utils.normalize_encode_params(rack_query_params) + end + rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e + raise ActionController::BadRequest.new("Invalid query parameters: #{e.message}") + end + alias :query_parameters :GET + + # Override Rack's POST method to support indifferent access. + def POST + fetch_header("action_dispatch.request.request_parameters") do + pr = parse_formatted_parameters(params_parsers) do |params| + super || {} + end + self.request_parameters = Request::Utils.normalize_encode_params(pr) + end + rescue Http::Parameters::ParseError # one of the parse strategies blew up + self.request_parameters = Request::Utils.normalize_encode_params(super || {}) + raise + rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e + raise ActionController::BadRequest.new("Invalid request parameters: #{e.message}") + end + alias :request_parameters :POST + + # Returns the authorization header regardless of whether it was specified directly or through one of the + # proxy alternatives. + def authorization + get_header("HTTP_AUTHORIZATION") || + get_header("X-HTTP_AUTHORIZATION") || + get_header("X_HTTP_AUTHORIZATION") || + get_header("REDIRECT_X_HTTP_AUTHORIZATION") + end + + # True if the request came from localhost, 127.0.0.1, or ::1. + def local? + LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip + end + + def request_parameters=(params) + raise if params.nil? + set_header("action_dispatch.request.request_parameters".freeze, params) + end + + def logger + get_header("action_dispatch.logger".freeze) + end + + def commit_flash + end + + def ssl? + super || scheme == "wss".freeze + end + + private + def check_method(name) + HTTP_METHOD_LOOKUP[name] || raise(ActionController::UnknownHttpMethod, "#{name}, accepted HTTP methods are #{HTTP_METHODS[0...-1].join(', ')}, and #{HTTP_METHODS[-1]}") + name + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/response.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/response.rb new file mode 100644 index 00000000..49348350 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/response.rb @@ -0,0 +1,519 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attribute_accessors" +require "action_dispatch/http/filter_redirect" +require "action_dispatch/http/cache" +require "monitor" + +module ActionDispatch # :nodoc: + # Represents an HTTP response generated by a controller action. Use it to + # retrieve the current state of the response, or customize the response. It can + # either represent a real HTTP response (i.e. one that is meant to be sent + # back to the web browser) or a TestResponse (i.e. one that is generated + # from integration tests). + # + # \Response is mostly a Ruby on \Rails framework implementation detail, and + # should never be used directly in controllers. Controllers should use the + # methods defined in ActionController::Base instead. For example, if you want + # to set the HTTP response's content MIME type, then use + # ActionControllerBase#headers instead of Response#headers. + # + # Nevertheless, integration tests may want to inspect controller responses in + # more detail, and that's when \Response can be useful for application + # developers. Integration test methods such as + # ActionDispatch::Integration::Session#get and + # ActionDispatch::Integration::Session#post return objects of type + # TestResponse (which are of course also of type \Response). + # + # For example, the following demo integration test prints the body of the + # controller response to the console: + # + # class DemoControllerTest < ActionDispatch::IntegrationTest + # def test_print_root_path_to_console + # get('/') + # puts response.body + # end + # end + class Response + class Header < DelegateClass(Hash) # :nodoc: + def initialize(response, header) + @response = response + super(header) + end + + def []=(k, v) + if @response.sending? || @response.sent? + raise ActionDispatch::IllegalStateError, "header already sent" + end + + super + end + + def merge(other) + self.class.new @response, __getobj__.merge(other) + end + + def to_hash + __getobj__.dup + end + end + + # The request that the response is responding to. + attr_accessor :request + + # The HTTP status code. + attr_reader :status + + # Get headers for this response. + attr_reader :header + + alias_method :headers, :header + + delegate :[], :[]=, to: :@header + + def each(&block) + sending! + x = @stream.each(&block) + sent! + x + end + + CONTENT_TYPE = "Content-Type".freeze + SET_COOKIE = "Set-Cookie".freeze + LOCATION = "Location".freeze + NO_CONTENT_CODES = [100, 101, 102, 204, 205, 304] + CONTENT_TYPE_PARSER = /\A(?[^;\s]+)?(?:.*;\s*charset=(?"?)(?[^;\s]+)\k)?/ # :nodoc: + + cattr_accessor :default_charset, default: "utf-8" + cattr_accessor :default_headers + + include Rack::Response::Helpers + # Aliasing these off because AD::Http::Cache::Response defines them. + alias :_cache_control :cache_control + alias :_cache_control= :cache_control= + + include ActionDispatch::Http::FilterRedirect + include ActionDispatch::Http::Cache::Response + include MonitorMixin + + class Buffer # :nodoc: + def initialize(response, buf) + @response = response + @buf = buf + @closed = false + @str_body = nil + end + + def body + @str_body ||= begin + buf = "".dup + each { |chunk| buf << chunk } + buf + end + end + + def write(string) + raise IOError, "closed stream" if closed? + + @str_body = nil + @response.commit! + @buf.push string + end + + def each(&block) + if @str_body + return enum_for(:each) unless block_given? + + yield @str_body + else + each_chunk(&block) + end + end + + def abort + end + + def close + @response.commit! + @closed = true + end + + def closed? + @closed + end + + private + + def each_chunk(&block) + @buf.each(&block) + end + end + + def self.create(status = 200, header = {}, body = [], default_headers: self.default_headers) + header = merge_default_headers(header, default_headers) + new status, header, body + end + + def self.merge_default_headers(original, default) + default.respond_to?(:merge) ? default.merge(original) : original + end + + # The underlying body, as a streamable object. + attr_reader :stream + + def initialize(status = 200, header = {}, body = []) + super() + + @header = Header.new(self, header) + + self.body, self.status = body, status + + @cv = new_cond + @committed = false + @sending = false + @sent = false + + prepare_cache_control! + + yield self if block_given? + end + + def has_header?(key); headers.key? key; end + def get_header(key); headers[key]; end + def set_header(key, v); headers[key] = v; end + def delete_header(key); headers.delete key; end + + def await_commit + synchronize do + @cv.wait_until { @committed } + end + end + + def await_sent + synchronize { @cv.wait_until { @sent } } + end + + def commit! + synchronize do + before_committed + @committed = true + @cv.broadcast + end + end + + def sending! + synchronize do + before_sending + @sending = true + @cv.broadcast + end + end + + def sent! + synchronize do + @sent = true + @cv.broadcast + end + end + + def sending?; synchronize { @sending }; end + def committed?; synchronize { @committed }; end + def sent?; synchronize { @sent }; end + + # Sets the HTTP status code. + def status=(status) + @status = Rack::Utils.status_code(status) + end + + # Sets the HTTP content type. + def content_type=(content_type) + return unless content_type + new_header_info = parse_content_type(content_type.to_s) + prev_header_info = parsed_content_type_header + charset = new_header_info.charset || prev_header_info.charset + charset ||= self.class.default_charset unless prev_header_info.mime_type + set_content_type new_header_info.mime_type, charset + end + + # Sets the HTTP response's content MIME type. For example, in the controller + # you could write this: + # + # response.content_type = "text/plain" + # + # If a character set has been defined for this response (see charset=) then + # the character set information will also be included in the content type + # information. + + def content_type + parsed_content_type_header.mime_type + end + + def sending_file=(v) + if true == v + self.charset = false + end + end + + # Sets the HTTP character set. In case of +nil+ parameter + # it sets the charset to +default_charset+. + # + # response.charset = 'utf-16' # => 'utf-16' + # response.charset = nil # => 'utf-8' + def charset=(charset) + content_type = parsed_content_type_header.mime_type + if false == charset + set_content_type content_type, nil + else + set_content_type content_type, charset || self.class.default_charset + end + end + + # The charset of the response. HTML wants to know the encoding of the + # content you're giving them, so we need to send that along. + def charset + header_info = parsed_content_type_header + header_info.charset || self.class.default_charset + end + + # The response code of the request. + def response_code + @status + end + + # Returns a string to ensure compatibility with Net::HTTPResponse. + def code + @status.to_s + end + + # Returns the corresponding message for the current HTTP status code: + # + # response.status = 200 + # response.message # => "OK" + # + # response.status = 404 + # response.message # => "Not Found" + # + def message + Rack::Utils::HTTP_STATUS_CODES[@status] + end + alias_method :status_message, :message + + # Returns the content of the response as a string. This contains the contents + # of any calls to render. + def body + @stream.body + end + + def write(string) + @stream.write string + end + + # Allows you to manually set or override the response body. + def body=(body) + if body.respond_to?(:to_path) + @stream = body + else + synchronize do + @stream = build_buffer self, munge_body_object(body) + end + end + end + + # Avoid having to pass an open file handle as the response body. + # Rack::Sendfile will usually intercept the response and uses + # the path directly, so there is no reason to open the file. + class FileBody #:nodoc: + attr_reader :to_path + + def initialize(path) + @to_path = path + end + + def body + File.binread(to_path) + end + + # Stream the file's contents if Rack::Sendfile isn't present. + def each + File.open(to_path, "rb") do |file| + while chunk = file.read(16384) + yield chunk + end + end + end + end + + # Send the file stored at +path+ as the response body. + def send_file(path) + commit! + @stream = FileBody.new(path) + end + + def reset_body! + @stream = build_buffer(self, []) + end + + def body_parts + parts = [] + @stream.each { |x| parts << x } + parts + end + + # The location header we'll be responding with. + alias_method :redirect_url, :location + + def close + stream.close if stream.respond_to?(:close) + end + + def abort + if stream.respond_to?(:abort) + stream.abort + elsif stream.respond_to?(:close) + # `stream.close` should really be reserved for a close from the + # other direction, but we must fall back to it for + # compatibility. + stream.close + end + end + + # Turns the Response into a Rack-compatible array of the status, headers, + # and body. Allows explicit splatting: + # + # status, headers, body = *response + def to_a + commit! + rack_response @status, @header.to_hash + end + alias prepare! to_a + + # Returns the response cookies, converted to a Hash of (name => value) pairs + # + # assert_equal 'AuthorOfNewPage', r.cookies['author'] + def cookies + cookies = {} + if header = get_header(SET_COOKIE) + header = header.split("\n") if header.respond_to?(:to_str) + header.each do |cookie| + if pair = cookie.split(";").first + key, value = pair.split("=").map { |v| Rack::Utils.unescape(v) } + cookies[key] = value + end + end + end + cookies + end + + private + + ContentTypeHeader = Struct.new :mime_type, :charset + NullContentTypeHeader = ContentTypeHeader.new nil, nil + + def parse_content_type(content_type) + if content_type && match = CONTENT_TYPE_PARSER.match(content_type) + ContentTypeHeader.new(match[:type], match[:charset]) + else + NullContentTypeHeader + end + end + + # Small internal convenience method to get the parsed version of the current + # content type header. + def parsed_content_type_header + parse_content_type(get_header(CONTENT_TYPE)) + end + + def set_content_type(content_type, charset) + type = (content_type || "").dup + type << "; charset=#{charset.to_s.downcase}" if charset + set_header CONTENT_TYPE, type + end + + def before_committed + return if committed? + assign_default_content_type_and_charset! + merge_and_normalize_cache_control!(@cache_control) + handle_conditional_get! + handle_no_content! + end + + def before_sending + # Normally we've already committed by now, but it's possible + # (e.g., if the controller action tries to read back its own + # response) to get here before that. In that case, we must force + # an "early" commit: we're about to freeze the headers, so this is + # our last chance. + commit! unless committed? + + headers.freeze + request.commit_cookie_jar! unless committed? + end + + def build_buffer(response, body) + Buffer.new response, body + end + + def munge_body_object(body) + body.respond_to?(:each) ? body : [body] + end + + def assign_default_content_type_and_charset! + return if content_type + + ct = parsed_content_type_header + set_content_type(ct.mime_type || Mime[:html].to_s, + ct.charset || self.class.default_charset) + end + + class RackBody + def initialize(response) + @response = response + end + + def each(*args, &block) + @response.each(*args, &block) + end + + def close + # Rack "close" maps to Response#abort, and *not* Response#close + # (which is used when the controller's finished writing) + @response.abort + end + + def body + @response.body + end + + def respond_to?(method, include_private = false) + if method.to_s == "to_path" + @response.stream.respond_to?(method) + else + super + end + end + + def to_path + @response.stream.to_path + end + + def to_ary + nil + end + end + + def handle_no_content! + if NO_CONTENT_CODES.include?(@status) + @header.delete CONTENT_TYPE + @header.delete "Content-Length" + end + end + + def rack_response(status, header) + if NO_CONTENT_CODES.include?(status) + [status, header, []] + else + [status, header, RackBody.new(self)] + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/upload.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/upload.rb new file mode 100644 index 00000000..0b162dc7 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/upload.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +module ActionDispatch + module Http + # Models uploaded files. + # + # The actual file is accessible via the +tempfile+ accessor, though some + # of its interface is available directly for convenience. + # + # Uploaded files are temporary files whose lifespan is one request. When + # the object is finalized Ruby unlinks the file, so there is no need to + # clean them with a separate maintenance task. + class UploadedFile + # The basename of the file in the client. + attr_accessor :original_filename + + # A string with the MIME type of the file. + attr_accessor :content_type + + # A +Tempfile+ object with the actual uploaded file. Note that some of + # its interface is available directly. + attr_accessor :tempfile + alias :to_io :tempfile + + # A string with the headers of the multipart request. + attr_accessor :headers + + def initialize(hash) # :nodoc: + @tempfile = hash[:tempfile] + raise(ArgumentError, ":tempfile is required") unless @tempfile + + if hash[:filename] + @original_filename = hash[:filename].dup + + begin + @original_filename.encode!(Encoding::UTF_8) + rescue EncodingError + @original_filename.force_encoding(Encoding::UTF_8) + end + else + @original_filename = nil + end + + @content_type = hash[:type] + @headers = hash[:head] + end + + # Shortcut for +tempfile.read+. + def read(length = nil, buffer = nil) + @tempfile.read(length, buffer) + end + + # Shortcut for +tempfile.open+. + def open + @tempfile.open + end + + # Shortcut for +tempfile.close+. + def close(unlink_now = false) + @tempfile.close(unlink_now) + end + + # Shortcut for +tempfile.path+. + def path + @tempfile.path + end + + # Shortcut for +tempfile.rewind+. + def rewind + @tempfile.rewind + end + + # Shortcut for +tempfile.size+. + def size + @tempfile.size + end + + # Shortcut for +tempfile.eof?+. + def eof? + @tempfile.eof? + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/url.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/url.rb new file mode 100644 index 00000000..35ba4400 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/http/url.rb @@ -0,0 +1,350 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attribute_accessors" + +module ActionDispatch + module Http + module URL + IP_HOST_REGEXP = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/ + HOST_REGEXP = /(^[^:]+:\/\/)?(\[[^\]]+\]|[^:]+)(?::(\d+$))?/ + PROTOCOL_REGEXP = /^([^:]+)(:)?(\/\/)?$/ + + mattr_accessor :tld_length, default: 1 + + class << self + # Returns the domain part of a host given the domain level. + # + # # Top-level domain example + # extract_domain('www.example.com', 1) # => "example.com" + # # Second-level domain example + # extract_domain('dev.www.example.co.uk', 2) # => "example.co.uk" + def extract_domain(host, tld_length) + extract_domain_from(host, tld_length) if named_host?(host) + end + + # Returns the subdomains of a host as an Array given the domain level. + # + # # Top-level domain example + # extract_subdomains('www.example.com', 1) # => ["www"] + # # Second-level domain example + # extract_subdomains('dev.www.example.co.uk', 2) # => ["dev", "www"] + def extract_subdomains(host, tld_length) + if named_host?(host) + extract_subdomains_from(host, tld_length) + else + [] + end + end + + # Returns the subdomains of a host as a String given the domain level. + # + # # Top-level domain example + # extract_subdomain('www.example.com', 1) # => "www" + # # Second-level domain example + # extract_subdomain('dev.www.example.co.uk', 2) # => "dev.www" + def extract_subdomain(host, tld_length) + extract_subdomains(host, tld_length).join(".") + end + + def url_for(options) + if options[:only_path] + path_for options + else + full_url_for options + end + end + + def full_url_for(options) + host = options[:host] + protocol = options[:protocol] + port = options[:port] + + unless host + raise ArgumentError, "Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true" + end + + build_host_url(host, port, protocol, options, path_for(options)) + end + + def path_for(options) + path = options[:script_name].to_s.chomp("/".freeze) + path << options[:path] if options.key?(:path) + + add_trailing_slash(path) if options[:trailing_slash] + add_params(path, options[:params]) if options.key?(:params) + add_anchor(path, options[:anchor]) if options.key?(:anchor) + + path + end + + private + + def add_params(path, params) + params = { params: params } unless params.is_a?(Hash) + params.reject! { |_, v| v.to_param.nil? } + query = params.to_query + path << "?#{query}" unless query.empty? + end + + def add_anchor(path, anchor) + if anchor + path << "##{Journey::Router::Utils.escape_fragment(anchor.to_param)}" + end + end + + def extract_domain_from(host, tld_length) + host.split(".").last(1 + tld_length).join(".") + end + + def extract_subdomains_from(host, tld_length) + parts = host.split(".") + parts[0..-(tld_length + 2)] + end + + def add_trailing_slash(path) + if path.include?("?") + path.sub!(/\?/, '/\&') + elsif !path.include?(".") + path.sub!(/[^\/]\z|\A\z/, '\&/') + end + end + + def build_host_url(host, port, protocol, options, path) + if match = host.match(HOST_REGEXP) + protocol ||= match[1] unless protocol == false + host = match[2] + port = match[3] unless options.key? :port + end + + protocol = normalize_protocol protocol + host = normalize_host(host, options) + + result = protocol.dup + + if options[:user] && options[:password] + result << "#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@" + end + + result << host + normalize_port(port, protocol) { |normalized_port| + result << ":#{normalized_port}" + } + + result.concat path + end + + def named_host?(host) + IP_HOST_REGEXP !~ host + end + + def normalize_protocol(protocol) + case protocol + when nil + "http://" + when false, "//" + "//" + when PROTOCOL_REGEXP + "#{$1}://" + else + raise ArgumentError, "Invalid :protocol option: #{protocol.inspect}" + end + end + + def normalize_host(_host, options) + return _host unless named_host?(_host) + + tld_length = options[:tld_length] || @@tld_length + subdomain = options.fetch :subdomain, true + domain = options[:domain] + + host = "".dup + if subdomain == true + return _host if domain.nil? + + host << extract_subdomains_from(_host, tld_length).join(".") + elsif subdomain + host << subdomain.to_param + end + host << "." unless host.empty? + host << (domain || extract_domain_from(_host, tld_length)) + host + end + + def normalize_port(port, protocol) + return unless port + + case protocol + when "//" then yield port + when "https://" + yield port unless port.to_i == 443 + else + yield port unless port.to_i == 80 + end + end + end + + def initialize + super + @protocol = nil + @port = nil + end + + # Returns the complete URL used for this request. + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' + # req.url # => "http://example.com" + def url + protocol + host_with_port + fullpath + end + + # Returns 'https://' if this is an SSL request and 'http://' otherwise. + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' + # req.protocol # => "http://" + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com', 'HTTPS' => 'on' + # req.protocol # => "https://" + def protocol + @protocol ||= ssl? ? "https://" : "http://" + end + + # Returns the \host and port for this request, such as "example.com:8080". + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' + # req.raw_host_with_port # => "example.com" + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' + # req.raw_host_with_port # => "example.com:80" + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' + # req.raw_host_with_port # => "example.com:8080" + def raw_host_with_port + if forwarded = x_forwarded_host.presence + forwarded.split(/,\s?/).last + else + get_header("HTTP_HOST") || "#{server_name || server_addr}:#{get_header('SERVER_PORT')}" + end + end + + # Returns the host for this request, such as "example.com". + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' + # req.host # => "example.com" + def host + raw_host_with_port.sub(/:\d+$/, "".freeze) + end + + # Returns a \host:\port string for this request, such as "example.com" or + # "example.com:8080". Port is only included if it is not a default port + # (80 or 443) + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' + # req.host_with_port # => "example.com" + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' + # req.host_with_port # => "example.com" + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' + # req.host_with_port # => "example.com:8080" + def host_with_port + "#{host}#{port_string}" + end + + # Returns the port number of this request as an integer. + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com' + # req.port # => 80 + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' + # req.port # => 8080 + def port + @port ||= begin + if raw_host_with_port =~ /:(\d+)$/ + $1.to_i + else + standard_port + end + end + end + + # Returns the standard \port number for this request's protocol. + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' + # req.standard_port # => 80 + def standard_port + case protocol + when "https://" then 443 + else 80 + end + end + + # Returns whether this request is using the standard port + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' + # req.standard_port? # => true + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' + # req.standard_port? # => false + def standard_port? + port == standard_port + end + + # Returns a number \port suffix like 8080 if the \port number of this request + # is not the default HTTP \port 80 or HTTPS \port 443. + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' + # req.optional_port # => nil + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' + # req.optional_port # => 8080 + def optional_port + standard_port? ? nil : port + end + + # Returns a string \port suffix, including colon, like ":8080" if the \port + # number of this request is not the default HTTP \port 80 or HTTPS \port 443. + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:80' + # req.port_string # => "" + # + # req = ActionDispatch::Request.new 'HTTP_HOST' => 'example.com:8080' + # req.port_string # => ":8080" + def port_string + standard_port? ? "" : ":#{port}" + end + + # Returns the requested port, such as 8080, based on SERVER_PORT + # + # req = ActionDispatch::Request.new 'SERVER_PORT' => '80' + # req.server_port # => 80 + # + # req = ActionDispatch::Request.new 'SERVER_PORT' => '8080' + # req.server_port # => 8080 + def server_port + get_header("SERVER_PORT").to_i + end + + # Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify + # a different tld_length, such as 2 to catch rubyonrails.co.uk in "www.rubyonrails.co.uk". + def domain(tld_length = @@tld_length) + ActionDispatch::Http::URL.extract_domain(host, tld_length) + end + + # Returns all the \subdomains as an array, so ["dev", "www"] would be + # returned for "dev.www.rubyonrails.org". You can specify a different tld_length, + # such as 2 to catch ["www"] instead of ["www", "rubyonrails"] + # in "www.rubyonrails.co.uk". + def subdomains(tld_length = @@tld_length) + ActionDispatch::Http::URL.extract_subdomains(host, tld_length) + end + + # Returns all the \subdomains as a string, so "dev.www" would be + # returned for "dev.www.rubyonrails.org". You can specify a different tld_length, + # such as 2 to catch "www" instead of "www.rubyonrails" + # in "www.rubyonrails.co.uk". + def subdomain(tld_length = @@tld_length) + ActionDispatch::Http::URL.extract_subdomain(host, tld_length) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey.rb new file mode 100644 index 00000000..2852efa6 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "action_dispatch/journey/router" +require "action_dispatch/journey/gtg/builder" +require "action_dispatch/journey/gtg/simulator" +require "action_dispatch/journey/nfa/builder" +require "action_dispatch/journey/nfa/simulator" diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/formatter.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/formatter.rb new file mode 100644 index 00000000..0f04839d --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/formatter.rb @@ -0,0 +1,189 @@ +# frozen_string_literal: true + +require "action_controller/metal/exceptions" + +module ActionDispatch + # :stopdoc: + module Journey + # The Formatter class is used for formatting URLs. For example, parameters + # passed to +url_for+ in Rails will eventually call Formatter#generate. + class Formatter + attr_reader :routes + + def initialize(routes) + @routes = routes + @cache = nil + end + + def generate(name, options, path_parameters, parameterize = nil) + constraints = path_parameters.merge(options) + missing_keys = nil + + match_route(name, constraints) do |route| + parameterized_parts = extract_parameterized_parts(route, options, path_parameters, parameterize) + + # Skip this route unless a name has been provided or it is a + # standard Rails route since we can't determine whether an options + # hash passed to url_for matches a Rack application or a redirect. + next unless name || route.dispatcher? + + missing_keys = missing_keys(route, parameterized_parts) + next if missing_keys && !missing_keys.empty? + params = options.dup.delete_if do |key, _| + parameterized_parts.key?(key) || route.defaults.key?(key) + end + + defaults = route.defaults + required_parts = route.required_parts + + route.parts.reverse_each do |key| + break if defaults[key].nil? && parameterized_parts[key].present? + next if parameterized_parts[key].to_s != defaults[key].to_s + break if required_parts.include?(key) + + parameterized_parts.delete(key) + end + + return [route.format(parameterized_parts), params] + end + + unmatched_keys = (missing_keys || []) & constraints.keys + missing_keys = (missing_keys || []) - unmatched_keys + + message = "No route matches #{Hash[constraints.sort_by { |k, v| k.to_s }].inspect}".dup + message << ", missing required keys: #{missing_keys.sort.inspect}" if missing_keys && !missing_keys.empty? + message << ", possible unmatched constraints: #{unmatched_keys.sort.inspect}" if unmatched_keys && !unmatched_keys.empty? + + raise ActionController::UrlGenerationError, message + end + + def clear + @cache = nil + end + + private + + def extract_parameterized_parts(route, options, recall, parameterize = nil) + parameterized_parts = recall.merge(options) + + keys_to_keep = route.parts.reverse_each.drop_while { |part| + !options.key?(part) || (options[part] || recall[part]).nil? + } | route.required_parts + + parameterized_parts.delete_if do |bad_key, _| + !keys_to_keep.include?(bad_key) + end + + if parameterize + parameterized_parts.each do |k, v| + parameterized_parts[k] = parameterize.call(k, v) + end + end + + parameterized_parts.keep_if { |_, v| v } + parameterized_parts + end + + def named_routes + routes.named_routes + end + + def match_route(name, options) + if named_routes.key?(name) + yield named_routes[name] + else + routes = non_recursive(cache, options) + + supplied_keys = options.each_with_object({}) do |(k, v), h| + h[k.to_s] = true if v + end + + hash = routes.group_by { |_, r| r.score(supplied_keys) } + + hash.keys.sort.reverse_each do |score| + break if score < 0 + + hash[score].sort_by { |i, _| i }.each do |_, route| + yield route + end + end + end + end + + def non_recursive(cache, options) + routes = [] + queue = [cache] + + while queue.any? + c = queue.shift + routes.concat(c[:___routes]) if c.key?(:___routes) + + options.each do |pair| + queue << c[pair] if c.key?(pair) + end + end + + routes + end + + module RegexCaseComparator + DEFAULT_INPUT = /[-_.a-zA-Z0-9]+\/[-_.a-zA-Z0-9]+/ + DEFAULT_REGEX = /\A#{DEFAULT_INPUT}\Z/ + + def self.===(regex) + DEFAULT_INPUT == regex + end + end + + # Returns an array populated with missing keys if any are present. + def missing_keys(route, parts) + missing_keys = nil + tests = route.path.requirements + route.required_parts.each { |key| + case tests[key] + when nil + unless parts[key] + missing_keys ||= [] + missing_keys << key + end + when RegexCaseComparator + unless RegexCaseComparator::DEFAULT_REGEX === parts[key] + missing_keys ||= [] + missing_keys << key + end + else + unless /\A#{tests[key]}\Z/ === parts[key] + missing_keys ||= [] + missing_keys << key + end + end + } + missing_keys + end + + def possibles(cache, options, depth = 0) + cache.fetch(:___routes) { [] } + options.find_all { |pair| + cache.key?(pair) + }.flat_map { |pair| + possibles(cache[pair], options, depth + 1) + } + end + + def build_cache + root = { ___routes: [] } + routes.routes.each_with_index do |route, i| + leaf = route.required_defaults.inject(root) do |h, tuple| + h[tuple] ||= {} + end + (leaf[:___routes] ||= []) << [i, route] + end + root + end + + def cache + @cache ||= build_cache + end + end + end + # :startdoc: +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/gtg/builder.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/gtg/builder.rb new file mode 100644 index 00000000..44c31053 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/gtg/builder.rb @@ -0,0 +1,164 @@ +# frozen_string_literal: true + +require "action_dispatch/journey/gtg/transition_table" + +module ActionDispatch + module Journey # :nodoc: + module GTG # :nodoc: + class Builder # :nodoc: + DUMMY = Nodes::Dummy.new + + attr_reader :root, :ast, :endpoints + + def initialize(root) + @root = root + @ast = Nodes::Cat.new root, DUMMY + @followpos = nil + end + + def transition_table + dtrans = TransitionTable.new + marked = {} + state_id = Hash.new { |h, k| h[k] = h.length } + + start = firstpos(root) + dstates = [start] + until dstates.empty? + s = dstates.shift + next if marked[s] + marked[s] = true # mark s + + s.group_by { |state| symbol(state) }.each do |sym, ps| + u = ps.flat_map { |l| followpos(l) } + next if u.empty? + + if u.uniq == [DUMMY] + from = state_id[s] + to = state_id[Object.new] + dtrans[from, to] = sym + + dtrans.add_accepting(to) + ps.each { |state| dtrans.add_memo(to, state.memo) } + else + dtrans[state_id[s], state_id[u]] = sym + + if u.include?(DUMMY) + to = state_id[u] + + accepting = ps.find_all { |l| followpos(l).include?(DUMMY) } + + accepting.each { |accepting_state| + dtrans.add_memo(to, accepting_state.memo) + } + + dtrans.add_accepting(state_id[u]) + end + end + + dstates << u + end + end + + dtrans + end + + def nullable?(node) + case node + when Nodes::Group + true + when Nodes::Star + true + when Nodes::Or + node.children.any? { |c| nullable?(c) } + when Nodes::Cat + nullable?(node.left) && nullable?(node.right) + when Nodes::Terminal + !node.left + when Nodes::Unary + nullable?(node.left) + else + raise ArgumentError, "unknown nullable: %s" % node.class.name + end + end + + def firstpos(node) + case node + when Nodes::Star + firstpos(node.left) + when Nodes::Cat + if nullable?(node.left) + firstpos(node.left) | firstpos(node.right) + else + firstpos(node.left) + end + when Nodes::Or + node.children.flat_map { |c| firstpos(c) }.uniq + when Nodes::Unary + firstpos(node.left) + when Nodes::Terminal + nullable?(node) ? [] : [node] + else + raise ArgumentError, "unknown firstpos: %s" % node.class.name + end + end + + def lastpos(node) + case node + when Nodes::Star + firstpos(node.left) + when Nodes::Or + node.children.flat_map { |c| lastpos(c) }.uniq + when Nodes::Cat + if nullable?(node.right) + lastpos(node.left) | lastpos(node.right) + else + lastpos(node.right) + end + when Nodes::Terminal + nullable?(node) ? [] : [node] + when Nodes::Unary + lastpos(node.left) + else + raise ArgumentError, "unknown lastpos: %s" % node.class.name + end + end + + def followpos(node) + followpos_table[node] + end + + private + + def followpos_table + @followpos ||= build_followpos + end + + def build_followpos + table = Hash.new { |h, k| h[k] = [] } + @ast.each do |n| + case n + when Nodes::Cat + lastpos(n.left).each do |i| + table[i] += firstpos(n.right) + end + when Nodes::Star + lastpos(n).each do |i| + table[i] += firstpos(n) + end + end + end + table + end + + def symbol(edge) + case edge + when Journey::Nodes::Symbol + edge.regexp + else + edge.left + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/gtg/simulator.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/gtg/simulator.rb new file mode 100644 index 00000000..2ee4f5c3 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/gtg/simulator.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require "strscan" + +module ActionDispatch + module Journey # :nodoc: + module GTG # :nodoc: + class MatchData # :nodoc: + attr_reader :memos + + def initialize(memos) + @memos = memos + end + end + + class Simulator # :nodoc: + attr_reader :tt + + def initialize(transition_table) + @tt = transition_table + end + + def memos(string) + input = StringScanner.new(string) + state = [0] + while sym = input.scan(%r([/.?]|[^/.?]+)) + state = tt.move(state, sym) + end + + acceptance_states = state.find_all { |s| + tt.accepting? s + } + + return yield if acceptance_states.empty? + + acceptance_states.flat_map { |x| tt.memo(x) }.compact + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/gtg/transition_table.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/gtg/transition_table.rb new file mode 100644 index 00000000..ea647e05 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/gtg/transition_table.rb @@ -0,0 +1,158 @@ +# frozen_string_literal: true + +require "action_dispatch/journey/nfa/dot" + +module ActionDispatch + module Journey # :nodoc: + module GTG # :nodoc: + class TransitionTable # :nodoc: + include Journey::NFA::Dot + + attr_reader :memos + + def initialize + @regexp_states = {} + @string_states = {} + @accepting = {} + @memos = Hash.new { |h, k| h[k] = [] } + end + + def add_accepting(state) + @accepting[state] = true + end + + def accepting_states + @accepting.keys + end + + def accepting?(state) + @accepting[state] + end + + def add_memo(idx, memo) + @memos[idx] << memo + end + + def memo(idx) + @memos[idx] + end + + def eclosure(t) + Array(t) + end + + def move(t, a) + return [] if t.empty? + + regexps = [] + + t.map { |s| + if states = @regexp_states[s] + regexps.concat states.map { |re, v| re === a ? v : nil } + end + + if states = @string_states[s] + states[a] + end + }.compact.concat regexps + end + + def as_json(options = nil) + simple_regexp = Hash.new { |h, k| h[k] = {} } + + @regexp_states.each do |from, hash| + hash.each do |re, to| + simple_regexp[from][re.source] = to + end + end + + { + regexp_states: simple_regexp, + string_states: @string_states, + accepting: @accepting + } + end + + def to_svg + svg = IO.popen("dot -Tsvg", "w+") { |f| + f.write(to_dot) + f.close_write + f.readlines + } + 3.times { svg.shift } + svg.join.sub(/width="[^"]*"/, "").sub(/height="[^"]*"/, "") + end + + def visualizer(paths, title = "FSM") + viz_dir = File.join __dir__, "..", "visualizer" + fsm_js = File.read File.join(viz_dir, "fsm.js") + fsm_css = File.read File.join(viz_dir, "fsm.css") + erb = File.read File.join(viz_dir, "index.html.erb") + states = "function tt() { return #{to_json}; }" + + fun_routes = paths.sample(3).map do |ast| + ast.map { |n| + case n + when Nodes::Symbol + case n.left + when ":id" then rand(100).to_s + when ":format" then %w{ xml json }.sample + else + "omg" + end + when Nodes::Terminal then n.symbol + else + nil + end + }.compact.join + end + + stylesheets = [fsm_css] + svg = to_svg + javascripts = [states, fsm_js] + + fun_routes = fun_routes + stylesheets = stylesheets + svg = svg + javascripts = javascripts + + require "erb" + template = ERB.new erb + template.result(binding) + end + + def []=(from, to, sym) + to_mappings = states_hash_for(sym)[from] ||= {} + to_mappings[sym] = to + end + + def states + ss = @string_states.keys + @string_states.values.flat_map(&:values) + rs = @regexp_states.keys + @regexp_states.values.flat_map(&:values) + (ss + rs).uniq + end + + def transitions + @string_states.flat_map { |from, hash| + hash.map { |s, to| [from, s, to] } + } + @regexp_states.flat_map { |from, hash| + hash.map { |s, to| [from, s, to] } + } + end + + private + + def states_hash_for(sym) + case sym + when String + @string_states + when Regexp + @regexp_states + else + raise ArgumentError, "unknown symbol: %s" % sym.class + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/nfa/builder.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/nfa/builder.rb new file mode 100644 index 00000000..d22302e1 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/nfa/builder.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require "action_dispatch/journey/nfa/transition_table" +require "action_dispatch/journey/gtg/transition_table" + +module ActionDispatch + module Journey # :nodoc: + module NFA # :nodoc: + class Visitor < Visitors::Visitor # :nodoc: + def initialize(tt) + @tt = tt + @i = -1 + end + + def visit_CAT(node) + left = visit(node.left) + right = visit(node.right) + + @tt.merge(left.last, right.first) + + [left.first, right.last] + end + + def visit_GROUP(node) + from = @i += 1 + left = visit(node.left) + to = @i += 1 + + @tt.accepting = to + + @tt[from, left.first] = nil + @tt[left.last, to] = nil + @tt[from, to] = nil + + [from, to] + end + + def visit_OR(node) + from = @i += 1 + children = node.children.map { |c| visit(c) } + to = @i += 1 + + children.each do |child| + @tt[from, child.first] = nil + @tt[child.last, to] = nil + end + + @tt.accepting = to + + [from, to] + end + + def terminal(node) + from_i = @i += 1 # new state + to_i = @i += 1 # new state + + @tt[from_i, to_i] = node + @tt.accepting = to_i + @tt.add_memo(to_i, node.memo) + + [from_i, to_i] + end + end + + class Builder # :nodoc: + def initialize(ast) + @ast = ast + end + + def transition_table + tt = TransitionTable.new + Visitor.new(tt).accept(@ast) + tt + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/nfa/dot.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/nfa/dot.rb new file mode 100644 index 00000000..56e9e3c8 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/nfa/dot.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module ActionDispatch + module Journey # :nodoc: + module NFA # :nodoc: + module Dot # :nodoc: + def to_dot + edges = transitions.map { |from, sym, to| + " #{from} -> #{to} [label=\"#{sym || 'ε'}\"];" + } + + # memo_nodes = memos.values.flatten.map { |n| + # label = n + # if Journey::Route === n + # label = "#{n.verb.source} #{n.path.spec}" + # end + # " #{n.object_id} [label=\"#{label}\", shape=box];" + # } + # memo_edges = memos.flat_map { |k, memos| + # (memos || []).map { |v| " #{k} -> #{v.object_id};" } + # }.uniq + + <<-eodot +digraph nfa { + rankdir=LR; + node [shape = doublecircle]; + #{accepting_states.join ' '}; + node [shape = circle]; +#{edges.join "\n"} +} + eodot + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/nfa/simulator.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/nfa/simulator.rb new file mode 100644 index 00000000..8efe48d9 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/nfa/simulator.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require "strscan" + +module ActionDispatch + module Journey # :nodoc: + module NFA # :nodoc: + class MatchData # :nodoc: + attr_reader :memos + + def initialize(memos) + @memos = memos + end + end + + class Simulator # :nodoc: + attr_reader :tt + + def initialize(transition_table) + @tt = transition_table + end + + def simulate(string) + input = StringScanner.new(string) + state = tt.eclosure(0) + until input.eos? + sym = input.scan(%r([/.?]|[^/.?]+)) + + # FIXME: tt.eclosure is not needed for the GTG + state = tt.eclosure(tt.move(state, sym)) + end + + acceptance_states = state.find_all { |s| + tt.accepting?(tt.eclosure(s).sort.last) + } + + return if acceptance_states.empty? + + memos = acceptance_states.flat_map { |x| tt.memo(x) }.compact + + MatchData.new(memos) + end + + alias :=~ :simulate + alias :match :simulate + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/nfa/transition_table.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/nfa/transition_table.rb new file mode 100644 index 00000000..fe558615 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/nfa/transition_table.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +require "action_dispatch/journey/nfa/dot" + +module ActionDispatch + module Journey # :nodoc: + module NFA # :nodoc: + class TransitionTable # :nodoc: + include Journey::NFA::Dot + + attr_accessor :accepting + attr_reader :memos + + def initialize + @table = Hash.new { |h, f| h[f] = {} } + @memos = {} + @accepting = nil + @inverted = nil + end + + def accepting?(state) + accepting == state + end + + def accepting_states + [accepting] + end + + def add_memo(idx, memo) + @memos[idx] = memo + end + + def memo(idx) + @memos[idx] + end + + def []=(i, f, s) + @table[f][i] = s + end + + def merge(left, right) + @memos[right] = @memos.delete(left) + @table[right] = @table.delete(left) + end + + def states + (@table.keys + @table.values.flat_map(&:keys)).uniq + end + + # Returns set of NFA states to which there is a transition on ast symbol + # +a+ from some state +s+ in +t+. + def following_states(t, a) + Array(t).flat_map { |s| inverted[s][a] }.uniq + end + + # Returns set of NFA states to which there is a transition on ast symbol + # +a+ from some state +s+ in +t+. + def move(t, a) + Array(t).map { |s| + inverted[s].keys.compact.find_all { |sym| + sym === a + }.map { |sym| inverted[s][sym] } + }.flatten.uniq + end + + def alphabet + inverted.values.flat_map(&:keys).compact.uniq.sort_by(&:to_s) + end + + # Returns a set of NFA states reachable from some NFA state +s+ in set + # +t+ on nil-transitions alone. + def eclosure(t) + stack = Array(t) + seen = {} + children = [] + + until stack.empty? + s = stack.pop + next if seen[s] + + seen[s] = true + children << s + + stack.concat(inverted[s][nil]) + end + + children.uniq + end + + def transitions + @table.flat_map { |to, hash| + hash.map { |from, sym| [from, sym, to] } + } + end + + private + + def inverted + return @inverted if @inverted + + @inverted = Hash.new { |h, from| + h[from] = Hash.new { |j, s| j[s] = [] } + } + + @table.each { |to, hash| + hash.each { |from, sym| + if sym + sym = Nodes::Symbol === sym ? sym.regexp : sym.left + end + + @inverted[from][sym] << to + } + } + + @inverted + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/nodes/node.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/nodes/node.rb new file mode 100644 index 00000000..08b931a3 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/nodes/node.rb @@ -0,0 +1,140 @@ +# frozen_string_literal: true + +require "action_dispatch/journey/visitors" + +module ActionDispatch + module Journey # :nodoc: + module Nodes # :nodoc: + class Node # :nodoc: + include Enumerable + + attr_accessor :left, :memo + + def initialize(left) + @left = left + @memo = nil + end + + def each(&block) + Visitors::Each::INSTANCE.accept(self, block) + end + + def to_s + Visitors::String::INSTANCE.accept(self, "") + end + + def to_dot + Visitors::Dot::INSTANCE.accept(self) + end + + def to_sym + name.to_sym + end + + def name + left.tr "*:".freeze, "".freeze + end + + def type + raise NotImplementedError + end + + def symbol?; false; end + def literal?; false; end + def terminal?; false; end + def star?; false; end + def cat?; false; end + def group?; false; end + end + + class Terminal < Node # :nodoc: + alias :symbol :left + def terminal?; true; end + end + + class Literal < Terminal # :nodoc: + def literal?; true; end + def type; :LITERAL; end + end + + class Dummy < Literal # :nodoc: + def initialize(x = Object.new) + super + end + + def literal?; false; end + end + + %w{ Symbol Slash Dot }.each do |t| + class_eval <<-eoruby, __FILE__, __LINE__ + 1 + class #{t} < Terminal; + def type; :#{t.upcase}; end + end + eoruby + end + + class Symbol < Terminal # :nodoc: + attr_accessor :regexp + alias :symbol :regexp + attr_reader :name + + DEFAULT_EXP = /[^\.\/\?]+/ + def initialize(left) + super + @regexp = DEFAULT_EXP + @name = left.tr "*:".freeze, "".freeze + end + + def default_regexp? + regexp == DEFAULT_EXP + end + + def symbol?; true; end + end + + class Unary < Node # :nodoc: + def children; [left] end + end + + class Group < Unary # :nodoc: + def type; :GROUP; end + def group?; true; end + end + + class Star < Unary # :nodoc: + def star?; true; end + def type; :STAR; end + + def name + left.name.tr "*:", "" + end + end + + class Binary < Node # :nodoc: + attr_accessor :right + + def initialize(left, right) + super(left) + @right = right + end + + def children; [left, right] end + end + + class Cat < Binary # :nodoc: + def cat?; true; end + def type; :CAT; end + end + + class Or < Node # :nodoc: + attr_reader :children + + def initialize(children) + @children = children + end + + def type; :OR; end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/parser.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/parser.rb new file mode 100644 index 00000000..e002755b --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/parser.rb @@ -0,0 +1,199 @@ +# +# DO NOT MODIFY!!!! +# This file is automatically generated by Racc 1.4.14 +# from Racc grammar file "". +# + +require 'racc/parser.rb' + +# :stopdoc: + +require "action_dispatch/journey/parser_extras" +module ActionDispatch + module Journey + class Parser < Racc::Parser +##### State transition tables begin ### + +racc_action_table = [ + 13, 15, 14, 7, 19, 16, 8, 19, 13, 15, + 14, 7, 17, 16, 8, 13, 15, 14, 7, 21, + 16, 8, 13, 15, 14, 7, 24, 16, 8 ] + +racc_action_check = [ + 2, 2, 2, 2, 22, 2, 2, 2, 19, 19, + 19, 19, 1, 19, 19, 7, 7, 7, 7, 17, + 7, 7, 0, 0, 0, 0, 20, 0, 0 ] + +racc_action_pointer = [ + 20, 12, -2, nil, nil, nil, nil, 13, nil, nil, + nil, nil, nil, nil, nil, nil, nil, 19, nil, 6, + 20, nil, -5, nil, nil ] + +racc_action_default = [ + -19, -19, -2, -3, -4, -5, -6, -19, -10, -11, + -12, -13, -14, -15, -16, -17, -18, -19, -1, -19, + -19, 25, -8, -9, -7 ] + +racc_goto_table = [ + 1, 22, 18, 23, nil, nil, nil, 20 ] + +racc_goto_check = [ + 1, 2, 1, 3, nil, nil, nil, 1 ] + +racc_goto_pointer = [ + nil, 0, -18, -16, nil, nil, nil, nil, nil, nil, + nil ] + +racc_goto_default = [ + nil, nil, 2, 3, 4, 5, 6, 9, 10, 11, + 12 ] + +racc_reduce_table = [ + 0, 0, :racc_error, + 2, 11, :_reduce_1, + 1, 11, :_reduce_2, + 1, 11, :_reduce_none, + 1, 12, :_reduce_none, + 1, 12, :_reduce_none, + 1, 12, :_reduce_none, + 3, 15, :_reduce_7, + 3, 13, :_reduce_8, + 3, 13, :_reduce_9, + 1, 16, :_reduce_10, + 1, 14, :_reduce_none, + 1, 14, :_reduce_none, + 1, 14, :_reduce_none, + 1, 14, :_reduce_none, + 1, 19, :_reduce_15, + 1, 17, :_reduce_16, + 1, 18, :_reduce_17, + 1, 20, :_reduce_18 ] + +racc_reduce_n = 19 + +racc_shift_n = 25 + +racc_token_table = { + false => 0, + :error => 1, + :SLASH => 2, + :LITERAL => 3, + :SYMBOL => 4, + :LPAREN => 5, + :RPAREN => 6, + :DOT => 7, + :STAR => 8, + :OR => 9 } + +racc_nt_base = 10 + +racc_use_result_var = false + +Racc_arg = [ + racc_action_table, + racc_action_check, + racc_action_default, + racc_action_pointer, + racc_goto_table, + racc_goto_check, + racc_goto_default, + racc_goto_pointer, + racc_nt_base, + racc_reduce_table, + racc_token_table, + racc_shift_n, + racc_reduce_n, + racc_use_result_var ] + +Racc_token_to_s_table = [ + "$end", + "error", + "SLASH", + "LITERAL", + "SYMBOL", + "LPAREN", + "RPAREN", + "DOT", + "STAR", + "OR", + "$start", + "expressions", + "expression", + "or", + "terminal", + "group", + "star", + "symbol", + "literal", + "slash", + "dot" ] + +Racc_debug_parser = false + +##### State transition tables end ##### + +# reduce 0 omitted + +def _reduce_1(val, _values) + Cat.new(val.first, val.last) +end + +def _reduce_2(val, _values) + val.first +end + +# reduce 3 omitted + +# reduce 4 omitted + +# reduce 5 omitted + +# reduce 6 omitted + +def _reduce_7(val, _values) + Group.new(val[1]) +end + +def _reduce_8(val, _values) + Or.new([val.first, val.last]) +end + +def _reduce_9(val, _values) + Or.new([val.first, val.last]) +end + +def _reduce_10(val, _values) + Star.new(Symbol.new(val.last)) +end + +# reduce 11 omitted + +# reduce 12 omitted + +# reduce 13 omitted + +# reduce 14 omitted + +def _reduce_15(val, _values) + Slash.new(val.first) +end + +def _reduce_16(val, _values) + Symbol.new(val.first) +end + +def _reduce_17(val, _values) + Literal.new(val.first) +end + +def _reduce_18(val, _values) + Dot.new(val.first) +end + +def _reduce_none(val, _values) + val[0] +end + + end # class Parser + end # module Journey + end # module ActionDispatch diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/parser.y b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/parser.y new file mode 100644 index 00000000..f9b1a7a9 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/parser.y @@ -0,0 +1,50 @@ +class ActionDispatch::Journey::Parser + options no_result_var +token SLASH LITERAL SYMBOL LPAREN RPAREN DOT STAR OR + +rule + expressions + : expression expressions { Cat.new(val.first, val.last) } + | expression { val.first } + | or + ; + expression + : terminal + | group + | star + ; + group + : LPAREN expressions RPAREN { Group.new(val[1]) } + ; + or + : expression OR expression { Or.new([val.first, val.last]) } + | expression OR or { Or.new([val.first, val.last]) } + ; + star + : STAR { Star.new(Symbol.new(val.last)) } + ; + terminal + : symbol + | literal + | slash + | dot + ; + slash + : SLASH { Slash.new(val.first) } + ; + symbol + : SYMBOL { Symbol.new(val.first) } + ; + literal + : LITERAL { Literal.new(val.first) } + ; + dot + : DOT { Dot.new(val.first) } + ; + +end + +---- header +# :stopdoc: + +require "action_dispatch/journey/parser_extras" diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/parser_extras.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/parser_extras.rb new file mode 100644 index 00000000..18ec6c9b --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/parser_extras.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require "action_dispatch/journey/scanner" +require "action_dispatch/journey/nodes/node" + +module ActionDispatch + # :stopdoc: + module Journey + class Parser < Racc::Parser + include Journey::Nodes + + def self.parse(string) + new.parse string + end + + def initialize + @scanner = Scanner.new + end + + def parse(string) + @scanner.scan_setup(string) + do_parse + end + + def next_token + @scanner.next_token + end + end + end + # :startdoc: +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/path/pattern.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/path/pattern.rb new file mode 100644 index 00000000..c6fefa7f --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/path/pattern.rb @@ -0,0 +1,198 @@ +# frozen_string_literal: true + +module ActionDispatch + module Journey # :nodoc: + module Path # :nodoc: + class Pattern # :nodoc: + attr_reader :spec, :requirements, :anchored + + def self.from_string(string) + build(string, {}, "/.?", true) + end + + def self.build(path, requirements, separators, anchored) + parser = Journey::Parser.new + ast = parser.parse path + new ast, requirements, separators, anchored + end + + def initialize(ast, requirements, separators, anchored) + @spec = ast + @requirements = requirements + @separators = separators + @anchored = anchored + + @names = nil + @optional_names = nil + @required_names = nil + @re = nil + @offsets = nil + end + + def build_formatter + Visitors::FormatBuilder.new.accept(spec) + end + + def eager_load! + required_names + offsets + to_regexp + nil + end + + def ast + @spec.find_all(&:symbol?).each do |node| + re = @requirements[node.to_sym] + node.regexp = re if re + end + + @spec.find_all(&:star?).each do |node| + node = node.left + node.regexp = @requirements[node.to_sym] || /(.+)/ + end + + @spec + end + + def names + @names ||= spec.find_all(&:symbol?).map(&:name) + end + + def required_names + @required_names ||= names - optional_names + end + + def optional_names + @optional_names ||= spec.find_all(&:group?).flat_map { |group| + group.find_all(&:symbol?) + }.map(&:name).uniq + end + + class AnchoredRegexp < Journey::Visitors::Visitor # :nodoc: + def initialize(separator, matchers) + @separator = separator + @matchers = matchers + @separator_re = "([^#{separator}]+)" + super() + end + + def accept(node) + %r{\A#{visit node}\Z} + end + + def visit_CAT(node) + [visit(node.left), visit(node.right)].join + end + + def visit_SYMBOL(node) + node = node.to_sym + + return @separator_re unless @matchers.key?(node) + + re = @matchers[node] + "(#{re})" + end + + def visit_GROUP(node) + "(?:#{visit node.left})?" + end + + def visit_LITERAL(node) + Regexp.escape(node.left) + end + alias :visit_DOT :visit_LITERAL + + def visit_SLASH(node) + node.left + end + + def visit_STAR(node) + re = @matchers[node.left.to_sym] || ".+" + "(#{re})" + end + + def visit_OR(node) + children = node.children.map { |n| visit n } + "(?:#{children.join(?|)})" + end + end + + class UnanchoredRegexp < AnchoredRegexp # :nodoc: + def accept(node) + %r{\A#{visit node}(?:\b|\Z)} + end + end + + class MatchData # :nodoc: + attr_reader :names + + def initialize(names, offsets, match) + @names = names + @offsets = offsets + @match = match + end + + def captures + Array.new(length - 1) { |i| self[i + 1] } + end + + def [](x) + idx = @offsets[x - 1] + x + @match[idx] + end + + def length + @offsets.length + end + + def post_match + @match.post_match + end + + def to_s + @match.to_s + end + end + + def match(other) + return unless match = to_regexp.match(other) + MatchData.new(names, offsets, match) + end + alias :=~ :match + + def source + to_regexp.source + end + + def to_regexp + @re ||= regexp_visitor.new(@separators, @requirements).accept spec + end + + private + + def regexp_visitor + @anchored ? AnchoredRegexp : UnanchoredRegexp + end + + def offsets + return @offsets if @offsets + + @offsets = [0] + + spec.find_all(&:symbol?).each do |node| + node = node.to_sym + + if @requirements.key?(node) + re = /#{@requirements[node]}|/ + @offsets.push((re.match("").length - 1) + @offsets.last) + else + @offsets << @offsets.last + end + end + + @offsets + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/route.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/route.rb new file mode 100644 index 00000000..8165709a --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/route.rb @@ -0,0 +1,203 @@ +# frozen_string_literal: true + +module ActionDispatch + # :stopdoc: + module Journey + class Route + attr_reader :app, :path, :defaults, :name, :precedence + + attr_reader :constraints, :internal + alias :conditions :constraints + + module VerbMatchers + VERBS = %w{ DELETE GET HEAD OPTIONS LINK PATCH POST PUT TRACE UNLINK } + VERBS.each do |v| + class_eval <<-eoc, __FILE__, __LINE__ + 1 + class #{v} + def self.verb; name.split("::").last; end + def self.call(req); req.#{v.downcase}?; end + end + eoc + end + + class Unknown + attr_reader :verb + + def initialize(verb) + @verb = verb + end + + def call(request); @verb === request.request_method; end + end + + class All + def self.call(_); true; end + def self.verb; ""; end + end + + VERB_TO_CLASS = VERBS.each_with_object(all: All) do |verb, hash| + klass = const_get verb + hash[verb] = klass + hash[verb.downcase] = klass + hash[verb.downcase.to_sym] = klass + end + end + + def self.verb_matcher(verb) + VerbMatchers::VERB_TO_CLASS.fetch(verb) do + VerbMatchers::Unknown.new verb.to_s.dasherize.upcase + end + end + + def self.build(name, app, path, constraints, required_defaults, defaults) + request_method_match = verb_matcher(constraints.delete(:request_method)) + new name, app, path, constraints, required_defaults, defaults, request_method_match, 0 + end + + ## + # +path+ is a path constraint. + # +constraints+ is a hash of constraints to be applied to this route. + def initialize(name, app, path, constraints, required_defaults, defaults, request_method_match, precedence, internal = false) + @name = name + @app = app + @path = path + + @request_method_match = request_method_match + @constraints = constraints + @defaults = defaults + @required_defaults = nil + @_required_defaults = required_defaults + @required_parts = nil + @parts = nil + @decorated_ast = nil + @precedence = precedence + @path_formatter = @path.build_formatter + @internal = internal + end + + def eager_load! + path.eager_load! + ast + parts + required_defaults + nil + end + + def ast + @decorated_ast ||= begin + decorated_ast = path.ast + decorated_ast.find_all(&:terminal?).each { |n| n.memo = self } + decorated_ast + end + end + + # Needed for `rails routes`. Picks up succinctly defined requirements + # for a route, for example route + # + # get 'photo/:id', :controller => 'photos', :action => 'show', + # :id => /[A-Z]\d{5}/ + # + # will have {:controller=>"photos", :action=>"show", :id=>/[A-Z]\d{5}/} + # as requirements. + def requirements + @defaults.merge(path.requirements).delete_if { |_, v| + /.+?/ == v + } + end + + def segments + path.names + end + + def required_keys + required_parts + required_defaults.keys + end + + def score(supplied_keys) + required_keys = path.required_names + + required_keys.each do |k| + return -1 unless supplied_keys.include?(k) + end + + score = 0 + path.names.each do |k| + score += 1 if supplied_keys.include?(k) + end + + score + (required_defaults.length * 2) + end + + def parts + @parts ||= segments.map(&:to_sym) + end + alias :segment_keys :parts + + def format(path_options) + @path_formatter.evaluate path_options + end + + def required_parts + @required_parts ||= path.required_names.map(&:to_sym) + end + + def required_default?(key) + @_required_defaults.include?(key) + end + + def required_defaults + @required_defaults ||= @defaults.dup.delete_if do |k, _| + parts.include?(k) || !required_default?(k) + end + end + + def glob? + !path.spec.grep(Nodes::Star).empty? + end + + def dispatcher? + @app.dispatcher? + end + + def matches?(request) + match_verb(request) && + constraints.all? { |method, value| + case value + when Regexp, String + value === request.send(method).to_s + when Array + value.include?(request.send(method)) + when TrueClass + request.send(method).present? + when FalseClass + request.send(method).blank? + else + value === request.send(method) + end + } + end + + def ip + constraints[:ip] || // + end + + def requires_matching_verb? + !@request_method_match.all? { |x| x == VerbMatchers::All } + end + + def verb + verbs.join("|") + end + + private + def verbs + @request_method_match.map(&:verb) + end + + def match_verb(request) + @request_method_match.any? { |m| m.call request } + end + end + end + # :startdoc: +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/router.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/router.rb new file mode 100644 index 00000000..30af3ff9 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/router.rb @@ -0,0 +1,156 @@ +# frozen_string_literal: true + +require "action_dispatch/journey/router/utils" +require "action_dispatch/journey/routes" +require "action_dispatch/journey/formatter" + +before = $-w +$-w = false +require "action_dispatch/journey/parser" +$-w = before + +require "action_dispatch/journey/route" +require "action_dispatch/journey/path/pattern" + +module ActionDispatch + module Journey # :nodoc: + class Router # :nodoc: + class RoutingError < ::StandardError # :nodoc: + end + + attr_accessor :routes + + def initialize(routes) + @routes = routes + end + + def eager_load! + # Eagerly trigger the simulator's initialization so + # it doesn't happen during a request cycle. + simulator + nil + end + + def serve(req) + find_routes(req).each do |match, parameters, route| + set_params = req.path_parameters + path_info = req.path_info + script_name = req.script_name + + unless route.path.anchored + req.script_name = (script_name.to_s + match.to_s).chomp("/") + req.path_info = match.post_match + req.path_info = "/" + req.path_info unless req.path_info.start_with? "/" + end + + parameters = route.defaults.merge parameters.transform_values { |val| + val.dup.force_encoding(::Encoding::UTF_8) + } + + req.path_parameters = set_params.merge parameters + + status, headers, body = route.app.serve(req) + + if "pass" == headers["X-Cascade"] + req.script_name = script_name + req.path_info = path_info + req.path_parameters = set_params + next + end + + return [status, headers, body] + end + + [404, { "X-Cascade" => "pass" }, ["Not Found"]] + end + + def recognize(rails_req) + find_routes(rails_req).each do |match, parameters, route| + unless route.path.anchored + rails_req.script_name = match.to_s + rails_req.path_info = match.post_match.sub(/^([^\/])/, '/\1') + end + + parameters = route.defaults.merge parameters + yield(route, parameters) + end + end + + def visualizer + tt = GTG::Builder.new(ast).transition_table + groups = partitioned_routes.first.map(&:ast).group_by(&:to_s) + asts = groups.values.map(&:first) + tt.visualizer(asts) + end + + private + + def partitioned_routes + routes.partition { |r| + r.path.anchored && r.ast.grep(Nodes::Symbol).all? { |n| n.default_regexp? } + } + end + + def ast + routes.ast + end + + def simulator + routes.simulator + end + + def custom_routes + routes.custom_routes + end + + def filter_routes(path) + return [] unless ast + simulator.memos(path) { [] } + end + + def find_routes(req) + routes = filter_routes(req.path_info).concat custom_routes.find_all { |r| + r.path.match(req.path_info) + } + + routes = + if req.head? + match_head_routes(routes, req) + else + match_routes(routes, req) + end + + routes.sort_by!(&:precedence) + + routes.map! { |r| + match_data = r.path.match(req.path_info) + path_parameters = {} + match_data.names.zip(match_data.captures) { |name, val| + path_parameters[name.to_sym] = Utils.unescape_uri(val) if val + } + [match_data, path_parameters, r] + } + end + + def match_head_routes(routes, req) + verb_specific_routes = routes.select(&:requires_matching_verb?) + head_routes = match_routes(verb_specific_routes, req) + + if head_routes.empty? + begin + req.request_method = "GET" + match_routes(routes, req) + ensure + req.request_method = "HEAD" + end + else + head_routes + end + end + + def match_routes(routes, req) + routes.select { |r| r.matches?(req) } + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/router/utils.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/router/utils.rb new file mode 100644 index 00000000..df3f79a4 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/router/utils.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +module ActionDispatch + module Journey # :nodoc: + class Router # :nodoc: + class Utils # :nodoc: + # Normalizes URI path. + # + # Strips off trailing slash and ensures there is a leading slash. + # Also converts downcase URL encoded string to uppercase. + # + # normalize_path("/foo") # => "/foo" + # normalize_path("/foo/") # => "/foo" + # normalize_path("foo") # => "/foo" + # normalize_path("") # => "/" + # normalize_path("/%ab") # => "/%AB" + def self.normalize_path(path) + path ||= "" + encoding = path.encoding + path = "/#{path}".dup + path.squeeze!("/".freeze) + path.sub!(%r{/+\Z}, "".freeze) + path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase } + path = "/".dup if path == "".freeze + path.force_encoding(encoding) + path + end + + # URI path and fragment escaping + # https://tools.ietf.org/html/rfc3986 + class UriEncoder # :nodoc: + ENCODE = "%%%02X".freeze + US_ASCII = Encoding::US_ASCII + UTF_8 = Encoding::UTF_8 + EMPTY = "".dup.force_encoding(US_ASCII).freeze + DEC2HEX = (0..255).to_a.map { |i| ENCODE % i }.map { |s| s.force_encoding(US_ASCII) } + + ALPHA = "a-zA-Z".freeze + DIGIT = "0-9".freeze + UNRESERVED = "#{ALPHA}#{DIGIT}\\-\\._~".freeze + SUB_DELIMS = "!\\$&'\\(\\)\\*\\+,;=".freeze + + ESCAPED = /%[a-zA-Z0-9]{2}/.freeze + + FRAGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/\?]/.freeze + SEGMENT = /[^#{UNRESERVED}#{SUB_DELIMS}:@]/.freeze + PATH = /[^#{UNRESERVED}#{SUB_DELIMS}:@\/]/.freeze + + def escape_fragment(fragment) + escape(fragment, FRAGMENT) + end + + def escape_path(path) + escape(path, PATH) + end + + def escape_segment(segment) + escape(segment, SEGMENT) + end + + def unescape_uri(uri) + encoding = uri.encoding == US_ASCII ? UTF_8 : uri.encoding + uri.gsub(ESCAPED) { |match| [match[1, 2].hex].pack("C") }.force_encoding(encoding) + end + + private + def escape(component, pattern) + component.gsub(pattern) { |unsafe| percent_encode(unsafe) }.force_encoding(US_ASCII) + end + + def percent_encode(unsafe) + safe = EMPTY.dup + unsafe.each_byte { |b| safe << DEC2HEX[b] } + safe + end + end + + ENCODER = UriEncoder.new + + def self.escape_path(path) + ENCODER.escape_path(path.to_s) + end + + def self.escape_segment(segment) + ENCODER.escape_segment(segment.to_s) + end + + def self.escape_fragment(fragment) + ENCODER.escape_fragment(fragment.to_s) + end + + # Replaces any escaped sequences with their unescaped representations. + # + # uri = "/topics?title=Ruby%20on%20Rails" + # unescape_uri(uri) #=> "/topics?title=Ruby on Rails" + def self.unescape_uri(uri) + ENCODER.unescape_uri(uri) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/routes.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/routes.rb new file mode 100644 index 00000000..c0377459 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/routes.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +module ActionDispatch + module Journey # :nodoc: + # The Routing table. Contains all routes for a system. Routes can be + # added to the table by calling Routes#add_route. + class Routes # :nodoc: + include Enumerable + + attr_reader :routes, :custom_routes, :anchored_routes + + def initialize + @routes = [] + @ast = nil + @anchored_routes = [] + @custom_routes = [] + @simulator = nil + end + + def empty? + routes.empty? + end + + def length + routes.length + end + alias :size :length + + def last + routes.last + end + + def each(&block) + routes.each(&block) + end + + def clear + routes.clear + anchored_routes.clear + custom_routes.clear + end + + def partition_route(route) + if route.path.anchored && route.ast.grep(Nodes::Symbol).all?(&:default_regexp?) + anchored_routes << route + else + custom_routes << route + end + end + + def ast + @ast ||= begin + asts = anchored_routes.map(&:ast) + Nodes::Or.new(asts) + end + end + + def simulator + return if ast.nil? + @simulator ||= begin + gtg = GTG::Builder.new(ast).transition_table + GTG::Simulator.new(gtg) + end + end + + def add_route(name, mapping) + route = mapping.make_route name, routes.length + routes << route + partition_route(route) + clear_cache! + route + end + + private + + def clear_cache! + @ast = nil + @simulator = nil + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/scanner.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/scanner.rb new file mode 100644 index 00000000..4ae77903 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/scanner.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require "strscan" + +module ActionDispatch + module Journey # :nodoc: + class Scanner # :nodoc: + def initialize + @ss = nil + end + + def scan_setup(str) + @ss = StringScanner.new(str) + end + + def eos? + @ss.eos? + end + + def pos + @ss.pos + end + + def pre_match + @ss.pre_match + end + + def next_token + return if @ss.eos? + + until token = scan || @ss.eos?; end + token + end + + private + + def scan + case + # / + when @ss.skip(/\//) + [:SLASH, "/"] + when @ss.skip(/\(/) + [:LPAREN, "("] + when @ss.skip(/\)/) + [:RPAREN, ")"] + when @ss.skip(/\|/) + [:OR, "|"] + when @ss.skip(/\./) + [:DOT, "."] + when text = @ss.scan(/:\w+/) + [:SYMBOL, text] + when text = @ss.scan(/\*\w+/) + [:STAR, text] + when text = @ss.scan(/(?:[\w%\-~!$&'*+,;=@]|\\[:()])+/) + text.tr! "\\", "" + [:LITERAL, text] + # any char + when text = @ss.scan(/./) + [:LITERAL, text] + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/visitors.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/visitors.rb new file mode 100644 index 00000000..3395471a --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/visitors.rb @@ -0,0 +1,268 @@ +# frozen_string_literal: true + +module ActionDispatch + # :stopdoc: + module Journey + class Format + ESCAPE_PATH = ->(value) { Router::Utils.escape_path(value) } + ESCAPE_SEGMENT = ->(value) { Router::Utils.escape_segment(value) } + + Parameter = Struct.new(:name, :escaper) do + def escape(value); escaper.call value; end + end + + def self.required_path(symbol) + Parameter.new symbol, ESCAPE_PATH + end + + def self.required_segment(symbol) + Parameter.new symbol, ESCAPE_SEGMENT + end + + def initialize(parts) + @parts = parts + @children = [] + @parameters = [] + + parts.each_with_index do |object, i| + case object + when Journey::Format + @children << i + when Parameter + @parameters << i + end + end + end + + def evaluate(hash) + parts = @parts.dup + + @parameters.each do |index| + param = parts[index] + value = hash[param.name] + return "".freeze unless value + parts[index] = param.escape value + end + + @children.each { |index| parts[index] = parts[index].evaluate(hash) } + + parts.join + end + end + + module Visitors # :nodoc: + class Visitor # :nodoc: + DISPATCH_CACHE = {} + + def accept(node) + visit(node) + end + + private + + def visit(node) + send(DISPATCH_CACHE[node.type], node) + end + + def binary(node) + visit(node.left) + visit(node.right) + end + def visit_CAT(n); binary(n); end + + def nary(node) + node.children.each { |c| visit(c) } + end + def visit_OR(n); nary(n); end + + def unary(node) + visit(node.left) + end + def visit_GROUP(n); unary(n); end + def visit_STAR(n); unary(n); end + + def terminal(node); end + def visit_LITERAL(n); terminal(n); end + def visit_SYMBOL(n); terminal(n); end + def visit_SLASH(n); terminal(n); end + def visit_DOT(n); terminal(n); end + + private_instance_methods(false).each do |pim| + next unless pim =~ /^visit_(.*)$/ + DISPATCH_CACHE[$1.to_sym] = pim + end + end + + class FunctionalVisitor # :nodoc: + DISPATCH_CACHE = {} + + def accept(node, seed) + visit(node, seed) + end + + def visit(node, seed) + send(DISPATCH_CACHE[node.type], node, seed) + end + + def binary(node, seed) + visit(node.right, visit(node.left, seed)) + end + def visit_CAT(n, seed); binary(n, seed); end + + def nary(node, seed) + node.children.inject(seed) { |s, c| visit(c, s) } + end + def visit_OR(n, seed); nary(n, seed); end + + def unary(node, seed) + visit(node.left, seed) + end + def visit_GROUP(n, seed); unary(n, seed); end + def visit_STAR(n, seed); unary(n, seed); end + + def terminal(node, seed); seed; end + def visit_LITERAL(n, seed); terminal(n, seed); end + def visit_SYMBOL(n, seed); terminal(n, seed); end + def visit_SLASH(n, seed); terminal(n, seed); end + def visit_DOT(n, seed); terminal(n, seed); end + + instance_methods(false).each do |pim| + next unless pim =~ /^visit_(.*)$/ + DISPATCH_CACHE[$1.to_sym] = pim + end + end + + class FormatBuilder < Visitor # :nodoc: + def accept(node); Journey::Format.new(super); end + def terminal(node); [node.left]; end + + def binary(node) + visit(node.left) + visit(node.right) + end + + def visit_GROUP(n); [Journey::Format.new(unary(n))]; end + + def visit_STAR(n) + [Journey::Format.required_path(n.left.to_sym)] + end + + def visit_SYMBOL(n) + symbol = n.to_sym + if symbol == :controller + [Journey::Format.required_path(symbol)] + else + [Journey::Format.required_segment(symbol)] + end + end + end + + # Loop through the requirements AST. + class Each < FunctionalVisitor # :nodoc: + def visit(node, block) + block.call(node) + super + end + + INSTANCE = new + end + + class String < FunctionalVisitor # :nodoc: + private + + def binary(node, seed) + visit(node.right, visit(node.left, seed)) + end + + def nary(node, seed) + last_child = node.children.last + node.children.inject(seed) { |s, c| + string = visit(c, s) + string << "|" unless last_child == c + string + } + end + + def terminal(node, seed) + seed + node.left + end + + def visit_GROUP(node, seed) + visit(node.left, seed.dup << "(") << ")" + end + + INSTANCE = new + end + + class Dot < FunctionalVisitor # :nodoc: + def initialize + @nodes = [] + @edges = [] + end + + def accept(node, seed = [[], []]) + super + nodes, edges = seed + <<-eodot + digraph parse_tree { + size="8,5" + node [shape = none]; + edge [dir = none]; + #{nodes.join "\n"} + #{edges.join("\n")} + } + eodot + end + + private + + def binary(node, seed) + seed.last.concat node.children.map { |c| + "#{node.object_id} -> #{c.object_id};" + } + super + end + + def nary(node, seed) + seed.last.concat node.children.map { |c| + "#{node.object_id} -> #{c.object_id};" + } + super + end + + def unary(node, seed) + seed.last << "#{node.object_id} -> #{node.left.object_id};" + super + end + + def visit_GROUP(node, seed) + seed.first << "#{node.object_id} [label=\"()\"];" + super + end + + def visit_CAT(node, seed) + seed.first << "#{node.object_id} [label=\"○\"];" + super + end + + def visit_STAR(node, seed) + seed.first << "#{node.object_id} [label=\"*\"];" + super + end + + def visit_OR(node, seed) + seed.first << "#{node.object_id} [label=\"|\"];" + super + end + + def terminal(node, seed) + value = node.left + + seed.first << "#{node.object_id} [label=\"#{value}\"];" + seed + end + INSTANCE = new + end + end + end + # :startdoc: +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/visualizer/fsm.css b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/visualizer/fsm.css new file mode 100644 index 00000000..403e16a7 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/visualizer/fsm.css @@ -0,0 +1,30 @@ +body { + font-family: "Helvetica Neue", Helvetica, Arial, Sans-Serif; + margin: 0; +} + +h1 { + font-size: 2.0em; font-weight: bold; text-align: center; + color: white; background-color: black; + padding: 5px 0; + margin: 0 0 20px; +} + +h2 { + text-align: center; + display: none; + font-size: 0.5em; +} + +.clearfix {display: inline-block; } +.input { overflow: show;} +.instruction { color: #666; padding: 0 30px 20px; font-size: 0.9em} +.instruction p { padding: 0 0 5px; } +.instruction li { padding: 0 10px 5px; } + +.form { background: #EEE; padding: 20px 30px; border-radius: 5px; margin-left: auto; margin-right: auto; width: 500px; margin-bottom: 20px} +.form p, .form form { text-align: center } +.form form {padding: 0 10px 5px; } +.form .fun_routes { font-size: 0.9em;} +.form .fun_routes a { margin: 0 5px 0 0; } + diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/visualizer/fsm.js b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/visualizer/fsm.js new file mode 100644 index 00000000..d9bcaef9 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/visualizer/fsm.js @@ -0,0 +1,134 @@ +function tokenize(input, callback) { + while(input.length > 0) { + callback(input.match(/^[\/\.\?]|[^\/\.\?]+/)[0]); + input = input.replace(/^[\/\.\?]|[^\/\.\?]+/, ''); + } +} + +var graph = d3.select("#chart-2 svg"); +var svg_edges = {}; +var svg_nodes = {}; + +graph.selectAll("g.edge").each(function() { + var node = d3.select(this); + var index = node.select("title").text().split("->"); + var left = parseInt(index[0]); + var right = parseInt(index[1]); + + if(!svg_edges[left]) { svg_edges[left] = {} } + svg_edges[left][right] = node; +}); + +graph.selectAll("g.node").each(function() { + var node = d3.select(this); + var index = parseInt(node.select("title").text()); + svg_nodes[index] = node; +}); + +function reset_graph() { + for(var key in svg_edges) { + for(var mkey in svg_edges[key]) { + var node = svg_edges[key][mkey]; + var path = node.select("path"); + var arrow = node.select("polygon"); + path.style("stroke", "black"); + arrow.style("stroke", "black").style("fill", "black"); + } + } + + for(var key in svg_nodes) { + var node = svg_nodes[key]; + node.select('ellipse').style("fill", "white"); + node.select('polygon').style("fill", "white"); + } + return false; +} + +function highlight_edge(from, to) { + var node = svg_edges[from][to]; + var path = node.select("path"); + var arrow = node.select("polygon"); + + path + .transition().duration(500) + .style("stroke", "green"); + + arrow + .transition().duration(500) + .style("stroke", "green").style("fill", "green"); +} + +function highlight_state(index, color) { + if(!color) { color = "green"; } + + svg_nodes[index].select('ellipse') + .style("fill", "white") + .transition().duration(500) + .style("fill", color); +} + +function highlight_finish(index) { + svg_nodes[index].select('polygon') + .style("fill", "while") + .transition().duration(500) + .style("fill", "blue"); +} + +function match(input) { + reset_graph(); + var table = tt(); + var states = [0]; + var regexp_states = table['regexp_states']; + var string_states = table['string_states']; + var accepting = table['accepting']; + + highlight_state(0); + + tokenize(input, function(token) { + var new_states = []; + for(var key in states) { + var state = states[key]; + + if(string_states[state] && string_states[state][token]) { + var new_state = string_states[state][token]; + highlight_edge(state, new_state); + highlight_state(new_state); + new_states.push(new_state); + } + + if(regexp_states[state]) { + for(var key in regexp_states[state]) { + var re = new RegExp("^" + key + "$"); + if(re.test(token)) { + var new_state = regexp_states[state][key]; + highlight_edge(state, new_state); + highlight_state(new_state); + new_states.push(new_state); + } + } + } + } + + if(new_states.length == 0) { + return; + } + states = new_states; + }); + + for(var key in states) { + var state = states[key]; + if(accepting[state]) { + for(var mkey in svg_edges[state]) { + if(!regexp_states[mkey] && !string_states[mkey]) { + highlight_edge(state, mkey); + highlight_finish(mkey); + } + } + } else { + highlight_state(state, "red"); + } + } + + return false; +} + diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/visualizer/index.html.erb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/visualizer/index.html.erb new file mode 100644 index 00000000..9b28a652 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/journey/visualizer/index.html.erb @@ -0,0 +1,52 @@ + + + + <%= title %> + + + + + +
+

Routes FSM with NFA simulation

+
+

+ Type a route in to the box and click "simulate". +

+
+ + + +
+

+ Some fun routes to try: + <% fun_routes.each do |path| %> + + <%= path %> + + <% end %> +

+
+
+ <%= svg %> +
+
+

+ This is a FSM for a system that has the following routes: +

+
    + <% paths.each do |route| %> +
  • <%= route %>
  • + <% end %> +
+
+
+ <% javascripts.each do |js| %> + + <% end %> + + diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/callbacks.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/callbacks.rb new file mode 100644 index 00000000..5b2ad36d --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/callbacks.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module ActionDispatch + # Provides callbacks to be executed before and after dispatching the request. + class Callbacks + include ActiveSupport::Callbacks + + define_callbacks :call + + class << self + def before(*args, &block) + set_callback(:call, :before, *args, &block) + end + + def after(*args, &block) + set_callback(:call, :after, *args, &block) + end + end + + def initialize(app) + @app = app + end + + def call(env) + error = nil + result = run_callbacks :call do + begin + @app.call(env) + rescue => error + end + end + raise error if error + result + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/cookies.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/cookies.rb new file mode 100644 index 00000000..21887955 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/cookies.rb @@ -0,0 +1,685 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/keys" +require "active_support/key_generator" +require "active_support/message_verifier" +require "active_support/json" +require "rack/utils" + +module ActionDispatch + class Request + def cookie_jar + fetch_header("action_dispatch.cookies".freeze) do + self.cookie_jar = Cookies::CookieJar.build(self, cookies) + end + end + + # :stopdoc: + prepend Module.new { + def commit_cookie_jar! + cookie_jar.commit! + end + } + + def have_cookie_jar? + has_header? "action_dispatch.cookies".freeze + end + + def cookie_jar=(jar) + set_header "action_dispatch.cookies".freeze, jar + end + + def key_generator + get_header Cookies::GENERATOR_KEY + end + + def signed_cookie_salt + get_header Cookies::SIGNED_COOKIE_SALT + end + + def encrypted_cookie_salt + get_header Cookies::ENCRYPTED_COOKIE_SALT + end + + def encrypted_signed_cookie_salt + get_header Cookies::ENCRYPTED_SIGNED_COOKIE_SALT + end + + def authenticated_encrypted_cookie_salt + get_header Cookies::AUTHENTICATED_ENCRYPTED_COOKIE_SALT + end + + def use_authenticated_cookie_encryption + get_header Cookies::USE_AUTHENTICATED_COOKIE_ENCRYPTION + end + + def encrypted_cookie_cipher + get_header Cookies::ENCRYPTED_COOKIE_CIPHER + end + + def signed_cookie_digest + get_header Cookies::SIGNED_COOKIE_DIGEST + end + + def secret_token + get_header Cookies::SECRET_TOKEN + end + + def secret_key_base + get_header Cookies::SECRET_KEY_BASE + end + + def cookies_serializer + get_header Cookies::COOKIES_SERIALIZER + end + + def cookies_digest + get_header Cookies::COOKIES_DIGEST + end + + def cookies_rotations + get_header Cookies::COOKIES_ROTATIONS + end + + # :startdoc: + end + + # \Cookies are read and written through ActionController#cookies. + # + # The cookies being read are the ones received along with the request, the cookies + # being written will be sent out with the response. Reading a cookie does not get + # the cookie object itself back, just the value it holds. + # + # Examples of writing: + # + # # Sets a simple session cookie. + # # This cookie will be deleted when the user's browser is closed. + # cookies[:user_name] = "david" + # + # # Cookie values are String based. Other data types need to be serialized. + # cookies[:lat_lon] = JSON.generate([47.68, -122.37]) + # + # # Sets a cookie that expires in 1 hour. + # cookies[:login] = { value: "XJ-122", expires: 1.hour } + # + # # Sets a cookie that expires at a specific time. + # cookies[:login] = { value: "XJ-122", expires: Time.utc(2020, 10, 15, 5) } + # + # # Sets a signed cookie, which prevents users from tampering with its value. + # # It can be read using the signed method `cookies.signed[:name]` + # cookies.signed[:user_id] = current_user.id + # + # # Sets an encrypted cookie value before sending it to the client which + # # prevent users from reading and tampering with its value. + # # It can be read using the encrypted method `cookies.encrypted[:name]` + # cookies.encrypted[:discount] = 45 + # + # # Sets a "permanent" cookie (which expires in 20 years from now). + # cookies.permanent[:login] = "XJ-122" + # + # # You can also chain these methods: + # cookies.signed.permanent[:login] = "XJ-122" + # + # Examples of reading: + # + # cookies[:user_name] # => "david" + # cookies.size # => 2 + # JSON.parse(cookies[:lat_lon]) # => [47.68, -122.37] + # cookies.signed[:login] # => "XJ-122" + # cookies.encrypted[:discount] # => 45 + # + # Example for deleting: + # + # cookies.delete :user_name + # + # Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie: + # + # cookies[:name] = { + # value: 'a yummy cookie', + # expires: 1.year, + # domain: 'domain.com' + # } + # + # cookies.delete(:name, domain: 'domain.com') + # + # The option symbols for setting cookies are: + # + # * :value - The cookie's value. + # * :path - The path for which this cookie applies. Defaults to the root + # of the application. + # * :domain - The domain for which this cookie applies so you can + # restrict to the domain level. If you use a schema like www.example.com + # and want to share session with user.example.com set :domain + # to :all. Make sure to specify the :domain option with + # :all or Array again when deleting cookies. + # + # domain: nil # Does not set cookie domain. (default) + # domain: :all # Allow the cookie for the top most level + # # domain and subdomains. + # domain: %w(.example.com .example.org) # Allow the cookie + # # for concrete domain names. + # + # * :tld_length - When using :domain => :all, this option can be used to explicitly + # set the TLD length when using a short (<= 3 character) domain that is being interpreted as part of a TLD. + # For example, to share cookies between user1.lvh.me and user2.lvh.me, set :tld_length to 2. + # * :expires - The time at which this cookie expires, as a \Time or ActiveSupport::Duration object. + # * :secure - Whether this cookie is only transmitted to HTTPS servers. + # Default is +false+. + # * :httponly - Whether this cookie is accessible via scripting or + # only HTTP. Defaults to +false+. + class Cookies + HTTP_HEADER = "Set-Cookie".freeze + GENERATOR_KEY = "action_dispatch.key_generator".freeze + SIGNED_COOKIE_SALT = "action_dispatch.signed_cookie_salt".freeze + ENCRYPTED_COOKIE_SALT = "action_dispatch.encrypted_cookie_salt".freeze + ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze + AUTHENTICATED_ENCRYPTED_COOKIE_SALT = "action_dispatch.authenticated_encrypted_cookie_salt".freeze + USE_AUTHENTICATED_COOKIE_ENCRYPTION = "action_dispatch.use_authenticated_cookie_encryption".freeze + ENCRYPTED_COOKIE_CIPHER = "action_dispatch.encrypted_cookie_cipher".freeze + SIGNED_COOKIE_DIGEST = "action_dispatch.signed_cookie_digest".freeze + SECRET_TOKEN = "action_dispatch.secret_token".freeze + SECRET_KEY_BASE = "action_dispatch.secret_key_base".freeze + COOKIES_SERIALIZER = "action_dispatch.cookies_serializer".freeze + COOKIES_DIGEST = "action_dispatch.cookies_digest".freeze + COOKIES_ROTATIONS = "action_dispatch.cookies_rotations".freeze + + # Cookies can typically store 4096 bytes. + MAX_COOKIE_SIZE = 4096 + + # Raised when storing more than 4K of session data. + CookieOverflow = Class.new StandardError + + # Include in a cookie jar to allow chaining, e.g. cookies.permanent.signed. + module ChainedCookieJars + # Returns a jar that'll automatically set the assigned cookies to have an expiration date 20 years from now. Example: + # + # cookies.permanent[:prefers_open_id] = true + # # => Set-Cookie: prefers_open_id=true; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT + # + # This jar is only meant for writing. You'll read permanent cookies through the regular accessor. + # + # This jar allows chaining with the signed jar as well, so you can set permanent, signed cookies. Examples: + # + # cookies.permanent.signed[:remember_me] = current_user.id + # # => Set-Cookie: remember_me=BAhU--848956038e692d7046deab32b7131856ab20e14e; path=/; expires=Sun, 16-Dec-2029 03:24:16 GMT + def permanent + @permanent ||= PermanentCookieJar.new(self) + end + + # Returns a jar that'll automatically generate a signed representation of cookie value and verify it when reading from + # the cookie again. This is useful for creating cookies with values that the user is not supposed to change. If a signed + # cookie was tampered with by the user (or a 3rd party), +nil+ will be returned. + # + # If +secret_key_base+ and +secrets.secret_token+ (deprecated) are both set, + # legacy cookies signed with the old key generator will be transparently upgraded. + # + # This jar requires that you set a suitable secret for the verification on your app's +secret_key_base+. + # + # Example: + # + # cookies.signed[:discount] = 45 + # # => Set-Cookie: discount=BAhpMg==--2c1c6906c90a3bc4fd54a51ffb41dffa4bf6b5f7; path=/ + # + # cookies.signed[:discount] # => 45 + def signed + @signed ||= SignedKeyRotatingCookieJar.new(self) + end + + # Returns a jar that'll automatically encrypt cookie values before sending them to the client and will decrypt them for read. + # If the cookie was tampered with by the user (or a 3rd party), +nil+ will be returned. + # + # If +secret_key_base+ and +secrets.secret_token+ (deprecated) are both set, + # legacy cookies signed with the old key generator will be transparently upgraded. + # + # If +config.action_dispatch.encrypted_cookie_salt+ and +config.action_dispatch.encrypted_signed_cookie_salt+ + # are both set, legacy cookies encrypted with HMAC AES-256-CBC will be transparently upgraded. + # + # This jar requires that you set a suitable secret for the verification on your app's +secret_key_base+. + # + # Example: + # + # cookies.encrypted[:discount] = 45 + # # => Set-Cookie: discount=DIQ7fw==--K3n//8vvnSbGq9dA--7Xh91HfLpwzbj1czhBiwOg==; path=/ + # + # cookies.encrypted[:discount] # => 45 + def encrypted + @encrypted ||= EncryptedKeyRotatingCookieJar.new(self) + end + + # Returns the +signed+ or +encrypted+ jar, preferring +encrypted+ if +secret_key_base+ is set. + # Used by ActionDispatch::Session::CookieStore to avoid the need to introduce new cookie stores. + def signed_or_encrypted + @signed_or_encrypted ||= + if request.secret_key_base.present? + encrypted + else + signed + end + end + + private + + def upgrade_legacy_signed_cookies? + request.secret_token.present? && request.secret_key_base.present? + end + + def upgrade_legacy_hmac_aes_cbc_cookies? + request.secret_key_base.present? && + request.encrypted_signed_cookie_salt.present? && + request.encrypted_cookie_salt.present? && + request.use_authenticated_cookie_encryption + end + + def encrypted_cookie_cipher + request.encrypted_cookie_cipher || "aes-256-gcm" + end + + def signed_cookie_digest + request.signed_cookie_digest || "SHA1" + end + end + + class CookieJar #:nodoc: + include Enumerable, ChainedCookieJars + + # This regular expression is used to split the levels of a domain. + # The top level domain can be any string without a period or + # **.**, ***.** style TLDs like co.uk or com.au + # + # www.example.co.uk gives: + # $& => example.co.uk + # + # example.com gives: + # $& => example.com + # + # lots.of.subdomains.example.local gives: + # $& => example.local + DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/ + + def self.build(req, cookies) + new(req).tap do |hash| + hash.update(cookies) + end + end + + attr_reader :request + + def initialize(request) + @set_cookies = {} + @delete_cookies = {} + @request = request + @cookies = {} + @committed = false + end + + def committed?; @committed; end + + def commit! + @committed = true + @set_cookies.freeze + @delete_cookies.freeze + end + + def each(&block) + @cookies.each(&block) + end + + # Returns the value of the cookie by +name+, or +nil+ if no such cookie exists. + def [](name) + @cookies[name.to_s] + end + + def fetch(name, *args, &block) + @cookies.fetch(name.to_s, *args, &block) + end + + def key?(name) + @cookies.key?(name.to_s) + end + alias :has_key? :key? + + # Returns the cookies as Hash. + alias :to_hash :to_h + + def update(other_hash) + @cookies.update other_hash.stringify_keys + self + end + + def update_cookies_from_jar + request_jar = @request.cookie_jar.instance_variable_get(:@cookies) + set_cookies = request_jar.reject { |k, _| @delete_cookies.key?(k) } + + @cookies.update set_cookies if set_cookies + end + + def to_header + @cookies.map { |k, v| "#{escape(k)}=#{escape(v)}" }.join "; " + end + + def handle_options(options) # :nodoc: + if options[:expires].respond_to?(:from_now) + options[:expires] = options[:expires].from_now + end + + options[:path] ||= "/" + + if options[:domain] == :all || options[:domain] == "all" + # If there is a provided tld length then we use it otherwise default domain regexp. + domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP + + # If host is not ip and matches domain regexp. + # (ip confirms to domain regexp so we explicitly check for ip) + options[:domain] = if (request.host !~ /^[\d.]+$/) && (request.host =~ domain_regexp) + ".#{$&}" + end + elsif options[:domain].is_a? Array + # If host matches one of the supplied domains without a dot in front of it. + options[:domain] = options[:domain].find { |domain| request.host.include? domain.sub(/^\./, "") } + end + end + + # Sets the cookie named +name+. The second argument may be the cookie's + # value or a hash of options as documented above. + def []=(name, options) + if options.is_a?(Hash) + options.symbolize_keys! + value = options[:value] + else + value = options + options = { value: value } + end + + handle_options(options) + + if @cookies[name.to_s] != value || options[:expires] + @cookies[name.to_s] = value + @set_cookies[name.to_s] = options + @delete_cookies.delete(name.to_s) + end + + value + end + + # Removes the cookie on the client machine by setting the value to an empty string + # and the expiration date in the past. Like []=, you can pass in + # an options hash to delete cookies with extra data such as a :path. + def delete(name, options = {}) + return unless @cookies.has_key? name.to_s + + options.symbolize_keys! + handle_options(options) + + value = @cookies.delete(name.to_s) + @delete_cookies[name.to_s] = options + value + end + + # Whether the given cookie is to be deleted by this CookieJar. + # Like []=, you can pass in an options hash to test if a + # deletion applies to a specific :path, :domain etc. + def deleted?(name, options = {}) + options.symbolize_keys! + handle_options(options) + @delete_cookies[name.to_s] == options + end + + # Removes all cookies on the client machine by calling delete for each cookie. + def clear(options = {}) + @cookies.each_key { |k| delete(k, options) } + end + + def write(headers) + if header = make_set_cookie_header(headers[HTTP_HEADER]) + headers[HTTP_HEADER] = header + end + end + + mattr_accessor :always_write_cookie, default: false + + private + + def escape(string) + ::Rack::Utils.escape(string) + end + + def make_set_cookie_header(header) + header = @set_cookies.inject(header) { |m, (k, v)| + if write_cookie?(v) + ::Rack::Utils.add_cookie_to_header(m, k, v) + else + m + end + } + @delete_cookies.inject(header) { |m, (k, v)| + ::Rack::Utils.add_remove_cookie_to_header(m, k, v) + } + end + + def write_cookie?(cookie) + request.ssl? || !cookie[:secure] || always_write_cookie + end + end + + class AbstractCookieJar # :nodoc: + include ChainedCookieJars + + def initialize(parent_jar) + @parent_jar = parent_jar + end + + def [](name) + if data = @parent_jar[name.to_s] + parse name, data + end + end + + def []=(name, options) + if options.is_a?(Hash) + options.symbolize_keys! + else + options = { value: options } + end + + commit(options) + @parent_jar[name] = options + end + + protected + def request; @parent_jar.request; end + + private + def expiry_options(options) + if request.use_authenticated_cookie_encryption + if options[:expires].respond_to?(:from_now) + { expires_in: options[:expires] } + else + { expires_at: options[:expires] } + end + else + {} + end + end + + def parse(name, data); data; end + def commit(options); end + end + + class PermanentCookieJar < AbstractCookieJar # :nodoc: + private + def commit(options) + options[:expires] = 20.years.from_now + end + end + + class JsonSerializer # :nodoc: + def self.load(value) + ActiveSupport::JSON.decode(value) + end + + def self.dump(value) + ActiveSupport::JSON.encode(value) + end + end + + module SerializedCookieJars # :nodoc: + MARSHAL_SIGNATURE = "\x04\x08".freeze + SERIALIZER = ActiveSupport::MessageEncryptor::NullSerializer + + protected + def needs_migration?(value) + request.cookies_serializer == :hybrid && value.start_with?(MARSHAL_SIGNATURE) + end + + def serialize(value) + serializer.dump(value) + end + + def deserialize(name) + rotate = false + value = yield -> { rotate = true } + + if value + case + when needs_migration?(value) + self[name] = Marshal.load(value) + when rotate + self[name] = serializer.load(value) + else + serializer.load(value) + end + end + end + + def serializer + serializer = request.cookies_serializer || :marshal + case serializer + when :marshal + Marshal + when :json, :hybrid + JsonSerializer + else + serializer + end + end + + def digest + request.cookies_digest || "SHA1" + end + end + + class SignedKeyRotatingCookieJar < AbstractCookieJar # :nodoc: + include SerializedCookieJars + + def initialize(parent_jar) + super + + secret = request.key_generator.generate_key(request.signed_cookie_salt) + @verifier = ActiveSupport::MessageVerifier.new(secret, digest: signed_cookie_digest, serializer: SERIALIZER) + + request.cookies_rotations.signed.each do |*secrets, **options| + @verifier.rotate(*secrets, serializer: SERIALIZER, **options) + end + + if upgrade_legacy_signed_cookies? + @verifier.rotate request.secret_token, serializer: SERIALIZER + end + end + + private + def parse(name, signed_message) + deserialize(name) do |rotate| + @verifier.verified(signed_message, on_rotation: rotate) + end + end + + def commit(options) + options[:value] = @verifier.generate(serialize(options[:value]), expiry_options(options)) + + raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE + end + end + + class EncryptedKeyRotatingCookieJar < AbstractCookieJar # :nodoc: + include SerializedCookieJars + + def initialize(parent_jar) + super + + if request.use_authenticated_cookie_encryption + key_len = ActiveSupport::MessageEncryptor.key_len(encrypted_cookie_cipher) + secret = request.key_generator.generate_key(request.authenticated_encrypted_cookie_salt, key_len) + @encryptor = ActiveSupport::MessageEncryptor.new(secret, cipher: encrypted_cookie_cipher, serializer: SERIALIZER) + else + key_len = ActiveSupport::MessageEncryptor.key_len("aes-256-cbc") + secret = request.key_generator.generate_key(request.encrypted_cookie_salt, key_len) + sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt) + @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, cipher: "aes-256-cbc", serializer: SERIALIZER) + end + + request.cookies_rotations.encrypted.each do |*secrets, **options| + @encryptor.rotate(*secrets, serializer: SERIALIZER, **options) + end + + if upgrade_legacy_hmac_aes_cbc_cookies? + legacy_cipher = "aes-256-cbc" + secret = request.key_generator.generate_key(request.encrypted_cookie_salt, ActiveSupport::MessageEncryptor.key_len(legacy_cipher)) + sign_secret = request.key_generator.generate_key(request.encrypted_signed_cookie_salt) + + @encryptor.rotate(secret, sign_secret, cipher: legacy_cipher, digest: digest, serializer: SERIALIZER) + end + + if upgrade_legacy_signed_cookies? + @legacy_verifier = ActiveSupport::MessageVerifier.new(request.secret_token, digest: digest, serializer: SERIALIZER) + end + end + + private + def parse(name, encrypted_message) + deserialize(name) do |rotate| + @encryptor.decrypt_and_verify(encrypted_message, on_rotation: rotate) + end + rescue ActiveSupport::MessageEncryptor::InvalidMessage, ActiveSupport::MessageVerifier::InvalidSignature + parse_legacy_signed_message(name, encrypted_message) + end + + def commit(options) + options[:value] = @encryptor.encrypt_and_sign(serialize(options[:value]), expiry_options(options)) + + raise CookieOverflow if options[:value].bytesize > MAX_COOKIE_SIZE + end + + def parse_legacy_signed_message(name, legacy_signed_message) + if defined?(@legacy_verifier) + deserialize(name) do |rotate| + rotate.call + + @legacy_verifier.verified(legacy_signed_message) + end + end + end + end + + def initialize(app) + @app = app + end + + def call(env) + request = ActionDispatch::Request.new env + + status, headers, body = @app.call(env) + + if request.have_cookie_jar? + cookie_jar = request.cookie_jar + unless cookie_jar.committed? + cookie_jar.write(headers) + if headers[HTTP_HEADER].respond_to?(:join) + headers[HTTP_HEADER] = headers[HTTP_HEADER].join("\n") + end + end + end + + [status, headers, body] + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/debug_exceptions.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/debug_exceptions.rb new file mode 100644 index 00000000..511306eb --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/debug_exceptions.rb @@ -0,0 +1,205 @@ +# frozen_string_literal: true + +require "action_dispatch/http/request" +require "action_dispatch/middleware/exception_wrapper" +require "action_dispatch/routing/inspector" +require "action_view" +require "action_view/base" + +require "pp" + +module ActionDispatch + # This middleware is responsible for logging exceptions and + # showing a debugging page in case the request is local. + class DebugExceptions + RESCUES_TEMPLATE_PATH = File.expand_path("templates", __dir__) + + class DebugView < ActionView::Base + def debug_params(params) + clean_params = params.clone + clean_params.delete("action") + clean_params.delete("controller") + + if clean_params.empty? + "None" + else + PP.pp(clean_params, "".dup, 200) + end + end + + def debug_headers(headers) + if headers.present? + headers.inspect.gsub(",", ",\n") + else + "None" + end + end + + def debug_hash(object) + object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n") + end + + def render(*) + logger = ActionView::Base.logger + + if logger && logger.respond_to?(:silence) + logger.silence { super } + else + super + end + end + end + + def initialize(app, routes_app = nil, response_format = :default) + @app = app + @routes_app = routes_app + @response_format = response_format + end + + def call(env) + request = ActionDispatch::Request.new env + _, headers, body = response = @app.call(env) + + if headers["X-Cascade"] == "pass" + body.close if body.respond_to?(:close) + raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}" + end + + response + rescue Exception => exception + raise exception unless request.show_exceptions? + render_exception(request, exception) + end + + private + + def render_exception(request, exception) + backtrace_cleaner = request.get_header("action_dispatch.backtrace_cleaner") + wrapper = ExceptionWrapper.new(backtrace_cleaner, exception) + log_error(request, wrapper) + + if request.get_header("action_dispatch.show_detailed_exceptions") + content_type = request.formats.first + + if api_request?(content_type) + render_for_api_request(content_type, wrapper) + else + render_for_browser_request(request, wrapper) + end + else + raise exception + end + end + + def render_for_browser_request(request, wrapper) + template = create_template(request, wrapper) + file = "rescues/#{wrapper.rescue_template}" + + if request.xhr? + body = template.render(template: file, layout: false, formats: [:text]) + format = "text/plain" + else + body = template.render(template: file, layout: "rescues/layout") + format = "text/html" + end + render(wrapper.status_code, body, format) + end + + def render_for_api_request(content_type, wrapper) + body = { + status: wrapper.status_code, + error: Rack::Utils::HTTP_STATUS_CODES.fetch( + wrapper.status_code, + Rack::Utils::HTTP_STATUS_CODES[500] + ), + exception: wrapper.exception.inspect, + traces: wrapper.traces + } + + to_format = "to_#{content_type.to_sym}" + + if content_type && body.respond_to?(to_format) + formatted_body = body.public_send(to_format) + format = content_type + else + formatted_body = body.to_json + format = Mime[:json] + end + + render(wrapper.status_code, formatted_body, format) + end + + def create_template(request, wrapper) + traces = wrapper.traces + + trace_to_show = "Application Trace" + if traces[trace_to_show].empty? && wrapper.rescue_template != "routing_error" + trace_to_show = "Full Trace" + end + + if source_to_show = traces[trace_to_show].first + source_to_show_id = source_to_show[:id] + end + + DebugView.new([RESCUES_TEMPLATE_PATH], + request: request, + exception: wrapper.exception, + traces: traces, + show_source_idx: source_to_show_id, + trace_to_show: trace_to_show, + routes_inspector: routes_inspector(wrapper.exception), + source_extracts: wrapper.source_extracts, + line_number: wrapper.line_number, + file: wrapper.file + ) + end + + def render(status, body, format) + [status, { "Content-Type" => "#{format}; charset=#{Response.default_charset}", "Content-Length" => body.bytesize.to_s }, [body]] + end + + def log_error(request, wrapper) + logger = logger(request) + return unless logger + + exception = wrapper.exception + + trace = wrapper.application_trace + trace = wrapper.framework_trace if trace.empty? + + ActiveSupport::Deprecation.silence do + logger.fatal " " + logger.fatal "#{exception.class} (#{exception.message}):" + log_array logger, exception.annoted_source_code if exception.respond_to?(:annoted_source_code) + logger.fatal " " + log_array logger, trace + end + end + + def log_array(logger, array) + if logger.formatter && logger.formatter.respond_to?(:tags_text) + logger.fatal array.join("\n#{logger.formatter.tags_text}") + else + logger.fatal array.join("\n") + end + end + + def logger(request) + request.logger || ActionView::Base.logger || stderr_logger + end + + def stderr_logger + @stderr_logger ||= ActiveSupport::Logger.new($stderr) + end + + def routes_inspector(exception) + if @routes_app.respond_to?(:routes) && (exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error)) + ActionDispatch::Routing::RoutesInspector.new(@routes_app.routes.routes) + end + end + + def api_request?(content_type) + @response_format == :api && !content_type.html? + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/debug_locks.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/debug_locks.rb new file mode 100644 index 00000000..03760438 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/debug_locks.rb @@ -0,0 +1,124 @@ +# frozen_string_literal: true + +module ActionDispatch + # This middleware can be used to diagnose deadlocks in the autoload interlock. + # + # To use it, insert it near the top of the middleware stack, using + # config/application.rb: + # + # config.middleware.insert_before Rack::Sendfile, ActionDispatch::DebugLocks + # + # After restarting the application and re-triggering the deadlock condition, + # /rails/locks will show a summary of all threads currently known to + # the interlock, which lock level they are holding or awaiting, and their + # current backtrace. + # + # Generally a deadlock will be caused by the interlock conflicting with some + # other external lock or blocking I/O call. These cannot be automatically + # identified, but should be visible in the displayed backtraces. + # + # NOTE: The formatting and content of this middleware's output is intended for + # human consumption, and should be expected to change between releases. + # + # This middleware exposes operational details of the server, with no access + # control. It should only be enabled when in use, and removed thereafter. + class DebugLocks + def initialize(app, path = "/rails/locks") + @app = app + @path = path + end + + def call(env) + req = ActionDispatch::Request.new env + + if req.get? + path = req.path_info.chomp("/".freeze) + if path == @path + return render_details(req) + end + end + + @app.call(env) + end + + private + def render_details(req) + threads = ActiveSupport::Dependencies.interlock.raw_state do |raw_threads| + # The Interlock itself comes to a complete halt as long as this block + # is executing. That gives us a more consistent picture of everything, + # but creates a pretty strong Observer Effect. + # + # Most directly, that means we need to do as little as possible in + # this block. More widely, it means this middleware should remain a + # strictly diagnostic tool (to be used when something has gone wrong), + # and not for any sort of general monitoring. + + raw_threads.each.with_index do |(thread, info), idx| + info[:index] = idx + info[:backtrace] = thread.backtrace + end + + raw_threads + end + + str = threads.map do |thread, info| + if info[:exclusive] + lock_state = "Exclusive".dup + elsif info[:sharing] > 0 + lock_state = "Sharing".dup + lock_state << " x#{info[:sharing]}" if info[:sharing] > 1 + else + lock_state = "No lock".dup + end + + if info[:waiting] + lock_state << " (yielded share)" + end + + msg = "Thread #{info[:index]} [0x#{thread.__id__.to_s(16)} #{thread.status || 'dead'}] #{lock_state}\n".dup + + if info[:sleeper] + msg << " Waiting in #{info[:sleeper]}" + msg << " to #{info[:purpose].to_s.inspect}" unless info[:purpose].nil? + msg << "\n" + + if info[:compatible] + compat = info[:compatible].map { |c| c == false ? "share" : c.to_s.inspect } + msg << " may be pre-empted for: #{compat.join(', ')}\n" + end + + blockers = threads.values.select { |binfo| blocked_by?(info, binfo, threads.values) } + msg << " blocked by: #{blockers.map { |i| i[:index] }.join(', ')}\n" if blockers.any? + end + + blockees = threads.values.select { |binfo| blocked_by?(binfo, info, threads.values) } + msg << " blocking: #{blockees.map { |i| i[:index] }.join(', ')}\n" if blockees.any? + + msg << "\n#{info[:backtrace].join("\n")}\n" if info[:backtrace] + end.join("\n\n---\n\n\n") + + [200, { "Content-Type" => "text/plain", "Content-Length" => str.size }, [str]] + end + + def blocked_by?(victim, blocker, all_threads) + return false if victim.equal?(blocker) + + case victim[:sleeper] + when :start_sharing + blocker[:exclusive] || + (!victim[:waiting] && blocker[:compatible] && !blocker[:compatible].include?(false)) + when :start_exclusive + blocker[:sharing] > 0 || + blocker[:exclusive] || + (blocker[:compatible] && !blocker[:compatible].include?(victim[:purpose])) + when :yield_shares + blocker[:exclusive] + when :stop_exclusive + blocker[:exclusive] || + victim[:compatible] && + victim[:compatible].include?(blocker[:purpose]) && + all_threads.all? { |other| !other[:compatible] || blocker.equal?(other) || other[:compatible].include?(blocker[:purpose]) } + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/exception_wrapper.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/exception_wrapper.rb new file mode 100644 index 00000000..d1b45083 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/exception_wrapper.rb @@ -0,0 +1,147 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attribute_accessors" +require "rack/utils" + +module ActionDispatch + class ExceptionWrapper + cattr_accessor :rescue_responses, default: Hash.new(:internal_server_error).merge!( + "ActionController::RoutingError" => :not_found, + "AbstractController::ActionNotFound" => :not_found, + "ActionController::MethodNotAllowed" => :method_not_allowed, + "ActionController::UnknownHttpMethod" => :method_not_allowed, + "ActionController::NotImplemented" => :not_implemented, + "ActionController::UnknownFormat" => :not_acceptable, + "ActionController::InvalidAuthenticityToken" => :unprocessable_entity, + "ActionController::InvalidCrossOriginRequest" => :unprocessable_entity, + "ActionDispatch::Http::Parameters::ParseError" => :bad_request, + "ActionController::BadRequest" => :bad_request, + "ActionController::ParameterMissing" => :bad_request, + "Rack::QueryParser::ParameterTypeError" => :bad_request, + "Rack::QueryParser::InvalidParameterError" => :bad_request + ) + + cattr_accessor :rescue_templates, default: Hash.new("diagnostics").merge!( + "ActionView::MissingTemplate" => "missing_template", + "ActionController::RoutingError" => "routing_error", + "AbstractController::ActionNotFound" => "unknown_action", + "ActiveRecord::StatementInvalid" => "invalid_statement", + "ActionView::Template::Error" => "template_error" + ) + + attr_reader :backtrace_cleaner, :exception, :line_number, :file + + def initialize(backtrace_cleaner, exception) + @backtrace_cleaner = backtrace_cleaner + @exception = original_exception(exception) + + expand_backtrace if exception.is_a?(SyntaxError) || exception.cause.is_a?(SyntaxError) + end + + def rescue_template + @@rescue_templates[@exception.class.name] + end + + def status_code + self.class.status_code_for_exception(@exception.class.name) + end + + def application_trace + clean_backtrace(:silent) + end + + def framework_trace + clean_backtrace(:noise) + end + + def full_trace + clean_backtrace(:all) + end + + def traces + application_trace_with_ids = [] + framework_trace_with_ids = [] + full_trace_with_ids = [] + + full_trace.each_with_index do |trace, idx| + trace_with_id = { id: idx, trace: trace } + + if application_trace.include?(trace) + application_trace_with_ids << trace_with_id + else + framework_trace_with_ids << trace_with_id + end + + full_trace_with_ids << trace_with_id + end + + { + "Application Trace" => application_trace_with_ids, + "Framework Trace" => framework_trace_with_ids, + "Full Trace" => full_trace_with_ids + } + end + + def self.status_code_for_exception(class_name) + Rack::Utils.status_code(@@rescue_responses[class_name]) + end + + def source_extracts + backtrace.map do |trace| + file, line_number = extract_file_and_line_number(trace) + + { + code: source_fragment(file, line_number), + line_number: line_number + } + end + end + + private + + def backtrace + Array(@exception.backtrace) + end + + def original_exception(exception) + if @@rescue_responses.has_key?(exception.cause.class.name) + exception.cause + else + exception + end + end + + def clean_backtrace(*args) + if backtrace_cleaner + backtrace_cleaner.clean(backtrace, *args) + else + backtrace + end + end + + def source_fragment(path, line) + return unless Rails.respond_to?(:root) && Rails.root + full_path = Rails.root.join(path) + if File.exist?(full_path) + File.open(full_path, "r") do |file| + start = [line - 3, 0].max + lines = file.each_line.drop(start).take(6) + Hash[*(start + 1..(lines.count + start)).zip(lines).flatten] + end + end + end + + def extract_file_and_line_number(trace) + # Split by the first colon followed by some digits, which works for both + # Windows and Unix path styles. + file, line = trace.match(/^(.+?):(\d+).*$/, &:captures) || trace + [file, line.to_i] + end + + def expand_backtrace + @exception.backtrace.unshift( + @exception.to_s.split("\n") + ).flatten! + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/executor.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/executor.rb new file mode 100644 index 00000000..129b18d3 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/executor.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require "rack/body_proxy" + +module ActionDispatch + class Executor + def initialize(app, executor) + @app, @executor = app, executor + end + + def call(env) + state = @executor.run! + begin + response = @app.call(env) + returned = response << ::Rack::BodyProxy.new(response.pop) { state.complete! } + ensure + state.complete! unless returned + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/flash.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/flash.rb new file mode 100644 index 00000000..fd05eec1 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/flash.rb @@ -0,0 +1,300 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/keys" + +module ActionDispatch + # The flash provides a way to pass temporary primitive-types (String, Array, Hash) between actions. Anything you place in the flash will be exposed + # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create + # action that sets flash[:notice] = "Post successfully created" before redirecting to a display action that can + # then expose the flash to its template. Actually, that exposure is automatically done. + # + # class PostsController < ActionController::Base + # def create + # # save post + # flash[:notice] = "Post successfully created" + # redirect_to @post + # end + # + # def show + # # doesn't need to assign the flash notice to the template, that's done automatically + # end + # end + # + # show.html.erb + # <% if flash[:notice] %> + #
<%= flash[:notice] %>
+ # <% end %> + # + # Since the +notice+ and +alert+ keys are a common idiom, convenience accessors are available: + # + # flash.alert = "You must be logged in" + # flash.notice = "Post successfully created" + # + # This example places a string in the flash. And of course, you can put as many as you like at a time too. If you want to pass + # non-primitive types, you will have to handle that in your application. Example: To show messages with links, you will have to + # use sanitize helper. + # + # Just remember: They'll be gone by the time the next action has been performed. + # + # See docs on the FlashHash class for more details about the flash. + class Flash + KEY = "action_dispatch.request.flash_hash".freeze + + module RequestMethods + # Access the contents of the flash. Use flash["notice"] to + # read a notice you put there or flash["notice"] = "hello" + # to put a new one. + def flash + flash = flash_hash + return flash if flash + self.flash = Flash::FlashHash.from_session_value(session["flash"]) + end + + def flash=(flash) + set_header Flash::KEY, flash + end + + def flash_hash # :nodoc: + get_header Flash::KEY + end + + def commit_flash # :nodoc: + session = self.session || {} + flash_hash = self.flash_hash + + if flash_hash && (flash_hash.present? || session.key?("flash")) + session["flash"] = flash_hash.to_session_value + self.flash = flash_hash.dup + end + + if (!session.respond_to?(:loaded?) || session.loaded?) && # reset_session uses {}, which doesn't implement #loaded? + session.key?("flash") && session["flash"].nil? + session.delete("flash") + end + end + + def reset_session # :nodoc: + super + self.flash = nil + end + end + + class FlashNow #:nodoc: + attr_accessor :flash + + def initialize(flash) + @flash = flash + end + + def []=(k, v) + k = k.to_s + @flash[k] = v + @flash.discard(k) + v + end + + def [](k) + @flash[k.to_s] + end + + # Convenience accessor for flash.now[:alert]=. + def alert=(message) + self[:alert] = message + end + + # Convenience accessor for flash.now[:notice]=. + def notice=(message) + self[:notice] = message + end + end + + class FlashHash + include Enumerable + + def self.from_session_value(value) #:nodoc: + case value + when FlashHash # Rails 3.1, 3.2 + flashes = value.instance_variable_get(:@flashes) + if discard = value.instance_variable_get(:@used) + flashes.except!(*discard) + end + new(flashes, flashes.keys) + when Hash # Rails 4.0 + flashes = value["flashes"] + if discard = value["discard"] + flashes.except!(*discard) + end + new(flashes, flashes.keys) + else + new + end + end + + # Builds a hash containing the flashes to keep for the next request. + # If there are none to keep, returns +nil+. + def to_session_value #:nodoc: + flashes_to_keep = @flashes.except(*@discard) + return nil if flashes_to_keep.empty? + { "discard" => [], "flashes" => flashes_to_keep } + end + + def initialize(flashes = {}, discard = []) #:nodoc: + @discard = Set.new(stringify_array(discard)) + @flashes = flashes.stringify_keys + @now = nil + end + + def initialize_copy(other) + if other.now_is_loaded? + @now = other.now.dup + @now.flash = self + end + super + end + + def []=(k, v) + k = k.to_s + @discard.delete k + @flashes[k] = v + end + + def [](k) + @flashes[k.to_s] + end + + def update(h) #:nodoc: + @discard.subtract stringify_array(h.keys) + @flashes.update h.stringify_keys + self + end + + def keys + @flashes.keys + end + + def key?(name) + @flashes.key? name.to_s + end + + def delete(key) + key = key.to_s + @discard.delete key + @flashes.delete key + self + end + + def to_hash + @flashes.dup + end + + def empty? + @flashes.empty? + end + + def clear + @discard.clear + @flashes.clear + end + + def each(&block) + @flashes.each(&block) + end + + alias :merge! :update + + def replace(h) #:nodoc: + @discard.clear + @flashes.replace h.stringify_keys + self + end + + # Sets a flash that will not be available to the next action, only to the current. + # + # flash.now[:message] = "Hello current action" + # + # This method enables you to use the flash as a central messaging system in your app. + # When you need to pass an object to the next action, you use the standard flash assign ([]=). + # When you need to pass an object to the current action, you use now, and your object will + # vanish when the current action is done. + # + # Entries set via now are accessed the same way as standard entries: flash['my-key']. + # + # Also, brings two convenience accessors: + # + # flash.now.alert = "Beware now!" + # # Equivalent to flash.now[:alert] = "Beware now!" + # + # flash.now.notice = "Good luck now!" + # # Equivalent to flash.now[:notice] = "Good luck now!" + def now + @now ||= FlashNow.new(self) + end + + # Keeps either the entire current flash or a specific flash entry available for the next action: + # + # flash.keep # keeps the entire flash + # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded + def keep(k = nil) + k = k.to_s if k + @discard.subtract Array(k || keys) + k ? self[k] : self + end + + # Marks the entire flash or a single flash entry to be discarded by the end of the current action: + # + # flash.discard # discard the entire flash at the end of the current action + # flash.discard(:warning) # discard only the "warning" entry at the end of the current action + def discard(k = nil) + k = k.to_s if k + @discard.merge Array(k || keys) + k ? self[k] : self + end + + # Mark for removal entries that were kept, and delete unkept ones. + # + # This method is called automatically by filters, so you generally don't need to care about it. + def sweep #:nodoc: + @discard.each { |k| @flashes.delete k } + @discard.replace @flashes.keys + end + + # Convenience accessor for flash[:alert]. + def alert + self[:alert] + end + + # Convenience accessor for flash[:alert]=. + def alert=(message) + self[:alert] = message + end + + # Convenience accessor for flash[:notice]. + def notice + self[:notice] + end + + # Convenience accessor for flash[:notice]=. + def notice=(message) + self[:notice] = message + end + + protected + def now_is_loaded? + @now + end + + private + def stringify_array(array) # :doc: + array.map do |item| + item.kind_of?(Symbol) ? item.to_s : item + end + end + end + + def self.new(app) app; end + end + + class Request + prepend Flash::RequestMethods + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/public_exceptions.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/public_exceptions.rb new file mode 100644 index 00000000..3feb3a19 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/public_exceptions.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module ActionDispatch + # When called, this middleware renders an error page. By default if an HTML + # response is expected it will render static error pages from the /public + # directory. For example when this middleware receives a 500 response it will + # render the template found in /public/500.html. + # If an internationalized locale is set, this middleware will attempt to render + # the template in /public/500..html. If an internationalized template + # is not found it will fall back on /public/500.html. + # + # When a request with a content type other than HTML is made, this middleware + # will attempt to convert error information into the appropriate response type. + class PublicExceptions + attr_accessor :public_path + + def initialize(public_path) + @public_path = public_path + end + + def call(env) + request = ActionDispatch::Request.new(env) + status = request.path_info[1..-1].to_i + content_type = request.formats.first + body = { status: status, error: Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500]) } + + render(status, content_type, body) + end + + private + + def render(status, content_type, body) + format = "to_#{content_type.to_sym}" if content_type + if format && body.respond_to?(format) + render_format(status, content_type, body.public_send(format)) + else + render_html(status) + end + end + + def render_format(status, content_type, body) + [status, { "Content-Type" => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}", + "Content-Length" => body.bytesize.to_s }, [body]] + end + + def render_html(status) + path = "#{public_path}/#{status}.#{I18n.locale}.html" + path = "#{public_path}/#{status}.html" unless (found = File.exist?(path)) + + if found || File.exist?(path) + render_format(status, "text/html", File.read(path)) + else + [404, { "X-Cascade" => "pass" }, []] + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/reloader.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/reloader.rb new file mode 100644 index 00000000..8bb3ba75 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/reloader.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module ActionDispatch + # ActionDispatch::Reloader wraps the request with callbacks provided by ActiveSupport::Reloader + # callbacks, intended to assist with code reloading during development. + # + # By default, ActionDispatch::Reloader is included in the middleware stack + # only in the development environment; specifically, when +config.cache_classes+ + # is false. + class Reloader < Executor + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/remote_ip.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/remote_ip.rb new file mode 100644 index 00000000..35158f90 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/remote_ip.rb @@ -0,0 +1,183 @@ +# frozen_string_literal: true + +require "ipaddr" + +module ActionDispatch + # This middleware calculates the IP address of the remote client that is + # making the request. It does this by checking various headers that could + # contain the address, and then picking the last-set address that is not + # on the list of trusted IPs. This follows the precedent set by e.g. + # {the Tomcat server}[https://issues.apache.org/bugzilla/show_bug.cgi?id=50453], + # with {reasoning explained at length}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection] + # by @gingerlime. A more detailed explanation of the algorithm is given + # at GetIp#calculate_ip. + # + # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2] + # requires. Some Rack servers simply drop preceding headers, and only report + # the value that was {given in the last header}[http://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers]. + # If you are behind multiple proxy servers (like NGINX to HAProxy to Unicorn) + # then you should test your Rack server to make sure your data is good. + # + # IF YOU DON'T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING. + # This middleware assumes that there is at least one proxy sitting around + # and setting headers with the client's remote IP address. If you don't use + # a proxy, because you are hosted on e.g. Heroku without SSL, any client can + # claim to have any IP address by setting the X-Forwarded-For header. If you + # care about that, then you need to explicitly drop or ignore those headers + # sometime before this middleware runs. + class RemoteIp + class IpSpoofAttackError < StandardError; end + + # The default trusted IPs list simply includes IP addresses that are + # guaranteed by the IP specification to be private addresses. Those will + # not be the ultimate client IP in production, and so are discarded. See + # https://en.wikipedia.org/wiki/Private_network for details. + TRUSTED_PROXIES = [ + "127.0.0.1", # localhost IPv4 + "::1", # localhost IPv6 + "fc00::/7", # private IPv6 range fc00::/7 + "10.0.0.0/8", # private IPv4 range 10.x.x.x + "172.16.0.0/12", # private IPv4 range 172.16.0.0 .. 172.31.255.255 + "192.168.0.0/16", # private IPv4 range 192.168.x.x + ].map { |proxy| IPAddr.new(proxy) } + + attr_reader :check_ip, :proxies + + # Create a new +RemoteIp+ middleware instance. + # + # The +ip_spoofing_check+ option is on by default. When on, an exception + # is raised if it looks like the client is trying to lie about its own IP + # address. It makes sense to turn off this check on sites aimed at non-IP + # clients (like WAP devices), or behind proxies that set headers in an + # incorrect or confusing way (like AWS ELB). + # + # The +custom_proxies+ argument can take an Array of string, IPAddr, or + # Regexp objects which will be used instead of +TRUSTED_PROXIES+. If a + # single string, IPAddr, or Regexp object is provided, it will be used in + # addition to +TRUSTED_PROXIES+. Any proxy setup will put the value you + # want in the middle (or at the beginning) of the X-Forwarded-For list, + # with your proxy servers after it. If your proxies aren't removed, pass + # them in via the +custom_proxies+ parameter. That way, the middleware will + # ignore those IP addresses, and return the one that you want. + def initialize(app, ip_spoofing_check = true, custom_proxies = nil) + @app = app + @check_ip = ip_spoofing_check + @proxies = if custom_proxies.blank? + TRUSTED_PROXIES + elsif custom_proxies.respond_to?(:any?) + custom_proxies + else + Array(custom_proxies) + TRUSTED_PROXIES + end + end + + # Since the IP address may not be needed, we store the object here + # without calculating the IP to keep from slowing down the majority of + # requests. For those requests that do need to know the IP, the + # GetIp#calculate_ip method will calculate the memoized client IP address. + def call(env) + req = ActionDispatch::Request.new env + req.remote_ip = GetIp.new(req, check_ip, proxies) + @app.call(req.env) + end + + # The GetIp class exists as a way to defer processing of the request data + # into an actual IP address. If the ActionDispatch::Request#remote_ip method + # is called, this class will calculate the value and then memoize it. + class GetIp + def initialize(req, check_ip, proxies) + @req = req + @check_ip = check_ip + @proxies = proxies + end + + # Sort through the various IP address headers, looking for the IP most + # likely to be the address of the actual remote client making this + # request. + # + # REMOTE_ADDR will be correct if the request is made directly against the + # Ruby process, on e.g. Heroku. When the request is proxied by another + # server like HAProxy or NGINX, the IP address that made the original + # request will be put in an X-Forwarded-For header. If there are multiple + # proxies, that header may contain a list of IPs. Other proxy services + # set the Client-Ip header instead, so we check that too. + # + # As discussed in {this post about Rails IP Spoofing}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/], + # while the first IP in the list is likely to be the "originating" IP, + # it could also have been set by the client maliciously. + # + # In order to find the first address that is (probably) accurate, we + # take the list of IPs, remove known and trusted proxies, and then take + # the last address left, which was presumably set by one of those proxies. + def calculate_ip + # Set by the Rack web server, this is a single value. + remote_addr = ips_from(@req.remote_addr).last + + # Could be a CSV list and/or repeated headers that were concatenated. + client_ips = ips_from(@req.client_ip).reverse + forwarded_ips = ips_from(@req.x_forwarded_for).reverse + + # +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set. + # If they are both set, it means that either: + # + # 1) This request passed through two proxies with incompatible IP header + # conventions. + # 2) The client passed one of +Client-Ip+ or +X-Forwarded-For+ + # (whichever the proxy servers weren't using) themselves. + # + # Either way, there is no way for us to determine which header is the + # right one after the fact. Since we have no idea, if we are concerned + # about IP spoofing we need to give up and explode. (If you're not + # concerned about IP spoofing you can turn the +ip_spoofing_check+ + # option off.) + should_check_ip = @check_ip && client_ips.last && forwarded_ips.last + if should_check_ip && !forwarded_ips.include?(client_ips.last) + # We don't know which came from the proxy, and which from the user + raise IpSpoofAttackError, "IP spoofing attack?! " \ + "HTTP_CLIENT_IP=#{@req.client_ip.inspect} " \ + "HTTP_X_FORWARDED_FOR=#{@req.x_forwarded_for.inspect}" + end + + # We assume these things about the IP headers: + # + # - X-Forwarded-For will be a list of IPs, one per proxy, or blank + # - Client-Ip is propagated from the outermost proxy, or is blank + # - REMOTE_ADDR will be the IP that made the request to Rack + ips = [forwarded_ips, client_ips, remote_addr].flatten.compact + + # If every single IP option is in the trusted list, just return REMOTE_ADDR + filter_proxies(ips).first || remote_addr + end + + # Memoizes the value returned by #calculate_ip and returns it for + # ActionDispatch::Request to use. + def to_s + @ip ||= calculate_ip + end + + private + + def ips_from(header) # :doc: + return [] unless header + # Split the comma-separated list into an array of strings. + ips = header.strip.split(/[,\s]+/) + ips.select do |ip| + begin + # Only return IPs that are valid according to the IPAddr#new method. + range = IPAddr.new(ip).to_range + # We want to make sure nobody is sneaking a netmask in. + range.begin == range.end + rescue ArgumentError + nil + end + end + end + + def filter_proxies(ips) # :doc: + ips.reject do |ip| + @proxies.any? { |proxy| proxy === ip } + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/request_id.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/request_id.rb new file mode 100644 index 00000000..da2871b5 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/request_id.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require "securerandom" +require "active_support/core_ext/string/access" + +module ActionDispatch + # Makes a unique request id available to the +action_dispatch.request_id+ env variable (which is then accessible + # through ActionDispatch::Request#request_id or the alias ActionDispatch::Request#uuid) and sends + # the same id to the client via the X-Request-Id header. + # + # The unique request id is either based on the X-Request-Id header in the request, which would typically be generated + # by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the + # header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only. + # + # The unique request id can be used to trace a request end-to-end and would typically end up being part of log files + # from multiple pieces of the stack. + class RequestId + X_REQUEST_ID = "X-Request-Id".freeze #:nodoc: + + def initialize(app) + @app = app + end + + def call(env) + req = ActionDispatch::Request.new env + req.request_id = make_request_id(req.x_request_id) + @app.call(env).tap { |_status, headers, _body| headers[X_REQUEST_ID] = req.request_id } + end + + private + def make_request_id(request_id) + if request_id.presence + request_id.gsub(/[^\w\-@]/, "".freeze).first(255) + else + internal_request_id + end + end + + def internal_request_id + SecureRandom.uuid + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/session/abstract_store.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/session/abstract_store.rb new file mode 100644 index 00000000..5b0be962 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/session/abstract_store.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require "rack/utils" +require "rack/request" +require "rack/session/abstract/id" +require "action_dispatch/middleware/cookies" +require "action_dispatch/request/session" + +module ActionDispatch + module Session + class SessionRestoreError < StandardError #:nodoc: + def initialize + super("Session contains objects whose class definition isn't available.\n" \ + "Remember to require the classes for all objects kept in the session.\n" \ + "(Original exception: #{$!.message} [#{$!.class}])\n") + set_backtrace $!.backtrace + end + end + + module Compatibility + def initialize(app, options = {}) + options[:key] ||= "_session_id" + super + end + + def generate_sid + sid = SecureRandom.hex(16) + sid.encode!(Encoding::UTF_8) + sid + end + + private + + def initialize_sid # :doc: + @default_options.delete(:sidbits) + @default_options.delete(:secure_random) + end + + def make_request(env) + ActionDispatch::Request.new env + end + end + + module StaleSessionCheck + def load_session(env) + stale_session_check! { super } + end + + def extract_session_id(env) + stale_session_check! { super } + end + + def stale_session_check! + yield + rescue ArgumentError => argument_error + if argument_error.message =~ %r{undefined class/module ([\w:]*\w)} + begin + # Note that the regexp does not allow $1 to end with a ':'. + $1.constantize + rescue LoadError, NameError + raise ActionDispatch::Session::SessionRestoreError + end + retry + else + raise + end + end + end + + module SessionObject # :nodoc: + def prepare_session(req) + Request::Session.create(self, req, @default_options) + end + + def loaded_session?(session) + !session.is_a?(Request::Session) || session.loaded? + end + end + + class AbstractStore < Rack::Session::Abstract::Persisted + include Compatibility + include StaleSessionCheck + include SessionObject + + private + + def set_cookie(request, session_id, cookie) + request.cookie_jar[key] = cookie + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/session/cache_store.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/session/cache_store.rb new file mode 100644 index 00000000..a6d965a6 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/session/cache_store.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require "action_dispatch/middleware/session/abstract_store" + +module ActionDispatch + module Session + # A session store that uses an ActiveSupport::Cache::Store to store the sessions. This store is most useful + # if you don't store critical data in your sessions and you don't need them to live for extended periods + # of time. + # + # ==== Options + # * cache - The cache to use. If it is not specified, Rails.cache will be used. + # * expire_after - The length of time a session will be stored before automatically expiring. + # By default, the :expires_in option of the cache is used. + class CacheStore < AbstractStore + def initialize(app, options = {}) + @cache = options[:cache] || Rails.cache + options[:expire_after] ||= @cache.options[:expires_in] + super + end + + # Get a session from the cache. + def find_session(env, sid) + unless sid && (session = @cache.read(cache_key(sid))) + sid, session = generate_sid, {} + end + [sid, session] + end + + # Set a session in the cache. + def write_session(env, sid, session, options) + key = cache_key(sid) + if session + @cache.write(key, session, expires_in: options[:expire_after]) + else + @cache.delete(key) + end + sid + end + + # Remove a session from the cache. + def delete_session(env, sid, options) + @cache.delete(cache_key(sid)) + generate_sid + end + + private + # Turn the session id into a cache key. + def cache_key(sid) + "_session_id:#{sid}" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/session/cookie_store.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/session/cookie_store.rb new file mode 100644 index 00000000..b7475d36 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/session/cookie_store.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/keys" +require "action_dispatch/middleware/session/abstract_store" +require "rack/session/cookie" + +module ActionDispatch + module Session + # This cookie-based session store is the Rails default. It is + # dramatically faster than the alternatives. + # + # Sessions typically contain at most a user_id and flash message; both fit + # within the 4K cookie size limit. A CookieOverflow exception is raised if + # you attempt to store more than 4K of data. + # + # The cookie jar used for storage is automatically configured to be the + # best possible option given your application's configuration. + # + # If you only have secret_token set, your cookies will be signed, but + # not encrypted. This means a user cannot alter their +user_id+ without + # knowing your app's secret key, but can easily read their +user_id+. This + # was the default for Rails 3 apps. + # + # Your cookies will be encrypted using your apps secret_key_base. This + # goes a step further than signed cookies in that encrypted cookies cannot + # be altered or read by users. This is the default starting in Rails 4. + # + # Configure your session store in config/initializers/session_store.rb: + # + # Rails.application.config.session_store :cookie_store, key: '_your_app_session' + # + # In the development and test environments your application's secret key base is + # generated by Rails and stored in a temporary file in tmp/development_secret.txt. + # In all other environments, it is stored encrypted in the + # config/credentials.yml.enc file. + # + # If your application was not updated to Rails 5.2 defaults, the secret_key_base + # will be found in the old config/secrets.yml file. + # + # Note that changing your secret_key_base will invalidate all existing session. + # Additionally, you should take care to make sure you are not relying on the + # ability to decode signed cookies generated by your app in external + # applications or JavaScript before changing it. + # + # Because CookieStore extends Rack::Session::Abstract::Persisted, many of the + # options described there can be used to customize the session cookie that + # is generated. For example: + # + # Rails.application.config.session_store :cookie_store, expire_after: 14.days + # + # would set the session cookie to expire automatically 14 days after creation. + # Other useful options include :key, :secure and + # :httponly. + class CookieStore < AbstractStore + def initialize(app, options = {}) + super(app, options.merge!(cookie_only: true)) + end + + def delete_session(req, session_id, options) + new_sid = generate_sid unless options[:drop] + # Reset hash and Assign the new session id + req.set_header("action_dispatch.request.unsigned_session_cookie", new_sid ? { "session_id" => new_sid } : {}) + new_sid + end + + def load_session(req) + stale_session_check! do + data = unpacked_cookie_data(req) + data = persistent_session_id!(data) + [data["session_id"], data] + end + end + + private + + def extract_session_id(req) + stale_session_check! do + unpacked_cookie_data(req)["session_id"] + end + end + + def unpacked_cookie_data(req) + req.fetch_header("action_dispatch.request.unsigned_session_cookie") do |k| + v = stale_session_check! do + if data = get_cookie(req) + data.stringify_keys! + end + data || {} + end + req.set_header k, v + end + end + + def persistent_session_id!(data, sid = nil) + data ||= {} + data["session_id"] ||= sid || generate_sid + data + end + + def write_session(req, sid, session_data, options) + session_data["session_id"] = sid + session_data + end + + def set_cookie(request, session_id, cookie) + cookie_jar(request)[@key] = cookie + end + + def get_cookie(req) + cookie_jar(req)[@key] + end + + def cookie_jar(request) + request.cookie_jar.signed_or_encrypted + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/session/mem_cache_store.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/session/mem_cache_store.rb new file mode 100644 index 00000000..914df3a2 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/session/mem_cache_store.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require "action_dispatch/middleware/session/abstract_store" +begin + require "rack/session/dalli" +rescue LoadError => e + $stderr.puts "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install" + raise e +end + +module ActionDispatch + module Session + # A session store that uses MemCache to implement storage. + # + # ==== Options + # * expire_after - The length of time a session will be stored before automatically expiring. + class MemCacheStore < Rack::Session::Dalli + include Compatibility + include StaleSessionCheck + include SessionObject + + def initialize(app, options = {}) + options[:expire_after] ||= options[:expires] + super + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/show_exceptions.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/show_exceptions.rb new file mode 100644 index 00000000..3c88afd4 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/show_exceptions.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require "action_dispatch/http/request" +require "action_dispatch/middleware/exception_wrapper" + +module ActionDispatch + # This middleware rescues any exception returned by the application + # and calls an exceptions app that will wrap it in a format for the end user. + # + # The exceptions app should be passed as parameter on initialization + # of ShowExceptions. Every time there is an exception, ShowExceptions will + # store the exception in env["action_dispatch.exception"], rewrite the + # PATH_INFO to the exception status code and call the Rack app. + # + # If the application returns a "X-Cascade" pass response, this middleware + # will send an empty response as result with the correct status code. + # If any exception happens inside the exceptions app, this middleware + # catches the exceptions and returns a FAILSAFE_RESPONSE. + class ShowExceptions + FAILSAFE_RESPONSE = [500, { "Content-Type" => "text/plain" }, + ["500 Internal Server Error\n" \ + "If you are the administrator of this website, then please read this web " \ + "application's log file and/or the web server's log file to find out what " \ + "went wrong."]] + + def initialize(app, exceptions_app) + @app = app + @exceptions_app = exceptions_app + end + + def call(env) + request = ActionDispatch::Request.new env + @app.call(env) + rescue Exception => exception + if request.show_exceptions? + render_exception(request, exception) + else + raise exception + end + end + + private + + def render_exception(request, exception) + backtrace_cleaner = request.get_header "action_dispatch.backtrace_cleaner" + wrapper = ExceptionWrapper.new(backtrace_cleaner, exception) + status = wrapper.status_code + request.set_header "action_dispatch.exception", wrapper.exception + request.set_header "action_dispatch.original_path", request.path_info + request.path_info = "/#{status}" + response = @exceptions_app.call(request.env) + response[1]["X-Cascade"] == "pass" ? pass_response(status) : response + rescue Exception => failsafe_error + $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}" + FAILSAFE_RESPONSE + end + + def pass_response(status) + [status, { "Content-Type" => "text/html; charset=#{Response.default_charset}", "Content-Length" => "0" }, []] + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/ssl.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/ssl.rb new file mode 100644 index 00000000..240269d1 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/ssl.rb @@ -0,0 +1,150 @@ +# frozen_string_literal: true + +module ActionDispatch + # This middleware is added to the stack when config.force_ssl = true, and is passed + # the options set in +config.ssl_options+. It does three jobs to enforce secure HTTP + # requests: + # + # 1. TLS redirect: Permanently redirects +http://+ requests to +https://+ + # with the same URL host, path, etc. Enabled by default. Set +config.ssl_options+ + # to modify the destination URL + # (e.g. redirect: { host: "secure.widgets.com", port: 8080 }), or set + # redirect: false to disable this feature. + # + # Requests can opt-out of redirection with +exclude+: + # + # config.ssl_options = { redirect: { exclude: -> request { request.path =~ /healthcheck/ } } } + # + # Cookies will not be flagged as secure for excluded requests. + # + # 2. Secure cookies: Sets the +secure+ flag on cookies to tell browsers they + # must not be sent along with +http://+ requests. Enabled by default. Set + # +config.ssl_options+ with secure_cookies: false to disable this feature. + # + # 3. HTTP Strict Transport Security (HSTS): Tells the browser to remember + # this site as TLS-only and automatically redirect non-TLS requests. + # Enabled by default. Configure +config.ssl_options+ with hsts: false to disable. + # + # Set +config.ssl_options+ with hsts: { ... } to configure HSTS: + # + # * +expires+: How long, in seconds, these settings will stick. The minimum + # required to qualify for browser preload lists is 1 year. Defaults to + # 1 year (recommended). + # + # * +subdomains+: Set to +true+ to tell the browser to apply these settings + # to all subdomains. This protects your cookies from interception by a + # vulnerable site on a subdomain. Defaults to +true+. + # + # * +preload+: Advertise that this site may be included in browsers' + # preloaded HSTS lists. HSTS protects your site on every visit except the + # first visit since it hasn't seen your HSTS header yet. To close this + # gap, browser vendors include a baked-in list of HSTS-enabled sites. + # Go to https://hstspreload.org to submit your site for inclusion. + # Defaults to +false+. + # + # To turn off HSTS, omitting the header is not enough. Browsers will remember the + # original HSTS directive until it expires. Instead, use the header to tell browsers to + # expire HSTS immediately. Setting hsts: false is a shortcut for + # hsts: { expires: 0 }. + class SSL + # :stopdoc: + + # Default to 1 year, the minimum for browser preload lists. + HSTS_EXPIRES_IN = 31536000 + + def self.default_hsts_options + { expires: HSTS_EXPIRES_IN, subdomains: true, preload: false } + end + + def initialize(app, redirect: {}, hsts: {}, secure_cookies: true) + @app = app + + @redirect = redirect + + @exclude = @redirect && @redirect[:exclude] || proc { !@redirect } + @secure_cookies = secure_cookies + + @hsts_header = build_hsts_header(normalize_hsts_options(hsts)) + end + + def call(env) + request = Request.new env + + if request.ssl? + @app.call(env).tap do |status, headers, body| + set_hsts_header! headers + flag_cookies_as_secure! headers if @secure_cookies && !@exclude.call(request) + end + else + return redirect_to_https request unless @exclude.call(request) + @app.call(env) + end + end + + private + def set_hsts_header!(headers) + headers["Strict-Transport-Security".freeze] ||= @hsts_header + end + + def normalize_hsts_options(options) + case options + # Explicitly disabling HSTS clears the existing setting from browsers + # by setting expiry to 0. + when false + self.class.default_hsts_options.merge(expires: 0) + # Default to enabled, with default options. + when nil, true + self.class.default_hsts_options + else + self.class.default_hsts_options.merge(options) + end + end + + # https://tools.ietf.org/html/rfc6797#section-6.1 + def build_hsts_header(hsts) + value = "max-age=#{hsts[:expires].to_i}".dup + value << "; includeSubDomains" if hsts[:subdomains] + value << "; preload" if hsts[:preload] + value + end + + def flag_cookies_as_secure!(headers) + if cookies = headers["Set-Cookie".freeze] + cookies = cookies.split("\n".freeze) + + headers["Set-Cookie".freeze] = cookies.map { |cookie| + if cookie !~ /;\s*secure\s*(;|$)/i + "#{cookie}; secure" + else + cookie + end + }.join("\n".freeze) + end + end + + def redirect_to_https(request) + [ @redirect.fetch(:status, redirection_status(request)), + { "Content-Type" => "text/html", + "Location" => https_location_for(request) }, + @redirect.fetch(:body, []) ] + end + + def redirection_status(request) + if request.get? || request.head? + 301 # Issue a permanent redirect via a GET request. + else + 307 # Issue a fresh request redirect to preserve the HTTP method. + end + end + + def https_location_for(request) + host = @redirect[:host] || request.host + port = @redirect[:port] || request.port + + location = "https://#{host}".dup + location << ":#{port}" if port != 80 && port != 443 + location << request.fullpath + location + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/stack.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/stack.rb new file mode 100644 index 00000000..b82f8aa3 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/stack.rb @@ -0,0 +1,116 @@ +# frozen_string_literal: true + +require "active_support/inflector/methods" +require "active_support/dependencies" + +module ActionDispatch + class MiddlewareStack + class Middleware + attr_reader :args, :block, :klass + + def initialize(klass, args, block) + @klass = klass + @args = args + @block = block + end + + def name; klass.name; end + + def ==(middleware) + case middleware + when Middleware + klass == middleware.klass + when Class + klass == middleware + end + end + + def inspect + if klass.is_a?(Class) + klass.to_s + else + klass.class.to_s + end + end + + def build(app) + klass.new(app, *args, &block) + end + end + + include Enumerable + + attr_accessor :middlewares + + def initialize(*args) + @middlewares = [] + yield(self) if block_given? + end + + def each + @middlewares.each { |x| yield x } + end + + def size + middlewares.size + end + + def last + middlewares.last + end + + def [](i) + middlewares[i] + end + + def unshift(klass, *args, &block) + middlewares.unshift(build_middleware(klass, args, block)) + end + + def initialize_copy(other) + self.middlewares = other.middlewares.dup + end + + def insert(index, klass, *args, &block) + index = assert_index(index, :before) + middlewares.insert(index, build_middleware(klass, args, block)) + end + + alias_method :insert_before, :insert + + def insert_after(index, *args, &block) + index = assert_index(index, :after) + insert(index + 1, *args, &block) + end + + def swap(target, *args, &block) + index = assert_index(target, :before) + insert(index, *args, &block) + middlewares.delete_at(index + 1) + end + + def delete(target) + middlewares.delete_if { |m| m.klass == target } + end + + def use(klass, *args, &block) + middlewares.push(build_middleware(klass, args, block)) + end + + def build(app = Proc.new) + middlewares.freeze.reverse.inject(app) { |a, e| e.build(a) } + end + + private + + def assert_index(index, where) + i = index.is_a?(Integer) ? index : middlewares.index { |m| m.klass == index } + raise "No such middleware to insert #{where}: #{index.inspect}" unless i + i + end + + def build_middleware(klass, args, block) + Middleware.new(klass, args, block) + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/static.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/static.rb new file mode 100644 index 00000000..e34c741f --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/static.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +require "rack/utils" +require "active_support/core_ext/uri" + +module ActionDispatch + # This middleware returns a file's contents from disk in the body response. + # When initialized, it can accept optional HTTP headers, which will be set + # when a response containing a file's contents is delivered. + # + # This middleware will render the file specified in env["PATH_INFO"] + # where the base path is in the +root+ directory. For example, if the +root+ + # is set to +public/+, then a request with env["PATH_INFO"] of + # +assets/application.js+ will return a response with the contents of a file + # located at +public/assets/application.js+ if the file exists. If the file + # does not exist, a 404 "File not Found" response will be returned. + class FileHandler + def initialize(root, index: "index", headers: {}) + @root = root.chomp("/").b + @file_server = ::Rack::File.new(@root, headers) + @index = index + end + + # Takes a path to a file. If the file is found, has valid encoding, and has + # correct read permissions, the return value is a URI-escaped string + # representing the filename. Otherwise, false is returned. + # + # Used by the +Static+ class to check the existence of a valid file + # in the server's +public/+ directory (see Static#call). + def match?(path) + path = ::Rack::Utils.unescape_path path + return false unless ::Rack::Utils.valid_path? path + path = ::Rack::Utils.clean_path_info path + + paths = [path, "#{path}#{ext}", "#{path}/#{@index}#{ext}"] + + if match = paths.detect { |p| + path = File.join(@root, p.b) + begin + File.file?(path) && File.readable?(path) + rescue SystemCallError + false + end + + } + return ::Rack::Utils.escape_path(match).b + end + end + + def call(env) + serve(Rack::Request.new(env)) + end + + def serve(request) + path = request.path_info + gzip_path = gzip_file_path(path) + + if gzip_path && gzip_encoding_accepted?(request) + request.path_info = gzip_path + status, headers, body = @file_server.call(request.env) + if status == 304 + return [status, headers, body] + end + headers["Content-Encoding"] = "gzip" + headers["Content-Type"] = content_type(path) + else + status, headers, body = @file_server.call(request.env) + end + + headers["Vary"] = "Accept-Encoding" if gzip_path + + return [status, headers, body] + ensure + request.path_info = path + end + + private + def ext + ::ActionController::Base.default_static_extension + end + + def content_type(path) + ::Rack::Mime.mime_type(::File.extname(path), "text/plain".freeze) + end + + def gzip_encoding_accepted?(request) + request.accept_encoding.any? { |enc, quality| enc =~ /\bgzip\b/i } + end + + def gzip_file_path(path) + can_gzip_mime = content_type(path) =~ /\A(?:text\/|application\/javascript)/ + gzip_path = "#{path}.gz" + if can_gzip_mime && File.exist?(File.join(@root, ::Rack::Utils.unescape_path(gzip_path).b)) + gzip_path.b + else + false + end + end + end + + # This middleware will attempt to return the contents of a file's body from + # disk in the response. If a file is not found on disk, the request will be + # delegated to the application stack. This middleware is commonly initialized + # to serve assets from a server's +public/+ directory. + # + # This middleware verifies the path to ensure that only files + # living in the root directory can be rendered. A request cannot + # produce a directory traversal using this middleware. Only 'GET' and 'HEAD' + # requests will result in a file being returned. + class Static + def initialize(app, path, index: "index", headers: {}) + @app = app + @file_handler = FileHandler.new(path, index: index, headers: headers) + end + + def call(env) + req = Rack::Request.new env + + if req.get? || req.head? + path = req.path_info.chomp("/".freeze) + if match = @file_handler.match?(path) + req.path_info = match + return @file_handler.serve(req) + end + end + + @app.call(req.env) + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb new file mode 100644 index 00000000..49b1e835 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb @@ -0,0 +1,22 @@ +<% unless @exception.blamed_files.blank? %> + <% if (hide = @exception.blamed_files.length > 8) %> + Toggle blamed files + <% end %> +
><%= @exception.describe_blame %>
+<% end %> + +

Request

+

Parameters:

<%= debug_params(@request.filtered_parameters) %>
+ +
+ + +
+ +
+ + +
+ +

Response

+

Headers:

<%= debug_headers(defined?(@response) ? @response.headers : {}) %>
diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb new file mode 100644 index 00000000..396768ec --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb @@ -0,0 +1,23 @@ +<% + clean_params = @request.filtered_parameters.clone + clean_params.delete("action") + clean_params.delete("controller") + + request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n") + + def debug_hash(object) + object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n") + end unless self.class.method_defined?(:debug_hash) +%> + +Request parameters +<%= request_dump %> + +Session dump +<%= debug_hash @request.session %> + +Env dump +<%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %> + +Response headers +<%= defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %> diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/_source.html.erb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/_source.html.erb new file mode 100644 index 00000000..e7b913bb --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/_source.html.erb @@ -0,0 +1,27 @@ +<% @source_extracts.each_with_index do |source_extract, index| %> + <% if source_extract[:code] %> +
" id="frame-source-<%=index%>"> +
+ Extracted source (around line #<%= source_extract[:line_number] %>): +
+
+ + + + + +
+
+                <% source_extract[:code].each_key do |line_number| %>
+<%= line_number -%>
+                <% end %>
+              
+
+
+<% source_extract[:code].each do |line, source| -%>
"><%= source -%>
<% end -%> +
+
+
+
+ <% end %> +<% end %> diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/_source.text.erb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/_source.text.erb new file mode 100644 index 00000000..23a9c7ba --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/_source.text.erb @@ -0,0 +1,8 @@ +<% @source_extracts.first(3).each do |source_extract| %> +<% if source_extract[:code] %> +Extracted source (around line #<%= source_extract[:line_number] %>): + +<% source_extract[:code].each do |line, source| -%> +<%= line == source_extract[:line_number] ? "*#{line}" : "##{line}" -%> <%= source -%><% end -%> +<% end %> +<% end %> diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb new file mode 100644 index 00000000..ab57b11c --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb @@ -0,0 +1,52 @@ +<% names = @traces.keys %> + +

Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %>

+ +
+ <% names.each do |name| %> + <% + show = "show('#{name.gsub(/\s/, '-')}');" + hide = (names - [name]).collect {|hide_name| "hide('#{hide_name.gsub(/\s/, '-')}');"} + %> + <%= name %> <%= '|' unless names.last == name %> + <% end %> + + <% @traces.each do |name, trace| %> +
+
<% trace.each do |frame| %><%= frame[:trace] %>
<% end %>
+
+ <% end %> + + +
diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb new file mode 100644 index 00000000..c0b53068 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb @@ -0,0 +1,9 @@ +Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %> + +<% @traces.each do |name, trace| %> +<% if trace.any? %> +<%= name %> +<%= trace.map { |t| t[:trace] }.join("\n") %> + +<% end %> +<% end %> diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb new file mode 100644 index 00000000..f154021a --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb @@ -0,0 +1,16 @@ +
+

+ <%= @exception.class.to_s %> + <% if @request.parameters['controller'] %> + in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %> + <% end %> +

+
+ +
+

<%= h @exception.message %>

+ + <%= render template: "rescues/_source" %> + <%= render template: "rescues/_trace" %> + <%= render template: "rescues/_request_and_response" %> +
diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb new file mode 100644 index 00000000..603de54b --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb @@ -0,0 +1,9 @@ +<%= @exception.class.to_s %><% + if @request.parameters['controller'] +%> in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %> +<% end %> + +<%= @exception.message %> +<%= render template: "rescues/_source" %> +<%= render template: "rescues/_trace" %> +<%= render template: "rescues/_request_and_response" %> diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb new file mode 100644 index 00000000..edc33674 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb @@ -0,0 +1,21 @@ +
+

+ <%= @exception.class.to_s %> + <% if @request.parameters['controller'] %> + in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %> + <% end %> +

+
+ +
+

+ <%= h @exception.message %> + <% if %r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}}.match?(@exception.message) %> +
To resolve this issue run: bin/rails active_storage:install + <% end %> +

+ + <%= render template: "rescues/_source" %> + <%= render template: "rescues/_trace" %> + <%= render template: "rescues/_request_and_response" %> +
diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb new file mode 100644 index 00000000..1e316edd --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb @@ -0,0 +1,13 @@ +<%= @exception.class.to_s %><% + if @request.parameters['controller'] +%> in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %> +<% end %> + +<%= @exception.message %> +<% if %r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}}.match?(@exception.message) %> +To resolve this issue run: bin/rails active_storage:install +<% end %> + +<%= render template: "rescues/_source" %> +<%= render template: "rescues/_trace" %> +<%= render template: "rescues/_request_and_response" %> diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/layout.erb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/layout.erb new file mode 100644 index 00000000..39ea25bd --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/layout.erb @@ -0,0 +1,161 @@ + + + + + Action Controller: Exception caught + + + + + + +<%= yield %> + + + diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb new file mode 100644 index 00000000..2a65fd06 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb @@ -0,0 +1,11 @@ +
+

Template is missing

+
+ +
+

<%= h @exception.message %>

+ + <%= render template: "rescues/_source" %> + <%= render template: "rescues/_trace" %> + <%= render template: "rescues/_request_and_response" %> +
diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb new file mode 100644 index 00000000..ae62d9eb --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb @@ -0,0 +1,3 @@ +Template is missing + +<%= @exception.message %> diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb new file mode 100644 index 00000000..55dd5ddc --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb @@ -0,0 +1,32 @@ +
+

Routing Error

+
+
+

<%= h @exception.message %>

+ <% unless @exception.failures.empty? %> +

+

Failure reasons:

+
    + <% @exception.failures.each do |route, reason| %> +
  1. <%= route.inspect.delete('\\') %> failed because <%= reason.downcase %>
  2. + <% end %> +
+

+ <% end %> + + <%= render template: "rescues/_trace" %> + + <% if @routes_inspector %> +

+ Routes +

+ +

+ Routes match in priority from top to bottom +

+ + <%= @routes_inspector.format(ActionDispatch::Routing::HtmlTableFormatter.new(self)) %> + <% end %> + + <%= render template: "rescues/_request_and_response" %> +
diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb new file mode 100644 index 00000000..f6e4dac1 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb @@ -0,0 +1,11 @@ +Routing Error + +<%= @exception.message %> +<% unless @exception.failures.empty? %> +Failure reasons: +<% @exception.failures.each do |route, reason| %> + - <%= route.inspect.delete('\\') %> failed because <%= reason.downcase %> +<% end %> +<% end %> + +<%= render template: "rescues/_trace", format: :text %> diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb new file mode 100644 index 00000000..5060da93 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb @@ -0,0 +1,20 @@ +
+

+ <%= @exception.cause.class.to_s %> in + <%= @request.parameters["controller"].camelize if @request.parameters["controller"] %>#<%= @request.parameters["action"] %> +

+
+ +
+

+ Showing <%= @exception.file_name %> where line #<%= @exception.line_number %> raised: +

+
<%= h @exception.message %>
+ + <%= render template: "rescues/_source" %> + +

<%= @exception.sub_template_message %>

+ + <%= render template: "rescues/_trace" %> + <%= render template: "rescues/_request_and_response" %> +
diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb new file mode 100644 index 00000000..78d52acd --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb @@ -0,0 +1,7 @@ +<%= @exception.cause.class.to_s %> in <%= @request.parameters["controller"].camelize if @request.parameters["controller"] %>#<%= @request.parameters["action"] %> + +Showing <%= @exception.file_name %> where line #<%= @exception.line_number %> raised: +<%= @exception.message %> +<%= @exception.sub_template_message %> +<%= render template: "rescues/_trace", format: :text %> +<%= render template: "rescues/_request_and_response", format: :text %> diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb new file mode 100644 index 00000000..259fb2bb --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb @@ -0,0 +1,6 @@ +
+

Unknown action

+
+
+

<%= h @exception.message %>

+
diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb new file mode 100644 index 00000000..83973add --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb @@ -0,0 +1,3 @@ +Unknown action + +<%= @exception.message %> diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/routes/_route.html.erb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/routes/_route.html.erb new file mode 100644 index 00000000..6e995c85 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/routes/_route.html.erb @@ -0,0 +1,16 @@ + + + <% if route[:name].present? %> + <%= route[:name] %>_path + <% end %> + + + <%= route[:verb] %> + + + <%= route[:path] %> + + + <%=simple_format route[:reqs] %> + + diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/routes/_table.html.erb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/routes/_table.html.erb new file mode 100644 index 00000000..1fa06913 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/middleware/templates/routes/_table.html.erb @@ -0,0 +1,200 @@ +<% content_for :style do %> + #route_table { + margin: 0; + border-collapse: collapse; + } + + #route_table thead tr { + border-bottom: 2px solid #ddd; + } + + #route_table thead tr.bottom { + border-bottom: none; + } + + #route_table thead tr.bottom th { + padding: 10px 0; + line-height: 15px; + } + + #route_table thead tr.bottom th input#search { + -webkit-appearance: textfield; + } + + #route_table tbody tr { + border-bottom: 1px solid #ddd; + } + + #route_table tbody tr:nth-child(odd) { + background: #f2f2f2; + } + + #route_table tbody.exact_matches, + #route_table tbody.fuzzy_matches { + background-color: LightGoldenRodYellow; + border-bottom: solid 2px SlateGrey; + } + + #route_table tbody.exact_matches tr, + #route_table tbody.fuzzy_matches tr { + background: none; + border-bottom: none; + } + + #route_table td { + padding: 4px 30px; + } + + #path_search { + width: 80%; + font-size: inherit; + } +<% end %> + + + + + + + + + + + + + + + + + + + + + + <%= yield %> + +
HelperHTTP VerbPathController#Action
<%# Helper %> + <%= link_to "Path", "#", 'data-route-helper' => '_path', + title: "Returns a relative path (without the http or domain)" %> / + <%= link_to "Url", "#", 'data-route-helper' => '_url', + title: "Returns an absolute URL (with the http and domain)" %> + <%# HTTP Verb %> + <%# Path %> + <%= search_field(:path, nil, id: 'search', placeholder: "Path Match") %> + <%# Controller#action %> +
+ + diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/railtie.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/railtie.rb new file mode 100644 index 00000000..eb6fbca6 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/railtie.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require "action_dispatch" +require "active_support/messages/rotation_configuration" + +module ActionDispatch + class Railtie < Rails::Railtie # :nodoc: + config.action_dispatch = ActiveSupport::OrderedOptions.new + config.action_dispatch.x_sendfile_header = nil + config.action_dispatch.ip_spoofing_check = true + config.action_dispatch.show_exceptions = true + config.action_dispatch.tld_length = 1 + config.action_dispatch.ignore_accept_header = false + config.action_dispatch.rescue_templates = {} + config.action_dispatch.rescue_responses = {} + config.action_dispatch.default_charset = nil + config.action_dispatch.rack_cache = false + config.action_dispatch.http_auth_salt = "http authentication" + config.action_dispatch.signed_cookie_salt = "signed cookie" + config.action_dispatch.encrypted_cookie_salt = "encrypted cookie" + config.action_dispatch.encrypted_signed_cookie_salt = "signed encrypted cookie" + config.action_dispatch.authenticated_encrypted_cookie_salt = "authenticated encrypted cookie" + config.action_dispatch.use_authenticated_cookie_encryption = false + config.action_dispatch.perform_deep_munge = true + + config.action_dispatch.default_headers = { + "X-Frame-Options" => "SAMEORIGIN", + "X-XSS-Protection" => "1; mode=block", + "X-Content-Type-Options" => "nosniff", + "X-Download-Options" => "noopen", + "X-Permitted-Cross-Domain-Policies" => "none", + "Referrer-Policy" => "strict-origin-when-cross-origin" + } + + config.action_dispatch.cookies_rotations = ActiveSupport::Messages::RotationConfiguration.new + + config.eager_load_namespaces << ActionDispatch + + initializer "action_dispatch.configure" do |app| + ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length + ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header + ActionDispatch::Request::Utils.perform_deep_munge = app.config.action_dispatch.perform_deep_munge + ActionDispatch::Response.default_charset = app.config.action_dispatch.default_charset || app.config.encoding + ActionDispatch::Response.default_headers = app.config.action_dispatch.default_headers + + ActionDispatch::ExceptionWrapper.rescue_responses.merge!(config.action_dispatch.rescue_responses) + ActionDispatch::ExceptionWrapper.rescue_templates.merge!(config.action_dispatch.rescue_templates) + + config.action_dispatch.always_write_cookie = Rails.env.development? if config.action_dispatch.always_write_cookie.nil? + ActionDispatch::Cookies::CookieJar.always_write_cookie = config.action_dispatch.always_write_cookie + + ActionDispatch.test_app = app + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/request/session.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/request/session.rb new file mode 100644 index 00000000..000847e1 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/request/session.rb @@ -0,0 +1,234 @@ +# frozen_string_literal: true + +require "rack/session/abstract/id" + +module ActionDispatch + class Request + # Session is responsible for lazily loading the session from store. + class Session # :nodoc: + ENV_SESSION_KEY = Rack::RACK_SESSION # :nodoc: + ENV_SESSION_OPTIONS_KEY = Rack::RACK_SESSION_OPTIONS # :nodoc: + + # Singleton object used to determine if an optional param wasn't specified. + Unspecified = Object.new + + # Creates a session hash, merging the properties of the previous session if any. + def self.create(store, req, default_options) + session_was = find req + session = Request::Session.new(store, req) + session.merge! session_was if session_was + + set(req, session) + Options.set(req, Request::Session::Options.new(store, default_options)) + session + end + + def self.find(req) + req.get_header ENV_SESSION_KEY + end + + def self.set(req, session) + req.set_header ENV_SESSION_KEY, session + end + + class Options #:nodoc: + def self.set(req, options) + req.set_header ENV_SESSION_OPTIONS_KEY, options + end + + def self.find(req) + req.get_header ENV_SESSION_OPTIONS_KEY + end + + def initialize(by, default_options) + @by = by + @delegate = default_options.dup + end + + def [](key) + @delegate[key] + end + + def id(req) + @delegate.fetch(:id) { + @by.send(:extract_session_id, req) + } + end + + def []=(k, v); @delegate[k] = v; end + def to_hash; @delegate.dup; end + def values_at(*args); @delegate.values_at(*args); end + end + + def initialize(by, req) + @by = by + @req = req + @delegate = {} + @loaded = false + @exists = nil # We haven't checked yet. + end + + def id + options.id(@req) + end + + def options + Options.find @req + end + + def destroy + clear + options = self.options || {} + @by.send(:delete_session, @req, options.id(@req), options) + + # Load the new sid to be written with the response. + @loaded = false + load_for_write! + end + + # Returns value of the key stored in the session or + # +nil+ if the given key is not found in the session. + def [](key) + load_for_read! + @delegate[key.to_s] + end + + # Returns true if the session has the given key or false. + def has_key?(key) + load_for_read! + @delegate.key?(key.to_s) + end + alias :key? :has_key? + alias :include? :has_key? + + # Returns keys of the session as Array. + def keys + load_for_read! + @delegate.keys + end + + # Returns values of the session as Array. + def values + load_for_read! + @delegate.values + end + + # Writes given value to given key of the session. + def []=(key, value) + load_for_write! + @delegate[key.to_s] = value + end + + # Clears the session. + def clear + load_for_write! + @delegate.clear + end + + # Returns the session as Hash. + def to_hash + load_for_read! + @delegate.dup.delete_if { |_, v| v.nil? } + end + alias :to_h :to_hash + + # Updates the session with given Hash. + # + # session.to_hash + # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2"} + # + # session.update({ "foo" => "bar" }) + # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2", "foo" => "bar"} + # + # session.to_hash + # # => {"session_id"=>"e29b9ea315edf98aad94cc78c34cc9b2", "foo" => "bar"} + def update(hash) + load_for_write! + @delegate.update stringify_keys(hash) + end + + # Deletes given key from the session. + def delete(key) + load_for_write! + @delegate.delete key.to_s + end + + # Returns value of the given key from the session, or raises +KeyError+ + # if can't find the given key and no default value is set. + # Returns default value if specified. + # + # session.fetch(:foo) + # # => KeyError: key not found: "foo" + # + # session.fetch(:foo, :bar) + # # => :bar + # + # session.fetch(:foo) do + # :bar + # end + # # => :bar + def fetch(key, default = Unspecified, &block) + load_for_read! + if default == Unspecified + @delegate.fetch(key.to_s, &block) + else + @delegate.fetch(key.to_s, default, &block) + end + end + + def inspect + if loaded? + super + else + "#<#{self.class}:0x#{(object_id << 1).to_s(16)} not yet loaded>" + end + end + + def exists? + return @exists unless @exists.nil? + @exists = @by.send(:session_exists?, @req) + end + + def loaded? + @loaded + end + + def empty? + load_for_read! + @delegate.empty? + end + + def merge!(other) + load_for_write! + @delegate.merge!(other) + end + + def each(&block) + to_hash.each(&block) + end + + private + + def load_for_read! + load! if !loaded? && exists? + end + + def load_for_write! + load! unless loaded? + end + + def load! + id, session = @by.load_session @req + options[:id] = id + @delegate.replace(stringify_keys(session)) + @loaded = true + end + + def stringify_keys(other) + other.each_with_object({}) { |(key, value), hash| + hash[key.to_s] = value + } + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/request/utils.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/request/utils.rb new file mode 100644 index 00000000..fb0efb9a --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/request/utils.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/indifferent_access" + +module ActionDispatch + class Request + class Utils # :nodoc: + mattr_accessor :perform_deep_munge, default: true + + def self.each_param_value(params, &block) + case params + when Array + params.each { |element| each_param_value(element, &block) } + when Hash + params.each_value { |value| each_param_value(value, &block) } + when String + block.call params + end + end + + def self.normalize_encode_params(params) + if perform_deep_munge + NoNilParamEncoder.normalize_encode_params params + else + ParamEncoder.normalize_encode_params params + end + end + + def self.check_param_encoding(params) + case params + when Array + params.each { |element| check_param_encoding(element) } + when Hash + params.each_value { |value| check_param_encoding(value) } + when String + unless params.valid_encoding? + # Raise Rack::Utils::InvalidParameterError for consistency with Rack. + # ActionDispatch::Request#GET will re-raise as a BadRequest error. + raise Rack::Utils::InvalidParameterError, "Invalid encoding for parameter: #{params.scrub}" + end + end + end + + class ParamEncoder # :nodoc: + # Convert nested Hash to HashWithIndifferentAccess. + def self.normalize_encode_params(params) + case params + when Array + handle_array params + when Hash + if params.has_key?(:tempfile) + ActionDispatch::Http::UploadedFile.new(params) + else + params.each_with_object({}) do |(key, val), new_hash| + new_hash[key] = normalize_encode_params(val) + end.with_indifferent_access + end + else + params + end + end + + def self.handle_array(params) + params.map! { |el| normalize_encode_params(el) } + end + end + + # Remove nils from the params hash. + class NoNilParamEncoder < ParamEncoder # :nodoc: + def self.handle_array(params) + list = super + list.compact! + list + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing.rb new file mode 100644 index 00000000..72f7407c --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing.rb @@ -0,0 +1,260 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/filters" + +module ActionDispatch + # The routing module provides URL rewriting in native Ruby. It's a way to + # redirect incoming requests to controllers and actions. This replaces + # mod_rewrite rules. Best of all, Rails' \Routing works with any web server. + # Routes are defined in config/routes.rb. + # + # Think of creating routes as drawing a map for your requests. The map tells + # them where to go based on some predefined pattern: + # + # Rails.application.routes.draw do + # Pattern 1 tells some request to go to one place + # Pattern 2 tell them to go to another + # ... + # end + # + # The following symbols are special: + # + # :controller maps to your controller name + # :action maps to an action with your controllers + # + # Other names simply map to a parameter as in the case of :id. + # + # == Resources + # + # Resource routing allows you to quickly declare all of the common routes + # for a given resourceful controller. Instead of declaring separate routes + # for your +index+, +show+, +new+, +edit+, +create+, +update+ and +destroy+ + # actions, a resourceful route declares them in a single line of code: + # + # resources :photos + # + # Sometimes, you have a resource that clients always look up without + # referencing an ID. A common example, /profile always shows the profile of + # the currently logged in user. In this case, you can use a singular resource + # to map /profile (rather than /profile/:id) to the show action. + # + # resource :profile + # + # It's common to have resources that are logically children of other + # resources: + # + # resources :magazines do + # resources :ads + # end + # + # You may wish to organize groups of controllers under a namespace. Most + # commonly, you might group a number of administrative controllers under + # an +admin+ namespace. You would place these controllers under the + # app/controllers/admin directory, and you can group them together + # in your router: + # + # namespace "admin" do + # resources :posts, :comments + # end + # + # Alternatively, you can add prefixes to your path without using a separate + # directory by using +scope+. +scope+ takes additional options which + # apply to all enclosed routes. + # + # scope path: "/cpanel", as: 'admin' do + # resources :posts, :comments + # end + # + # For more, see Routing::Mapper::Resources#resources, + # Routing::Mapper::Scoping#namespace, and + # Routing::Mapper::Scoping#scope. + # + # == Non-resourceful routes + # + # For routes that don't fit the resources mold, you can use the HTTP helper + # methods get, post, patch, put and delete. + # + # get 'post/:id' => 'posts#show' + # post 'post/:id' => 'posts#create_comment' + # + # Now, if you POST to /posts/:id, it will route to the create_comment action. A GET on the same + # URL will route to the show action. + # + # If your route needs to respond to more than one HTTP method (or all methods) then using the + # :via option on match is preferable. + # + # match 'post/:id' => 'posts#show', via: [:get, :post] + # + # == Named routes + # + # Routes can be named by passing an :as option, + # allowing for easy reference within your source as +name_of_route_url+ + # for the full URL and +name_of_route_path+ for the URI path. + # + # Example: + # + # # In config/routes.rb + # get '/login' => 'accounts#login', as: 'login' + # + # # With render, redirect_to, tests, etc. + # redirect_to login_url + # + # Arguments can be passed as well. + # + # redirect_to show_item_path(id: 25) + # + # Use root as a shorthand to name a route for the root path "/". + # + # # In config/routes.rb + # root to: 'blogs#index' + # + # # would recognize http://www.example.com/ as + # params = { controller: 'blogs', action: 'index' } + # + # # and provide these named routes + # root_url # => 'http://www.example.com/' + # root_path # => '/' + # + # Note: when using +controller+, the route is simply named after the + # method you call on the block parameter rather than map. + # + # # In config/routes.rb + # controller :blog do + # get 'blog/show' => :list + # get 'blog/delete' => :delete + # get 'blog/edit' => :edit + # end + # + # # provides named routes for show, delete, and edit + # link_to @article.title, blog_show_path(id: @article.id) + # + # == Pretty URLs + # + # Routes can generate pretty URLs. For example: + # + # get '/articles/:year/:month/:day' => 'articles#find_by_id', constraints: { + # year: /\d{4}/, + # month: /\d{1,2}/, + # day: /\d{1,2}/ + # } + # + # Using the route above, the URL "http://localhost:3000/articles/2005/11/06" + # maps to + # + # params = {year: '2005', month: '11', day: '06'} + # + # == Regular Expressions and parameters + # You can specify a regular expression to define a format for a parameter. + # + # controller 'geocode' do + # get 'geocode/:postalcode' => :show, constraints: { + # postalcode: /\d{5}(-\d{4})?/ + # } + # end + # + # Constraints can include the 'ignorecase' and 'extended syntax' regular + # expression modifiers: + # + # controller 'geocode' do + # get 'geocode/:postalcode' => :show, constraints: { + # postalcode: /hx\d\d\s\d[a-z]{2}/i + # } + # end + # + # controller 'geocode' do + # get 'geocode/:postalcode' => :show, constraints: { + # postalcode: /# Postalcode format + # \d{5} #Prefix + # (-\d{4})? #Suffix + # /x + # } + # end + # + # Using the multiline modifier will raise an +ArgumentError+. + # Encoding regular expression modifiers are silently ignored. The + # match will always use the default encoding or ASCII. + # + # == External redirects + # + # You can redirect any path to another path using the redirect helper in your router: + # + # get "/stories" => redirect("/posts") + # + # == Unicode character routes + # + # You can specify unicode character routes in your router: + # + # get "こんにちは" => "welcome#index" + # + # == Routing to Rack Applications + # + # Instead of a String, like posts#index, which corresponds to the + # index action in the PostsController, you can specify any Rack application + # as the endpoint for a matcher: + # + # get "/application.js" => Sprockets + # + # == Reloading routes + # + # You can reload routes if you feel you must: + # + # Rails.application.reload_routes! + # + # This will clear all named routes and reload config/routes.rb if the file has been modified from + # last load. To absolutely force reloading, use reload!. + # + # == Testing Routes + # + # The two main methods for testing your routes: + # + # === +assert_routing+ + # + # def test_movie_route_properly_splits + # opts = {controller: "plugin", action: "checkout", id: "2"} + # assert_routing "plugin/checkout/2", opts + # end + # + # +assert_routing+ lets you test whether or not the route properly resolves into options. + # + # === +assert_recognizes+ + # + # def test_route_has_options + # opts = {controller: "plugin", action: "show", id: "12"} + # assert_recognizes opts, "/plugins/show/12" + # end + # + # Note the subtle difference between the two: +assert_routing+ tests that + # a URL fits options while +assert_recognizes+ tests that a URL + # breaks into parameters properly. + # + # In tests you can simply pass the URL or named route to +get+ or +post+. + # + # def send_to_jail + # get '/jail' + # assert_response :success + # end + # + # def goes_to_login + # get login_url + # #... + # end + # + # == View a list of all your routes + # + # rails routes + # + # Target specific controllers by prefixing the command with -c option. + # + module Routing + extend ActiveSupport::Autoload + + autoload :Mapper + autoload :RouteSet + autoload :RoutesProxy + autoload :UrlFor + autoload :PolymorphicRoutes + + SEPARATORS = %w( / . ? ) #:nodoc: + HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] #:nodoc: + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/endpoint.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/endpoint.rb new file mode 100644 index 00000000..28bb20d6 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/endpoint.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActionDispatch + module Routing + class Endpoint # :nodoc: + def dispatcher?; false; end + def redirect?; false; end + def matches?(req); true; end + def app; self; end + def rack_app; app; end + + def engine? + rack_app.is_a?(Class) && rack_app < Rails::Engine + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/inspector.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/inspector.rb new file mode 100644 index 00000000..c8d4ccdb --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/inspector.rb @@ -0,0 +1,225 @@ +# frozen_string_literal: true + +require "delegate" +require "active_support/core_ext/string/strip" + +module ActionDispatch + module Routing + class RouteWrapper < SimpleDelegator + def endpoint + app.dispatcher? ? "#{controller}##{action}" : rack_app.inspect + end + + def constraints + requirements.except(:controller, :action) + end + + def rack_app + app.rack_app + end + + def path + super.spec.to_s + end + + def name + super.to_s + end + + def reqs + @reqs ||= begin + reqs = endpoint + reqs += " #{constraints}" unless constraints.empty? + reqs + end + end + + def controller + parts.include?(:controller) ? ":controller" : requirements[:controller] + end + + def action + parts.include?(:action) ? ":action" : requirements[:action] + end + + def internal? + internal + end + + def engine? + app.engine? + end + end + + ## + # This class is just used for displaying route information when someone + # executes `rails routes` or looks at the RoutingError page. + # People should not use this class. + class RoutesInspector # :nodoc: + def initialize(routes) + @engines = {} + @routes = routes + end + + def format(formatter, filter = nil) + routes_to_display = filter_routes(normalize_filter(filter)) + routes = collect_routes(routes_to_display) + if routes.none? + formatter.no_routes(collect_routes(@routes)) + return formatter.result + end + + formatter.header routes + formatter.section routes + + @engines.each do |name, engine_routes| + formatter.section_title "Routes for #{name}" + formatter.section engine_routes + end + + formatter.result + end + + private + + def normalize_filter(filter) + if filter.is_a?(Hash) && filter[:controller] + { controller: /#{filter[:controller].underscore.sub(/_?controller\z/, "")}/ } + elsif filter + { controller: /#{filter}/, action: /#{filter}/, verb: /#{filter}/, name: /#{filter}/, path: /#{filter}/ } + end + end + + def filter_routes(filter) + if filter + @routes.select do |route| + route_wrapper = RouteWrapper.new(route) + filter.any? { |default, value| route_wrapper.send(default) =~ value } + end + else + @routes + end + end + + def collect_routes(routes) + routes.collect do |route| + RouteWrapper.new(route) + end.reject(&:internal?).collect do |route| + collect_engine_routes(route) + + { name: route.name, + verb: route.verb, + path: route.path, + reqs: route.reqs } + end + end + + def collect_engine_routes(route) + name = route.endpoint + return unless route.engine? + return if @engines[name] + + routes = route.rack_app.routes + if routes.is_a?(ActionDispatch::Routing::RouteSet) + @engines[name] = collect_routes(routes.routes) + end + end + end + + class ConsoleFormatter + def initialize + @buffer = [] + end + + def result + @buffer.join("\n") + end + + def section_title(title) + @buffer << "\n#{title}:" + end + + def section(routes) + @buffer << draw_section(routes) + end + + def header(routes) + @buffer << draw_header(routes) + end + + def no_routes(routes) + @buffer << + if routes.none? + <<-MESSAGE.strip_heredoc + You don't have any routes defined! + + Please add some routes in config/routes.rb. + MESSAGE + else + "No routes were found for this controller" + end + @buffer << "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html." + end + + private + def draw_section(routes) + header_lengths = ["Prefix", "Verb", "URI Pattern"].map(&:length) + name_width, verb_width, path_width = widths(routes).zip(header_lengths).map(&:max) + + routes.map do |r| + "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}" + end + end + + def draw_header(routes) + name_width, verb_width, path_width = widths(routes) + + "#{"Prefix".rjust(name_width)} #{"Verb".ljust(verb_width)} #{"URI Pattern".ljust(path_width)} Controller#Action" + end + + def widths(routes) + [routes.map { |r| r[:name].length }.max || 0, + routes.map { |r| r[:verb].length }.max || 0, + routes.map { |r| r[:path].length }.max || 0] + end + end + + class HtmlTableFormatter + def initialize(view) + @view = view + @buffer = [] + end + + def section_title(title) + @buffer << %(#{title}) + end + + def section(routes) + @buffer << @view.render(partial: "routes/route", collection: routes) + end + + # The header is part of the HTML page, so we don't construct it here. + def header(routes) + end + + def no_routes(*) + @buffer << <<-MESSAGE.strip_heredoc +

You don't have any routes defined!

+ + MESSAGE + end + + def result + @view.raw @view.render(layout: "routes/table") { + @view.raw @buffer.join("\n") + } + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/mapper.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/mapper.rb new file mode 100644 index 00000000..091987c4 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/mapper.rb @@ -0,0 +1,2267 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/slice" +require "active_support/core_ext/enumerable" +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/regexp" +require "action_dispatch/routing/redirection" +require "action_dispatch/routing/endpoint" + +module ActionDispatch + module Routing + class Mapper + URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port] + + class Constraints < Routing::Endpoint #:nodoc: + attr_reader :app, :constraints + + SERVE = ->(app, req) { app.serve req } + CALL = ->(app, req) { app.call req.env } + + def initialize(app, constraints, strategy) + # Unwrap Constraints objects. I don't actually think it's possible + # to pass a Constraints object to this constructor, but there were + # multiple places that kept testing children of this object. I + # *think* they were just being defensive, but I have no idea. + if app.is_a?(self.class) + constraints += app.constraints + app = app.app + end + + @strategy = strategy + + @app, @constraints, = app, constraints + end + + def dispatcher?; @strategy == SERVE; end + + def matches?(req) + @constraints.all? do |constraint| + (constraint.respond_to?(:matches?) && constraint.matches?(req)) || + (constraint.respond_to?(:call) && constraint.call(*constraint_args(constraint, req))) + end + end + + def serve(req) + return [ 404, { "X-Cascade" => "pass" }, [] ] unless matches?(req) + + @strategy.call @app, req + end + + private + def constraint_args(constraint, request) + constraint.arity == 1 ? [request] : [request.path_parameters, request] + end + end + + class Mapping #:nodoc: + ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z} + OPTIONAL_FORMAT_REGEX = %r{(?:\(\.:format\)+|\.:format|/)\Z} + + attr_reader :requirements, :defaults + attr_reader :to, :default_controller, :default_action + attr_reader :required_defaults, :ast + + def self.build(scope, set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options) + options = scope[:options].merge(options) if scope[:options] + + defaults = (scope[:defaults] || {}).dup + scope_constraints = scope[:constraints] || {} + + new set, ast, defaults, controller, default_action, scope[:module], to, formatted, scope_constraints, scope[:blocks] || [], via, options_constraints, anchor, options + end + + def self.check_via(via) + if via.empty? + msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \ + "If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \ + "If you want to expose your action to GET, use `get` in the router:\n" \ + " Instead of: match \"controller#action\"\n" \ + " Do: get \"controller#action\"" + raise ArgumentError, msg + end + via + end + + def self.normalize_path(path, format) + path = Mapper.normalize_path(path) + + if format == true + "#{path}.:format" + elsif optional_format?(path, format) + "#{path}(.:format)" + else + path + end + end + + def self.optional_format?(path, format) + format != false && path !~ OPTIONAL_FORMAT_REGEX + end + + def initialize(set, ast, defaults, controller, default_action, modyoule, to, formatted, scope_constraints, blocks, via, options_constraints, anchor, options) + @defaults = defaults + @set = set + + @to = to + @default_controller = controller + @default_action = default_action + @ast = ast + @anchor = anchor + @via = via + @internal = options.delete(:internal) + + path_params = ast.find_all(&:symbol?).map(&:to_sym) + + options = add_wildcard_options(options, formatted, ast) + + options = normalize_options!(options, path_params, modyoule) + + split_options = constraints(options, path_params) + + constraints = scope_constraints.merge Hash[split_options[:constraints] || []] + + if options_constraints.is_a?(Hash) + @defaults = Hash[options_constraints.find_all { |key, default| + URL_OPTIONS.include?(key) && (String === default || Integer === default) + }].merge @defaults + @blocks = blocks + constraints.merge! options_constraints + else + @blocks = blocks(options_constraints) + end + + requirements, conditions = split_constraints path_params, constraints + verify_regexp_requirements requirements.map(&:last).grep(Regexp) + + formats = normalize_format(formatted) + + @requirements = formats[:requirements].merge Hash[requirements] + @conditions = Hash[conditions] + @defaults = formats[:defaults].merge(@defaults).merge(normalize_defaults(options)) + + if path_params.include?(:action) && !@requirements.key?(:action) + @defaults[:action] ||= "index" + end + + @required_defaults = (split_options[:required_defaults] || []).map(&:first) + end + + def make_route(name, precedence) + route = Journey::Route.new(name, + application, + path, + conditions, + required_defaults, + defaults, + request_method, + precedence, + @internal) + + route + end + + def application + app(@blocks) + end + + def path + build_path @ast, requirements, @anchor + end + + def conditions + build_conditions @conditions, @set.request_class + end + + def build_conditions(current_conditions, request_class) + conditions = current_conditions.dup + + conditions.keep_if do |k, _| + request_class.public_method_defined?(k) + end + end + private :build_conditions + + def request_method + @via.map { |x| Journey::Route.verb_matcher(x) } + end + private :request_method + + JOINED_SEPARATORS = SEPARATORS.join # :nodoc: + + def build_path(ast, requirements, anchor) + pattern = Journey::Path::Pattern.new(ast, requirements, JOINED_SEPARATORS, anchor) + + # Find all the symbol nodes that are adjacent to literal nodes and alter + # the regexp so that Journey will partition them into custom routes. + ast.find_all { |node| + next unless node.cat? + + if node.left.literal? && node.right.symbol? + symbol = node.right + elsif node.left.literal? && node.right.cat? && node.right.left.symbol? + symbol = node.right.left + elsif node.left.symbol? && node.right.literal? + symbol = node.left + elsif node.left.symbol? && node.right.cat? && node.right.left.literal? + symbol = node.left + else + next + end + + if symbol + symbol.regexp = /(?:#{Regexp.union(symbol.regexp, '-')})+/ + end + } + + pattern + end + private :build_path + + private + def add_wildcard_options(options, formatted, path_ast) + # Add a constraint for wildcard route to make it non-greedy and match the + # optional format part of the route by default. + if formatted != false + path_ast.grep(Journey::Nodes::Star).each_with_object({}) { |node, hash| + hash[node.name.to_sym] ||= /.+?/ + }.merge options + else + options + end + end + + def normalize_options!(options, path_params, modyoule) + if path_params.include?(:controller) + raise ArgumentError, ":controller segment is not allowed within a namespace block" if modyoule + + # Add a default constraint for :controller path segments that matches namespaced + # controllers with default routes like :controller/:action/:id(.:format), e.g: + # GET /admin/products/show/1 + # => { controller: 'admin/products', action: 'show', id: '1' } + options[:controller] ||= /.+?/ + end + + if to.respond_to?(:action) || to.respond_to?(:call) + options + else + to_endpoint = split_to to + controller = to_endpoint[0] || default_controller + action = to_endpoint[1] || default_action + + controller = add_controller_module(controller, modyoule) + + options.merge! check_controller_and_action(path_params, controller, action) + end + end + + def split_constraints(path_params, constraints) + constraints.partition do |key, requirement| + path_params.include?(key) || key == :controller + end + end + + def normalize_format(formatted) + case formatted + when true + { requirements: { format: /.+/ }, + defaults: {} } + when Regexp + { requirements: { format: formatted }, + defaults: { format: nil } } + when String + { requirements: { format: Regexp.compile(formatted) }, + defaults: { format: formatted } } + else + { requirements: {}, defaults: {} } + end + end + + def verify_regexp_requirements(requirements) + requirements.each do |requirement| + if requirement.source =~ ANCHOR_CHARACTERS_REGEX + raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}" + end + + if requirement.multiline? + raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}" + end + end + end + + def normalize_defaults(options) + Hash[options.reject { |_, default| Regexp === default }] + end + + def app(blocks) + if to.respond_to?(:action) + Routing::RouteSet::StaticDispatcher.new to + elsif to.respond_to?(:call) + Constraints.new(to, blocks, Constraints::CALL) + elsif blocks.any? + Constraints.new(dispatcher(defaults.key?(:controller)), blocks, Constraints::SERVE) + else + dispatcher(defaults.key?(:controller)) + end + end + + def check_controller_and_action(path_params, controller, action) + hash = check_part(:controller, controller, path_params, {}) do |part| + translate_controller(part) { + message = "'#{part}' is not a supported controller name. This can lead to potential routing problems.".dup + message << " See http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use" + + raise ArgumentError, message + } + end + + check_part(:action, action, path_params, hash) { |part| + part.is_a?(Regexp) ? part : part.to_s + } + end + + def check_part(name, part, path_params, hash) + if part + hash[name] = yield(part) + else + unless path_params.include?(name) + message = "Missing :#{name} key on routes definition, please check your routes." + raise ArgumentError, message + end + end + hash + end + + def split_to(to) + if to =~ /#/ + to.split("#") + else + [] + end + end + + def add_controller_module(controller, modyoule) + if modyoule && !controller.is_a?(Regexp) + if controller =~ %r{\A/} + controller[1..-1] + else + [modyoule, controller].compact.join("/") + end + else + controller + end + end + + def translate_controller(controller) + return controller if Regexp === controller + return controller.to_s if controller =~ /\A[a-z_0-9][a-z_0-9\/]*\z/ + + yield + end + + def blocks(callable_constraint) + unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?) + raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?" + end + [callable_constraint] + end + + def constraints(options, path_params) + options.group_by do |key, option| + if Regexp === option + :constraints + else + if path_params.include?(key) + :path_params + else + :required_defaults + end + end + end + end + + def dispatcher(raise_on_name_error) + Routing::RouteSet::Dispatcher.new raise_on_name_error + end + end + + # Invokes Journey::Router::Utils.normalize_path and ensure that + # (:locale) becomes (/:locale) instead of /(:locale). Except + # for root cases, where the latter is the correct one. + def self.normalize_path(path) + path = Journey::Router::Utils.normalize_path(path) + path.gsub!(%r{/(\(+)/?}, '\1/') unless path =~ %r{^/(\(+[^)]+\)){1,}$} + path + end + + def self.normalize_name(name) + normalize_path(name)[1..-1].tr("/", "_") + end + + module Base + # Matches a URL pattern to one or more routes. + # + # You should not use the +match+ method in your router + # without specifying an HTTP method. + # + # If you want to expose your action to both GET and POST, use: + # + # # sets :controller, :action and :id in params + # match ':controller/:action/:id', via: [:get, :post] + # + # Note that +:controller+, +:action+ and +:id+ are interpreted as URL + # query parameters and thus available through +params+ in an action. + # + # If you want to expose your action to GET, use +get+ in the router: + # + # Instead of: + # + # match ":controller/:action/:id" + # + # Do: + # + # get ":controller/:action/:id" + # + # Two of these symbols are special, +:controller+ maps to the controller + # and +:action+ to the controller's action. A pattern can also map + # wildcard segments (globs) to params: + # + # get 'songs/*category/:title', to: 'songs#show' + # + # # 'songs/rock/classic/stairway-to-heaven' sets + # # params[:category] = 'rock/classic' + # # params[:title] = 'stairway-to-heaven' + # + # To match a wildcard parameter, it must have a name assigned to it. + # Without a variable name to attach the glob parameter to, the route + # can't be parsed. + # + # When a pattern points to an internal route, the route's +:action+ and + # +:controller+ should be set in options or hash shorthand. Examples: + # + # match 'photos/:id' => 'photos#show', via: :get + # match 'photos/:id', to: 'photos#show', via: :get + # match 'photos/:id', controller: 'photos', action: 'show', via: :get + # + # A pattern can also point to a +Rack+ endpoint i.e. anything that + # responds to +call+: + # + # match 'photos/:id', to: -> (hash) { [200, {}, ["Coming soon"]] }, via: :get + # match 'photos/:id', to: PhotoRackApp, via: :get + # # Yes, controller actions are just rack endpoints + # match 'photos/:id', to: PhotosController.action(:show), via: :get + # + # Because requesting various HTTP verbs with a single action has security + # implications, you must either specify the actions in + # the via options or use one of the HttpHelpers[rdoc-ref:HttpHelpers] + # instead +match+ + # + # === Options + # + # Any options not seen here are passed on as params with the URL. + # + # [:controller] + # The route's controller. + # + # [:action] + # The route's action. + # + # [:param] + # Overrides the default resource identifier +:id+ (name of the + # dynamic segment used to generate the routes). + # You can access that segment from your controller using + # params[<:param>]. + # In your router: + # + # resources :users, param: :name + # + # The +users+ resource here will have the following routes generated for it: + # + # GET /users(.:format) + # POST /users(.:format) + # GET /users/new(.:format) + # GET /users/:name/edit(.:format) + # GET /users/:name(.:format) + # PATCH/PUT /users/:name(.:format) + # DELETE /users/:name(.:format) + # + # You can override ActiveRecord::Base#to_param of a related + # model to construct a URL: + # + # class User < ActiveRecord::Base + # def to_param + # name + # end + # end + # + # user = User.find_by(name: 'Phusion') + # user_path(user) # => "/users/Phusion" + # + # [:path] + # The path prefix for the routes. + # + # [:module] + # The namespace for :controller. + # + # match 'path', to: 'c#a', module: 'sekret', controller: 'posts', via: :get + # # => Sekret::PostsController + # + # See Scoping#namespace for its scope equivalent. + # + # [:as] + # The name used to generate routing helpers. + # + # [:via] + # Allowed HTTP verb(s) for route. + # + # match 'path', to: 'c#a', via: :get + # match 'path', to: 'c#a', via: [:get, :post] + # match 'path', to: 'c#a', via: :all + # + # [:to] + # Points to a +Rack+ endpoint. Can be an object that responds to + # +call+ or a string representing a controller's action. + # + # match 'path', to: 'controller#action', via: :get + # match 'path', to: -> (env) { [200, {}, ["Success!"]] }, via: :get + # match 'path', to: RackApp, via: :get + # + # [:on] + # Shorthand for wrapping routes in a specific RESTful context. Valid + # values are +:member+, +:collection+, and +:new+. Only use within + # resource(s) block. For example: + # + # resource :bar do + # match 'foo', to: 'c#a', on: :member, via: [:get, :post] + # end + # + # Is equivalent to: + # + # resource :bar do + # member do + # match 'foo', to: 'c#a', via: [:get, :post] + # end + # end + # + # [:constraints] + # Constrains parameters with a hash of regular expressions + # or an object that responds to matches?. In addition, constraints + # other than path can also be specified with any object + # that responds to === (eg. String, Array, Range, etc.). + # + # match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }, via: :get + # + # match 'json_only', constraints: { format: 'json' }, via: :get + # + # class Whitelist + # def matches?(request) request.remote_ip == '1.2.3.4' end + # end + # match 'path', to: 'c#a', constraints: Whitelist.new, via: :get + # + # See Scoping#constraints for more examples with its scope + # equivalent. + # + # [:defaults] + # Sets defaults for parameters + # + # # Sets params[:format] to 'jpg' by default + # match 'path', to: 'c#a', defaults: { format: 'jpg' }, via: :get + # + # See Scoping#defaults for its scope equivalent. + # + # [:anchor] + # Boolean to anchor a match pattern. Default is true. When set to + # false, the pattern matches any request prefixed with the given path. + # + # # Matches any request starting with 'path' + # match 'path', to: 'c#a', anchor: false, via: :get + # + # [:format] + # Allows you to specify the default value for optional +format+ + # segment or disable it by supplying +false+. + def match(path, options = nil) + end + + # Mount a Rack-based application to be used within the application. + # + # mount SomeRackApp, at: "some_route" + # + # Alternatively: + # + # mount(SomeRackApp => "some_route") + # + # For options, see +match+, as +mount+ uses it internally. + # + # All mounted applications come with routing helpers to access them. + # These are named after the class specified, so for the above example + # the helper is either +some_rack_app_path+ or +some_rack_app_url+. + # To customize this helper's name, use the +:as+ option: + # + # mount(SomeRackApp => "some_route", as: "exciting") + # + # This will generate the +exciting_path+ and +exciting_url+ helpers + # which can be used to navigate to this mounted app. + def mount(app, options = nil) + if options + path = options.delete(:at) + elsif Hash === app + options = app + app, path = options.find { |k, _| k.respond_to?(:call) } + options.delete(app) if app + end + + raise ArgumentError, "A rack application must be specified" unless app.respond_to?(:call) + raise ArgumentError, <<-MSG.strip_heredoc unless path + Must be called with mount point + + mount SomeRackApp, at: "some_route" + or + mount(SomeRackApp => "some_route") + MSG + + rails_app = rails_app? app + options[:as] ||= app_name(app, rails_app) + + target_as = name_for_action(options[:as], path) + options[:via] ||= :all + + match(path, options.merge(to: app, anchor: false, format: false)) + + define_generate_prefix(app, target_as) if rails_app + self + end + + def default_url_options=(options) + @set.default_url_options = options + end + alias_method :default_url_options, :default_url_options= + + def with_default_scope(scope, &block) + scope(scope) do + instance_exec(&block) + end + end + + # Query if the following named route was already defined. + def has_named_route?(name) + @set.named_routes.key? name + end + + private + def rails_app?(app) + app.is_a?(Class) && app < Rails::Railtie + end + + def app_name(app, rails_app) + if rails_app + app.railtie_name + elsif app.is_a?(Class) + class_name = app.name + ActiveSupport::Inflector.underscore(class_name).tr("/", "_") + end + end + + def define_generate_prefix(app, name) + _route = @set.named_routes.get name + _routes = @set + _url_helpers = @set.url_helpers + + script_namer = ->(options) do + prefix_options = options.slice(*_route.segment_keys) + prefix_options[:relative_url_root] = "".freeze + + if options[:_recall] + prefix_options.reverse_merge!(options[:_recall].slice(*_route.segment_keys)) + end + + # We must actually delete prefix segment keys to avoid passing them to next url_for. + _route.segment_keys.each { |k| options.delete(k) } + _url_helpers.send("#{name}_path", prefix_options) + end + + app.routes.define_mounted_helper(name, script_namer) + + app.routes.extend Module.new { + def optimize_routes_generation?; false; end + + define_method :find_script_name do |options| + if options.key? :script_name + super(options) + else + script_namer.call(options) + end + end + } + end + end + + module HttpHelpers + # Define a route that only recognizes HTTP GET. + # For supported arguments, see match[rdoc-ref:Base#match] + # + # get 'bacon', to: 'food#bacon' + def get(*args, &block) + map_method(:get, args, &block) + end + + # Define a route that only recognizes HTTP POST. + # For supported arguments, see match[rdoc-ref:Base#match] + # + # post 'bacon', to: 'food#bacon' + def post(*args, &block) + map_method(:post, args, &block) + end + + # Define a route that only recognizes HTTP PATCH. + # For supported arguments, see match[rdoc-ref:Base#match] + # + # patch 'bacon', to: 'food#bacon' + def patch(*args, &block) + map_method(:patch, args, &block) + end + + # Define a route that only recognizes HTTP PUT. + # For supported arguments, see match[rdoc-ref:Base#match] + # + # put 'bacon', to: 'food#bacon' + def put(*args, &block) + map_method(:put, args, &block) + end + + # Define a route that only recognizes HTTP DELETE. + # For supported arguments, see match[rdoc-ref:Base#match] + # + # delete 'broccoli', to: 'food#broccoli' + def delete(*args, &block) + map_method(:delete, args, &block) + end + + private + def map_method(method, args, &block) + options = args.extract_options! + options[:via] = method + match(*args, options, &block) + self + end + end + + # You may wish to organize groups of controllers under a namespace. + # Most commonly, you might group a number of administrative controllers + # under an +admin+ namespace. You would place these controllers under + # the app/controllers/admin directory, and you can group them + # together in your router: + # + # namespace "admin" do + # resources :posts, :comments + # end + # + # This will create a number of routes for each of the posts and comments + # controller. For Admin::PostsController, Rails will create: + # + # GET /admin/posts + # GET /admin/posts/new + # POST /admin/posts + # GET /admin/posts/1 + # GET /admin/posts/1/edit + # PATCH/PUT /admin/posts/1 + # DELETE /admin/posts/1 + # + # If you want to route /posts (without the prefix /admin) to + # Admin::PostsController, you could use + # + # scope module: "admin" do + # resources :posts + # end + # + # or, for a single case + # + # resources :posts, module: "admin" + # + # If you want to route /admin/posts to +PostsController+ + # (without the Admin:: module prefix), you could use + # + # scope "/admin" do + # resources :posts + # end + # + # or, for a single case + # + # resources :posts, path: "/admin/posts" + # + # In each of these cases, the named routes remain the same as if you did + # not use scope. In the last case, the following paths map to + # +PostsController+: + # + # GET /admin/posts + # GET /admin/posts/new + # POST /admin/posts + # GET /admin/posts/1 + # GET /admin/posts/1/edit + # PATCH/PUT /admin/posts/1 + # DELETE /admin/posts/1 + module Scoping + # Scopes a set of routes to the given default options. + # + # Take the following route definition as an example: + # + # scope path: ":account_id", as: "account" do + # resources :projects + # end + # + # This generates helpers such as +account_projects_path+, just like +resources+ does. + # The difference here being that the routes generated are like /:account_id/projects, + # rather than /accounts/:account_id/projects. + # + # === Options + # + # Takes same options as Base#match and Resources#resources. + # + # # route /posts (without the prefix /admin) to Admin::PostsController + # scope module: "admin" do + # resources :posts + # end + # + # # prefix the posts resource's requests with '/admin' + # scope path: "/admin" do + # resources :posts + # end + # + # # prefix the routing helper name: +sekret_posts_path+ instead of +posts_path+ + # scope as: "sekret" do + # resources :posts + # end + def scope(*args) + options = args.extract_options!.dup + scope = {} + + options[:path] = args.flatten.join("/") if args.any? + options[:constraints] ||= {} + + unless nested_scope? + options[:shallow_path] ||= options[:path] if options.key?(:path) + options[:shallow_prefix] ||= options[:as] if options.key?(:as) + end + + if options[:constraints].is_a?(Hash) + defaults = options[:constraints].select do |k, v| + URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Integer)) + end + + options[:defaults] = defaults.merge(options[:defaults] || {}) + else + block, options[:constraints] = options[:constraints], {} + end + + if options.key?(:only) || options.key?(:except) + scope[:action_options] = { only: options.delete(:only), + except: options.delete(:except) } + end + + if options.key? :anchor + raise ArgumentError, "anchor is ignored unless passed to `match`" + end + + @scope.options.each do |option| + if option == :blocks + value = block + elsif option == :options + value = options + else + value = options.delete(option) { POISON } + end + + unless POISON == value + scope[option] = send("merge_#{option}_scope", @scope[option], value) + end + end + + @scope = @scope.new scope + yield + self + ensure + @scope = @scope.parent + end + + POISON = Object.new # :nodoc: + + # Scopes routes to a specific controller + # + # controller "food" do + # match "bacon", action: :bacon, via: :get + # end + def controller(controller) + @scope = @scope.new(controller: controller) + yield + ensure + @scope = @scope.parent + end + + # Scopes routes to a specific namespace. For example: + # + # namespace :admin do + # resources :posts + # end + # + # This generates the following routes: + # + # admin_posts GET /admin/posts(.:format) admin/posts#index + # admin_posts POST /admin/posts(.:format) admin/posts#create + # new_admin_post GET /admin/posts/new(.:format) admin/posts#new + # edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit + # admin_post GET /admin/posts/:id(.:format) admin/posts#show + # admin_post PATCH/PUT /admin/posts/:id(.:format) admin/posts#update + # admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy + # + # === Options + # + # The +:path+, +:as+, +:module+, +:shallow_path+ and +:shallow_prefix+ + # options all default to the name of the namespace. + # + # For options, see Base#match. For +:shallow_path+ option, see + # Resources#resources. + # + # # accessible through /sekret/posts rather than /admin/posts + # namespace :admin, path: "sekret" do + # resources :posts + # end + # + # # maps to Sekret::PostsController rather than Admin::PostsController + # namespace :admin, module: "sekret" do + # resources :posts + # end + # + # # generates +sekret_posts_path+ rather than +admin_posts_path+ + # namespace :admin, as: "sekret" do + # resources :posts + # end + def namespace(path, options = {}) + path = path.to_s + + defaults = { + module: path, + as: options.fetch(:as, path), + shallow_path: options.fetch(:path, path), + shallow_prefix: options.fetch(:as, path) + } + + path_scope(options.delete(:path) { path }) do + scope(defaults.merge!(options)) { yield } + end + end + + # === Parameter Restriction + # Allows you to constrain the nested routes based on a set of rules. + # For instance, in order to change the routes to allow for a dot character in the +id+ parameter: + # + # constraints(id: /\d+\.\d+/) do + # resources :posts + # end + # + # Now routes such as +/posts/1+ will no longer be valid, but +/posts/1.1+ will be. + # The +id+ parameter must match the constraint passed in for this example. + # + # You may use this to also restrict other parameters: + # + # resources :posts do + # constraints(post_id: /\d+\.\d+/) do + # resources :comments + # end + # end + # + # === Restricting based on IP + # + # Routes can also be constrained to an IP or a certain range of IP addresses: + # + # constraints(ip: /192\.168\.\d+\.\d+/) do + # resources :posts + # end + # + # Any user connecting from the 192.168.* range will be able to see this resource, + # where as any user connecting outside of this range will be told there is no such route. + # + # === Dynamic request matching + # + # Requests to routes can be constrained based on specific criteria: + # + # constraints(-> (req) { req.env["HTTP_USER_AGENT"] =~ /iPhone/ }) do + # resources :iphones + # end + # + # You are able to move this logic out into a class if it is too complex for routes. + # This class must have a +matches?+ method defined on it which either returns +true+ + # if the user should be given access to that route, or +false+ if the user should not. + # + # class Iphone + # def self.matches?(request) + # request.env["HTTP_USER_AGENT"] =~ /iPhone/ + # end + # end + # + # An expected place for this code would be +lib/constraints+. + # + # This class is then used like this: + # + # constraints(Iphone) do + # resources :iphones + # end + def constraints(constraints = {}) + scope(constraints: constraints) { yield } + end + + # Allows you to set default parameters for a route, such as this: + # defaults id: 'home' do + # match 'scoped_pages/(:id)', to: 'pages#show' + # end + # Using this, the +:id+ parameter here will default to 'home'. + def defaults(defaults = {}) + @scope = @scope.new(defaults: merge_defaults_scope(@scope[:defaults], defaults)) + yield + ensure + @scope = @scope.parent + end + + private + def merge_path_scope(parent, child) + Mapper.normalize_path("#{parent}/#{child}") + end + + def merge_shallow_path_scope(parent, child) + Mapper.normalize_path("#{parent}/#{child}") + end + + def merge_as_scope(parent, child) + parent ? "#{parent}_#{child}" : child + end + + def merge_shallow_prefix_scope(parent, child) + parent ? "#{parent}_#{child}" : child + end + + def merge_module_scope(parent, child) + parent ? "#{parent}/#{child}" : child + end + + def merge_controller_scope(parent, child) + child + end + + def merge_action_scope(parent, child) + child + end + + def merge_via_scope(parent, child) + child + end + + def merge_format_scope(parent, child) + child + end + + def merge_path_names_scope(parent, child) + merge_options_scope(parent, child) + end + + def merge_constraints_scope(parent, child) + merge_options_scope(parent, child) + end + + def merge_defaults_scope(parent, child) + merge_options_scope(parent, child) + end + + def merge_blocks_scope(parent, child) + merged = parent ? parent.dup : [] + merged << child if child + merged + end + + def merge_options_scope(parent, child) + (parent || {}).merge(child) + end + + def merge_shallow_scope(parent, child) + child ? true : false + end + + def merge_to_scope(parent, child) + child + end + end + + # Resource routing allows you to quickly declare all of the common routes + # for a given resourceful controller. Instead of declaring separate routes + # for your +index+, +show+, +new+, +edit+, +create+, +update+ and +destroy+ + # actions, a resourceful route declares them in a single line of code: + # + # resources :photos + # + # Sometimes, you have a resource that clients always look up without + # referencing an ID. A common example, /profile always shows the profile of + # the currently logged in user. In this case, you can use a singular resource + # to map /profile (rather than /profile/:id) to the show action. + # + # resource :profile + # + # It's common to have resources that are logically children of other + # resources: + # + # resources :magazines do + # resources :ads + # end + # + # You may wish to organize groups of controllers under a namespace. Most + # commonly, you might group a number of administrative controllers under + # an +admin+ namespace. You would place these controllers under the + # app/controllers/admin directory, and you can group them together + # in your router: + # + # namespace "admin" do + # resources :posts, :comments + # end + # + # By default the +:id+ parameter doesn't accept dots. If you need to + # use dots as part of the +:id+ parameter add a constraint which + # overrides this restriction, e.g: + # + # resources :articles, id: /[^\/]+/ + # + # This allows any character other than a slash as part of your +:id+. + # + module Resources + # CANONICAL_ACTIONS holds all actions that does not need a prefix or + # a path appended since they fit properly in their scope level. + VALID_ON_OPTIONS = [:new, :collection, :member] + RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns] + CANONICAL_ACTIONS = %w(index create new show update destroy) + + class Resource #:nodoc: + attr_reader :controller, :path, :param + + def initialize(entities, api_only, shallow, options = {}) + @name = entities.to_s + @path = (options[:path] || @name).to_s + @controller = (options[:controller] || @name).to_s + @as = options[:as] + @param = (options[:param] || :id).to_sym + @options = options + @shallow = shallow + @api_only = api_only + @only = options.delete :only + @except = options.delete :except + end + + def default_actions + if @api_only + [:index, :create, :show, :update, :destroy] + else + [:index, :create, :new, :show, :update, :destroy, :edit] + end + end + + def actions + if @only + Array(@only).map(&:to_sym) + elsif @except + default_actions - Array(@except).map(&:to_sym) + else + default_actions + end + end + + def name + @as || @name + end + + def plural + @plural ||= name.to_s + end + + def singular + @singular ||= name.to_s.singularize + end + + alias :member_name :singular + + # Checks for uncountable plurals, and appends "_index" if the plural + # and singular form are the same. + def collection_name + singular == plural ? "#{plural}_index" : plural + end + + def resource_scope + controller + end + + alias :collection_scope :path + + def member_scope + "#{path}/:#{param}" + end + + alias :shallow_scope :member_scope + + def new_scope(new_path) + "#{path}/#{new_path}" + end + + def nested_param + :"#{singular}_#{param}" + end + + def nested_scope + "#{path}/:#{nested_param}" + end + + def shallow? + @shallow + end + + def singleton?; false; end + end + + class SingletonResource < Resource #:nodoc: + def initialize(entities, api_only, shallow, options) + super + @as = nil + @controller = (options[:controller] || plural).to_s + @as = options[:as] + end + + def default_actions + if @api_only + [:show, :create, :update, :destroy] + else + [:show, :create, :update, :destroy, :new, :edit] + end + end + + def plural + @plural ||= name.to_s.pluralize + end + + def singular + @singular ||= name.to_s + end + + alias :member_name :singular + alias :collection_name :singular + + alias :member_scope :path + alias :nested_scope :path + + def singleton?; true; end + end + + def resources_path_names(options) + @scope[:path_names].merge!(options) + end + + # Sometimes, you have a resource that clients always look up without + # referencing an ID. A common example, /profile always shows the + # profile of the currently logged in user. In this case, you can use + # a singular resource to map /profile (rather than /profile/:id) to + # the show action: + # + # resource :profile + # + # This creates six different routes in your application, all mapping to + # the +Profiles+ controller (note that the controller is named after + # the plural): + # + # GET /profile/new + # GET /profile + # GET /profile/edit + # PATCH/PUT /profile + # DELETE /profile + # POST /profile + # + # === Options + # Takes same options as resources[rdoc-ref:#resources] + def resource(*resources, &block) + options = resources.extract_options!.dup + + if apply_common_behavior_for(:resource, resources, options, &block) + return self + end + + with_scope_level(:resource) do + options = apply_action_options options + resource_scope(SingletonResource.new(resources.pop, api_only?, @scope[:shallow], options)) do + yield if block_given? + + concerns(options[:concerns]) if options[:concerns] + + new do + get :new + end if parent_resource.actions.include?(:new) + + set_member_mappings_for_resource + + collection do + post :create + end if parent_resource.actions.include?(:create) + end + end + + self + end + + # In Rails, a resourceful route provides a mapping between HTTP verbs + # and URLs and controller actions. By convention, each action also maps + # to particular CRUD operations in a database. A single entry in the + # routing file, such as + # + # resources :photos + # + # creates seven different routes in your application, all mapping to + # the +Photos+ controller: + # + # GET /photos + # GET /photos/new + # POST /photos + # GET /photos/:id + # GET /photos/:id/edit + # PATCH/PUT /photos/:id + # DELETE /photos/:id + # + # Resources can also be nested infinitely by using this block syntax: + # + # resources :photos do + # resources :comments + # end + # + # This generates the following comments routes: + # + # GET /photos/:photo_id/comments + # GET /photos/:photo_id/comments/new + # POST /photos/:photo_id/comments + # GET /photos/:photo_id/comments/:id + # GET /photos/:photo_id/comments/:id/edit + # PATCH/PUT /photos/:photo_id/comments/:id + # DELETE /photos/:photo_id/comments/:id + # + # === Options + # Takes same options as match[rdoc-ref:Base#match] as well as: + # + # [:path_names] + # Allows you to change the segment component of the +edit+ and +new+ actions. + # Actions not specified are not changed. + # + # resources :posts, path_names: { new: "brand_new" } + # + # The above example will now change /posts/new to /posts/brand_new. + # + # [:path] + # Allows you to change the path prefix for the resource. + # + # resources :posts, path: 'postings' + # + # The resource and all segments will now route to /postings instead of /posts. + # + # [:only] + # Only generate routes for the given actions. + # + # resources :cows, only: :show + # resources :cows, only: [:show, :index] + # + # [:except] + # Generate all routes except for the given actions. + # + # resources :cows, except: :show + # resources :cows, except: [:show, :index] + # + # [:shallow] + # Generates shallow routes for nested resource(s). When placed on a parent resource, + # generates shallow routes for all nested resources. + # + # resources :posts, shallow: true do + # resources :comments + # end + # + # Is the same as: + # + # resources :posts do + # resources :comments, except: [:show, :edit, :update, :destroy] + # end + # resources :comments, only: [:show, :edit, :update, :destroy] + # + # This allows URLs for resources that otherwise would be deeply nested such + # as a comment on a blog post like /posts/a-long-permalink/comments/1234 + # to be shortened to just /comments/1234. + # + # [:shallow_path] + # Prefixes nested shallow routes with the specified path. + # + # scope shallow_path: "sekret" do + # resources :posts do + # resources :comments, shallow: true + # end + # end + # + # The +comments+ resource here will have the following routes generated for it: + # + # post_comments GET /posts/:post_id/comments(.:format) + # post_comments POST /posts/:post_id/comments(.:format) + # new_post_comment GET /posts/:post_id/comments/new(.:format) + # edit_comment GET /sekret/comments/:id/edit(.:format) + # comment GET /sekret/comments/:id(.:format) + # comment PATCH/PUT /sekret/comments/:id(.:format) + # comment DELETE /sekret/comments/:id(.:format) + # + # [:shallow_prefix] + # Prefixes nested shallow route names with specified prefix. + # + # scope shallow_prefix: "sekret" do + # resources :posts do + # resources :comments, shallow: true + # end + # end + # + # The +comments+ resource here will have the following routes generated for it: + # + # post_comments GET /posts/:post_id/comments(.:format) + # post_comments POST /posts/:post_id/comments(.:format) + # new_post_comment GET /posts/:post_id/comments/new(.:format) + # edit_sekret_comment GET /comments/:id/edit(.:format) + # sekret_comment GET /comments/:id(.:format) + # sekret_comment PATCH/PUT /comments/:id(.:format) + # sekret_comment DELETE /comments/:id(.:format) + # + # [:format] + # Allows you to specify the default value for optional +format+ + # segment or disable it by supplying +false+. + # + # === Examples + # + # # routes call Admin::PostsController + # resources :posts, module: "admin" + # + # # resource actions are at /admin/posts. + # resources :posts, path: "admin/posts" + def resources(*resources, &block) + options = resources.extract_options!.dup + + if apply_common_behavior_for(:resources, resources, options, &block) + return self + end + + with_scope_level(:resources) do + options = apply_action_options options + resource_scope(Resource.new(resources.pop, api_only?, @scope[:shallow], options)) do + yield if block_given? + + concerns(options[:concerns]) if options[:concerns] + + collection do + get :index if parent_resource.actions.include?(:index) + post :create if parent_resource.actions.include?(:create) + end + + new do + get :new + end if parent_resource.actions.include?(:new) + + set_member_mappings_for_resource + end + end + + self + end + + # To add a route to the collection: + # + # resources :photos do + # collection do + # get 'search' + # end + # end + # + # This will enable Rails to recognize paths such as /photos/search + # with GET, and route to the search action of +PhotosController+. It will also + # create the search_photos_url and search_photos_path + # route helpers. + def collection + unless resource_scope? + raise ArgumentError, "can't use collection outside resource(s) scope" + end + + with_scope_level(:collection) do + path_scope(parent_resource.collection_scope) do + yield + end + end + end + + # To add a member route, add a member block into the resource block: + # + # resources :photos do + # member do + # get 'preview' + # end + # end + # + # This will recognize /photos/1/preview with GET, and route to the + # preview action of +PhotosController+. It will also create the + # preview_photo_url and preview_photo_path helpers. + def member + unless resource_scope? + raise ArgumentError, "can't use member outside resource(s) scope" + end + + with_scope_level(:member) do + if shallow? + shallow_scope { + path_scope(parent_resource.member_scope) { yield } + } + else + path_scope(parent_resource.member_scope) { yield } + end + end + end + + def new + unless resource_scope? + raise ArgumentError, "can't use new outside resource(s) scope" + end + + with_scope_level(:new) do + path_scope(parent_resource.new_scope(action_path(:new))) do + yield + end + end + end + + def nested + unless resource_scope? + raise ArgumentError, "can't use nested outside resource(s) scope" + end + + with_scope_level(:nested) do + if shallow? && shallow_nesting_depth >= 1 + shallow_scope do + path_scope(parent_resource.nested_scope) do + scope(nested_options) { yield } + end + end + else + path_scope(parent_resource.nested_scope) do + scope(nested_options) { yield } + end + end + end + end + + # See ActionDispatch::Routing::Mapper::Scoping#namespace. + def namespace(path, options = {}) + if resource_scope? + nested { super } + else + super + end + end + + def shallow + @scope = @scope.new(shallow: true) + yield + ensure + @scope = @scope.parent + end + + def shallow? + !parent_resource.singleton? && @scope[:shallow] + end + + # Matches a URL pattern to one or more routes. + # For more information, see match[rdoc-ref:Base#match]. + # + # match 'path' => 'controller#action', via: :patch + # match 'path', to: 'controller#action', via: :post + # match 'path', 'otherpath', on: :member, via: :get + def match(path, *rest, &block) + if rest.empty? && Hash === path + options = path + path, to = options.find { |name, _value| name.is_a?(String) } + + raise ArgumentError, "Route path not specified" if path.nil? + + case to + when Symbol + options[:action] = to + when String + if to =~ /#/ + options[:to] = to + else + options[:controller] = to + end + else + options[:to] = to + end + + options.delete(path) + paths = [path] + else + options = rest.pop || {} + paths = [path] + rest + end + + if options.key?(:defaults) + defaults(options.delete(:defaults)) { map_match(paths, options, &block) } + else + map_match(paths, options, &block) + end + end + + # You can specify what Rails should route "/" to with the root method: + # + # root to: 'pages#main' + # + # For options, see +match+, as +root+ uses it internally. + # + # You can also pass a string which will expand + # + # root 'pages#main' + # + # You should put the root route at the top of config/routes.rb, + # because this means it will be matched first. As this is the most popular route + # of most Rails applications, this is beneficial. + def root(path, options = {}) + if path.is_a?(String) + options[:to] = path + elsif path.is_a?(Hash) && options.empty? + options = path + else + raise ArgumentError, "must be called with a path and/or options" + end + + if @scope.resources? + with_scope_level(:root) do + path_scope(parent_resource.path) do + match_root_route(options) + end + end + else + match_root_route(options) + end + end + + private + + def parent_resource + @scope[:scope_level_resource] + end + + def apply_common_behavior_for(method, resources, options, &block) + if resources.length > 1 + resources.each { |r| send(method, r, options, &block) } + return true + end + + if options.delete(:shallow) + shallow do + send(method, resources.pop, options, &block) + end + return true + end + + if resource_scope? + nested { send(method, resources.pop, options, &block) } + return true + end + + options.keys.each do |k| + (options[:constraints] ||= {})[k] = options.delete(k) if options[k].is_a?(Regexp) + end + + scope_options = options.slice!(*RESOURCE_OPTIONS) + unless scope_options.empty? + scope(scope_options) do + send(method, resources.pop, options, &block) + end + return true + end + + false + end + + def apply_action_options(options) + return options if action_options? options + options.merge scope_action_options + end + + def action_options?(options) + options[:only] || options[:except] + end + + def scope_action_options + @scope[:action_options] || {} + end + + def resource_scope? + @scope.resource_scope? + end + + def resource_method_scope? + @scope.resource_method_scope? + end + + def nested_scope? + @scope.nested? + end + + def with_scope_level(kind) # :doc: + @scope = @scope.new_level(kind) + yield + ensure + @scope = @scope.parent + end + + def resource_scope(resource) + @scope = @scope.new(scope_level_resource: resource) + + controller(resource.resource_scope) { yield } + ensure + @scope = @scope.parent + end + + def nested_options + options = { as: parent_resource.member_name } + options[:constraints] = { + parent_resource.nested_param => param_constraint + } if param_constraint? + + options + end + + def shallow_nesting_depth + @scope.find_all { |node| + node.frame[:scope_level_resource] + }.count { |node| node.frame[:scope_level_resource].shallow? } + end + + def param_constraint? + @scope[:constraints] && @scope[:constraints][parent_resource.param].is_a?(Regexp) + end + + def param_constraint + @scope[:constraints][parent_resource.param] + end + + def canonical_action?(action) + resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s) + end + + def shallow_scope + scope = { as: @scope[:shallow_prefix], + path: @scope[:shallow_path] } + @scope = @scope.new scope + + yield + ensure + @scope = @scope.parent + end + + def path_for_action(action, path) + return "#{@scope[:path]}/#{path}" if path + + if canonical_action?(action) + @scope[:path].to_s + else + "#{@scope[:path]}/#{action_path(action)}" + end + end + + def action_path(name) + @scope[:path_names][name.to_sym] || name + end + + def prefix_name_for_action(as, action) + if as + prefix = as + elsif !canonical_action?(action) + prefix = action + end + + if prefix && prefix != "/" && !prefix.empty? + Mapper.normalize_name prefix.to_s.tr("-", "_") + end + end + + def name_for_action(as, action) + prefix = prefix_name_for_action(as, action) + name_prefix = @scope[:as] + + if parent_resource + return nil unless as || action + + collection_name = parent_resource.collection_name + member_name = parent_resource.member_name + end + + action_name = @scope.action_name(name_prefix, prefix, collection_name, member_name) + candidate = action_name.select(&:present?).join("_") + + unless candidate.empty? + # If a name was not explicitly given, we check if it is valid + # and return nil in case it isn't. Otherwise, we pass the invalid name + # forward so the underlying router engine treats it and raises an exception. + if as.nil? + candidate unless candidate !~ /\A[_a-z]/i || has_named_route?(candidate) + else + candidate + end + end + end + + def set_member_mappings_for_resource # :doc: + member do + get :edit if parent_resource.actions.include?(:edit) + get :show if parent_resource.actions.include?(:show) + if parent_resource.actions.include?(:update) + patch :update + put :update + end + delete :destroy if parent_resource.actions.include?(:destroy) + end + end + + def api_only? # :doc: + @set.api_only? + end + + def path_scope(path) + @scope = @scope.new(path: merge_path_scope(@scope[:path], path)) + yield + ensure + @scope = @scope.parent + end + + def map_match(paths, options) + if options[:on] && !VALID_ON_OPTIONS.include?(options[:on]) + raise ArgumentError, "Unknown scope #{on.inspect} given to :on" + end + + if @scope[:to] + options[:to] ||= @scope[:to] + end + + if @scope[:controller] && @scope[:action] + options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}" + end + + controller = options.delete(:controller) || @scope[:controller] + option_path = options.delete :path + to = options.delete :to + via = Mapping.check_via Array(options.delete(:via) { + @scope[:via] + }) + formatted = options.delete(:format) { @scope[:format] } + anchor = options.delete(:anchor) { true } + options_constraints = options.delete(:constraints) || {} + + path_types = paths.group_by(&:class) + path_types.fetch(String, []).each do |_path| + route_options = options.dup + if _path && option_path + raise ArgumentError, "Ambiguous route definition. Both :path and the route path were specified as strings." + end + to = get_to_from_path(_path, to, route_options[:action]) + decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints) + end + + path_types.fetch(Symbol, []).each do |action| + route_options = options.dup + decomposed_match(action, controller, route_options, option_path, to, via, formatted, anchor, options_constraints) + end + + self + end + + def get_to_from_path(path, to, action) + return to if to || action + + path_without_format = path.sub(/\(\.:format\)$/, "") + if using_match_shorthand?(path_without_format) + path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1').tr("-", "_") + else + nil + end + end + + def using_match_shorthand?(path) + path =~ %r{^/?[-\w]+/[-\w/]+$} + end + + def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) + if on = options.delete(:on) + send(on) { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) } + else + case @scope.scope_level + when :resources + nested { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) } + when :resource + member { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) } + else + add_route(path, controller, options, _path, to, via, formatted, anchor, options_constraints) + end + end + end + + def add_route(action, controller, options, _path, to, via, formatted, anchor, options_constraints) + path = path_for_action(action, _path) + raise ArgumentError, "path is required" if path.blank? + + action = action.to_s + + default_action = options.delete(:action) || @scope[:action] + + if action =~ /^[\w\-\/]+$/ + default_action ||= action.tr("-", "_") unless action.include?("/") + else + action = nil + end + + as = if !options.fetch(:as, true) # if it's set to nil or false + options.delete(:as) + else + name_for_action(options.delete(:as), action) + end + + path = Mapping.normalize_path URI.parser.escape(path), formatted + ast = Journey::Parser.parse path + + mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options) + @set.add_route(mapping, as) + end + + def match_root_route(options) + name = has_named_route?(name_for_action(:root, nil)) ? nil : :root + args = ["/", { as: name, via: :get }.merge!(options)] + + match(*args) + end + end + + # Routing Concerns allow you to declare common routes that can be reused + # inside others resources and routes. + # + # concern :commentable do + # resources :comments + # end + # + # concern :image_attachable do + # resources :images, only: :index + # end + # + # These concerns are used in Resources routing: + # + # resources :messages, concerns: [:commentable, :image_attachable] + # + # or in a scope or namespace: + # + # namespace :posts do + # concerns :commentable + # end + module Concerns + # Define a routing concern using a name. + # + # Concerns may be defined inline, using a block, or handled by + # another object, by passing that object as the second parameter. + # + # The concern object, if supplied, should respond to call, + # which will receive two parameters: + # + # * The current mapper + # * A hash of options which the concern object may use + # + # Options may also be used by concerns defined in a block by accepting + # a block parameter. So, using a block, you might do something as + # simple as limit the actions available on certain resources, passing + # standard resource options through the concern: + # + # concern :commentable do |options| + # resources :comments, options + # end + # + # resources :posts, concerns: :commentable + # resources :archived_posts do + # # Don't allow comments on archived posts + # concerns :commentable, only: [:index, :show] + # end + # + # Or, using a callable object, you might implement something more + # specific to your application, which would be out of place in your + # routes file. + # + # # purchasable.rb + # class Purchasable + # def initialize(defaults = {}) + # @defaults = defaults + # end + # + # def call(mapper, options = {}) + # options = @defaults.merge(options) + # mapper.resources :purchases + # mapper.resources :receipts + # mapper.resources :returns if options[:returnable] + # end + # end + # + # # routes.rb + # concern :purchasable, Purchasable.new(returnable: true) + # + # resources :toys, concerns: :purchasable + # resources :electronics, concerns: :purchasable + # resources :pets do + # concerns :purchasable, returnable: false + # end + # + # Any routing helpers can be used inside a concern. If using a + # callable, they're accessible from the Mapper that's passed to + # call. + def concern(name, callable = nil, &block) + callable ||= lambda { |mapper, options| mapper.instance_exec(options, &block) } + @concerns[name] = callable + end + + # Use the named concerns + # + # resources :posts do + # concerns :commentable + # end + # + # Concerns also work in any routes helper that you want to use: + # + # namespace :posts do + # concerns :commentable + # end + def concerns(*args) + options = args.extract_options! + args.flatten.each do |name| + if concern = @concerns[name] + concern.call(self, options) + else + raise ArgumentError, "No concern named #{name} was found!" + end + end + end + end + + module CustomUrls + # Define custom URL helpers that will be added to the application's + # routes. This allows you to override and/or replace the default behavior + # of routing helpers, e.g: + # + # direct :homepage do + # "http://www.rubyonrails.org" + # end + # + # direct :commentable do |model| + # [ model, anchor: model.dom_id ] + # end + # + # direct :main do + # { controller: "pages", action: "index", subdomain: "www" } + # end + # + # The return value from the block passed to +direct+ must be a valid set of + # arguments for +url_for+ which will actually build the URL string. This can + # be one of the following: + # + # * A string, which is treated as a generated URL + # * A hash, e.g. { controller: "pages", action: "index" } + # * An array, which is passed to +polymorphic_url+ + # * An Active Model instance + # * An Active Model class + # + # NOTE: Other URL helpers can be called in the block but be careful not to invoke + # your custom URL helper again otherwise it will result in a stack overflow error. + # + # You can also specify default options that will be passed through to + # your URL helper definition, e.g: + # + # direct :browse, page: 1, size: 10 do |options| + # [ :products, options.merge(params.permit(:page, :size).to_h.symbolize_keys) ] + # end + # + # In this instance the +params+ object comes from the context in which the + # block is executed, e.g. generating a URL inside a controller action or a view. + # If the block is executed where there isn't a +params+ object such as this: + # + # Rails.application.routes.url_helpers.browse_path + # + # then it will raise a +NameError+. Because of this you need to be aware of the + # context in which you will use your custom URL helper when defining it. + # + # NOTE: The +direct+ method can't be used inside of a scope block such as + # +namespace+ or +scope+ and will raise an error if it detects that it is. + def direct(name, options = {}, &block) + unless @scope.root? + raise RuntimeError, "The direct method can't be used inside a routes scope block" + end + + @set.add_url_helper(name, options, &block) + end + + # Define custom polymorphic mappings of models to URLs. This alters the + # behavior of +polymorphic_url+ and consequently the behavior of + # +link_to+ and +form_for+ when passed a model instance, e.g: + # + # resource :basket + # + # resolve "Basket" do + # [:basket] + # end + # + # This will now generate "/basket" when a +Basket+ instance is passed to + # +link_to+ or +form_for+ instead of the standard "/baskets/:id". + # + # NOTE: This custom behavior only applies to simple polymorphic URLs where + # a single model instance is passed and not more complicated forms, e.g: + # + # # config/routes.rb + # resource :profile + # namespace :admin do + # resources :users + # end + # + # resolve("User") { [:profile] } + # + # # app/views/application/_menu.html.erb + # link_to "Profile", @current_user + # link_to "Profile", [:admin, @current_user] + # + # The first +link_to+ will generate "/profile" but the second will generate + # the standard polymorphic URL of "/admin/users/1". + # + # You can pass options to a polymorphic mapping - the arity for the block + # needs to be two as the instance is passed as the first argument, e.g: + # + # resolve "Basket", anchor: "items" do |basket, options| + # [:basket, options] + # end + # + # This generates the URL "/basket#items" because when the last item in an + # array passed to +polymorphic_url+ is a hash then it's treated as options + # to the URL helper that gets called. + # + # NOTE: The +resolve+ method can't be used inside of a scope block such as + # +namespace+ or +scope+ and will raise an error if it detects that it is. + def resolve(*args, &block) + unless @scope.root? + raise RuntimeError, "The resolve method can't be used inside a routes scope block" + end + + options = args.extract_options! + args = args.flatten(1) + + args.each do |klass| + @set.add_polymorphic_mapping(klass, options, &block) + end + end + end + + class Scope # :nodoc: + OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module, + :controller, :action, :path_names, :constraints, + :shallow, :blocks, :defaults, :via, :format, :options, :to] + + RESOURCE_SCOPES = [:resource, :resources] + RESOURCE_METHOD_SCOPES = [:collection, :member, :new] + + attr_reader :parent, :scope_level + + def initialize(hash, parent = NULL, scope_level = nil) + @hash = hash + @parent = parent + @scope_level = scope_level + end + + def nested? + scope_level == :nested + end + + def null? + @hash.nil? && @parent.nil? + end + + def root? + @parent.null? + end + + def resources? + scope_level == :resources + end + + def resource_method_scope? + RESOURCE_METHOD_SCOPES.include? scope_level + end + + def action_name(name_prefix, prefix, collection_name, member_name) + case scope_level + when :nested + [name_prefix, prefix] + when :collection + [prefix, name_prefix, collection_name] + when :new + [prefix, :new, name_prefix, member_name] + when :member + [prefix, name_prefix, member_name] + when :root + [name_prefix, collection_name, prefix] + else + [name_prefix, member_name, prefix] + end + end + + def resource_scope? + RESOURCE_SCOPES.include? scope_level + end + + def options + OPTIONS + end + + def new(hash) + self.class.new hash, self, scope_level + end + + def new_level(level) + self.class.new(frame, self, level) + end + + def [](key) + scope = find { |node| node.frame.key? key } + scope && scope.frame[key] + end + + include Enumerable + + def each + node = self + until node.equal? NULL + yield node + node = node.parent + end + end + + def frame; @hash; end + + NULL = Scope.new(nil, nil) + end + + def initialize(set) #:nodoc: + @set = set + @scope = Scope.new(path_names: @set.resources_path_names) + @concerns = {} + end + + include Base + include HttpHelpers + include Redirection + include Scoping + include Concerns + include Resources + include CustomUrls + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/polymorphic_routes.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/polymorphic_routes.rb new file mode 100644 index 00000000..6da869c0 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/polymorphic_routes.rb @@ -0,0 +1,352 @@ +# frozen_string_literal: true + +module ActionDispatch + module Routing + # Polymorphic URL helpers are methods for smart resolution to a named route call when + # given an Active Record model instance. They are to be used in combination with + # ActionController::Resources. + # + # These methods are useful when you want to generate the correct URL or path to a RESTful + # resource without having to know the exact type of the record in question. + # + # Nested resources and/or namespaces are also supported, as illustrated in the example: + # + # polymorphic_url([:admin, @article, @comment]) + # + # results in: + # + # admin_article_comment_url(@article, @comment) + # + # == Usage within the framework + # + # Polymorphic URL helpers are used in a number of places throughout the \Rails framework: + # + # * url_for, so you can use it with a record as the argument, e.g. + # url_for(@article); + # * ActionView::Helpers::FormHelper uses polymorphic_path, so you can write + # form_for(@article) without having to specify :url parameter for the form + # action; + # * redirect_to (which, in fact, uses url_for) so you can write + # redirect_to(post) in your controllers; + # * ActionView::Helpers::AtomFeedHelper, so you don't have to explicitly specify URLs + # for feed entries. + # + # == Prefixed polymorphic helpers + # + # In addition to polymorphic_url and polymorphic_path methods, a + # number of prefixed helpers are available as a shorthand to action: "..." + # in options. Those are: + # + # * edit_polymorphic_url, edit_polymorphic_path + # * new_polymorphic_url, new_polymorphic_path + # + # Example usage: + # + # edit_polymorphic_path(@post) # => "/posts/1/edit" + # polymorphic_path(@post, format: :pdf) # => "/posts/1.pdf" + # + # == Usage with mounted engines + # + # If you are using a mounted engine and you need to use a polymorphic_url + # pointing at the engine's routes, pass in the engine's route proxy as the first + # argument to the method. For example: + # + # polymorphic_url([blog, @post]) # calls blog.post_path(@post) + # form_for([blog, @post]) # => "/blog/posts/1" + # + module PolymorphicRoutes + # Constructs a call to a named RESTful route for the given record and returns the + # resulting URL string. For example: + # + # # calls post_url(post) + # polymorphic_url(post) # => "http://example.com/posts/1" + # polymorphic_url([blog, post]) # => "http://example.com/blogs/1/posts/1" + # polymorphic_url([:admin, blog, post]) # => "http://example.com/admin/blogs/1/posts/1" + # polymorphic_url([user, :blog, post]) # => "http://example.com/users/1/blog/posts/1" + # polymorphic_url(Comment) # => "http://example.com/comments" + # + # ==== Options + # + # * :action - Specifies the action prefix for the named route: + # :new or :edit. Default is no prefix. + # * :routing_type - Allowed values are :path or :url. + # Default is :url. + # + # Also includes all the options from url_for. These include such + # things as :anchor or :trailing_slash. Example usage + # is given below: + # + # polymorphic_url([blog, post], anchor: 'my_anchor') + # # => "http://example.com/blogs/1/posts/1#my_anchor" + # polymorphic_url([blog, post], anchor: 'my_anchor', script_name: "/my_app") + # # => "http://example.com/my_app/blogs/1/posts/1#my_anchor" + # + # For all of these options, see the documentation for {url_for}[rdoc-ref:ActionDispatch::Routing::UrlFor]. + # + # ==== Functionality + # + # # an Article record + # polymorphic_url(record) # same as article_url(record) + # + # # a Comment record + # polymorphic_url(record) # same as comment_url(record) + # + # # it recognizes new records and maps to the collection + # record = Comment.new + # polymorphic_url(record) # same as comments_url() + # + # # the class of a record will also map to the collection + # polymorphic_url(Comment) # same as comments_url() + # + def polymorphic_url(record_or_hash_or_array, options = {}) + if Hash === record_or_hash_or_array + options = record_or_hash_or_array.merge(options) + record = options.delete :id + return polymorphic_url record, options + end + + if mapping = polymorphic_mapping(record_or_hash_or_array) + return mapping.call(self, [record_or_hash_or_array, options], false) + end + + opts = options.dup + action = opts.delete :action + type = opts.delete(:routing_type) || :url + + HelperMethodBuilder.polymorphic_method self, + record_or_hash_or_array, + action, + type, + opts + end + + # Returns the path component of a URL for the given record. It uses + # polymorphic_url with routing_type: :path. + def polymorphic_path(record_or_hash_or_array, options = {}) + if Hash === record_or_hash_or_array + options = record_or_hash_or_array.merge(options) + record = options.delete :id + return polymorphic_path record, options + end + + if mapping = polymorphic_mapping(record_or_hash_or_array) + return mapping.call(self, [record_or_hash_or_array, options], true) + end + + opts = options.dup + action = opts.delete :action + type = :path + + HelperMethodBuilder.polymorphic_method self, + record_or_hash_or_array, + action, + type, + opts + end + + %w(edit new).each do |action| + module_eval <<-EOT, __FILE__, __LINE__ + 1 + def #{action}_polymorphic_url(record_or_hash, options = {}) + polymorphic_url_for_action("#{action}", record_or_hash, options) + end + + def #{action}_polymorphic_path(record_or_hash, options = {}) + polymorphic_path_for_action("#{action}", record_or_hash, options) + end + EOT + end + + private + + def polymorphic_url_for_action(action, record_or_hash, options) + polymorphic_url(record_or_hash, options.merge(action: action)) + end + + def polymorphic_path_for_action(action, record_or_hash, options) + polymorphic_path(record_or_hash, options.merge(action: action)) + end + + def polymorphic_mapping(record) + if record.respond_to?(:to_model) + _routes.polymorphic_mappings[record.to_model.model_name.name] + else + _routes.polymorphic_mappings[record.class.name] + end + end + + class HelperMethodBuilder # :nodoc: + CACHE = { "path" => {}, "url" => {} } + + def self.get(action, type) + type = type.to_s + CACHE[type].fetch(action) { build action, type } + end + + def self.url; CACHE["url".freeze][nil]; end + def self.path; CACHE["path".freeze][nil]; end + + def self.build(action, type) + prefix = action ? "#{action}_" : "" + suffix = type + if action.to_s == "new" + HelperMethodBuilder.singular prefix, suffix + else + HelperMethodBuilder.plural prefix, suffix + end + end + + def self.singular(prefix, suffix) + new(->(name) { name.singular_route_key }, prefix, suffix) + end + + def self.plural(prefix, suffix) + new(->(name) { name.route_key }, prefix, suffix) + end + + def self.polymorphic_method(recipient, record_or_hash_or_array, action, type, options) + builder = get action, type + + case record_or_hash_or_array + when Array + record_or_hash_or_array = record_or_hash_or_array.compact + if record_or_hash_or_array.empty? + raise ArgumentError, "Nil location provided. Can't build URI." + end + if record_or_hash_or_array.first.is_a?(ActionDispatch::Routing::RoutesProxy) + recipient = record_or_hash_or_array.shift + end + + method, args = builder.handle_list record_or_hash_or_array + when String, Symbol + method, args = builder.handle_string record_or_hash_or_array + when Class + method, args = builder.handle_class record_or_hash_or_array + + when nil + raise ArgumentError, "Nil location provided. Can't build URI." + else + method, args = builder.handle_model record_or_hash_or_array + end + + if options.empty? + recipient.send(method, *args) + else + recipient.send(method, *args, options) + end + end + + attr_reader :suffix, :prefix + + def initialize(key_strategy, prefix, suffix) + @key_strategy = key_strategy + @prefix = prefix + @suffix = suffix + end + + def handle_string(record) + [get_method_for_string(record), []] + end + + def handle_string_call(target, str) + target.send get_method_for_string str + end + + def handle_class(klass) + [get_method_for_class(klass), []] + end + + def handle_class_call(target, klass) + target.send get_method_for_class klass + end + + def handle_model(record) + args = [] + + model = record.to_model + named_route = if model.persisted? + args << model + get_method_for_string model.model_name.singular_route_key + else + get_method_for_class model + end + + [named_route, args] + end + + def handle_model_call(target, record) + if mapping = polymorphic_mapping(target, record) + mapping.call(target, [record], suffix == "path") + else + method, args = handle_model(record) + target.send(method, *args) + end + end + + def handle_list(list) + record_list = list.dup + record = record_list.pop + + args = [] + + route = record_list.map { |parent| + case parent + when Symbol, String + parent.to_s + when Class + args << parent + parent.model_name.singular_route_key + else + args << parent.to_model + parent.to_model.model_name.singular_route_key + end + } + + route << + case record + when Symbol, String + record.to_s + when Class + @key_strategy.call record.model_name + else + model = record.to_model + if model.persisted? + args << model + model.model_name.singular_route_key + else + @key_strategy.call model.model_name + end + end + + route << suffix + + named_route = prefix + route.join("_") + [named_route, args] + end + + private + + def polymorphic_mapping(target, record) + if record.respond_to?(:to_model) + target._routes.polymorphic_mappings[record.to_model.model_name.name] + else + target._routes.polymorphic_mappings[record.class.name] + end + end + + def get_method_for_class(klass) + name = @key_strategy.call klass.model_name + get_method_for_string name + end + + def get_method_for_string(str) + "#{prefix}#{str}_#{suffix}" + end + + [nil, "new", "edit"].each do |action| + CACHE["url"][action] = build action, "url" + CACHE["path"][action] = build action, "path" + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/redirection.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/redirection.rb new file mode 100644 index 00000000..143a4b3d --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/redirection.rb @@ -0,0 +1,201 @@ +# frozen_string_literal: true + +require "action_dispatch/http/request" +require "active_support/core_ext/uri" +require "active_support/core_ext/array/extract_options" +require "rack/utils" +require "action_controller/metal/exceptions" +require "action_dispatch/routing/endpoint" + +module ActionDispatch + module Routing + class Redirect < Endpoint # :nodoc: + attr_reader :status, :block + + def initialize(status, block) + @status = status + @block = block + end + + def redirect?; true; end + + def call(env) + serve Request.new env + end + + def serve(req) + uri = URI.parse(path(req.path_parameters, req)) + + unless uri.host + if relative_path?(uri.path) + uri.path = "#{req.script_name}/#{uri.path}" + elsif uri.path.empty? + uri.path = req.script_name.empty? ? "/" : req.script_name + end + end + + uri.scheme ||= req.scheme + uri.host ||= req.host + uri.port ||= req.port unless req.standard_port? + + req.commit_flash + + body = %(You are being redirected.) + + headers = { + "Location" => uri.to_s, + "Content-Type" => "text/html", + "Content-Length" => body.length.to_s + } + + [ status, headers, [body] ] + end + + def path(params, request) + block.call params, request + end + + def inspect + "redirect(#{status})" + end + + private + def relative_path?(path) + path && !path.empty? && path[0] != "/" + end + + def escape(params) + Hash[params.map { |k, v| [k, Rack::Utils.escape(v)] }] + end + + def escape_fragment(params) + Hash[params.map { |k, v| [k, Journey::Router::Utils.escape_fragment(v)] }] + end + + def escape_path(params) + Hash[params.map { |k, v| [k, Journey::Router::Utils.escape_path(v)] }] + end + end + + class PathRedirect < Redirect + URL_PARTS = /\A([^?]+)?(\?[^#]+)?(#.+)?\z/ + + def path(params, request) + if block.match(URL_PARTS) + path = interpolation_required?($1, params) ? $1 % escape_path(params) : $1 + query = interpolation_required?($2, params) ? $2 % escape(params) : $2 + fragment = interpolation_required?($3, params) ? $3 % escape_fragment(params) : $3 + + "#{path}#{query}#{fragment}" + else + interpolation_required?(block, params) ? block % escape(params) : block + end + end + + def inspect + "redirect(#{status}, #{block})" + end + + private + def interpolation_required?(string, params) + !params.empty? && string && string.match(/%\{\w*\}/) + end + end + + class OptionRedirect < Redirect # :nodoc: + alias :options :block + + def path(params, request) + url_options = { + protocol: request.protocol, + host: request.host, + port: request.optional_port, + path: request.path, + params: request.query_parameters + }.merge! options + + if !params.empty? && url_options[:path].match(/%\{\w*\}/) + url_options[:path] = (url_options[:path] % escape_path(params)) + end + + unless options[:host] || options[:domain] + if relative_path?(url_options[:path]) + url_options[:path] = "/#{url_options[:path]}" + url_options[:script_name] = request.script_name + elsif url_options[:path].empty? + url_options[:path] = request.script_name.empty? ? "/" : "" + url_options[:script_name] = request.script_name + end + end + + ActionDispatch::Http::URL.url_for url_options + end + + def inspect + "redirect(#{status}, #{options.map { |k, v| "#{k}: #{v}" }.join(', ')})" + end + end + + module Redirection + # Redirect any path to another path: + # + # get "/stories" => redirect("/posts") + # + # This will redirect the user, while ignoring certain parts of the request, including query string, etc. + # /stories, /stories?foo=bar, etc all redirect to /posts. + # + # You can also use interpolation in the supplied redirect argument: + # + # get 'docs/:article', to: redirect('/wiki/%{article}') + # + # Note that if you return a path without a leading slash then the URL is prefixed with the + # current SCRIPT_NAME environment variable. This is typically '/' but may be different in + # a mounted engine or where the application is deployed to a subdirectory of a website. + # + # Alternatively you can use one of the other syntaxes: + # + # The block version of redirect allows for the easy encapsulation of any logic associated with + # the redirect in question. Either the params and request are supplied as arguments, or just + # params, depending of how many arguments your block accepts. A string is required as a + # return value. + # + # get 'jokes/:number', to: redirect { |params, request| + # path = (params[:number].to_i.even? ? "wheres-the-beef" : "i-love-lamp") + # "http://#{request.host_with_port}/#{path}" + # } + # + # Note that the +do end+ syntax for the redirect block wouldn't work, as Ruby would pass + # the block to +get+ instead of +redirect+. Use { ... } instead. + # + # The options version of redirect allows you to supply only the parts of the URL which need + # to change, it also supports interpolation of the path similar to the first example. + # + # get 'stores/:name', to: redirect(subdomain: 'stores', path: '/%{name}') + # get 'stores/:name(*all)', to: redirect(subdomain: 'stores', path: '/%{name}%{all}') + # get '/stories', to: redirect(path: '/posts') + # + # This will redirect the user, while changing only the specified parts of the request, + # for example the +path+ option in the last example. + # /stories, /stories?foo=bar, redirect to /posts and /posts?foo=bar respectively. + # + # Finally, an object which responds to call can be supplied to redirect, allowing you to reuse + # common redirect routes. The call method must accept two arguments, params and request, and return + # a string. + # + # get 'accounts/:name' => redirect(SubdomainRedirector.new('api')) + # + def redirect(*args, &block) + options = args.extract_options! + status = options.delete(:status) || 301 + path = args.shift + + return OptionRedirect.new(status, options) if options.any? + return PathRedirect.new(status, path) if String === path + + block = path if path.respond_to? :call + raise ArgumentError, "redirection argument not supported" unless block + Redirect.new status, block + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/route_set.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/route_set.rb new file mode 100644 index 00000000..d9d0c311 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/route_set.rb @@ -0,0 +1,890 @@ +# frozen_string_literal: true + +require "action_dispatch/journey" +require "active_support/core_ext/object/to_query" +require "active_support/core_ext/hash/slice" +require "active_support/core_ext/module/redefine_method" +require "active_support/core_ext/module/remove_method" +require "active_support/core_ext/array/extract_options" +require "action_controller/metal/exceptions" +require "action_dispatch/http/request" +require "action_dispatch/routing/endpoint" + +module ActionDispatch + module Routing + # :stopdoc: + class RouteSet + # Since the router holds references to many parts of the system + # like engines, controllers and the application itself, inspecting + # the route set can actually be really slow, therefore we default + # alias inspect to to_s. + alias inspect to_s + + class Dispatcher < Routing::Endpoint + def initialize(raise_on_name_error) + @raise_on_name_error = raise_on_name_error + end + + def dispatcher?; true; end + + def serve(req) + params = req.path_parameters + controller = controller req + res = controller.make_response! req + dispatch(controller, params[:action], req, res) + rescue ActionController::RoutingError + if @raise_on_name_error + raise + else + return [404, { "X-Cascade" => "pass" }, []] + end + end + + private + + def controller(req) + req.controller_class + rescue NameError => e + raise ActionController::RoutingError, e.message, e.backtrace + end + + def dispatch(controller, action, req, res) + controller.dispatch(action, req, res) + end + end + + class StaticDispatcher < Dispatcher + def initialize(controller_class) + super(false) + @controller_class = controller_class + end + + private + + def controller(_); @controller_class; end + end + + # A NamedRouteCollection instance is a collection of named routes, and also + # maintains an anonymous module that can be used to install helpers for the + # named routes. + class NamedRouteCollection + include Enumerable + attr_reader :routes, :url_helpers_module, :path_helpers_module + private :routes + + def initialize + @routes = {} + @path_helpers = Set.new + @url_helpers = Set.new + @url_helpers_module = Module.new + @path_helpers_module = Module.new + end + + def route_defined?(name) + key = name.to_sym + @path_helpers.include?(key) || @url_helpers.include?(key) + end + + def helper_names + @path_helpers.map(&:to_s) + @url_helpers.map(&:to_s) + end + + def clear! + @path_helpers.each do |helper| + @path_helpers_module.send :remove_method, helper + end + + @url_helpers.each do |helper| + @url_helpers_module.send :remove_method, helper + end + + @routes.clear + @path_helpers.clear + @url_helpers.clear + end + + def add(name, route) + key = name.to_sym + path_name = :"#{name}_path" + url_name = :"#{name}_url" + + if routes.key? key + @path_helpers_module.send :undef_method, path_name + @url_helpers_module.send :undef_method, url_name + end + routes[key] = route + define_url_helper @path_helpers_module, route, path_name, route.defaults, name, PATH + define_url_helper @url_helpers_module, route, url_name, route.defaults, name, UNKNOWN + + @path_helpers << path_name + @url_helpers << url_name + end + + def get(name) + routes[name.to_sym] + end + + def key?(name) + return unless name + routes.key? name.to_sym + end + + alias []= add + alias [] get + alias clear clear! + + def each + routes.each { |name, route| yield name, route } + self + end + + def names + routes.keys + end + + def length + routes.length + end + + # Given a +name+, defines name_path and name_url helpers. + # Used by 'direct', 'resolve', and 'polymorphic' route helpers. + def add_url_helper(name, defaults, &block) + helper = CustomUrlHelper.new(name, defaults, &block) + path_name = :"#{name}_path" + url_name = :"#{name}_url" + + @path_helpers_module.module_eval do + define_method(path_name) do |*args| + helper.call(self, args, true) + end + end + + @url_helpers_module.module_eval do + define_method(url_name) do |*args| + helper.call(self, args, false) + end + end + + @path_helpers << path_name + @url_helpers << url_name + + self + end + + class UrlHelper + def self.create(route, options, route_name, url_strategy) + if optimize_helper?(route) + OptimizedUrlHelper.new(route, options, route_name, url_strategy) + else + new route, options, route_name, url_strategy + end + end + + def self.optimize_helper?(route) + !route.glob? && route.path.requirements.empty? + end + + attr_reader :url_strategy, :route_name + + class OptimizedUrlHelper < UrlHelper + attr_reader :arg_size + + def initialize(route, options, route_name, url_strategy) + super + @required_parts = @route.required_parts + @arg_size = @required_parts.size + end + + def call(t, args, inner_options) + if args.size == arg_size && !inner_options && optimize_routes_generation?(t) + options = t.url_options.merge @options + options[:path] = optimized_helper(args) + + original_script_name = options.delete(:original_script_name) + script_name = t._routes.find_script_name(options) + + if original_script_name + script_name = original_script_name + script_name + end + + options[:script_name] = script_name + + url_strategy.call options + else + super + end + end + + private + + def optimized_helper(args) + params = parameterize_args(args) do + raise_generation_error(args) + end + + @route.format params + end + + def optimize_routes_generation?(t) + t.send(:optimize_routes_generation?) + end + + def parameterize_args(args) + params = {} + @arg_size.times { |i| + key = @required_parts[i] + value = args[i].to_param + yield key if value.nil? || value.empty? + params[key] = value + } + params + end + + def raise_generation_error(args) + missing_keys = [] + params = parameterize_args(args) { |missing_key| + missing_keys << missing_key + } + constraints = Hash[@route.requirements.merge(params).sort_by { |k, v| k.to_s }] + message = "No route matches #{constraints.inspect}".dup + message << ", missing required keys: #{missing_keys.sort.inspect}" + + raise ActionController::UrlGenerationError, message + end + end + + def initialize(route, options, route_name, url_strategy) + @options = options + @segment_keys = route.segment_keys.uniq + @route = route + @url_strategy = url_strategy + @route_name = route_name + end + + def call(t, args, inner_options) + controller_options = t.url_options + options = controller_options.merge @options + hash = handle_positional_args(controller_options, + inner_options || {}, + args, + options, + @segment_keys) + + t._routes.url_for(hash, route_name, url_strategy) + end + + def handle_positional_args(controller_options, inner_options, args, result, path_params) + if args.size > 0 + # take format into account + if path_params.include?(:format) + path_params_size = path_params.size - 1 + else + path_params_size = path_params.size + end + + if args.size < path_params_size + path_params -= controller_options.keys + path_params -= result.keys + else + path_params = path_params.dup + end + inner_options.each_key do |key| + path_params.delete(key) + end + + args.each_with_index do |arg, index| + param = path_params[index] + result[param] = arg if param + end + end + + result.merge!(inner_options) + end + end + + private + # Create a URL helper allowing ordered parameters to be associated + # with corresponding dynamic segments, so you can do: + # + # foo_url(bar, baz, bang) + # + # Instead of: + # + # foo_url(bar: bar, baz: baz, bang: bang) + # + # Also allow options hash, so you can do: + # + # foo_url(bar, baz, bang, sort_by: 'baz') + # + def define_url_helper(mod, route, name, opts, route_key, url_strategy) + helper = UrlHelper.create(route, opts, route_key, url_strategy) + mod.module_eval do + define_method(name) do |*args| + last = args.last + options = \ + case last + when Hash + args.pop + when ActionController::Parameters + args.pop.to_h + end + helper.call self, args, options + end + end + end + end + + # strategy for building urls to send to the client + PATH = ->(options) { ActionDispatch::Http::URL.path_for(options) } + UNKNOWN = ->(options) { ActionDispatch::Http::URL.url_for(options) } + + attr_accessor :formatter, :set, :named_routes, :default_scope, :router + attr_accessor :disable_clear_and_finalize, :resources_path_names + attr_accessor :default_url_options + attr_reader :env_key, :polymorphic_mappings + + alias :routes :set + + def self.default_resources_path_names + { new: "new", edit: "edit" } + end + + def self.new_with_config(config) + route_set_config = DEFAULT_CONFIG + + # engines apparently don't have this set + if config.respond_to? :relative_url_root + route_set_config.relative_url_root = config.relative_url_root + end + + if config.respond_to? :api_only + route_set_config.api_only = config.api_only + end + + new route_set_config + end + + Config = Struct.new :relative_url_root, :api_only + + DEFAULT_CONFIG = Config.new(nil, false) + + def initialize(config = DEFAULT_CONFIG) + self.named_routes = NamedRouteCollection.new + self.resources_path_names = self.class.default_resources_path_names + self.default_url_options = {} + + @config = config + @append = [] + @prepend = [] + @disable_clear_and_finalize = false + @finalized = false + @env_key = "ROUTES_#{object_id}_SCRIPT_NAME".freeze + + @set = Journey::Routes.new + @router = Journey::Router.new @set + @formatter = Journey::Formatter.new self + @polymorphic_mappings = {} + end + + def eager_load! + router.eager_load! + routes.each(&:eager_load!) + nil + end + + def relative_url_root + @config.relative_url_root + end + + def api_only? + @config.api_only + end + + def request_class + ActionDispatch::Request + end + + def make_request(env) + request_class.new env + end + private :make_request + + def draw(&block) + clear! unless @disable_clear_and_finalize + eval_block(block) + finalize! unless @disable_clear_and_finalize + nil + end + + def append(&block) + @append << block + end + + def prepend(&block) + @prepend << block + end + + def eval_block(block) + mapper = Mapper.new(self) + if default_scope + mapper.with_default_scope(default_scope, &block) + else + mapper.instance_exec(&block) + end + end + private :eval_block + + def finalize! + return if @finalized + @append.each { |blk| eval_block(blk) } + @finalized = true + end + + def clear! + @finalized = false + named_routes.clear + set.clear + formatter.clear + @polymorphic_mappings.clear + @prepend.each { |blk| eval_block(blk) } + end + + module MountedHelpers + extend ActiveSupport::Concern + include UrlFor + end + + # Contains all the mounted helpers across different + # engines and the `main_app` helper for the application. + # You can include this in your classes if you want to + # access routes for other engines. + def mounted_helpers + MountedHelpers + end + + def define_mounted_helper(name, script_namer = nil) + return if MountedHelpers.method_defined?(name) + + routes = self + helpers = routes.url_helpers + + MountedHelpers.class_eval do + define_method "_#{name}" do + RoutesProxy.new(routes, _routes_context, helpers, script_namer) + end + end + + MountedHelpers.class_eval(<<-RUBY, __FILE__, __LINE__ + 1) + def #{name} + @_#{name} ||= _#{name} + end + RUBY + end + + def url_helpers(supports_path = true) + routes = self + + Module.new do + extend ActiveSupport::Concern + include UrlFor + + # Define url_for in the singleton level so one can do: + # Rails.application.routes.url_helpers.url_for(args) + proxy_class = Class.new do + include UrlFor + include routes.named_routes.path_helpers_module + include routes.named_routes.url_helpers_module + + attr_reader :_routes + + def initialize(routes) + @_routes = routes + end + + def optimize_routes_generation? + @_routes.optimize_routes_generation? + end + end + + @_proxy = proxy_class.new(routes) + + class << self + def url_for(options) + @_proxy.url_for(options) + end + + def full_url_for(options) + @_proxy.full_url_for(options) + end + + def route_for(name, *args) + @_proxy.route_for(name, *args) + end + + def optimize_routes_generation? + @_proxy.optimize_routes_generation? + end + + def polymorphic_url(record_or_hash_or_array, options = {}) + @_proxy.polymorphic_url(record_or_hash_or_array, options) + end + + def polymorphic_path(record_or_hash_or_array, options = {}) + @_proxy.polymorphic_path(record_or_hash_or_array, options) + end + + def _routes; @_proxy._routes; end + def url_options; {}; end + end + + url_helpers = routes.named_routes.url_helpers_module + + # Make named_routes available in the module singleton + # as well, so one can do: + # Rails.application.routes.url_helpers.posts_path + extend url_helpers + + # Any class that includes this module will get all + # named routes... + include url_helpers + + if supports_path + path_helpers = routes.named_routes.path_helpers_module + + include path_helpers + extend path_helpers + end + + # plus a singleton class method called _routes ... + included do + redefine_singleton_method(:_routes) { routes } + end + + # And an instance method _routes. Note that + # UrlFor (included in this module) add extra + # conveniences for working with @_routes. + define_method(:_routes) { @_routes || routes } + + define_method(:_generate_paths_by_default) do + supports_path + end + + private :_generate_paths_by_default + end + end + + def empty? + routes.empty? + end + + def add_route(mapping, name) + raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i) + + if name && named_routes[name] + raise ArgumentError, "Invalid route name, already in use: '#{name}' \n" \ + "You may have defined two routes with the same name using the `:as` option, or " \ + "you may be overriding a route already defined by a resource with the same naming. " \ + "For the latter, you can restrict the routes created with `resources` as explained here: \n" \ + "http://guides.rubyonrails.org/routing.html#restricting-the-routes-created" + end + + route = @set.add_route(name, mapping) + named_routes[name] = route if name + + if route.segment_keys.include?(:controller) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Using a dynamic :controller segment in a route is deprecated and + will be removed in Rails 6.0. + MSG + end + + if route.segment_keys.include?(:action) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Using a dynamic :action segment in a route is deprecated and + will be removed in Rails 6.0. + MSG + end + + route + end + + def add_polymorphic_mapping(klass, options, &block) + @polymorphic_mappings[klass] = CustomUrlHelper.new(klass, options, &block) + end + + def add_url_helper(name, options, &block) + named_routes.add_url_helper(name, options, &block) + end + + class CustomUrlHelper + attr_reader :name, :defaults, :block + + def initialize(name, defaults, &block) + @name = name + @defaults = defaults + @block = block + end + + def call(t, args, only_path = false) + options = args.extract_options! + url = t.full_url_for(eval_block(t, args, options)) + + if only_path + "/" + url.partition(%r{(? e + raise ActionController::RoutingError, e.message + end + + req = make_request(env) + recognize_path_with_request(req, path, extras) + end + + def recognize_path_with_request(req, path, extras, raise_on_missing: true) + @router.recognize(req) do |route, params| + params.merge!(extras) + params.each do |key, value| + if value.is_a?(String) + value = value.dup.force_encoding(Encoding::BINARY) + params[key] = URI.parser.unescape(value) + end + end + req.path_parameters = params + app = route.app + if app.matches?(req) && app.dispatcher? + begin + req.controller_class + rescue NameError + raise ActionController::RoutingError, "A route matches #{path.inspect}, but references missing controller: #{params[:controller].camelize}Controller" + end + + return req.path_parameters + elsif app.matches?(req) && app.engine? + path_parameters = app.rack_app.routes.recognize_path_with_request(req, path, extras, raise_on_missing: false) + return path_parameters if path_parameters + end + end + + if raise_on_missing + raise ActionController::RoutingError, "No route matches #{path.inspect}" + end + end + end + # :startdoc: + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/routes_proxy.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/routes_proxy.rb new file mode 100644 index 00000000..587a7272 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/routes_proxy.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array/extract_options" + +module ActionDispatch + module Routing + class RoutesProxy #:nodoc: + include ActionDispatch::Routing::UrlFor + + attr_accessor :scope, :routes + alias :_routes :routes + + def initialize(routes, scope, helpers, script_namer = nil) + @routes, @scope = routes, scope + @helpers = helpers + @script_namer = script_namer + end + + def url_options + scope.send(:_with_routes, routes) do + scope.url_options + end + end + + private + def respond_to_missing?(method, _) + super || @helpers.respond_to?(method) + end + + def method_missing(method, *args) + if @helpers.respond_to?(method) + self.class.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{method}(*args) + options = args.extract_options! + options = url_options.merge((options || {}).symbolize_keys) + + if @script_namer + options[:script_name] = merge_script_names( + options[:script_name], + @script_namer.call(options) + ) + end + + args << options + @helpers.#{method}(*args) + end + RUBY + public_send(method, *args) + else + super + end + end + + # Keeps the part of the script name provided by the global + # context via ENV["SCRIPT_NAME"], which `mount` doesn't know + # about since it depends on the specific request, but use our + # script name resolver for the mount point dependent part. + def merge_script_names(previous_script_name, new_script_name) + return new_script_name unless previous_script_name + + resolved_parts = new_script_name.count("/") + previous_parts = previous_script_name.count("/") + context_parts = previous_parts - resolved_parts + 1 + + (previous_script_name.split("/").slice(0, context_parts).join("/")) + new_script_name + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/url_for.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/url_for.rb new file mode 100644 index 00000000..1a31c7db --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/routing/url_for.rb @@ -0,0 +1,236 @@ +# frozen_string_literal: true + +module ActionDispatch + module Routing + # In config/routes.rb you define URL-to-controller mappings, but the reverse + # is also possible: a URL can be generated from one of your routing definitions. + # URL generation functionality is centralized in this module. + # + # See ActionDispatch::Routing for general information about routing and routes.rb. + # + # Tip: If you need to generate URLs from your models or some other place, + # then ActionController::UrlFor is what you're looking for. Read on for + # an introduction. In general, this module should not be included on its own, + # as it is usually included by url_helpers (as in Rails.application.routes.url_helpers). + # + # == URL generation from parameters + # + # As you may know, some functions, such as ActionController::Base#url_for + # and ActionView::Helpers::UrlHelper#link_to, can generate URLs given a set + # of parameters. For example, you've probably had the chance to write code + # like this in one of your views: + # + # <%= link_to('Click here', controller: 'users', + # action: 'new', message: 'Welcome!') %> + # # => Click here + # + # link_to, and all other functions that require URL generation functionality, + # actually use ActionController::UrlFor under the hood. And in particular, + # they use the ActionController::UrlFor#url_for method. One can generate + # the same path as the above example by using the following code: + # + # include UrlFor + # url_for(controller: 'users', + # action: 'new', + # message: 'Welcome!', + # only_path: true) + # # => "/users/new?message=Welcome%21" + # + # Notice the only_path: true part. This is because UrlFor has no + # information about the website hostname that your Rails app is serving. So if you + # want to include the hostname as well, then you must also pass the :host + # argument: + # + # include UrlFor + # url_for(controller: 'users', + # action: 'new', + # message: 'Welcome!', + # host: 'www.example.com') + # # => "http://www.example.com/users/new?message=Welcome%21" + # + # By default, all controllers and views have access to a special version of url_for, + # that already knows what the current hostname is. So if you use url_for in your + # controllers or your views, then you don't need to explicitly pass the :host + # argument. + # + # For convenience reasons, mailers provide a shortcut for ActionController::UrlFor#url_for. + # So within mailers, you only have to type +url_for+ instead of 'ActionController::UrlFor#url_for' + # in full. However, mailers don't have hostname information, and you still have to provide + # the +:host+ argument or set the default host that will be used in all mailers using the + # configuration option +config.action_mailer.default_url_options+. For more information on + # url_for in mailers read the ActionMailer#Base documentation. + # + # + # == URL generation for named routes + # + # UrlFor also allows one to access methods that have been auto-generated from + # named routes. For example, suppose that you have a 'users' resource in your + # config/routes.rb: + # + # resources :users + # + # This generates, among other things, the method users_path. By default, + # this method is accessible from your controllers, views and mailers. If you need + # to access this auto-generated method from other places (such as a model), then + # you can do that by including Rails.application.routes.url_helpers in your class: + # + # class User < ActiveRecord::Base + # include Rails.application.routes.url_helpers + # + # def base_uri + # user_path(self) + # end + # end + # + # User.find(1).base_uri # => "/users/1" + # + module UrlFor + extend ActiveSupport::Concern + include PolymorphicRoutes + + included do + unless method_defined?(:default_url_options) + # Including in a class uses an inheritable hash. Modules get a plain hash. + if respond_to?(:class_attribute) + class_attribute :default_url_options + else + mattr_writer :default_url_options + end + + self.default_url_options = {} + end + + include(*_url_for_modules) if respond_to?(:_url_for_modules) + end + + def initialize(*) + @_routes = nil + super + end + + # Hook overridden in controller to add request information + # with +default_url_options+. Application logic should not + # go into url_options. + def url_options + default_url_options + end + + # Generate a URL based on the options provided, default_url_options and the + # routes defined in routes.rb. The following options are supported: + # + # * :only_path - If true, the relative URL is returned. Defaults to +false+. + # * :protocol - The protocol to connect to. Defaults to 'http'. + # * :host - Specifies the host the link should be targeted at. + # If :only_path is false, this option must be + # provided either explicitly, or via +default_url_options+. + # * :subdomain - Specifies the subdomain of the link, using the +tld_length+ + # to split the subdomain from the host. + # If false, removes all subdomains from the host part of the link. + # * :domain - Specifies the domain of the link, using the +tld_length+ + # to split the domain from the host. + # * :tld_length - Number of labels the TLD id composed of, only used if + # :subdomain or :domain are supplied. Defaults to + # ActionDispatch::Http::URL.tld_length, which in turn defaults to 1. + # * :port - Optionally specify the port to connect to. + # * :anchor - An anchor name to be appended to the path. + # * :trailing_slash - If true, adds a trailing slash, as in "/archive/2009/" + # * :script_name - Specifies application path relative to domain root. If provided, prepends application path. + # + # Any other key (:controller, :action, etc.) given to + # +url_for+ is forwarded to the Routes module. + # + # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', port: '8080' + # # => 'http://somehost.org:8080/tasks/testing' + # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', anchor: 'ok', only_path: true + # # => '/tasks/testing#ok' + # url_for controller: 'tasks', action: 'testing', trailing_slash: true + # # => 'http://somehost.org/tasks/testing/' + # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', number: '33' + # # => 'http://somehost.org/tasks/testing?number=33' + # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', script_name: "/myapp" + # # => 'http://somehost.org/myapp/tasks/testing' + # url_for controller: 'tasks', action: 'testing', host: 'somehost.org', script_name: "/myapp", only_path: true + # # => '/myapp/tasks/testing' + # + # Missing routes keys may be filled in from the current request's parameters + # (e.g. +:controller+, +:action+, +:id+ and any other parameters that are + # placed in the path). Given that the current action has been reached + # through GET /users/1: + # + # url_for(only_path: true) # => '/users/1' + # url_for(only_path: true, action: 'edit') # => '/users/1/edit' + # url_for(only_path: true, action: 'edit', id: 2) # => '/users/2/edit' + # + # Notice that no +:id+ parameter was provided to the first +url_for+ call + # and the helper used the one from the route's path. Any path parameter + # implicitly used by +url_for+ can always be overwritten like shown on the + # last +url_for+ calls. + def url_for(options = nil) + full_url_for(options) + end + + def full_url_for(options = nil) # :nodoc: + case options + when nil + _routes.url_for(url_options.symbolize_keys) + when Hash, ActionController::Parameters + route_name = options.delete :use_route + merged_url_options = options.to_h.symbolize_keys.reverse_merge!(url_options) + _routes.url_for(merged_url_options, route_name) + when String + options + when Symbol + HelperMethodBuilder.url.handle_string_call self, options + when Array + components = options.dup + polymorphic_url(components, components.extract_options!) + when Class + HelperMethodBuilder.url.handle_class_call self, options + else + HelperMethodBuilder.url.handle_model_call self, options + end + end + + # Allows calling direct or regular named route. + # + # resources :buckets + # + # direct :recordable do |recording| + # route_for(:bucket, recording.bucket) + # end + # + # direct :threadable do |threadable| + # route_for(:recordable, threadable.parent) + # end + # + # This maintains the context of the original caller on + # whether to return a path or full URL, e.g: + # + # threadable_path(threadable) # => "/buckets/1" + # threadable_url(threadable) # => "http://example.com/buckets/1" + # + def route_for(name, *args) + public_send(:"#{name}_url", *args) + end + + protected + + def optimize_routes_generation? + _routes.optimize_routes_generation? && default_url_options.empty? + end + + private + + def _with_routes(routes) # :doc: + old_routes, @_routes = @_routes, routes + yield + ensure + @_routes = old_routes + end + + def _routes_context # :doc: + self + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/system_test_case.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/system_test_case.rb new file mode 100644 index 00000000..c74c0ccc --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/system_test_case.rb @@ -0,0 +1,147 @@ +# frozen_string_literal: true + +gem "capybara", ">= 2.15" + +require "capybara/dsl" +require "capybara/minitest" +require "action_controller" +require "action_dispatch/system_testing/driver" +require "action_dispatch/system_testing/browser" +require "action_dispatch/system_testing/server" +require "action_dispatch/system_testing/test_helpers/screenshot_helper" +require "action_dispatch/system_testing/test_helpers/setup_and_teardown" +require "action_dispatch/system_testing/test_helpers/undef_methods" + +module ActionDispatch + # = System Testing + # + # System tests let you test applications in the browser. Because system + # tests use a real browser experience, you can test all of your JavaScript + # easily from your test suite. + # + # To create a system test in your application, extend your test class + # from ApplicationSystemTestCase. System tests use Capybara as a + # base and allow you to configure the settings through your + # application_system_test_case.rb file that is generated with a new + # application or scaffold. + # + # Here is an example system test: + # + # require 'application_system_test_case' + # + # class Users::CreateTest < ApplicationSystemTestCase + # test "adding a new user" do + # visit users_path + # click_on 'New User' + # + # fill_in 'Name', with: 'Arya' + # click_on 'Create User' + # + # assert_text 'Arya' + # end + # end + # + # When generating an application or scaffold, an +application_system_test_case.rb+ + # file will also be generated containing the base class for system testing. + # This is where you can change the driver, add Capybara settings, and other + # configuration for your system tests. + # + # require "test_helper" + # + # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + # driven_by :selenium, using: :chrome, screen_size: [1400, 1400] + # end + # + # By default, ActionDispatch::SystemTestCase is driven by the + # Selenium driver, with the Chrome browser, and a browser size of 1400x1400. + # + # Changing the driver configuration options is easy. Let's say you want to use + # the Firefox browser instead of Chrome. In your +application_system_test_case.rb+ + # file add the following: + # + # require "test_helper" + # + # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + # driven_by :selenium, using: :firefox + # end + # + # +driven_by+ has a required argument for the driver name. The keyword + # arguments are +:using+ for the browser and +:screen_size+ to change the + # size of the browser screen. These two options are not applicable for + # headless drivers and will be silently ignored if passed. + # + # Headless browsers such as headless Chrome and headless Firefox are also supported. + # You can use these browsers by setting the +:using+ argument to +:headless_chrome+ or +:headless_firefox+. + # + # To use a headless driver, like Poltergeist, update your Gemfile to use + # Poltergeist instead of Selenium and then declare the driver name in the + # +application_system_test_case.rb+ file. In this case, you would leave out + # the +:using+ option because the driver is headless, but you can still use + # +:screen_size+ to change the size of the browser screen, also you can use + # +:options+ to pass options supported by the driver. Please refer to your + # driver documentation to learn about supported options. + # + # require "test_helper" + # require "capybara/poltergeist" + # + # class ApplicationSystemTestCase < ActionDispatch::SystemTestCase + # driven_by :poltergeist, screen_size: [1400, 1400], options: + # { js_errors: true } + # end + # + # Because ActionDispatch::SystemTestCase is a shim between Capybara + # and Rails, any driver that is supported by Capybara is supported by system + # tests as long as you include the required gems and files. + class SystemTestCase < IntegrationTest + include Capybara::DSL + include Capybara::Minitest::Assertions + include SystemTesting::TestHelpers::SetupAndTeardown + include SystemTesting::TestHelpers::ScreenshotHelper + include SystemTesting::TestHelpers::UndefMethods + + def initialize(*) # :nodoc: + super + self.class.driver.use + end + + def self.start_application # :nodoc: + Capybara.app = Rack::Builder.new do + map "/" do + run Rails.application + end + end + + SystemTesting::Server.new.run + end + + class_attribute :driver, instance_accessor: false + + # System Test configuration options + # + # The default settings are Selenium, using Chrome, with a screen size + # of 1400x1400. + # + # Examples: + # + # driven_by :poltergeist + # + # driven_by :selenium, screen_size: [800, 800] + # + # driven_by :selenium, using: :chrome + # + # driven_by :selenium, using: :headless_chrome + # + # driven_by :selenium, using: :firefox + # + # driven_by :selenium, using: :headless_firefox + def self.driven_by(driver, using: :chrome, screen_size: [1400, 1400], options: {}) + self.driver = SystemTesting::Driver.new(driver, using: using, screen_size: screen_size, options: options) + end + + driven_by :selenium + + ActiveSupport.run_load_hooks(:action_dispatch_system_test_case, self) + end + + SystemTestCase.start_application +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/system_testing/browser.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/system_testing/browser.rb new file mode 100644 index 00000000..1b0bce6b --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/system_testing/browser.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module ActionDispatch + module SystemTesting + class Browser # :nodoc: + attr_reader :name + + def initialize(name) + @name = name + end + + def type + case name + when :headless_chrome + :chrome + when :headless_firefox + :firefox + else + name + end + end + + def options + case name + when :headless_chrome + headless_chrome_browser_options + when :headless_firefox + headless_firefox_browser_options + end + end + + private + def headless_chrome_browser_options + options = Selenium::WebDriver::Chrome::Options.new + options.args << "--headless" + options.args << "--disable-gpu" if Gem.win_platform? + + options + end + + def headless_firefox_browser_options + options = Selenium::WebDriver::Firefox::Options.new + options.args << "-headless" + + options + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/system_testing/driver.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/system_testing/driver.rb new file mode 100644 index 00000000..5252ff67 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/system_testing/driver.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module ActionDispatch + module SystemTesting + class Driver # :nodoc: + def initialize(name, **options) + @name = name + @browser = Browser.new(options[:using]) + @screen_size = options[:screen_size] + @options = options[:options] + end + + def use + register if registerable? + + setup + end + + private + def registerable? + [:selenium, :poltergeist, :webkit].include?(@name) + end + + def register + Capybara.register_driver @name do |app| + case @name + when :selenium then register_selenium(app) + when :poltergeist then register_poltergeist(app) + when :webkit then register_webkit(app) + end + end + end + + def browser_options + @options.merge(options: @browser.options).compact + end + + def register_selenium(app) + Capybara::Selenium::Driver.new(app, { browser: @browser.type }.merge(browser_options)).tap do |driver| + driver.browser.manage.window.size = Selenium::WebDriver::Dimension.new(*@screen_size) + end + end + + def register_poltergeist(app) + Capybara::Poltergeist::Driver.new(app, @options.merge(window_size: @screen_size)) + end + + def register_webkit(app) + Capybara::Webkit::Driver.new(app, Capybara::Webkit::Configuration.to_hash.merge(@options)).tap do |driver| + driver.resize_window_to(driver.current_window_handle, *@screen_size) + end + end + + def setup + Capybara.current_driver = @name + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/system_testing/server.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/system_testing/server.rb new file mode 100644 index 00000000..4fc1f337 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/system_testing/server.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module ActionDispatch + module SystemTesting + class Server # :nodoc: + class << self + attr_accessor :silence_puma + end + + self.silence_puma = false + + def run + setup + end + + private + def setup + set_server + set_port + end + + def set_server + Capybara.server = :puma, { Silent: self.class.silence_puma } if Capybara.server == Capybara.servers[:default] + end + + def set_port + Capybara.always_include_port = true + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb new file mode 100644 index 00000000..df0c5d3f --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +module ActionDispatch + module SystemTesting + module TestHelpers + # Screenshot helper for system testing. + module ScreenshotHelper + # Takes a screenshot of the current page in the browser. + # + # +take_screenshot+ can be used at any point in your system tests to take + # a screenshot of the current state. This can be useful for debugging or + # automating visual testing. + # + # The screenshot will be displayed in your console, if supported. + # + # You can set the +RAILS_SYSTEM_TESTING_SCREENSHOT+ environment variable to + # control the output. Possible values are: + # * [+simple+ (default)] Only displays the screenshot path. + # This is the default value. + # * [+inline+] Display the screenshot in the terminal using the + # iTerm image protocol (https://iterm2.com/documentation-images.html). + # * [+artifact+] Display the screenshot in the terminal, using the terminal + # artifact format (https://buildkite.github.io/terminal/inline-images/). + def take_screenshot + save_image + puts display_image + end + + # Takes a screenshot of the current page in the browser if the test + # failed. + # + # +take_failed_screenshot+ is included in application_system_test_case.rb + # that is generated with the application. To take screenshots when a test + # fails add +take_failed_screenshot+ to the teardown block before clearing + # sessions. + def take_failed_screenshot + take_screenshot if failed? && supports_screenshot? + end + + private + def image_name + failed? ? "failures_#{method_name}" : method_name + end + + def image_path + @image_path ||= absolute_image_path.relative_path_from(Pathname.pwd).to_s + end + + def absolute_image_path + Rails.root.join("tmp/screenshots/#{image_name}.png") + end + + def save_image + page.save_screenshot(absolute_image_path) + end + + def output_type + # Environment variables have priority + output_type = ENV["RAILS_SYSTEM_TESTING_SCREENSHOT"] || ENV["CAPYBARA_INLINE_SCREENSHOT"] + + # Default to outputting a path to the screenshot + output_type ||= "simple" + + output_type + end + + def display_image + message = "[Screenshot]: #{image_path}\n".dup + + case output_type + when "artifact" + message << "\e]1338;url=artifact://#{absolute_image_path}\a\n" + when "inline" + name = inline_base64(File.basename(absolute_image_path)) + image = inline_base64(File.read(absolute_image_path)) + message << "\e]1337;File=name=#{name};height=400px;inline=1:#{image}\a\n" + end + + message + end + + def inline_base64(path) + Base64.encode64(path).gsub("\n", "") + end + + def failed? + !passed? && !skipped? + end + + def supports_screenshot? + Capybara.current_driver != :rack_test + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb new file mode 100644 index 00000000..600e9c73 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module ActionDispatch + module SystemTesting + module TestHelpers + module SetupAndTeardown # :nodoc: + DEFAULT_HOST = "http://127.0.0.1" + + def host!(host) + super + Capybara.app_host = host + end + + def before_setup + host! DEFAULT_HOST + super + end + + def after_teardown + begin + take_failed_screenshot + ensure + Capybara.reset_sessions! + end + ensure + super + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb new file mode 100644 index 00000000..d64be3b3 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module ActionDispatch + module SystemTesting + module TestHelpers + module UndefMethods # :nodoc: + extend ActiveSupport::Concern + included do + METHODS = %i(get post put patch delete).freeze + + METHODS.each do |verb| + undef_method verb + end + + def method_missing(method, *args, &block) + if METHODS.include?(method) + raise NoMethodError, "System tests cannot make direct requests via ##{method}; use #visit and #click_on instead. See http://www.rubydoc.info/github/teamcapybara/capybara/master#The_DSL for more information." + else + super + end + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/testing/assertion_response.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/testing/assertion_response.rb new file mode 100644 index 00000000..dc019db6 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/testing/assertion_response.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module ActionDispatch + # This is a class that abstracts away an asserted response. It purposely + # does not inherit from Response because it doesn't need it. That means it + # does not have headers or a body. + class AssertionResponse + attr_reader :code, :name + + GENERIC_RESPONSE_CODES = { # :nodoc: + success: "2XX", + missing: "404", + redirect: "3XX", + error: "5XX" + } + + # Accepts a specific response status code as an Integer (404) or String + # ('404') or a response status range as a Symbol pseudo-code (:success, + # indicating any 200-299 status code). + def initialize(code_or_name) + if code_or_name.is_a?(Symbol) + @name = code_or_name + @code = code_from_name(code_or_name) + else + @name = name_from_code(code_or_name) + @code = code_or_name + end + + raise ArgumentError, "Invalid response name: #{name}" if @code.nil? + raise ArgumentError, "Invalid response code: #{code}" if @name.nil? + end + + def code_and_name + "#{code}: #{name}" + end + + private + + def code_from_name(name) + GENERIC_RESPONSE_CODES[name] || Rack::Utils::SYMBOL_TO_STATUS_CODE[name] + end + + def name_from_code(code) + GENERIC_RESPONSE_CODES.invert[code] || Rack::Utils::HTTP_STATUS_CODES[code] + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/testing/assertions.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/testing/assertions.rb new file mode 100644 index 00000000..08c29696 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/testing/assertions.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "rails-dom-testing" + +module ActionDispatch + module Assertions + autoload :ResponseAssertions, "action_dispatch/testing/assertions/response" + autoload :RoutingAssertions, "action_dispatch/testing/assertions/routing" + + extend ActiveSupport::Concern + + include ResponseAssertions + include RoutingAssertions + include Rails::Dom::Testing::Assertions + + def html_document + @html_document ||= if @response.content_type.to_s.end_with?("xml") + Nokogiri::XML::Document.parse(@response.body) + else + Nokogiri::HTML::Document.parse(@response.body) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/testing/assertions/response.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/testing/assertions/response.rb new file mode 100644 index 00000000..98b1965d --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/testing/assertions/response.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +module ActionDispatch + module Assertions + # A small suite of assertions that test responses from \Rails applications. + module ResponseAssertions + RESPONSE_PREDICATES = { # :nodoc: + success: :successful?, + missing: :not_found?, + redirect: :redirection?, + error: :server_error?, + } + + # Asserts that the response is one of the following types: + # + # * :success - Status code was in the 200-299 range + # * :redirect - Status code was in the 300-399 range + # * :missing - Status code was 404 + # * :error - Status code was in the 500-599 range + # + # You can also pass an explicit status number like assert_response(501) + # or its symbolic equivalent assert_response(:not_implemented). + # See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list. + # + # # Asserts that the response was a redirection + # assert_response :redirect + # + # # Asserts that the response code was status code 401 (unauthorized) + # assert_response 401 + def assert_response(type, message = nil) + message ||= generate_response_message(type) + + if RESPONSE_PREDICATES.keys.include?(type) + assert @response.send(RESPONSE_PREDICATES[type]), message + else + assert_equal AssertionResponse.new(type).code, @response.response_code, message + end + end + + # Asserts that the redirection options passed in match those of the redirect called in the latest action. + # This match can be partial, such that assert_redirected_to(controller: "weblog") will also + # match the redirection of redirect_to(controller: "weblog", action: "show") and so on. + # + # # Asserts that the redirection was to the "index" action on the WeblogController + # assert_redirected_to controller: "weblog", action: "index" + # + # # Asserts that the redirection was to the named route login_url + # assert_redirected_to login_url + # + # # Asserts that the redirection was to the URL for @customer + # assert_redirected_to @customer + # + # # Asserts that the redirection matches the regular expression + # assert_redirected_to %r(\Ahttp://example.org) + def assert_redirected_to(options = {}, message = nil) + assert_response(:redirect, message) + return true if options === @response.location + + redirect_is = normalize_argument_to_redirection(@response.location) + redirect_expected = normalize_argument_to_redirection(options) + + message ||= "Expected response to be a redirect to <#{redirect_expected}> but was a redirect to <#{redirect_is}>" + assert_operator redirect_expected, :===, redirect_is, message + end + + private + # Proxy to to_param if the object will respond to it. + def parameterize(value) + value.respond_to?(:to_param) ? value.to_param : value + end + + def normalize_argument_to_redirection(fragment) + if Regexp === fragment + fragment + else + handle = @controller || ActionController::Redirecting + handle._compute_redirect_to_location(@request, fragment) + end + end + + def generate_response_message(expected, actual = @response.response_code) + "Expected response to be a <#{code_with_name(expected)}>,"\ + " but was a <#{code_with_name(actual)}>" + .dup.concat(location_if_redirected).concat(response_body_if_short) + end + + def response_body_if_short + return "" if @response.body.size > 500 + "\nResponse body: #{@response.body}" + end + + def location_if_redirected + return "" unless @response.redirection? && @response.location.present? + location = normalize_argument_to_redirection(@response.location) + " redirect to <#{location}>" + end + + def code_with_name(code_or_name) + if RESPONSE_PREDICATES.values.include?("#{code_or_name}?".to_sym) + code_or_name = RESPONSE_PREDICATES.invert["#{code_or_name}?".to_sym] + end + + AssertionResponse.new(code_or_name).code_and_name + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/testing/assertions/routing.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/testing/assertions/routing.rb new file mode 100644 index 00000000..53905811 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/testing/assertions/routing.rb @@ -0,0 +1,222 @@ +# frozen_string_literal: true + +require "uri" +require "active_support/core_ext/hash/indifferent_access" +require "active_support/core_ext/string/access" +require "action_controller/metal/exceptions" + +module ActionDispatch + module Assertions + # Suite of assertions to test routes generated by \Rails and the handling of requests made to them. + module RoutingAssertions + # Asserts that the routing of the given +path+ was handled correctly and that the parsed options (given in the +expected_options+ hash) + # match +path+. Basically, it asserts that \Rails recognizes the route given by +expected_options+. + # + # Pass a hash in the second argument (+path+) to specify the request method. This is useful for routes + # requiring a specific HTTP method. The hash should contain a :path with the incoming request path + # and a :method containing the required HTTP verb. + # + # # Asserts that POSTing to /items will call the create action on ItemsController + # assert_recognizes({controller: 'items', action: 'create'}, {path: 'items', method: :post}) + # + # You can also pass in +extras+ with a hash containing URL parameters that would normally be in the query string. This can be used + # to assert that values in the query string will end up in the params hash correctly. To test query strings you must use the extras + # argument because appending the query string on the path directly will not work. For example: + # + # # Asserts that a path of '/items/list/1?view=print' returns the correct options + # assert_recognizes({controller: 'items', action: 'list', id: '1', view: 'print'}, 'items/list/1', { view: "print" }) + # + # The +message+ parameter allows you to pass in an error message that is displayed upon failure. + # + # # Check the default route (i.e., the index action) + # assert_recognizes({controller: 'items', action: 'index'}, 'items') + # + # # Test a specific action + # assert_recognizes({controller: 'items', action: 'list'}, 'items/list') + # + # # Test an action with a parameter + # assert_recognizes({controller: 'items', action: 'destroy', id: '1'}, 'items/destroy/1') + # + # # Test a custom route + # assert_recognizes({controller: 'items', action: 'show', id: '1'}, 'view/item1') + def assert_recognizes(expected_options, path, extras = {}, msg = nil) + if path.is_a?(Hash) && path[:method].to_s == "all" + [:get, :post, :put, :delete].each do |method| + assert_recognizes(expected_options, path.merge(method: method), extras, msg) + end + else + request = recognized_request_for(path, extras, msg) + + expected_options = expected_options.clone + + expected_options.stringify_keys! + + msg = message(msg, "") { + sprintf("The recognized options <%s> did not match <%s>, difference:", + request.path_parameters, expected_options) + } + + assert_equal(expected_options, request.path_parameters, msg) + end + end + + # Asserts that the provided options can be used to generate the provided path. This is the inverse of +assert_recognizes+. + # The +extras+ parameter is used to tell the request the names and values of additional request parameters that would be in + # a query string. The +message+ parameter allows you to specify a custom error message for assertion failures. + # + # The +defaults+ parameter is unused. + # + # # Asserts that the default action is generated for a route with no action + # assert_generates "/items", controller: "items", action: "index" + # + # # Tests that the list action is properly routed + # assert_generates "/items/list", controller: "items", action: "list" + # + # # Tests the generation of a route with a parameter + # assert_generates "/items/list/1", { controller: "items", action: "list", id: "1" } + # + # # Asserts that the generated route gives us our custom route + # assert_generates "changesets/12", { controller: 'scm', action: 'show_diff', revision: "12" } + def assert_generates(expected_path, options, defaults = {}, extras = {}, message = nil) + if expected_path =~ %r{://} + fail_on(URI::InvalidURIError, message) do + uri = URI.parse(expected_path) + expected_path = uri.path.to_s.empty? ? "/" : uri.path + end + else + expected_path = "/#{expected_path}" unless expected_path.first == "/" + end + # Load routes.rb if it hasn't been loaded. + + options = options.clone + generated_path, query_string_keys = @routes.generate_extras(options, defaults) + found_extras = options.reject { |k, _| ! query_string_keys.include? k } + + msg = message || sprintf("found extras <%s>, not <%s>", found_extras, extras) + assert_equal(extras, found_extras, msg) + + msg = message || sprintf("The generated path <%s> did not match <%s>", generated_path, + expected_path) + assert_equal(expected_path, generated_path, msg) + end + + # Asserts that path and options match both ways; in other words, it verifies that path generates + # options and then that options generates path. This essentially combines +assert_recognizes+ + # and +assert_generates+ into one step. + # + # The +extras+ hash allows you to specify options that would normally be provided as a query string to the action. The + # +message+ parameter allows you to specify a custom error message to display upon failure. + # + # # Asserts a basic route: a controller with the default action (index) + # assert_routing '/home', controller: 'home', action: 'index' + # + # # Test a route generated with a specific controller, action, and parameter (id) + # assert_routing '/entries/show/23', controller: 'entries', action: 'show', id: 23 + # + # # Asserts a basic route (controller + default action), with an error message if it fails + # assert_routing '/store', { controller: 'store', action: 'index' }, {}, {}, 'Route for store index not generated properly' + # + # # Tests a route, providing a defaults hash + # assert_routing 'controller/action/9', {id: "9", item: "square"}, {controller: "controller", action: "action"}, {}, {item: "square"} + # + # # Tests a route with an HTTP method + # assert_routing({ method: 'put', path: '/product/321' }, { controller: "product", action: "update", id: "321" }) + def assert_routing(path, options, defaults = {}, extras = {}, message = nil) + assert_recognizes(options, path, extras, message) + + controller, default_controller = options[:controller], defaults[:controller] + if controller && controller.include?(?/) && default_controller && default_controller.include?(?/) + options[:controller] = "/#{controller}" + end + + generate_options = options.dup.delete_if { |k, _| defaults.key?(k) } + assert_generates(path.is_a?(Hash) ? path[:path] : path, generate_options, defaults, extras, message) + end + + # A helper to make it easier to test different route configurations. + # This method temporarily replaces @routes with a new RouteSet instance. + # + # The new instance is yielded to the passed block. Typically the block + # will create some routes using set.draw { match ... }: + # + # with_routing do |set| + # set.draw do + # resources :users + # end + # assert_equal "/users", users_path + # end + # + def with_routing + old_routes, @routes = @routes, ActionDispatch::Routing::RouteSet.new + if defined?(@controller) && @controller + old_controller, @controller = @controller, @controller.clone + _routes = @routes + + @controller.singleton_class.include(_routes.url_helpers) + + if @controller.respond_to? :view_context_class + @controller.view_context_class = Class.new(@controller.view_context_class) do + include _routes.url_helpers + end + end + end + yield @routes + ensure + @routes = old_routes + if defined?(@controller) && @controller + @controller = old_controller + end + end + + # ROUTES TODO: These assertions should really work in an integration context + def method_missing(selector, *args, &block) + if defined?(@controller) && @controller && defined?(@routes) && @routes && @routes.named_routes.route_defined?(selector) + @controller.send(selector, *args, &block) + else + super + end + end + + private + # Recognizes the route for a given path. + def recognized_request_for(path, extras = {}, msg) + if path.is_a?(Hash) + method = path[:method] + path = path[:path] + else + method = :get + end + + request = ActionController::TestRequest.create @controller.class + + if path =~ %r{://} + fail_on(URI::InvalidURIError, msg) do + uri = URI.parse(path) + request.env["rack.url_scheme"] = uri.scheme || "http" + request.host = uri.host if uri.host + request.port = uri.port if uri.port + request.path = uri.path.to_s.empty? ? "/" : uri.path + end + else + path = "/#{path}" unless path.first == "/" + request.path = path + end + + request.request_method = method if method + + params = fail_on(ActionController::RoutingError, msg) do + @routes.recognize_path(path, method: method, extras: extras) + end + request.path_parameters = params.with_indifferent_access + + request + end + + def fail_on(exception_class, message) + yield + rescue exception_class => e + raise Minitest::Assertion, message || e.message + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/testing/integration.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/testing/integration.rb new file mode 100644 index 00000000..7171b694 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/testing/integration.rb @@ -0,0 +1,652 @@ +# frozen_string_literal: true + +require "stringio" +require "uri" +require "active_support/core_ext/kernel/singleton_class" +require "active_support/core_ext/object/try" +require "rack/test" +require "minitest" + +require "action_dispatch/testing/request_encoder" + +module ActionDispatch + module Integration #:nodoc: + module RequestHelpers + # Performs a GET request with the given parameters. See ActionDispatch::Integration::Session#process + # for more details. + def get(path, **args) + process(:get, path, **args) + end + + # Performs a POST request with the given parameters. See ActionDispatch::Integration::Session#process + # for more details. + def post(path, **args) + process(:post, path, **args) + end + + # Performs a PATCH request with the given parameters. See ActionDispatch::Integration::Session#process + # for more details. + def patch(path, **args) + process(:patch, path, **args) + end + + # Performs a PUT request with the given parameters. See ActionDispatch::Integration::Session#process + # for more details. + def put(path, **args) + process(:put, path, **args) + end + + # Performs a DELETE request with the given parameters. See ActionDispatch::Integration::Session#process + # for more details. + def delete(path, **args) + process(:delete, path, **args) + end + + # Performs a HEAD request with the given parameters. See ActionDispatch::Integration::Session#process + # for more details. + def head(path, *args) + process(:head, path, *args) + end + + # Follow a single redirect response. If the last response was not a + # redirect, an exception will be raised. Otherwise, the redirect is + # performed on the location header. + def follow_redirect! + raise "not a redirect! #{status} #{status_message}" unless redirect? + get(response.location) + status + end + end + + # An instance of this class represents a set of requests and responses + # performed sequentially by a test process. Because you can instantiate + # multiple sessions and run them side-by-side, you can also mimic (to some + # limited extent) multiple simultaneous users interacting with your system. + # + # Typically, you will instantiate a new session using + # IntegrationTest#open_session, rather than instantiating + # Integration::Session directly. + class Session + DEFAULT_HOST = "www.example.com" + + include Minitest::Assertions + include TestProcess, RequestHelpers, Assertions + + %w( status status_message headers body redirect? ).each do |method| + delegate method, to: :response, allow_nil: true + end + + %w( path ).each do |method| + delegate method, to: :request, allow_nil: true + end + + # The hostname used in the last request. + def host + @host || DEFAULT_HOST + end + attr_writer :host + + # The remote_addr used in the last request. + attr_accessor :remote_addr + + # The Accept header to send. + attr_accessor :accept + + # A map of the cookies returned by the last response, and which will be + # sent with the next request. + def cookies + _mock_session.cookie_jar + end + + # A reference to the controller instance used by the last request. + attr_reader :controller + + # A reference to the request instance used by the last request. + attr_reader :request + + # A reference to the response instance used by the last request. + attr_reader :response + + # A running counter of the number of requests processed. + attr_accessor :request_count + + include ActionDispatch::Routing::UrlFor + + # Create and initialize a new Session instance. + def initialize(app) + super() + @app = app + + reset! + end + + def url_options + @url_options ||= default_url_options.dup.tap do |url_options| + url_options.reverse_merge!(controller.url_options) if controller + + if @app.respond_to?(:routes) + url_options.reverse_merge!(@app.routes.default_url_options) + end + + url_options.reverse_merge!(host: host, protocol: https? ? "https" : "http") + end + end + + # Resets the instance. This can be used to reset the state information + # in an existing session instance, so it can be used from a clean-slate + # condition. + # + # session.reset! + def reset! + @https = false + @controller = @request = @response = nil + @_mock_session = nil + @request_count = 0 + @url_options = nil + + self.host = DEFAULT_HOST + self.remote_addr = "127.0.0.1" + self.accept = "text/xml,application/xml,application/xhtml+xml," \ + "text/html;q=0.9,text/plain;q=0.8,image/png," \ + "*/*;q=0.5" + + unless defined? @named_routes_configured + # the helpers are made protected by default--we make them public for + # easier access during testing and troubleshooting. + @named_routes_configured = true + end + end + + # Specify whether or not the session should mimic a secure HTTPS request. + # + # session.https! + # session.https!(false) + def https!(flag = true) + @https = flag + end + + # Returns +true+ if the session is mimicking a secure HTTPS request. + # + # if session.https? + # ... + # end + def https? + @https + end + + # Performs the actual request. + # + # - +method+: The HTTP method (GET, POST, PATCH, PUT, DELETE, HEAD, OPTIONS) + # as a symbol. + # - +path+: The URI (as a String) on which you want to perform the + # request. + # - +params+: The HTTP parameters that you want to pass. This may + # be +nil+, + # a Hash, or a String that is appropriately encoded + # (application/x-www-form-urlencoded or + # multipart/form-data). + # - +headers+: Additional headers to pass, as a Hash. The headers will be + # merged into the Rack env hash. + # - +env+: Additional env to pass, as a Hash. The headers will be + # merged into the Rack env hash. + # + # This method is rarely used directly. Use +#get+, +#post+, or other standard + # HTTP methods in integration tests. +#process+ is only required when using a + # request method that doesn't have a method defined in the integration tests. + # + # This method returns the response status, after performing the request. + # Furthermore, if this method was called from an ActionDispatch::IntegrationTest object, + # then that object's @response instance variable will point to a Response object + # which one can use to inspect the details of the response. + # + # Example: + # process :get, '/author', params: { since: 201501011400 } + def process(method, path, params: nil, headers: nil, env: nil, xhr: false, as: nil) + request_encoder = RequestEncoder.encoder(as) + headers ||= {} + + if method == :get && as == :json && params + headers["X-Http-Method-Override"] = "GET" + method = :post + end + + if path =~ %r{://} + path = build_expanded_path(path) do |location| + https! URI::HTTPS === location if location.scheme + + if url_host = location.host + default = Rack::Request::DEFAULT_PORTS[location.scheme] + url_host += ":#{location.port}" if default != location.port + host! url_host + end + end + end + + hostname, port = host.split(":") + + request_env = { + :method => method, + :params => request_encoder.encode_params(params), + + "SERVER_NAME" => hostname, + "SERVER_PORT" => port || (https? ? "443" : "80"), + "HTTPS" => https? ? "on" : "off", + "rack.url_scheme" => https? ? "https" : "http", + + "REQUEST_URI" => path, + "HTTP_HOST" => host, + "REMOTE_ADDR" => remote_addr, + "CONTENT_TYPE" => request_encoder.content_type, + "HTTP_ACCEPT" => request_encoder.accept_header || accept + } + + wrapped_headers = Http::Headers.from_hash({}) + wrapped_headers.merge!(headers) if headers + + if xhr + wrapped_headers["HTTP_X_REQUESTED_WITH"] = "XMLHttpRequest" + wrapped_headers["HTTP_ACCEPT"] ||= [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ") + end + + # This modifies the passed request_env directly. + if wrapped_headers.present? + Http::Headers.from_hash(request_env).merge!(wrapped_headers) + end + if env.present? + Http::Headers.from_hash(request_env).merge!(env) + end + + session = Rack::Test::Session.new(_mock_session) + + # NOTE: rack-test v0.5 doesn't build a default uri correctly + # Make sure requested path is always a full URI. + session.request(build_full_uri(path, request_env), request_env) + + @request_count += 1 + @request = ActionDispatch::Request.new(session.last_request.env) + response = _mock_session.last_response + @response = ActionDispatch::TestResponse.from_response(response) + @response.request = @request + @html_document = nil + @url_options = nil + + @controller = @request.controller_instance + + response.status + end + + # Set the host name to use in the next request. + # + # session.host! "www.example.com" + alias :host! :host= + + private + def _mock_session + @_mock_session ||= Rack::MockSession.new(@app, host) + end + + def build_full_uri(path, env) + "#{env['rack.url_scheme']}://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{path}" + end + + def build_expanded_path(path) + location = URI.parse(path) + yield location if block_given? + path = location.path + location.query ? "#{path}?#{location.query}" : path + end + end + + module Runner + include ActionDispatch::Assertions + + APP_SESSIONS = {} + + attr_reader :app + + def initialize(*args, &blk) + super(*args, &blk) + @integration_session = nil + end + + def before_setup # :nodoc: + @app = nil + super + end + + def integration_session + @integration_session ||= create_session(app) + end + + # Reset the current session. This is useful for testing multiple sessions + # in a single test case. + def reset! + @integration_session = create_session(app) + end + + def create_session(app) + klass = APP_SESSIONS[app] ||= Class.new(Integration::Session) { + # If the app is a Rails app, make url_helpers available on the session. + # This makes app.url_for and app.foo_path available in the console. + if app.respond_to?(:routes) + include app.routes.url_helpers + include app.routes.mounted_helpers + end + } + klass.new(app) + end + + def remove! # :nodoc: + @integration_session = nil + end + + %w(get post patch put head delete cookies assigns follow_redirect!).each do |method| + define_method(method) do |*args| + # reset the html_document variable, except for cookies/assigns calls + unless method == "cookies" || method == "assigns" + @html_document = nil + end + + integration_session.__send__(method, *args).tap do + copy_session_variables! + end + end + end + + # Open a new session instance. If a block is given, the new session is + # yielded to the block before being returned. + # + # session = open_session do |sess| + # sess.extend(CustomAssertions) + # end + # + # By default, a single session is automatically created for you, but you + # can use this method to open multiple sessions that ought to be tested + # simultaneously. + def open_session + dup.tap do |session| + session.reset! + yield session if block_given? + end + end + + # Copy the instance variables from the current session instance into the + # test instance. + def copy_session_variables! #:nodoc: + @controller = @integration_session.controller + @response = @integration_session.response + @request = @integration_session.request + end + + def default_url_options + integration_session.default_url_options + end + + def default_url_options=(options) + integration_session.default_url_options = options + end + + private + def respond_to_missing?(method, _) + integration_session.respond_to?(method) || super + end + + # Delegate unhandled messages to the current session instance. + def method_missing(method, *args, &block) + if integration_session.respond_to?(method) + integration_session.public_send(method, *args, &block).tap do + copy_session_variables! + end + else + super + end + end + end + end + + # An integration test spans multiple controllers and actions, + # tying them all together to ensure they work together as expected. It tests + # more completely than either unit or functional tests do, exercising the + # entire stack, from the dispatcher to the database. + # + # At its simplest, you simply extend IntegrationTest and write your tests + # using the get/post methods: + # + # require "test_helper" + # + # class ExampleTest < ActionDispatch::IntegrationTest + # fixtures :people + # + # def test_login + # # get the login page + # get "/login" + # assert_equal 200, status + # + # # post the login and follow through to the home page + # post "/login", params: { username: people(:jamis).username, + # password: people(:jamis).password } + # follow_redirect! + # assert_equal 200, status + # assert_equal "/home", path + # end + # end + # + # However, you can also have multiple session instances open per test, and + # even extend those instances with assertions and methods to create a very + # powerful testing DSL that is specific for your application. You can even + # reference any named routes you happen to have defined. + # + # require "test_helper" + # + # class AdvancedTest < ActionDispatch::IntegrationTest + # fixtures :people, :rooms + # + # def test_login_and_speak + # jamis, david = login(:jamis), login(:david) + # room = rooms(:office) + # + # jamis.enter(room) + # jamis.speak(room, "anybody home?") + # + # david.enter(room) + # david.speak(room, "hello!") + # end + # + # private + # + # module CustomAssertions + # def enter(room) + # # reference a named route, for maximum internal consistency! + # get(room_url(id: room.id)) + # assert(...) + # ... + # end + # + # def speak(room, message) + # post "/say/#{room.id}", xhr: true, params: { message: message } + # assert(...) + # ... + # end + # end + # + # def login(who) + # open_session do |sess| + # sess.extend(CustomAssertions) + # who = people(who) + # sess.post "/login", params: { username: who.username, + # password: who.password } + # assert(...) + # end + # end + # end + # + # Another longer example would be: + # + # A simple integration test that exercises multiple controllers: + # + # require 'test_helper' + # + # class UserFlowsTest < ActionDispatch::IntegrationTest + # test "login and browse site" do + # # login via https + # https! + # get "/login" + # assert_response :success + # + # post "/login", params: { username: users(:david).username, password: users(:david).password } + # follow_redirect! + # assert_equal '/welcome', path + # assert_equal 'Welcome david!', flash[:notice] + # + # https!(false) + # get "/articles/all" + # assert_response :success + # assert_select 'h1', 'Articles' + # end + # end + # + # As you can see the integration test involves multiple controllers and + # exercises the entire stack from database to dispatcher. In addition you can + # have multiple session instances open simultaneously in a test and extend + # those instances with assertion methods to create a very powerful testing + # DSL (domain-specific language) just for your application. + # + # Here's an example of multiple sessions and custom DSL in an integration test + # + # require 'test_helper' + # + # class UserFlowsTest < ActionDispatch::IntegrationTest + # test "login and browse site" do + # # User david logs in + # david = login(:david) + # # User guest logs in + # guest = login(:guest) + # + # # Both are now available in different sessions + # assert_equal 'Welcome david!', david.flash[:notice] + # assert_equal 'Welcome guest!', guest.flash[:notice] + # + # # User david can browse site + # david.browses_site + # # User guest can browse site as well + # guest.browses_site + # + # # Continue with other assertions + # end + # + # private + # + # module CustomDsl + # def browses_site + # get "/products/all" + # assert_response :success + # assert_select 'h1', 'Products' + # end + # end + # + # def login(user) + # open_session do |sess| + # sess.extend(CustomDsl) + # u = users(user) + # sess.https! + # sess.post "/login", params: { username: u.username, password: u.password } + # assert_equal '/welcome', sess.path + # sess.https!(false) + # end + # end + # end + # + # See the {request helpers documentation}[rdoc-ref:ActionDispatch::Integration::RequestHelpers] for help on how to + # use +get+, etc. + # + # === Changing the request encoding + # + # You can also test your JSON API easily by setting what the request should + # be encoded as: + # + # require "test_helper" + # + # class ApiTest < ActionDispatch::IntegrationTest + # test "creates articles" do + # assert_difference -> { Article.count } do + # post articles_path, params: { article: { title: "Ahoy!" } }, as: :json + # end + # + # assert_response :success + # assert_equal({ id: Article.last.id, title: "Ahoy!" }, response.parsed_body) + # end + # end + # + # The +as+ option passes an "application/json" Accept header (thereby setting + # the request format to JSON unless overridden), sets the content type to + # "application/json" and encodes the parameters as JSON. + # + # Calling +parsed_body+ on the response parses the response body based on the + # last response MIME type. + # + # Out of the box, only :json is supported. But for any custom MIME + # types you've registered, you can add your own encoders with: + # + # ActionDispatch::IntegrationTest.register_encoder :wibble, + # param_encoder: -> params { params.to_wibble }, + # response_parser: -> body { body } + # + # Where +param_encoder+ defines how the params should be encoded and + # +response_parser+ defines how the response body should be parsed through + # +parsed_body+. + # + # Consult the Rails Testing Guide for more. + + class IntegrationTest < ActiveSupport::TestCase + include TestProcess::FixtureFile + + module UrlOptions + extend ActiveSupport::Concern + def url_options + integration_session.url_options + end + end + + module Behavior + extend ActiveSupport::Concern + + include Integration::Runner + include ActionController::TemplateAssertions + + included do + include ActionDispatch::Routing::UrlFor + include UrlOptions # don't let UrlFor override the url_options method + ActiveSupport.run_load_hooks(:action_dispatch_integration_test, self) + @@app = nil + end + + module ClassMethods + def app + if defined?(@@app) && @@app + @@app + else + ActionDispatch.test_app + end + end + + def app=(app) + @@app = app + end + + def register_encoder(*args) + RequestEncoder.register_encoder(*args) + end + end + + def app + super || self.class.app + end + + def document_root_element + html_document.root + end + end + + include Behavior + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/testing/request_encoder.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/testing/request_encoder.rb new file mode 100644 index 00000000..9889f619 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/testing/request_encoder.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module ActionDispatch + class RequestEncoder # :nodoc: + class IdentityEncoder + def content_type; end + def accept_header; end + def encode_params(params); params; end + def response_parser; -> body { body }; end + end + + @encoders = { identity: IdentityEncoder.new } + + attr_reader :response_parser + + def initialize(mime_name, param_encoder, response_parser) + @mime = Mime[mime_name] + + unless @mime + raise ArgumentError, "Can't register a request encoder for " \ + "unregistered MIME Type: #{mime_name}. See `Mime::Type.register`." + end + + @response_parser = response_parser || -> body { body } + @param_encoder = param_encoder || :"to_#{@mime.symbol}".to_proc + end + + def content_type + @mime.to_s + end + + def accept_header + @mime.to_s + end + + def encode_params(params) + @param_encoder.call(params) if params + end + + def self.parser(content_type) + mime = Mime::Type.lookup(content_type) + encoder(mime ? mime.ref : nil).response_parser + end + + def self.encoder(name) + @encoders[name] || @encoders[:identity] + end + + def self.register_encoder(mime_name, param_encoder: nil, response_parser: nil) + @encoders[mime_name] = new(mime_name, param_encoder, response_parser) + end + + register_encoder :json, response_parser: -> body { JSON.parse(body) } + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/testing/test_process.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/testing/test_process.rb new file mode 100644 index 00000000..8ac50c73 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/testing/test_process.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "action_dispatch/middleware/cookies" +require "action_dispatch/middleware/flash" + +module ActionDispatch + module TestProcess + module FixtureFile + # Shortcut for Rack::Test::UploadedFile.new(File.join(ActionDispatch::IntegrationTest.fixture_path, path), type): + # + # post :change_avatar, avatar: fixture_file_upload('files/spongebob.png', 'image/png') + # + # To upload binary files on Windows, pass :binary as the last parameter. + # This will not affect other platforms: + # + # post :change_avatar, avatar: fixture_file_upload('files/spongebob.png', 'image/png', :binary) + def fixture_file_upload(path, mime_type = nil, binary = false) + if self.class.respond_to?(:fixture_path) && self.class.fixture_path && + !File.exist?(path) + path = File.join(self.class.fixture_path, path) + end + Rack::Test::UploadedFile.new(path, mime_type, binary) + end + end + + include FixtureFile + + def assigns(key = nil) + raise NoMethodError, + "assigns has been extracted to a gem. To continue using it, + add `gem 'rails-controller-testing'` to your Gemfile." + end + + def session + @request.session + end + + def flash + @request.flash + end + + def cookies + @cookie_jar ||= Cookies::CookieJar.build(@request, @request.cookies) + end + + def redirect_to_url + @response.redirect_url + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/testing/test_request.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/testing/test_request.rb new file mode 100644 index 00000000..6c5b7af5 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/testing/test_request.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/indifferent_access" +require "rack/utils" + +module ActionDispatch + class TestRequest < Request + DEFAULT_ENV = Rack::MockRequest.env_for("/", + "HTTP_HOST" => "test.host", + "REMOTE_ADDR" => "0.0.0.0", + "HTTP_USER_AGENT" => "Rails Testing", + ) + + # Create a new test request with default +env+ values. + def self.create(env = {}) + env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application + env["rack.request.cookie_hash"] ||= {}.with_indifferent_access + new(default_env.merge(env)) + end + + def self.default_env + DEFAULT_ENV + end + private_class_method :default_env + + def request_method=(method) + super(method.to_s.upcase) + end + + def host=(host) + set_header("HTTP_HOST", host) + end + + def port=(number) + set_header("SERVER_PORT", number.to_i) + end + + def request_uri=(uri) + set_header("REQUEST_URI", uri) + end + + def path=(path) + set_header("PATH_INFO", path) + end + + def action=(action_name) + path_parameters[:action] = action_name.to_s + end + + def if_modified_since=(last_modified) + set_header("HTTP_IF_MODIFIED_SINCE", last_modified) + end + + def if_none_match=(etag) + set_header("HTTP_IF_NONE_MATCH", etag) + end + + def remote_addr=(addr) + set_header("REMOTE_ADDR", addr) + end + + def user_agent=(user_agent) + set_header("HTTP_USER_AGENT", user_agent) + end + + def accept=(mime_types) + delete_header("action_dispatch.request.accepts") + set_header("HTTP_ACCEPT", Array(mime_types).collect(&:to_s).join(",")) + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/testing/test_response.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/testing/test_response.rb new file mode 100644 index 00000000..1e6b21f2 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_dispatch/testing/test_response.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require "action_dispatch/testing/request_encoder" + +module ActionDispatch + # Integration test methods such as ActionDispatch::Integration::Session#get + # and ActionDispatch::Integration::Session#post return objects of class + # TestResponse, which represent the HTTP response results of the requested + # controller actions. + # + # See Response for more information on controller response objects. + class TestResponse < Response + def self.from_response(response) + new response.status, response.headers, response.body + end + + def initialize(*) # :nodoc: + super + @response_parser = RequestEncoder.parser(content_type) + end + + # Was the response successful? + def success? + ActiveSupport::Deprecation.warn(<<-MSG.squish) + The success? predicate is deprecated and will be removed in Rails 6.0. + Please use successful? as provided by Rack::Response::Helpers. + MSG + successful? + end + + # Was the URL not found? + def missing? + ActiveSupport::Deprecation.warn(<<-MSG.squish) + The missing? predicate is deprecated and will be removed in Rails 6.0. + Please use not_found? as provided by Rack::Response::Helpers. + MSG + not_found? + end + + # Was there a server-side error? + def error? + ActiveSupport::Deprecation.warn(<<-MSG.squish) + The error? predicate is deprecated and will be removed in Rails 6.0. + Please use server_error? as provided by Rack::Response::Helpers. + MSG + server_error? + end + + def parsed_body + @parsed_body ||= @response_parser.call(body) + end + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_pack.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_pack.rb new file mode 100644 index 00000000..3f691096 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_pack.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +#-- +# Copyright (c) 2004-2018 David Heinemeier Hansson +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ + +require "action_pack/version" diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_pack/gem_version.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_pack/gem_version.rb new file mode 100644 index 00000000..a032984a --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_pack/gem_version.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActionPack + # Returns the version of the currently loaded Action Pack as a Gem::Version + def self.gem_version + Gem::Version.new VERSION::STRING + end + + module VERSION + MAJOR = 5 + MINOR = 2 + TINY = 3 + PRE = nil + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") + end +end diff --git a/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_pack/version.rb b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_pack/version.rb new file mode 100644 index 00000000..fd039fe1 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionpack-5.2.3/lib/action_pack/version.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require_relative "gem_version" + +module ActionPack + # Returns the version of the currently loaded ActionPack as a Gem::Version + def self.version + gem_version + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/CHANGELOG.md b/path/ruby/2.6.0/gems/actionview-5.2.3/CHANGELOG.md new file mode 100644 index 00000000..775e2d8d --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/CHANGELOG.md @@ -0,0 +1,142 @@ +## Rails 5.2.3 (March 27, 2019) ## + +* Prevent non-primary mouse keys from triggering Rails UJS click handlers. + Firefox fires click events even if the click was triggered by non-primary mouse keys such as right- or scroll-wheel-clicks. + For example, right-clicking a link such as the one described below (with an underlying ajax request registered on click) should not cause that request to occur. + + ``` + <%= link_to 'Remote', remote_path, class: 'remote', remote: true, data: { type: :json } %> + ``` + + Fixes #34541 + + *Wolfgang Hobmaier* + + +## Rails 5.2.2.1 (March 11, 2019) ## + +* No changes. + + +## Rails 5.2.2 (December 04, 2018) ## + +* No changes. + + +## Rails 5.2.1.1 (November 27, 2018) ## + +* No changes. + + +## Rails 5.2.1 (August 07, 2018) ## + +* Fix leak of `skip_default_ids` and `allow_method_names_outside_object` options + to HTML attributes. + + *Yurii Cherniavskyi* + +* Fix issue with `button_to`'s `to_form_params` + + `button_to` was throwing exception when invoked with `params` hash that + contains symbol and string keys. The reason for the exception was that + `to_form_params` was comparing the given symbol and string keys. + + The issue is fixed by turning all keys to strings inside + `to_form_params` before comparing them. + + *Georgi Georgiev* + +* Fix JavaScript views rendering does not work with Firefox when using + Content Security Policy. + + Fixes #32577. + + *Yuji Yaginuma* + +* Add the `nonce: true` option for `javascript_include_tag` helper to + support automatic nonce generation for Content Security Policy. + Works the same way as `javascript_tag nonce: true` does. + + *Yaroslav Markin* + + +## Rails 5.2.0 (April 09, 2018) ## + +* Pass the `:skip_pipeline` option in `image_submit_tag` when calling `path_to_image`. + + Fixes #32248. + + *Andrew White* + +* Allow the use of callable objects as group methods for grouped selects. + + Until now, the `option_groups_from_collection_for_select` method was only able to + handle method names as `group_method` and `group_label_method` parameters, + it is now able to receive procs and other callable objects too. + + *Jérémie Bonal* + +* Add `preload_link_tag` helper. + + This helper that allows to the browser to initiate early fetch of resources + (different to the specified in `javascript_include_tag` and `stylesheet_link_tag`). + Additionally, this sends Early Hints if supported by browser. + + *Guillermo Iguaran* + +* Change `form_with` to generates ids by default. + + When `form_with` was introduced we disabled the automatic generation of ids + that was enabled in `form_for`. This usually is not an good idea since labels don't work + when the input doesn't have an id and it made harder to test with Capybara. + + You can still disable the automatic generation of ids setting `config.action_view.form_with_generates_ids` + to `false.` + + *Nick Pezza* + +* Fix issues with `field_error_proc` wrapping `optgroup` and select divider `option`. + + Fixes #31088 + + *Matthias Neumayr* + +* Remove deprecated Erubis ERB handler. + + *Rafael Mendonça França* + +* Remove default `alt` text generation. + + Fixes #30096 + + *Cameron Cundiff* + +* Add `srcset` option to `image_tag` helper. + + *Roberto Miranda* + +* Fix issues with scopes and engine on `current_page?` method. + + Fixes #29401. + + *Nikita Savrov* + +* Generate field ids in `collection_check_boxes` and `collection_radio_buttons`. + + This makes sure that the labels are linked up with the fields. + + Fixes #29014. + + *Yuji Yaginuma* + +* Add `:json` type to `auto_discovery_link_tag` to support [JSON Feeds](https://jsonfeed.org/version/1). + + *Mike Gunderloy* + +* Update `distance_of_time_in_words` helper to display better error messages + for bad input. + + *Jay Hayes* + + +Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/actionview/CHANGELOG.md) for previous changes. diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/MIT-LICENSE b/path/ruby/2.6.0/gems/actionview-5.2.3/MIT-LICENSE new file mode 100644 index 00000000..1cb3add0 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/MIT-LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2004-2018 David Heinemeier Hansson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/README.rdoc b/path/ruby/2.6.0/gems/actionview-5.2.3/README.rdoc new file mode 100644 index 00000000..42588feb --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/README.rdoc @@ -0,0 +1,38 @@ += Action View + +Action View is a framework for handling view template lookup and rendering, and provides +view helpers that assist when building HTML forms, Atom feeds and more. +Template formats that Action View handles are ERB (embedded Ruby, typically +used to inline short Ruby snippets inside HTML), and XML Builder. + +== Download and installation + +The latest version of Action View can be installed with RubyGems: + + $ gem install actionview + +Source code can be downloaded as part of the Rails project on GitHub: + +* https://github.com/rails/rails/tree/5-2-stable/actionview + + +== License + +Action View is released under the MIT license: + +* https://opensource.org/licenses/MIT + + +== Support + +API documentation is at + +* http://api.rubyonrails.org + +Bug reports for the Ruby on Rails project can be filed here: + +* https://github.com/rails/rails/issues + +Feature requests should be discussed on the rails-core mailing list here: + +* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view.rb new file mode 100644 index 00000000..c1eeda75 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +#-- +# Copyright (c) 2004-2018 David Heinemeier Hansson +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ + +require "active_support" +require "active_support/rails" +require "action_view/version" + +module ActionView + extend ActiveSupport::Autoload + + ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*' + + eager_autoload do + autoload :Base + autoload :Context + autoload :CompiledTemplates, "action_view/context" + autoload :Digestor + autoload :Helpers + autoload :LookupContext + autoload :Layouts + autoload :PathSet + autoload :RecordIdentifier + autoload :Rendering + autoload :RoutingUrlFor + autoload :Template + autoload :ViewPaths + + autoload_under "renderer" do + autoload :Renderer + autoload :AbstractRenderer + autoload :PartialRenderer + autoload :TemplateRenderer + autoload :StreamingTemplateRenderer + end + + autoload_at "action_view/template/resolver" do + autoload :Resolver + autoload :PathResolver + autoload :OptimizedFileSystemResolver + autoload :FallbackFileSystemResolver + end + + autoload_at "action_view/buffers" do + autoload :OutputBuffer + autoload :StreamingBuffer + end + + autoload_at "action_view/flows" do + autoload :OutputFlow + autoload :StreamingFlow + end + + autoload_at "action_view/template/error" do + autoload :MissingTemplate + autoload :ActionViewError + autoload :EncodingError + autoload :TemplateError + autoload :WrongEncodingError + end + end + + autoload :TestCase + + def self.eager_load! + super + ActionView::Helpers.eager_load! + ActionView::Template.eager_load! + end +end + +require "active_support/core_ext/string/output_safety" + +ActiveSupport.on_load(:i18n) do + I18n.load_path << File.expand_path("action_view/locale/en.yml", __dir__) +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/base.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/base.rb new file mode 100644 index 00000000..d41fe2a6 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/base.rb @@ -0,0 +1,215 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attr_internal" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/ordered_options" +require "action_view/log_subscriber" +require "action_view/helpers" +require "action_view/context" +require "action_view/template" +require "action_view/lookup_context" + +module ActionView #:nodoc: + # = Action View Base + # + # Action View templates can be written in several ways. + # If the template file has a .erb extension, then it uses the erubi[https://rubygems.org/gems/erubi] + # template system which can embed Ruby into an HTML document. + # If the template file has a .builder extension, then Jim Weirich's Builder::XmlMarkup library is used. + # + # == ERB + # + # You trigger ERB by using embeddings such as <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output. Consider the + # following loop for names: + # + # Names of all the people + # <% @people.each do |person| %> + # Name: <%= person.name %>
+ # <% end %> + # + # The loop is setup in regular embedding tags <% %>, and the name is written using the output embedding tag <%= %>. Note that this + # is not just a usage suggestion. Regular output functions like print or puts won't work with ERB templates. So this would be wrong: + # + # <%# WRONG %> + # Hi, Mr. <% puts "Frodo" %> + # + # If you absolutely must write from within a function use +concat+. + # + # When on a line that only contains whitespaces except for the tag, <% %> suppresses leading and trailing whitespace, + # including the trailing newline. <% %> and <%- -%> are the same. + # Note however that <%= %> and <%= -%> are different: only the latter removes trailing whitespaces. + # + # === Using sub templates + # + # Using sub templates allows you to sidestep tedious replication and extract common display structures in shared templates. The + # classic example is the use of a header and footer (even though the Action Pack-way would be to use Layouts): + # + # <%= render "shared/header" %> + # Something really specific and terrific + # <%= render "shared/footer" %> + # + # As you see, we use the output embeddings for the render methods. The render call itself will just return a string holding the + # result of the rendering. The output embedding writes it to the current template. + # + # But you don't have to restrict yourself to static includes. Templates can share variables amongst themselves by using instance + # variables defined using the regular embedding tags. Like this: + # + # <% @page_title = "A Wonderful Hello" %> + # <%= render "shared/header" %> + # + # Now the header can pick up on the @page_title variable and use it for outputting a title tag: + # + # <%= @page_title %> + # + # === Passing local variables to sub templates + # + # You can pass local variables to sub templates by using a hash with the variable names as keys and the objects as values: + # + # <%= render "shared/header", { headline: "Welcome", person: person } %> + # + # These can now be accessed in shared/header with: + # + # Headline: <%= headline %> + # First name: <%= person.first_name %> + # + # The local variables passed to sub templates can be accessed as a hash using the local_assigns hash. This lets you access the + # variables as: + # + # Headline: <%= local_assigns[:headline] %> + # + # This is useful in cases where you aren't sure if the local variable has been assigned. Alternatively, you could also use + # defined? headline to first check if the variable has been assigned before using it. + # + # === Template caching + # + # By default, Rails will compile each template to a method in order to render it. When you alter a template, + # Rails will check the file's modification time and recompile it in development mode. + # + # == Builder + # + # Builder templates are a more programmatic alternative to ERB. They are especially useful for generating XML content. An XmlMarkup object + # named +xml+ is automatically made available to templates with a .builder extension. + # + # Here are some basic examples: + # + # xml.em("emphasized") # => emphasized + # xml.em { xml.b("emph & bold") } # => emph & bold + # xml.a("A Link", "href" => "http://onestepback.org") # => A Link + # xml.target("name" => "compile", "option" => "fast") # => + # # NOTE: order of attributes is not specified. + # + # Any method with a block will be treated as an XML markup tag with nested markup in the block. For example, the following: + # + # xml.div do + # xml.h1(@person.name) + # xml.p(@person.bio) + # end + # + # would produce something like: + # + #
+ #

David Heinemeier Hansson

+ #

A product of Danish Design during the Winter of '79...

+ #
+ # + # Here is a full-length RSS example actually used on Basecamp: + # + # xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do + # xml.channel do + # xml.title(@feed_title) + # xml.link(@url) + # xml.description "Basecamp: Recent items" + # xml.language "en-us" + # xml.ttl "40" + # + # @recent_items.each do |item| + # xml.item do + # xml.title(item_title(item)) + # xml.description(item_description(item)) if item_description(item) + # xml.pubDate(item_pubDate(item)) + # xml.guid(@person.firm.account.url + @recent_items.url(item)) + # xml.link(@person.firm.account.url + @recent_items.url(item)) + # + # xml.tag!("dc:creator", item.author_name) if item_has_creator?(item) + # end + # end + # end + # end + # + # For more information on Builder please consult the {source + # code}[https://github.com/jimweirich/builder]. + class Base + include Helpers, ::ERB::Util, Context + + # Specify the proc used to decorate input tags that refer to attributes with errors. + cattr_accessor :field_error_proc, default: Proc.new { |html_tag, instance| "
#{html_tag}
".html_safe } + + # How to complete the streaming when an exception occurs. + # This is our best guess: first try to close the attribute, then the tag. + cattr_accessor :streaming_completion_on_exception, default: %(">) + + # Specify whether rendering within namespaced controllers should prefix + # the partial paths for ActiveModel objects with the namespace. + # (e.g., an Admin::PostsController would render @post using /admin/posts/_post.erb) + cattr_accessor :prefix_partial_path_with_controller_namespace, default: true + + # Specify default_formats that can be rendered. + cattr_accessor :default_formats + + # Specify whether an error should be raised for missing translations + cattr_accessor :raise_on_missing_translations, default: false + + # Specify whether submit_tag should automatically disable on click + cattr_accessor :automatically_disable_submit_tag, default: true + + class_attribute :_routes + class_attribute :logger + + class << self + delegate :erb_trim_mode=, to: "ActionView::Template::Handlers::ERB" + + def cache_template_loading + ActionView::Resolver.caching? + end + + def cache_template_loading=(value) + ActionView::Resolver.caching = value + end + + def xss_safe? #:nodoc: + true + end + end + + attr_accessor :view_renderer + attr_internal :config, :assigns + + delegate :lookup_context, to: :view_renderer + delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, to: :lookup_context + + def assign(new_assigns) # :nodoc: + @_assigns = new_assigns.each { |key, value| instance_variable_set("@#{key}", value) } + end + + def initialize(context = nil, assigns = {}, controller = nil, formats = nil) #:nodoc: + @_config = ActiveSupport::InheritableOptions.new + + if context.is_a?(ActionView::Renderer) + @view_renderer = context + else + lookup_context = context.is_a?(ActionView::LookupContext) ? + context : ActionView::LookupContext.new(context) + lookup_context.formats = formats if formats + lookup_context.prefixes = controller._prefixes if controller + @view_renderer = ActionView::Renderer.new(lookup_context) + end + + @cache_hit = {} + assign(assigns) + assign_controller(controller) + _prepare_context + end + + ActiveSupport.run_load_hooks(:action_view, self) + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/buffers.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/buffers.rb new file mode 100644 index 00000000..2a378fdc --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/buffers.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/output_safety" + +module ActionView + class OutputBuffer < ActiveSupport::SafeBuffer #:nodoc: + def initialize(*) + super + encode! + end + + def <<(value) + return self if value.nil? + super(value.to_s) + end + alias :append= :<< + + def safe_expr_append=(val) + return self if val.nil? + safe_concat val.to_s + end + + alias :safe_append= :safe_concat + end + + class StreamingBuffer #:nodoc: + def initialize(block) + @block = block + end + + def <<(value) + value = value.to_s + value = ERB::Util.h(value) unless value.html_safe? + @block.call(value) + end + alias :concat :<< + alias :append= :<< + + def safe_concat(value) + @block.call(value.to_s) + end + alias :safe_append= :safe_concat + + def html_safe? + true + end + + def html_safe + self + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/context.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/context.rb new file mode 100644 index 00000000..665a9e31 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/context.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module ActionView + module CompiledTemplates #:nodoc: + # holds compiled template code + end + + # = Action View Context + # + # Action View contexts are supplied to Action Controller to render a template. + # The default Action View context is ActionView::Base. + # + # In order to work with ActionController, a Context must just include this module. + # The initialization of the variables used by the context (@output_buffer, @view_flow, + # and @virtual_path) is responsibility of the object that includes this module + # (although you can call _prepare_context defined below). + module Context + include CompiledTemplates + attr_accessor :output_buffer, :view_flow + + # Prepares the context by setting the appropriate instance variables. + def _prepare_context + @view_flow = OutputFlow.new + @output_buffer = nil + @virtual_path = nil + end + + # Encapsulates the interaction with the view flow so it + # returns the correct buffer on +yield+. This is usually + # overwritten by helpers to add more behavior. + def _layout_for(name = nil) + name ||= :layout + view_flow.get(name).html_safe + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/dependency_tracker.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/dependency_tracker.rb new file mode 100644 index 00000000..182f6e2e --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/dependency_tracker.rb @@ -0,0 +1,175 @@ +# frozen_string_literal: true + +require "concurrent/map" +require "action_view/path_set" + +module ActionView + class DependencyTracker # :nodoc: + @trackers = Concurrent::Map.new + + def self.find_dependencies(name, template, view_paths = nil) + tracker = @trackers[template.handler] + return [] unless tracker + + tracker.call(name, template, view_paths) + end + + def self.register_tracker(extension, tracker) + handler = Template.handler_for_extension(extension) + if tracker.respond_to?(:supports_view_paths?) + @trackers[handler] = tracker + else + @trackers[handler] = lambda { |name, template, _| + tracker.call(name, template) + } + end + end + + def self.remove_tracker(handler) + @trackers.delete(handler) + end + + class ERBTracker # :nodoc: + EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/ + + # A valid ruby identifier - suitable for class, method and specially variable names + IDENTIFIER = / + [[:alpha:]_] # at least one uppercase letter, lowercase letter or underscore + [[:word:]]* # followed by optional letters, numbers or underscores + /x + + # Any kind of variable name. e.g. @instance, @@class, $global or local. + # Possibly following a method call chain + VARIABLE_OR_METHOD_CHAIN = / + (?:\$|@{1,2})? # optional global, instance or class variable indicator + (?:#{IDENTIFIER}\.)* # followed by an optional chain of zero-argument method calls + (?#{IDENTIFIER}) # and a final valid identifier, captured as DYNAMIC + /x + + # A simple string literal. e.g. "School's out!" + STRING = / + (?['"]) # an opening quote + (?.*?) # with anything inside, captured as STATIC + \k # and a matching closing quote + /x + + # Part of any hash containing the :partial key + PARTIAL_HASH_KEY = / + (?:\bpartial:|:partial\s*=>) # partial key in either old or new style hash syntax + \s* # followed by optional spaces + /x + + # Part of any hash containing the :layout key + LAYOUT_HASH_KEY = / + (?:\blayout:|:layout\s*=>) # layout key in either old or new style hash syntax + \s* # followed by optional spaces + /x + + # Matches: + # partial: "comments/comment", collection: @all_comments => "comments/comment" + # (object: @single_comment, partial: "comments/comment") => "comments/comment" + # + # "comments/comments" + # 'comments/comments' + # ('comments/comments') + # + # (@topic) => "topics/topic" + # topics => "topics/topic" + # (message.topics) => "topics/topic" + RENDER_ARGUMENTS = /\A + (?:\s*\(?\s*) # optional opening paren surrounded by spaces + (?:.*?#{PARTIAL_HASH_KEY}|#{LAYOUT_HASH_KEY})? # optional hash, up to the partial or layout key declaration + (?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest + /xm + + LAYOUT_DEPENDENCY = /\A + (?:\s*\(?\s*) # optional opening paren surrounded by spaces + (?:.*?#{LAYOUT_HASH_KEY}) # check if the line has layout key declaration + (?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest + /xm + + def self.supports_view_paths? # :nodoc: + true + end + + def self.call(name, template, view_paths = nil) + new(name, template, view_paths).dependencies + end + + def initialize(name, template, view_paths = nil) + @name, @template, @view_paths = name, template, view_paths + end + + def dependencies + render_dependencies + explicit_dependencies + end + + attr_reader :name, :template + private :name, :template + + private + def source + template.source + end + + def directory + name.split("/")[0..-2].join("/") + end + + def render_dependencies + render_dependencies = [] + render_calls = source.split(/\brender\b/).drop(1) + + render_calls.each do |arguments| + add_dependencies(render_dependencies, arguments, LAYOUT_DEPENDENCY) + add_dependencies(render_dependencies, arguments, RENDER_ARGUMENTS) + end + + render_dependencies.uniq + end + + def add_dependencies(render_dependencies, arguments, pattern) + arguments.scan(pattern) do + add_dynamic_dependency(render_dependencies, Regexp.last_match[:dynamic]) + add_static_dependency(render_dependencies, Regexp.last_match[:static]) + end + end + + def add_dynamic_dependency(dependencies, dependency) + if dependency + dependencies << "#{dependency.pluralize}/#{dependency.singularize}" + end + end + + def add_static_dependency(dependencies, dependency) + if dependency + if dependency.include?("/") + dependencies << dependency + else + dependencies << "#{directory}/#{dependency}" + end + end + end + + def resolve_directories(wildcard_dependencies) + return [] unless @view_paths + + wildcard_dependencies.flat_map { |query, templates| + @view_paths.find_all_with_query(query).map do |template| + "#{File.dirname(query)}/#{File.basename(template).split('.').first}" + end + }.sort + end + + def explicit_dependencies + dependencies = source.scan(EXPLICIT_DEPENDENCY).flatten.uniq + + wildcards, explicits = dependencies.partition { |dependency| dependency[-1] == "*" } + + (explicits + resolve_directories(wildcards)).uniq + end + end + + register_tracker :erb, ERBTracker + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/digestor.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/digestor.rb new file mode 100644 index 00000000..ffc3d425 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/digestor.rb @@ -0,0 +1,134 @@ +# frozen_string_literal: true + +require "concurrent/map" +require "action_view/dependency_tracker" +require "monitor" + +module ActionView + class Digestor + @@digest_mutex = Mutex.new + + module PerExecutionDigestCacheExpiry + def self.before(target) + ActionView::LookupContext::DetailsKey.clear + end + end + + class << self + # Supported options: + # + # * name - Template name + # * finder - An instance of ActionView::LookupContext + # * dependencies - An array of dependent views + def digest(name:, finder:, dependencies: []) + dependencies ||= [] + cache_key = [ name, finder.rendered_format, dependencies ].flatten.compact.join(".") + + # this is a correctly done double-checked locking idiom + # (Concurrent::Map's lookups have volatile semantics) + finder.digest_cache[cache_key] || @@digest_mutex.synchronize do + finder.digest_cache.fetch(cache_key) do # re-check under lock + partial = name.include?("/_") + root = tree(name, finder, partial) + dependencies.each do |injected_dep| + root.children << Injected.new(injected_dep, nil, nil) + end + finder.digest_cache[cache_key] = root.digest(finder) + end + end + end + + def logger + ActionView::Base.logger || NullLogger + end + + # Create a dependency tree for template named +name+. + def tree(name, finder, partial = false, seen = {}) + logical_name = name.gsub(%r|/_|, "/") + + if template = find_template(finder, logical_name, [], partial, []) + finder.rendered_format ||= template.formats.first + + if node = seen[template.identifier] # handle cycles in the tree + node + else + node = seen[template.identifier] = Node.create(name, logical_name, template, partial) + + deps = DependencyTracker.find_dependencies(name, template, finder.view_paths) + deps.uniq { |n| n.gsub(%r|/_|, "/") }.each do |dep_file| + node.children << tree(dep_file, finder, true, seen) + end + node + end + else + unless name.include?("#") # Dynamic template partial names can never be tracked + logger.error " Couldn't find template for digesting: #{name}" + end + + seen[name] ||= Missing.new(name, logical_name, nil) + end + end + + private + def find_template(finder, name, prefixes, partial, keys) + finder.disable_cache do + format = finder.rendered_format + result = finder.find_all(name, prefixes, partial, keys, formats: [format]).first if format + result || finder.find_all(name, prefixes, partial, keys).first + end + end + end + + class Node + attr_reader :name, :logical_name, :template, :children + + def self.create(name, logical_name, template, partial) + klass = partial ? Partial : Node + klass.new(name, logical_name, template, []) + end + + def initialize(name, logical_name, template, children = []) + @name = name + @logical_name = logical_name + @template = template + @children = children + end + + def digest(finder, stack = []) + ActiveSupport::Digest.hexdigest("#{template.source}-#{dependency_digest(finder, stack)}") + end + + def dependency_digest(finder, stack) + children.map do |node| + if stack.include?(node) + false + else + finder.digest_cache[node.name] ||= begin + stack.push node + node.digest(finder, stack).tap { stack.pop } + end + end + end.join("-") + end + + def to_dep_map + children.any? ? { name => children.map(&:to_dep_map) } : name + end + end + + class Partial < Node; end + + class Missing < Node + def digest(finder, _ = []) "" end + end + + class Injected < Node + def digest(finder, _ = []) name end + end + + class NullLogger + def self.debug(_); end + def self.error(_); end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/flows.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/flows.rb new file mode 100644 index 00000000..ff44fa66 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/flows.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/output_safety" + +module ActionView + class OutputFlow #:nodoc: + attr_reader :content + + def initialize + @content = Hash.new { |h, k| h[k] = ActiveSupport::SafeBuffer.new } + end + + # Called by _layout_for to read stored values. + def get(key) + @content[key] + end + + # Called by each renderer object to set the layout contents. + def set(key, value) + @content[key] = ActiveSupport::SafeBuffer.new(value) + end + + # Called by content_for + def append(key, value) + @content[key] << value + end + alias_method :append!, :append + end + + class StreamingFlow < OutputFlow #:nodoc: + def initialize(view, fiber) + @view = view + @parent = nil + @child = view.output_buffer + @content = view.view_flow.content + @fiber = fiber + @root = Fiber.current.object_id + end + + # Try to get stored content. If the content + # is not available and we're inside the layout fiber, + # then it will begin waiting for the given key and yield. + def get(key) + return super if @content.key?(key) + + if inside_fiber? + view = @view + + begin + @waiting_for = key + view.output_buffer, @parent = @child, view.output_buffer + Fiber.yield + ensure + @waiting_for = nil + view.output_buffer, @child = @parent, view.output_buffer + end + end + + super + end + + # Appends the contents for the given key. This is called + # by providing and resuming back to the fiber, + # if that's the key it's waiting for. + def append!(key, value) + super + @fiber.resume if @waiting_for == key + end + + private + + def inside_fiber? + Fiber.current.object_id != @root + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/gem_version.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/gem_version.rb new file mode 100644 index 00000000..d2119c95 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/gem_version.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActionView + # Returns the version of the currently loaded Action View as a Gem::Version + def self.gem_version + Gem::Version.new VERSION::STRING + end + + module VERSION + MAJOR = 5 + MINOR = 2 + TINY = 3 + PRE = nil + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers.rb new file mode 100644 index 00000000..8cc80137 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require "active_support/benchmarkable" + +module ActionView #:nodoc: + module Helpers #:nodoc: + extend ActiveSupport::Autoload + + autoload :ActiveModelHelper + autoload :AssetTagHelper + autoload :AssetUrlHelper + autoload :AtomFeedHelper + autoload :CacheHelper + autoload :CaptureHelper + autoload :ControllerHelper + autoload :CspHelper + autoload :CsrfHelper + autoload :DateHelper + autoload :DebugHelper + autoload :FormHelper + autoload :FormOptionsHelper + autoload :FormTagHelper + autoload :JavaScriptHelper, "action_view/helpers/javascript_helper" + autoload :NumberHelper + autoload :OutputSafetyHelper + autoload :RecordTagHelper + autoload :RenderingHelper + autoload :SanitizeHelper + autoload :TagHelper + autoload :TextHelper + autoload :TranslationHelper + autoload :UrlHelper + autoload :Tags + + def self.eager_load! + super + Tags.eager_load! + end + + extend ActiveSupport::Concern + + include ActiveSupport::Benchmarkable + include ActiveModelHelper + include AssetTagHelper + include AssetUrlHelper + include AtomFeedHelper + include CacheHelper + include CaptureHelper + include ControllerHelper + include CspHelper + include CsrfHelper + include DateHelper + include DebugHelper + include FormHelper + include FormOptionsHelper + include FormTagHelper + include JavaScriptHelper + include NumberHelper + include OutputSafetyHelper + include RecordTagHelper + include RenderingHelper + include SanitizeHelper + include TagHelper + include TextHelper + include TranslationHelper + include UrlHelper + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/active_model_helper.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/active_model_helper.rb new file mode 100644 index 00000000..e41a95d2 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/active_model_helper.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/enumerable" + +module ActionView + # = Active Model Helpers + module Helpers #:nodoc: + module ActiveModelHelper + end + + module ActiveModelInstanceTag + def object + @active_model_object ||= begin + object = super + object.respond_to?(:to_model) ? object.to_model : object + end + end + + def content_tag(type, options, *) + select_markup_helper?(type) ? super : error_wrapping(super) + end + + def tag(type, options, *) + tag_generate_errors?(options) ? error_wrapping(super) : super + end + + def error_wrapping(html_tag) + if object_has_errors? + Base.field_error_proc.call(html_tag, self) + else + html_tag + end + end + + def error_message + object.errors[@method_name] + end + + private + + def object_has_errors? + object.respond_to?(:errors) && object.errors.respond_to?(:[]) && error_message.present? + end + + def select_markup_helper?(type) + ["optgroup", "option"].include?(type) + end + + def tag_generate_errors?(options) + options["type"] != "hidden" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/asset_tag_helper.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/asset_tag_helper.rb new file mode 100644 index 00000000..257080d9 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/asset_tag_helper.rb @@ -0,0 +1,511 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/object/inclusion" +require "active_support/core_ext/object/try" +require "action_view/helpers/asset_url_helper" +require "action_view/helpers/tag_helper" + +module ActionView + # = Action View Asset Tag Helpers + module Helpers #:nodoc: + # This module provides methods for generating HTML that links views to assets such + # as images, JavaScripts, stylesheets, and feeds. These methods do not verify + # the assets exist before linking to them: + # + # image_tag("rails.png") + # # => + # stylesheet_link_tag("application") + # # => + module AssetTagHelper + extend ActiveSupport::Concern + + include AssetUrlHelper + include TagHelper + + # Returns an HTML script tag for each of the +sources+ provided. + # + # Sources may be paths to JavaScript files. Relative paths are assumed to be relative + # to assets/javascripts, full paths are assumed to be relative to the document + # root. Relative paths are idiomatic, use absolute paths only when needed. + # + # When passing paths, the ".js" extension is optional. If you do not want ".js" + # appended to the path extname: false can be set on the options. + # + # You can modify the HTML attributes of the script tag by passing a hash as the + # last argument. + # + # When the Asset Pipeline is enabled, you can pass the name of your manifest as + # source, and include other JavaScript or CoffeeScript files inside the manifest. + # + # If the server supports Early Hints header links for these assets will be + # automatically pushed. + # + # ==== Options + # + # When the last parameter is a hash you can add HTML attributes using that + # parameter. The following options are supported: + # + # * :extname - Append an extension to the generated URL unless the extension + # already exists. This only applies for relative URLs. + # * :protocol - Sets the protocol of the generated URL. This option only + # applies when a relative URL and +host+ options are provided. + # * :host - When a relative URL is provided the host is added to the + # that path. + # * :skip_pipeline - This option is used to bypass the asset pipeline + # when it is set to true. + # * :nonce - When set to true, adds an automatic nonce value if + # you have Content Security Policy enabled. + # + # ==== Examples + # + # javascript_include_tag "xmlhr" + # # => + # + # javascript_include_tag "xmlhr", host: "localhost", protocol: "https" + # # => + # + # javascript_include_tag "template.jst", extname: false + # # => + # + # javascript_include_tag "xmlhr.js" + # # => + # + # javascript_include_tag "common.javascript", "/elsewhere/cools" + # # => + # # + # + # javascript_include_tag "http://www.example.com/xmlhr" + # # => + # + # javascript_include_tag "http://www.example.com/xmlhr.js" + # # => + # + # javascript_include_tag "http://www.example.com/xmlhr.js", nonce: true + # # => + def javascript_include_tag(*sources) + options = sources.extract_options!.stringify_keys + path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys + early_hints_links = [] + + sources_tags = sources.uniq.map { |source| + href = path_to_javascript(source, path_options) + early_hints_links << "<#{href}>; rel=preload; as=script" + tag_options = { + "src" => href + }.merge!(options) + if tag_options["nonce"] == true + tag_options["nonce"] = content_security_policy_nonce + end + content_tag("script".freeze, "", tag_options) + }.join("\n").html_safe + + request.send_early_hints("Link" => early_hints_links.join("\n")) if respond_to?(:request) && request + + sources_tags + end + + # Returns a stylesheet link tag for the sources specified as arguments. If + # you don't specify an extension, .css will be appended automatically. + # You can modify the link attributes by passing a hash as the last argument. + # For historical reasons, the 'media' attribute will always be present and defaults + # to "screen", so you must explicitly set it to "all" for the stylesheet(s) to + # apply to all media types. + # + # If the server supports Early Hints header links for these assets will be + # automatically pushed. + # + # stylesheet_link_tag "style" + # # => + # + # stylesheet_link_tag "style.css" + # # => + # + # stylesheet_link_tag "http://www.example.com/style.css" + # # => + # + # stylesheet_link_tag "style", media: "all" + # # => + # + # stylesheet_link_tag "style", media: "print" + # # => + # + # stylesheet_link_tag "random.styles", "/css/stylish" + # # => + # # + def stylesheet_link_tag(*sources) + options = sources.extract_options!.stringify_keys + path_options = options.extract!("protocol", "host", "skip_pipeline").symbolize_keys + early_hints_links = [] + + sources_tags = sources.uniq.map { |source| + href = path_to_stylesheet(source, path_options) + early_hints_links << "<#{href}>; rel=preload; as=style" + tag_options = { + "rel" => "stylesheet", + "media" => "screen", + "href" => href + }.merge!(options) + tag(:link, tag_options) + }.join("\n").html_safe + + request.send_early_hints("Link" => early_hints_links.join("\n")) if respond_to?(:request) && request + + sources_tags + end + + # Returns a link tag that browsers and feed readers can use to auto-detect + # an RSS, Atom, or JSON feed. The +type+ can be :rss (default), + # :atom, or :json. Control the link options in url_for format + # using the +url_options+. You can modify the LINK tag itself in +tag_options+. + # + # ==== Options + # + # * :rel - Specify the relation of this link, defaults to "alternate" + # * :type - Override the auto-generated mime type + # * :title - Specify the title of the link, defaults to the +type+ + # + # ==== Examples + # + # auto_discovery_link_tag + # # => + # auto_discovery_link_tag(:atom) + # # => + # auto_discovery_link_tag(:json) + # # => + # auto_discovery_link_tag(:rss, {action: "feed"}) + # # => + # auto_discovery_link_tag(:rss, {action: "feed"}, {title: "My RSS"}) + # # => + # auto_discovery_link_tag(:rss, {controller: "news", action: "feed"}) + # # => + # auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {title: "Example RSS"}) + # # => + def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {}) + if !(type == :rss || type == :atom || type == :json) && tag_options[:type].blank? + raise ArgumentError.new("You should pass :type tag_option key explicitly, because you have passed #{type} type other than :rss, :atom, or :json.") + end + + tag( + "link", + "rel" => tag_options[:rel] || "alternate", + "type" => tag_options[:type] || Template::Types[type].to_s, + "title" => tag_options[:title] || type.to_s.upcase, + "href" => url_options.is_a?(Hash) ? url_for(url_options.merge(only_path: false)) : url_options + ) + end + + # Returns a link tag for a favicon managed by the asset pipeline. + # + # If a page has no link like the one generated by this helper, browsers + # ask for /favicon.ico automatically, and cache the file if the + # request succeeds. If the favicon changes it is hard to get it updated. + # + # To have better control applications may let the asset pipeline manage + # their favicon storing the file under app/assets/images, and + # using this helper to generate its corresponding link tag. + # + # The helper gets the name of the favicon file as first argument, which + # defaults to "favicon.ico", and also supports +:rel+ and +:type+ options + # to override their defaults, "shortcut icon" and "image/x-icon" + # respectively: + # + # favicon_link_tag + # # => + # + # favicon_link_tag 'myicon.ico' + # # => + # + # Mobile Safari looks for a different link tag, pointing to an image that + # will be used if you add the page to the home screen of an iOS device. + # The following call would generate such a tag: + # + # favicon_link_tag 'mb-icon.png', rel: 'apple-touch-icon', type: 'image/png' + # # => + def favicon_link_tag(source = "favicon.ico", options = {}) + tag("link", { + rel: "shortcut icon", + type: "image/x-icon", + href: path_to_image(source, skip_pipeline: options.delete(:skip_pipeline)) + }.merge!(options.symbolize_keys)) + end + + # Returns a link tag that browsers can use to preload the +source+. + # The +source+ can be the path of a resource managed by asset pipeline, + # a full path, or an URI. + # + # ==== Options + # + # * :type - Override the auto-generated mime type, defaults to the mime type for +source+ extension. + # * :as - Override the auto-generated value for as attribute, calculated using +source+ extension and mime type. + # * :crossorigin - Specify the crossorigin attribute, required to load cross-origin resources. + # * :nopush - Specify if the use of server push is not desired for the resource. Defaults to +false+. + # + # ==== Examples + # + # preload_link_tag("custom_theme.css") + # # => + # + # preload_link_tag("/videos/video.webm") + # # => + # + # preload_link_tag(post_path(format: :json), as: "fetch") + # # => + # + # preload_link_tag("worker.js", as: "worker") + # # => + # + # preload_link_tag("//example.com/font.woff2") + # # => + # + # preload_link_tag("//example.com/font.woff2", crossorigin: "use-credentials") + # # => + # + # preload_link_tag("/media/audio.ogg", nopush: true) + # # => + # + def preload_link_tag(source, options = {}) + href = asset_path(source, skip_pipeline: options.delete(:skip_pipeline)) + extname = File.extname(source).downcase.delete(".") + mime_type = options.delete(:type) || Template::Types[extname].try(:to_s) + as_type = options.delete(:as) || resolve_link_as(extname, mime_type) + crossorigin = options.delete(:crossorigin) + crossorigin = "anonymous" if crossorigin == true || (crossorigin.blank? && as_type == "font") + nopush = options.delete(:nopush) || false + + link_tag = tag.link({ + rel: "preload", + href: href, + as: as_type, + type: mime_type, + crossorigin: crossorigin + }.merge!(options.symbolize_keys)) + + early_hints_link = "<#{href}>; rel=preload; as=#{as_type}" + early_hints_link += "; type=#{mime_type}" if mime_type + early_hints_link += "; crossorigin=#{crossorigin}" if crossorigin + early_hints_link += "; nopush" if nopush + + request.send_early_hints("Link" => early_hints_link) if respond_to?(:request) && request + + link_tag + end + + # Returns an HTML image tag for the +source+. The +source+ can be a full + # path, a file, or an Active Storage attachment. + # + # ==== Options + # + # You can add HTML attributes using the +options+. The +options+ supports + # additional keys for convenience and conformance: + # + # * :size - Supplied as "{Width}x{Height}" or "{Number}", so "30x45" becomes + # width="30" and height="45", and "50" becomes width="50" and height="50". + # :size will be ignored if the value is not in the correct format. + # * :srcset - If supplied as a hash or array of [source, descriptor] + # pairs, each image path will be expanded before the list is formatted as a string. + # + # ==== Examples + # + # Assets (images that are part of your app): + # + # image_tag("icon") + # # => + # image_tag("icon.png") + # # => + # image_tag("icon.png", size: "16x10", alt: "Edit Entry") + # # => Edit Entry + # image_tag("/icons/icon.gif", size: "16") + # # => + # image_tag("/icons/icon.gif", height: '32', width: '32') + # # => + # image_tag("/icons/icon.gif", class: "menu_icon") + # # => + # image_tag("/icons/icon.gif", data: { title: 'Rails Application' }) + # # => + # image_tag("icon.png", srcset: { "icon_2x.png" => "2x", "icon_4x.png" => "4x" }) + # # => + # image_tag("pic.jpg", srcset: [["pic_1024.jpg", "1024w"], ["pic_1980.jpg", "1980w"]], sizes: "100vw") + # # => + # + # Active Storage (images that are uploaded by the users of your app): + # + # image_tag(user.avatar) + # # => + # image_tag(user.avatar.variant(resize: "100x100")) + # # => + # image_tag(user.avatar.variant(resize: "100x100"), size: '100') + # # => + def image_tag(source, options = {}) + options = options.symbolize_keys + check_for_image_tag_errors(options) + skip_pipeline = options.delete(:skip_pipeline) + + options[:src] = resolve_image_source(source, skip_pipeline) + + if options[:srcset] && !options[:srcset].is_a?(String) + options[:srcset] = options[:srcset].map do |src_path, size| + src_path = path_to_image(src_path, skip_pipeline: skip_pipeline) + "#{src_path} #{size}" + end.join(", ") + end + + options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size] + tag("img", options) + end + + # Returns a string suitable for an HTML image tag alt attribute. + # The +src+ argument is meant to be an image file path. + # The method removes the basename of the file path and the digest, + # if any. It also removes hyphens and underscores from file names and + # replaces them with spaces, returning a space-separated, titleized + # string. + # + # ==== Examples + # + # image_alt('rails.png') + # # => Rails + # + # image_alt('hyphenated-file-name.png') + # # => Hyphenated file name + # + # image_alt('underscored_file_name.png') + # # => Underscored file name + def image_alt(src) + ActiveSupport::Deprecation.warn("image_alt is deprecated and will be removed from Rails 6.0. You must explicitly set alt text on images.") + + File.basename(src, ".*".freeze).sub(/-[[:xdigit:]]{32,64}\z/, "".freeze).tr("-_".freeze, " ".freeze).capitalize + end + + # Returns an HTML video tag for the +sources+. If +sources+ is a string, + # a single video tag will be returned. If +sources+ is an array, a video + # tag with nested source tags for each source will be returned. The + # +sources+ can be full paths or files that exist in your public videos + # directory. + # + # ==== Options + # + # When the last parameter is a hash you can add HTML attributes using that + # parameter. The following options are supported: + # + # * :poster - Set an image (like a screenshot) to be shown + # before the video loads. The path is calculated like the +src+ of +image_tag+. + # * :size - Supplied as "{Width}x{Height}" or "{Number}", so "30x45" becomes + # width="30" and height="45", and "50" becomes width="50" and height="50". + # :size will be ignored if the value is not in the correct format. + # * :poster_skip_pipeline will bypass the asset pipeline when using + # the :poster option instead using an asset in the public folder. + # + # ==== Examples + # + # video_tag("trailer") + # # => + # video_tag("trailer.ogg") + # # => + # video_tag("trailer.ogg", controls: true, preload: 'none') + # # => + # video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png") + # # => + # video_tag("trailer.m4v", size: "16x10", poster: "screenshot.png", poster_skip_pipeline: true) + # # => + # video_tag("/trailers/hd.avi", size: "16x16") + # # => + # video_tag("/trailers/hd.avi", size: "16") + # # => + # video_tag("/trailers/hd.avi", height: '32', width: '32') + # # => + # video_tag("trailer.ogg", "trailer.flv") + # # => + # video_tag(["trailer.ogg", "trailer.flv"]) + # # => + # video_tag(["trailer.ogg", "trailer.flv"], size: "160x120") + # # => + def video_tag(*sources) + options = sources.extract_options!.symbolize_keys + public_poster_folder = options.delete(:poster_skip_pipeline) + sources << options + multiple_sources_tag_builder("video", sources) do |tag_options| + tag_options[:poster] = path_to_image(tag_options[:poster], skip_pipeline: public_poster_folder) if tag_options[:poster] + tag_options[:width], tag_options[:height] = extract_dimensions(tag_options.delete(:size)) if tag_options[:size] + end + end + + # Returns an HTML audio tag for the +sources+. If +sources+ is a string, + # a single audio tag will be returned. If +sources+ is an array, an audio + # tag with nested source tags for each source will be returned. The + # +sources+ can be full paths or files that exist in your public audios + # directory. + # + # When the last parameter is a hash you can add HTML attributes using that + # parameter. + # + # audio_tag("sound") + # # => + # audio_tag("sound.wav") + # # => + # audio_tag("sound.wav", autoplay: true, controls: true) + # # => + # audio_tag("sound.wav", "sound.mid") + # # => + def audio_tag(*sources) + multiple_sources_tag_builder("audio", sources) + end + + private + def multiple_sources_tag_builder(type, sources) + options = sources.extract_options!.symbolize_keys + skip_pipeline = options.delete(:skip_pipeline) + sources.flatten! + + yield options if block_given? + + if sources.size > 1 + content_tag(type, options) do + safe_join sources.map { |source| tag("source", src: send("path_to_#{type}", source, skip_pipeline: skip_pipeline)) } + end + else + options[:src] = send("path_to_#{type}", sources.first, skip_pipeline: skip_pipeline) + content_tag(type, nil, options) + end + end + + def resolve_image_source(source, skip_pipeline) + if source.is_a?(Symbol) || source.is_a?(String) + path_to_image(source, skip_pipeline: skip_pipeline) + else + polymorphic_url(source) + end + rescue NoMethodError => e + raise ArgumentError, "Can't resolve image into URL: #{e}" + end + + def extract_dimensions(size) + size = size.to_s + if /\A\d+x\d+\z/.match?(size) + size.split("x") + elsif /\A\d+\z/.match?(size) + [size, size] + end + end + + def check_for_image_tag_errors(options) + if options[:size] && (options[:height] || options[:width]) + raise ArgumentError, "Cannot pass a :size option with a :height or :width option" + end + end + + def resolve_link_as(extname, mime_type) + if extname == "js" + "script" + elsif extname == "css" + "style" + elsif extname == "vtt" + "track" + elsif (type = mime_type.to_s.split("/")[0]) && type.in?(%w(audio video font)) + type + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/asset_url_helper.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/asset_url_helper.rb new file mode 100644 index 00000000..8cbe107e --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/asset_url_helper.rb @@ -0,0 +1,469 @@ +# frozen_string_literal: true + +require "zlib" + +module ActionView + # = Action View Asset URL Helpers + module Helpers #:nodoc: + # This module provides methods for generating asset paths and + # URLs. + # + # image_path("rails.png") + # # => "/assets/rails.png" + # + # image_url("rails.png") + # # => "http://www.example.com/assets/rails.png" + # + # === Using asset hosts + # + # By default, Rails links to these assets on the current host in the public + # folder, but you can direct Rails to link to assets from a dedicated asset + # server by setting ActionController::Base.asset_host in the application + # configuration, typically in config/environments/production.rb. + # For example, you'd define assets.example.com to be your asset + # host this way, inside the configure block of your environment-specific + # configuration files or config/application.rb: + # + # config.action_controller.asset_host = "assets.example.com" + # + # Helpers take that into account: + # + # image_tag("rails.png") + # # => + # stylesheet_link_tag("application") + # # => + # + # Browsers open a limited number of simultaneous connections to a single + # host. The exact number varies by browser and version. This limit may cause + # some asset downloads to wait for previous assets to finish before they can + # begin. You can use the %d wildcard in the +asset_host+ to + # distribute the requests over four hosts. For example, + # assets%d.example.com will spread the asset requests over + # "assets0.example.com", ..., "assets3.example.com". + # + # image_tag("rails.png") + # # => + # stylesheet_link_tag("application") + # # => + # + # This may improve the asset loading performance of your application. + # It is also possible the combination of additional connection overhead + # (DNS, SSL) and the overall browser connection limits may result in this + # solution being slower. You should be sure to measure your actual + # performance across targeted browsers both before and after this change. + # + # To implement the corresponding hosts you can either setup four actual + # hosts or use wildcard DNS to CNAME the wildcard to a single asset host. + # You can read more about setting up your DNS CNAME records from your ISP. + # + # Note: This is purely a browser performance optimization and is not meant + # for server load balancing. See https://www.die.net/musings/page_load_time/ + # for background and https://www.browserscope.org/?category=network for + # connection limit data. + # + # Alternatively, you can exert more control over the asset host by setting + # +asset_host+ to a proc like this: + # + # ActionController::Base.asset_host = Proc.new { |source| + # "http://assets#{Digest::MD5.hexdigest(source).to_i(16) % 2 + 1}.example.com" + # } + # image_tag("rails.png") + # # => + # stylesheet_link_tag("application") + # # => + # + # The example above generates "http://assets1.example.com" and + # "http://assets2.example.com". This option is useful for example if + # you need fewer/more than four hosts, custom host names, etc. + # + # As you see the proc takes a +source+ parameter. That's a string with the + # absolute path of the asset, for example "/assets/rails.png". + # + # ActionController::Base.asset_host = Proc.new { |source| + # if source.ends_with?('.css') + # "http://stylesheets.example.com" + # else + # "http://assets.example.com" + # end + # } + # image_tag("rails.png") + # # => + # stylesheet_link_tag("application") + # # => + # + # Alternatively you may ask for a second parameter +request+. That one is + # particularly useful for serving assets from an SSL-protected page. The + # example proc below disables asset hosting for HTTPS connections, while + # still sending assets for plain HTTP requests from asset hosts. If you don't + # have SSL certificates for each of the asset hosts this technique allows you + # to avoid warnings in the client about mixed media. + # Note that the +request+ parameter might not be supplied, e.g. when the assets + # are precompiled via a Rake task. Make sure to use a +Proc+ instead of a lambda, + # since a +Proc+ allows missing parameters and sets them to +nil+. + # + # config.action_controller.asset_host = Proc.new { |source, request| + # if request && request.ssl? + # "#{request.protocol}#{request.host_with_port}" + # else + # "#{request.protocol}assets.example.com" + # end + # } + # + # You can also implement a custom asset host object that responds to +call+ + # and takes either one or two parameters just like the proc. + # + # config.action_controller.asset_host = AssetHostingWithMinimumSsl.new( + # "http://asset%d.example.com", "https://asset1.example.com" + # ) + # + module AssetUrlHelper + URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//}i + + # This is the entry point for all assets. + # When using the asset pipeline (i.e. sprockets and sprockets-rails), the + # behavior is "enhanced". You can bypass the asset pipeline by passing in + # skip_pipeline: true to the options. + # + # All other asset *_path helpers delegate through this method. + # + # === With the asset pipeline + # + # All options passed to +asset_path+ will be passed to +compute_asset_path+ + # which is implemented by sprockets-rails. + # + # asset_path("application.js") # => "/assets/application-60aa4fdc5cea14baf5400fba1abf4f2a46a5166bad4772b1effe341570f07de9.js" + # + # === Without the asset pipeline (skip_pipeline: true) + # + # Accepts a type option that can specify the asset's extension. No error + # checking is done to verify the source passed into +asset_path+ is valid + # and that the file exists on disk. + # + # asset_path("application.js", skip_pipeline: true) # => "application.js" + # asset_path("filedoesnotexist.png", skip_pipeline: true) # => "filedoesnotexist.png" + # asset_path("application", type: :javascript, skip_pipeline: true) # => "/javascripts/application.js" + # asset_path("application", type: :stylesheet, skip_pipeline: true) # => "/stylesheets/application.css" + # + # === Options applying to all assets + # + # Below lists scenarios that apply to +asset_path+ whether or not you're + # using the asset pipeline. + # + # - All fully qualified URLs are returned immediately. This bypasses the + # asset pipeline and all other behavior described. + # + # asset_path("http://www.example.com/js/xmlhr.js") # => "http://www.example.com/js/xmlhr.js" + # + # - All assets that begin with a forward slash are assumed to be full + # URLs and will not be expanded. This will bypass the asset pipeline. + # + # asset_path("/foo.png") # => "/foo.png" + # + # - All blank strings will be returned immediately. This bypasses the + # asset pipeline and all other behavior described. + # + # asset_path("") # => "" + # + # - If config.relative_url_root is specified, all assets will have that + # root prepended. + # + # Rails.application.config.relative_url_root = "bar" + # asset_path("foo.js", skip_pipeline: true) # => "bar/foo.js" + # + # - A different asset host can be specified via config.action_controller.asset_host + # this is commonly used in conjunction with a CDN. + # + # Rails.application.config.action_controller.asset_host = "assets.example.com" + # asset_path("foo.js", skip_pipeline: true) # => "http://assets.example.com/foo.js" + # + # - An extension name can be specified manually with extname. + # + # asset_path("foo", skip_pipeline: true, extname: ".js") # => "/foo.js" + # asset_path("foo.css", skip_pipeline: true, extname: ".js") # => "/foo.css.js" + def asset_path(source, options = {}) + raise ArgumentError, "nil is not a valid asset source" if source.nil? + + source = source.to_s + return "" if source.blank? + return source if URI_REGEXP.match?(source) + + tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, "".freeze) + + if extname = compute_asset_extname(source, options) + source = "#{source}#{extname}" + end + + if source[0] != ?/ + if options[:skip_pipeline] + source = public_compute_asset_path(source, options) + else + source = compute_asset_path(source, options) + end + end + + relative_url_root = defined?(config.relative_url_root) && config.relative_url_root + if relative_url_root + source = File.join(relative_url_root, source) unless source.starts_with?("#{relative_url_root}/") + end + + if host = compute_asset_host(source, options) + source = File.join(host, source) + end + + "#{source}#{tail}" + end + alias_method :path_to_asset, :asset_path # aliased to avoid conflicts with an asset_path named route + + # Computes the full URL to an asset in the public directory. This + # will use +asset_path+ internally, so most of their behaviors + # will be the same. If :host options is set, it overwrites global + # +config.action_controller.asset_host+ setting. + # + # All other options provided are forwarded to +asset_path+ call. + # + # asset_url "application.js" # => http://example.com/assets/application.js + # asset_url "application.js", host: "http://cdn.example.com" # => http://cdn.example.com/assets/application.js + # + def asset_url(source, options = {}) + path_to_asset(source, options.merge(protocol: :request)) + end + alias_method :url_to_asset, :asset_url # aliased to avoid conflicts with an asset_url named route + + ASSET_EXTENSIONS = { + javascript: ".js", + stylesheet: ".css" + } + + # Compute extname to append to asset path. Returns +nil+ if + # nothing should be added. + def compute_asset_extname(source, options = {}) + return if options[:extname] == false + extname = options[:extname] || ASSET_EXTENSIONS[options[:type]] + if extname && File.extname(source) != extname + extname + else + nil + end + end + + # Maps asset types to public directory. + ASSET_PUBLIC_DIRECTORIES = { + audio: "/audios", + font: "/fonts", + image: "/images", + javascript: "/javascripts", + stylesheet: "/stylesheets", + video: "/videos" + } + + # Computes asset path to public directory. Plugins and + # extensions can override this method to point to custom assets + # or generate digested paths or query strings. + def compute_asset_path(source, options = {}) + dir = ASSET_PUBLIC_DIRECTORIES[options[:type]] || "" + File.join(dir, source) + end + alias :public_compute_asset_path :compute_asset_path + + # Pick an asset host for this source. Returns +nil+ if no host is set, + # the host if no wildcard is set, the host interpolated with the + # numbers 0-3 if it contains %d (the number is the source hash mod 4), + # or the value returned from invoking call on an object responding to call + # (proc or otherwise). + def compute_asset_host(source = "", options = {}) + request = self.request if respond_to?(:request) + host = options[:host] + host ||= config.asset_host if defined? config.asset_host + + if host + if host.respond_to?(:call) + arity = host.respond_to?(:arity) ? host.arity : host.method(:call).arity + args = [source] + args << request if request && (arity > 1 || arity < 0) + host = host.call(*args) + elsif host.include?("%d") + host = host % (Zlib.crc32(source) % 4) + end + end + + host ||= request.base_url if request && options[:protocol] == :request + return unless host + + if URI_REGEXP.match?(host) + host + else + protocol = options[:protocol] || config.default_asset_host_protocol || (request ? :request : :relative) + case protocol + when :relative + "//#{host}" + when :request + "#{request.protocol}#{host}" + else + "#{protocol}://#{host}" + end + end + end + + # Computes the path to a JavaScript asset in the public javascripts directory. + # If the +source+ filename has no extension, .js will be appended (except for explicit URIs) + # Full paths from the document root will be passed through. + # Used internally by +javascript_include_tag+ to build the script path. + # + # javascript_path "xmlhr" # => /assets/xmlhr.js + # javascript_path "dir/xmlhr.js" # => /assets/dir/xmlhr.js + # javascript_path "/dir/xmlhr" # => /dir/xmlhr.js + # javascript_path "http://www.example.com/js/xmlhr" # => http://www.example.com/js/xmlhr + # javascript_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js + def javascript_path(source, options = {}) + path_to_asset(source, { type: :javascript }.merge!(options)) + end + alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route + + # Computes the full URL to a JavaScript asset in the public javascripts directory. + # This will use +javascript_path+ internally, so most of their behaviors will be the same. + # Since +javascript_url+ is based on +asset_url+ method you can set :host options. If :host + # options is set, it overwrites global +config.action_controller.asset_host+ setting. + # + # javascript_url "js/xmlhr.js", host: "http://stage.example.com" # => http://stage.example.com/assets/js/xmlhr.js + # + def javascript_url(source, options = {}) + url_to_asset(source, { type: :javascript }.merge!(options)) + end + alias_method :url_to_javascript, :javascript_url # aliased to avoid conflicts with a javascript_url named route + + # Computes the path to a stylesheet asset in the public stylesheets directory. + # If the +source+ filename has no extension, .css will be appended (except for explicit URIs). + # Full paths from the document root will be passed through. + # Used internally by +stylesheet_link_tag+ to build the stylesheet path. + # + # stylesheet_path "style" # => /assets/style.css + # stylesheet_path "dir/style.css" # => /assets/dir/style.css + # stylesheet_path "/dir/style.css" # => /dir/style.css + # stylesheet_path "http://www.example.com/css/style" # => http://www.example.com/css/style + # stylesheet_path "http://www.example.com/css/style.css" # => http://www.example.com/css/style.css + def stylesheet_path(source, options = {}) + path_to_asset(source, { type: :stylesheet }.merge!(options)) + end + alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route + + # Computes the full URL to a stylesheet asset in the public stylesheets directory. + # This will use +stylesheet_path+ internally, so most of their behaviors will be the same. + # Since +stylesheet_url+ is based on +asset_url+ method you can set :host options. If :host + # options is set, it overwrites global +config.action_controller.asset_host+ setting. + # + # stylesheet_url "css/style.css", host: "http://stage.example.com" # => http://stage.example.com/assets/css/style.css + # + def stylesheet_url(source, options = {}) + url_to_asset(source, { type: :stylesheet }.merge!(options)) + end + alias_method :url_to_stylesheet, :stylesheet_url # aliased to avoid conflicts with a stylesheet_url named route + + # Computes the path to an image asset. + # Full paths from the document root will be passed through. + # Used internally by +image_tag+ to build the image path: + # + # image_path("edit") # => "/assets/edit" + # image_path("edit.png") # => "/assets/edit.png" + # image_path("icons/edit.png") # => "/assets/icons/edit.png" + # image_path("/icons/edit.png") # => "/icons/edit.png" + # image_path("http://www.example.com/img/edit.png") # => "http://www.example.com/img/edit.png" + # + # If you have images as application resources this method may conflict with their named routes. + # The alias +path_to_image+ is provided to avoid that. Rails uses the alias internally, and + # plugin authors are encouraged to do so. + def image_path(source, options = {}) + path_to_asset(source, { type: :image }.merge!(options)) + end + alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route + + # Computes the full URL to an image asset. + # This will use +image_path+ internally, so most of their behaviors will be the same. + # Since +image_url+ is based on +asset_url+ method you can set :host options. If :host + # options is set, it overwrites global +config.action_controller.asset_host+ setting. + # + # image_url "edit.png", host: "http://stage.example.com" # => http://stage.example.com/assets/edit.png + # + def image_url(source, options = {}) + url_to_asset(source, { type: :image }.merge!(options)) + end + alias_method :url_to_image, :image_url # aliased to avoid conflicts with an image_url named route + + # Computes the path to a video asset in the public videos directory. + # Full paths from the document root will be passed through. + # Used internally by +video_tag+ to build the video path. + # + # video_path("hd") # => /videos/hd + # video_path("hd.avi") # => /videos/hd.avi + # video_path("trailers/hd.avi") # => /videos/trailers/hd.avi + # video_path("/trailers/hd.avi") # => /trailers/hd.avi + # video_path("http://www.example.com/vid/hd.avi") # => http://www.example.com/vid/hd.avi + def video_path(source, options = {}) + path_to_asset(source, { type: :video }.merge!(options)) + end + alias_method :path_to_video, :video_path # aliased to avoid conflicts with a video_path named route + + # Computes the full URL to a video asset in the public videos directory. + # This will use +video_path+ internally, so most of their behaviors will be the same. + # Since +video_url+ is based on +asset_url+ method you can set :host options. If :host + # options is set, it overwrites global +config.action_controller.asset_host+ setting. + # + # video_url "hd.avi", host: "http://stage.example.com" # => http://stage.example.com/videos/hd.avi + # + def video_url(source, options = {}) + url_to_asset(source, { type: :video }.merge!(options)) + end + alias_method :url_to_video, :video_url # aliased to avoid conflicts with a video_url named route + + # Computes the path to an audio asset in the public audios directory. + # Full paths from the document root will be passed through. + # Used internally by +audio_tag+ to build the audio path. + # + # audio_path("horse") # => /audios/horse + # audio_path("horse.wav") # => /audios/horse.wav + # audio_path("sounds/horse.wav") # => /audios/sounds/horse.wav + # audio_path("/sounds/horse.wav") # => /sounds/horse.wav + # audio_path("http://www.example.com/sounds/horse.wav") # => http://www.example.com/sounds/horse.wav + def audio_path(source, options = {}) + path_to_asset(source, { type: :audio }.merge!(options)) + end + alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route + + # Computes the full URL to an audio asset in the public audios directory. + # This will use +audio_path+ internally, so most of their behaviors will be the same. + # Since +audio_url+ is based on +asset_url+ method you can set :host options. If :host + # options is set, it overwrites global +config.action_controller.asset_host+ setting. + # + # audio_url "horse.wav", host: "http://stage.example.com" # => http://stage.example.com/audios/horse.wav + # + def audio_url(source, options = {}) + url_to_asset(source, { type: :audio }.merge!(options)) + end + alias_method :url_to_audio, :audio_url # aliased to avoid conflicts with an audio_url named route + + # Computes the path to a font asset. + # Full paths from the document root will be passed through. + # + # font_path("font") # => /fonts/font + # font_path("font.ttf") # => /fonts/font.ttf + # font_path("dir/font.ttf") # => /fonts/dir/font.ttf + # font_path("/dir/font.ttf") # => /dir/font.ttf + # font_path("http://www.example.com/dir/font.ttf") # => http://www.example.com/dir/font.ttf + def font_path(source, options = {}) + path_to_asset(source, { type: :font }.merge!(options)) + end + alias_method :path_to_font, :font_path # aliased to avoid conflicts with a font_path named route + + # Computes the full URL to a font asset. + # This will use +font_path+ internally, so most of their behaviors will be the same. + # Since +font_url+ is based on +asset_url+ method you can set :host options. If :host + # options is set, it overwrites global +config.action_controller.asset_host+ setting. + # + # font_url "font.ttf", host: "http://stage.example.com" # => http://stage.example.com/fonts/font.ttf + # + def font_url(source, options = {}) + url_to_asset(source, { type: :font }.merge!(options)) + end + alias_method :url_to_font, :font_url # aliased to avoid conflicts with a font_url named route + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/atom_feed_helper.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/atom_feed_helper.rb new file mode 100644 index 00000000..e6b98782 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/atom_feed_helper.rb @@ -0,0 +1,205 @@ +# frozen_string_literal: true + +require "set" + +module ActionView + # = Action View Atom Feed Helpers + module Helpers #:nodoc: + module AtomFeedHelper + # Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERB or any other + # template languages). + # + # Full usage example: + # + # config/routes.rb: + # Rails.application.routes.draw do + # resources :posts + # root to: "posts#index" + # end + # + # app/controllers/posts_controller.rb: + # class PostsController < ApplicationController + # # GET /posts.html + # # GET /posts.atom + # def index + # @posts = Post.all + # + # respond_to do |format| + # format.html + # format.atom + # end + # end + # end + # + # app/views/posts/index.atom.builder: + # atom_feed do |feed| + # feed.title("My great blog!") + # feed.updated(@posts[0].created_at) if @posts.length > 0 + # + # @posts.each do |post| + # feed.entry(post) do |entry| + # entry.title(post.title) + # entry.content(post.body, type: 'html') + # + # entry.author do |author| + # author.name("DHH") + # end + # end + # end + # end + # + # The options for atom_feed are: + # + # * :language: Defaults to "en-US". + # * :root_url: The HTML alternative that this feed is doubling for. Defaults to / on the current host. + # * :url: The URL for this feed. Defaults to the current URL. + # * :id: The id for this feed. Defaults to "tag:localhost,2005:/posts", in this case. + # * :schema_date: The date at which the tag scheme for the feed was first used. A good default is the year you + # created the feed. See http://feedvalidator.org/docs/error/InvalidTAG.html for more information. If not specified, + # 2005 is used (as an "I don't care" value). + # * :instruct: Hash of XML processing instructions in the form {target => {attribute => value, }} or {target => [{attribute => value, }, ]} + # + # Other namespaces can be added to the root element: + # + # app/views/posts/index.atom.builder: + # atom_feed({'xmlns:app' => 'http://www.w3.org/2007/app', + # 'xmlns:openSearch' => 'http://a9.com/-/spec/opensearch/1.1/'}) do |feed| + # feed.title("My great blog!") + # feed.updated((@posts.first.created_at)) + # feed.tag!('openSearch:totalResults', 10) + # + # @posts.each do |post| + # feed.entry(post) do |entry| + # entry.title(post.title) + # entry.content(post.body, type: 'html') + # entry.tag!('app:edited', Time.now) + # + # entry.author do |author| + # author.name("DHH") + # end + # end + # end + # end + # + # The Atom spec defines five elements (content rights title subtitle + # summary) which may directly contain xhtml content if type: 'xhtml' + # is specified as an attribute. If so, this helper will take care of + # the enclosing div and xhtml namespace declaration. Example usage: + # + # entry.summary type: 'xhtml' do |xhtml| + # xhtml.p pluralize(order.line_items.count, "line item") + # xhtml.p "Shipped to #{order.address}" + # xhtml.p "Paid by #{order.pay_type}" + # end + # + # + # atom_feed yields an +AtomFeedBuilder+ instance. Nested elements yield + # an +AtomBuilder+ instance. + def atom_feed(options = {}, &block) + if options[:schema_date] + options[:schema_date] = options[:schema_date].strftime("%Y-%m-%d") if options[:schema_date].respond_to?(:strftime) + else + options[:schema_date] = "2005" # The Atom spec copyright date + end + + xml = options.delete(:xml) || eval("xml", block.binding) + xml.instruct! + if options[:instruct] + options[:instruct].each do |target, attrs| + if attrs.respond_to?(:keys) + xml.instruct!(target, attrs) + elsif attrs.respond_to?(:each) + attrs.each { |attr_group| xml.instruct!(target, attr_group) } + end + end + end + + feed_opts = { "xml:lang" => options[:language] || "en-US", "xmlns" => "http://www.w3.org/2005/Atom" } + feed_opts.merge!(options).reject! { |k, v| !k.to_s.match(/^xml/) } + + xml.feed(feed_opts) do + xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}") + xml.link(rel: "alternate", type: "text/html", href: options[:root_url] || (request.protocol + request.host_with_port)) + xml.link(rel: "self", type: "application/atom+xml", href: options[:url] || request.url) + + yield AtomFeedBuilder.new(xml, self, options) + end + end + + class AtomBuilder #:nodoc: + XHTML_TAG_NAMES = %w(content rights title subtitle summary).to_set + + def initialize(xml) + @xml = xml + end + + private + # Delegate to xml builder, first wrapping the element in an xhtml + # namespaced div element if the method and arguments indicate + # that an xhtml_block? is desired. + def method_missing(method, *arguments, &block) + if xhtml_block?(method, arguments) + @xml.__send__(method, *arguments) do + @xml.div(xmlns: "http://www.w3.org/1999/xhtml") do |xhtml| + block.call(xhtml) + end + end + else + @xml.__send__(method, *arguments, &block) + end + end + + # True if the method name matches one of the five elements defined + # in the Atom spec as potentially containing XHTML content and + # if type: 'xhtml' is, in fact, specified. + def xhtml_block?(method, arguments) + if XHTML_TAG_NAMES.include?(method.to_s) + last = arguments.last + last.is_a?(Hash) && last[:type].to_s == "xhtml" + end + end + end + + class AtomFeedBuilder < AtomBuilder #:nodoc: + def initialize(xml, view, feed_options = {}) + @xml, @view, @feed_options = xml, view, feed_options + end + + # Accepts a Date or Time object and inserts it in the proper format. If +nil+ is passed, current time in UTC is used. + def updated(date_or_time = nil) + @xml.updated((date_or_time || Time.now.utc).xmlschema) + end + + # Creates an entry tag for a specific record and prefills the id using class and id. + # + # Options: + # + # * :published: Time first published. Defaults to the created_at attribute on the record if one such exists. + # * :updated: Time of update. Defaults to the updated_at attribute on the record if one such exists. + # * :url: The URL for this entry or +false+ or +nil+ for not having a link tag. Defaults to the +polymorphic_url+ for the record. + # * :id: The ID for this entry. Defaults to "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}" + # * :type: The TYPE for this entry. Defaults to "text/html". + def entry(record, options = {}) + @xml.entry do + @xml.id(options[:id] || "tag:#{@view.request.host},#{@feed_options[:schema_date]}:#{record.class}/#{record.id}") + + if options[:published] || (record.respond_to?(:created_at) && record.created_at) + @xml.published((options[:published] || record.created_at).xmlschema) + end + + if options[:updated] || (record.respond_to?(:updated_at) && record.updated_at) + @xml.updated((options[:updated] || record.updated_at).xmlschema) + end + + type = options.fetch(:type, "text/html") + + url = options.fetch(:url) { @view.polymorphic_url(record) } + @xml.link(rel: "alternate", type: type, href: url) if url + + yield AtomBuilder.new(@xml) + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/cache_helper.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/cache_helper.rb new file mode 100644 index 00000000..3cbb1ed1 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/cache_helper.rb @@ -0,0 +1,263 @@ +# frozen_string_literal: true + +module ActionView + # = Action View Cache Helper + module Helpers #:nodoc: + module CacheHelper + # This helper exposes a method for caching fragments of a view + # rather than an entire action or page. This technique is useful + # caching pieces like menus, lists of new topics, static HTML + # fragments, and so on. This method takes a block that contains + # the content you wish to cache. + # + # The best way to use this is by doing recyclable key-based cache expiration + # on top of a cache store like Memcached or Redis that'll automatically + # kick out old entries. + # + # When using this method, you list the cache dependency as the name of the cache, like so: + # + # <% cache project do %> + # All the topics on this project + # <%= render project.topics %> + # <% end %> + # + # This approach will assume that when a new topic is added, you'll touch + # the project. The cache key generated from this call will be something like: + # + # views/template/action.html.erb:7a1156131a6928cb0026877f8b749ac9/projects/123 + # ^template path ^template tree digest ^class ^id + # + # This cache key is stable, but it's combined with a cache version derived from the project + # record. When the project updated_at is touched, the #cache_version changes, even + # if the key stays stable. This means that unlike a traditional key-based cache expiration + # approach, you won't be generating cache trash, unused keys, simply because the dependent + # record is updated. + # + # If your template cache depends on multiple sources (try to avoid this to keep things simple), + # you can name all these dependencies as part of an array: + # + # <% cache [ project, current_user ] do %> + # All the topics on this project + # <%= render project.topics %> + # <% end %> + # + # This will include both records as part of the cache key and updating either of them will + # expire the cache. + # + # ==== \Template digest + # + # The template digest that's added to the cache key is computed by taking an MD5 of the + # contents of the entire template file. This ensures that your caches will automatically + # expire when you change the template file. + # + # Note that the MD5 is taken of the entire template file, not just what's within the + # cache do/end call. So it's possible that changing something outside of that call will + # still expire the cache. + # + # Additionally, the digestor will automatically look through your template file for + # explicit and implicit dependencies, and include those as part of the digest. + # + # The digestor can be bypassed by passing skip_digest: true as an option to the cache call: + # + # <% cache project, skip_digest: true do %> + # All the topics on this project + # <%= render project.topics %> + # <% end %> + # + # ==== Implicit dependencies + # + # Most template dependencies can be derived from calls to render in the template itself. + # Here are some examples of render calls that Cache Digests knows how to decode: + # + # render partial: "comments/comment", collection: commentable.comments + # render "comments/comments" + # render 'comments/comments' + # render('comments/comments') + # + # render "header" translates to render("comments/header") + # + # render(@topic) translates to render("topics/topic") + # render(topics) translates to render("topics/topic") + # render(message.topics) translates to render("topics/topic") + # + # It's not possible to derive all render calls like that, though. + # Here are a few examples of things that can't be derived: + # + # render group_of_attachments + # render @project.documents.where(published: true).order('created_at') + # + # You will have to rewrite those to the explicit form: + # + # render partial: 'attachments/attachment', collection: group_of_attachments + # render partial: 'documents/document', collection: @project.documents.where(published: true).order('created_at') + # + # === Explicit dependencies + # + # Sometimes you'll have template dependencies that can't be derived at all. This is typically + # the case when you have template rendering that happens in helpers. Here's an example: + # + # <%= render_sortable_todolists @project.todolists %> + # + # You'll need to use a special comment format to call those out: + # + # <%# Template Dependency: todolists/todolist %> + # <%= render_sortable_todolists @project.todolists %> + # + # In some cases, like a single table inheritance setup, you might have + # a bunch of explicit dependencies. Instead of writing every template out, + # you can use a wildcard to match any template in a directory: + # + # <%# Template Dependency: events/* %> + # <%= render_categorizable_events @person.events %> + # + # This marks every template in the directory as a dependency. To find those + # templates, the wildcard path must be absolutely defined from app/views or paths + # otherwise added with +prepend_view_path+ or +append_view_path+. + # This way the wildcard for app/views/recordings/events would be recordings/events/* etc. + # + # The pattern used to match explicit dependencies is /# Template Dependency: (\S+)/, + # so it's important that you type it out just so. + # You can only declare one template dependency per line. + # + # === External dependencies + # + # If you use a helper method, for example, inside a cached block and + # you then update that helper, you'll have to bump the cache as well. + # It doesn't really matter how you do it, but the MD5 of the template file + # must change. One recommendation is to simply be explicit in a comment, like: + # + # <%# Helper Dependency Updated: May 6, 2012 at 6pm %> + # <%= some_helper_method(person) %> + # + # Now all you have to do is change that timestamp when the helper method changes. + # + # === Collection Caching + # + # When rendering a collection of objects that each use the same partial, a :cached + # option can be passed. + # + # For collections rendered such: + # + # <%= render partial: 'projects/project', collection: @projects, cached: true %> + # + # The cached: true will make Action View's rendering read several templates + # from cache at once instead of one call per template. + # + # Templates in the collection not already cached are written to cache. + # + # Works great alongside individual template fragment caching. + # For instance if the template the collection renders is cached like: + # + # # projects/_project.html.erb + # <% cache project do %> + # <%# ... %> + # <% end %> + # + # Any collection renders will find those cached templates when attempting + # to read multiple templates at once. + # + # If your collection cache depends on multiple sources (try to avoid this to keep things simple), + # you can name all these dependencies as part of a block that returns an array: + # + # <%= render partial: 'projects/project', collection: @projects, cached: -> project { [ project, current_user ] } %> + # + # This will include both records as part of the cache key and updating either of them will + # expire the cache. + def cache(name = {}, options = {}, &block) + if controller.respond_to?(:perform_caching) && controller.perform_caching + name_options = options.slice(:skip_digest, :virtual_path) + safe_concat(fragment_for(cache_fragment_name(name, name_options), options, &block)) + else + yield + end + + nil + end + + # Cache fragments of a view if +condition+ is true + # + # <% cache_if admin?, project do %> + # All the topics on this project + # <%= render project.topics %> + # <% end %> + def cache_if(condition, name = {}, options = {}, &block) + if condition + cache(name, options, &block) + else + yield + end + + nil + end + + # Cache fragments of a view unless +condition+ is true + # + # <% cache_unless admin?, project do %> + # All the topics on this project + # <%= render project.topics %> + # <% end %> + def cache_unless(condition, name = {}, options = {}, &block) + cache_if !condition, name, options, &block + end + + # This helper returns the name of a cache key for a given fragment cache + # call. By supplying +skip_digest:+ true to cache, the digestion of cache + # fragments can be manually bypassed. This is useful when cache fragments + # cannot be manually expired unless you know the exact key which is the + # case when using memcached. + # + # The digest will be generated using +virtual_path:+ if it is provided. + # + def cache_fragment_name(name = {}, skip_digest: nil, virtual_path: nil) + if skip_digest + name + else + fragment_name_with_digest(name, virtual_path) + end + end + + private + + def fragment_name_with_digest(name, virtual_path) + virtual_path ||= @virtual_path + + if virtual_path + name = controller.url_for(name).split("://").last if name.is_a?(Hash) + + if digest = Digestor.digest(name: virtual_path, finder: lookup_context, dependencies: view_cache_dependencies).presence + [ "#{virtual_path}:#{digest}", name ] + else + [ virtual_path, name ] + end + else + name + end + end + + def fragment_for(name = {}, options = nil, &block) + if content = read_fragment_for(name, options) + @view_renderer.cache_hits[@virtual_path] = :hit if defined?(@view_renderer) + content + else + @view_renderer.cache_hits[@virtual_path] = :miss if defined?(@view_renderer) + write_fragment_for(name, options, &block) + end + end + + def read_fragment_for(name, options) + controller.read_fragment(name, options) + end + + def write_fragment_for(name, options) + pos = output_buffer.length + yield + output_safe = output_buffer.html_safe? + fragment = output_buffer.slice!(pos..-1) + if output_safe + self.output_buffer = output_buffer.class.new(output_buffer) + end + controller.write_fragment(name, fragment, options) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/capture_helper.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/capture_helper.rb new file mode 100644 index 00000000..92f7ddb7 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/capture_helper.rb @@ -0,0 +1,212 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/output_safety" + +module ActionView + # = Action View Capture Helper + module Helpers #:nodoc: + # CaptureHelper exposes methods to let you extract generated markup which + # can be used in other parts of a template or layout file. + # + # It provides a method to capture blocks into variables through capture and + # a way to capture a block of markup for use in a layout through {content_for}[rdoc-ref:ActionView::Helpers::CaptureHelper#content_for]. + module CaptureHelper + # The capture method extracts part of a template as a String object. + # You can then use this object anywhere in your templates, layout, or helpers. + # + # The capture method can be used in ERB templates... + # + # <% @greeting = capture do %> + # Welcome to my shiny new web page! The date and time is + # <%= Time.now %> + # <% end %> + # + # ...and Builder (RXML) templates. + # + # @timestamp = capture do + # "The current timestamp is #{Time.now}." + # end + # + # You can then use that variable anywhere else. For example: + # + # + # <%= @greeting %> + # + # <%= @greeting %> + # + # + # + def capture(*args) + value = nil + buffer = with_output_buffer { value = yield(*args) } + if (string = buffer.presence || value) && string.is_a?(String) + ERB::Util.html_escape string + end + end + + # Calling content_for stores a block of markup in an identifier for later use. + # In order to access this stored content in other templates, helper modules + # or the layout, you would pass the identifier as an argument to content_for. + # + # Note: yield can still be used to retrieve the stored content, but calling + # yield doesn't work in helper modules, while content_for does. + # + # <% content_for :not_authorized do %> + # alert('You are not authorized to do that!') + # <% end %> + # + # You can then use content_for :not_authorized anywhere in your templates. + # + # <%= content_for :not_authorized if current_user.nil? %> + # + # This is equivalent to: + # + # <%= yield :not_authorized if current_user.nil? %> + # + # content_for, however, can also be used in helper modules. + # + # module StorageHelper + # def stored_content + # content_for(:storage) || "Your storage is empty" + # end + # end + # + # This helper works just like normal helpers. + # + # <%= stored_content %> + # + # You can also use the yield syntax alongside an existing call to + # yield in a layout. For example: + # + # <%# This is the layout %> + # + # + # My Website + # <%= yield :script %> + # + # + # <%= yield %> + # + # + # + # And now, we'll create a view that has a content_for call that + # creates the script identifier. + # + # <%# This is our view %> + # Please login! + # + # <% content_for :script do %> + # + # <% end %> + # + # Then, in another view, you could to do something like this: + # + # <%= link_to 'Logout', action: 'logout', remote: true %> + # + # <% content_for :script do %> + # <%= javascript_include_tag :defaults %> + # <% end %> + # + # That will place +script+ tags for your default set of JavaScript files on the page; + # this technique is useful if you'll only be using these scripts in a few views. + # + # Note that content_for concatenates (default) the blocks it is given for a particular + # identifier in order. For example: + # + # <% content_for :navigation do %> + #
  • <%= link_to 'Home', action: 'index' %>
  • + # <% end %> + # + # And in another place: + # + # <% content_for :navigation do %> + #
  • <%= link_to 'Login', action: 'login' %>
  • + # <% end %> + # + # Then, in another template or layout, this code would render both links in order: + # + #
      <%= content_for :navigation %>
    + # + # If the flush parameter is +true+ content_for replaces the blocks it is given. For example: + # + # <% content_for :navigation do %> + #
  • <%= link_to 'Home', action: 'index' %>
  • + # <% end %> + # + # <%# Add some other content, or use a different template: %> + # + # <% content_for :navigation, flush: true do %> + #
  • <%= link_to 'Login', action: 'login' %>
  • + # <% end %> + # + # Then, in another template or layout, this code would render only the last link: + # + #
      <%= content_for :navigation %>
    + # + # Lastly, simple content can be passed as a parameter: + # + # <% content_for :script, javascript_include_tag(:defaults) %> + # + # WARNING: content_for is ignored in caches. So you shouldn't use it for elements that will be fragment cached. + def content_for(name, content = nil, options = {}, &block) + if content || block_given? + if block_given? + options = content if content + content = capture(&block) + end + if content + options[:flush] ? @view_flow.set(name, content) : @view_flow.append(name, content) + end + nil + else + @view_flow.get(name).presence + end + end + + # The same as +content_for+ but when used with streaming flushes + # straight back to the layout. In other words, if you want to + # concatenate several times to the same buffer when rendering a given + # template, you should use +content_for+, if not, use +provide+ to tell + # the layout to stop looking for more contents. + def provide(name, content = nil, &block) + content = capture(&block) if block_given? + result = @view_flow.append!(name, content) if content + result unless content + end + + # content_for? checks whether any content has been captured yet using content_for. + # Useful to render parts of your layout differently based on what is in your views. + # + # <%# This is the layout %> + # + # + # My Website + # <%= yield :script %> + # + # + # <%= yield %> + # <%= yield :right_col %> + # + # + def content_for?(name) + @view_flow.get(name).present? + end + + # Use an alternate output buffer for the duration of the block. + # Defaults to a new empty string. + def with_output_buffer(buf = nil) #:nodoc: + unless buf + buf = ActionView::OutputBuffer.new + if output_buffer && output_buffer.respond_to?(:encoding) + buf.force_encoding(output_buffer.encoding) + end + end + self.output_buffer, old_buffer = buf, output_buffer + yield + output_buffer + ensure + self.output_buffer = old_buffer + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/controller_helper.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/controller_helper.rb new file mode 100644 index 00000000..79cf86c7 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/controller_helper.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attr_internal" + +module ActionView + module Helpers #:nodoc: + # This module keeps all methods and behavior in ActionView + # that simply delegates to the controller. + module ControllerHelper #:nodoc: + attr_internal :controller, :request + + CONTROLLER_DELEGATES = [:request_forgery_protection_token, :params, + :session, :cookies, :response, :headers, :flash, :action_name, + :controller_name, :controller_path] + + delegate(*CONTROLLER_DELEGATES, to: :controller) + + def assign_controller(controller) + if @_controller = controller + @_request = controller.request if controller.respond_to?(:request) + @_config = controller.config.inheritable_copy if controller.respond_to?(:config) + @_default_form_builder = controller.default_form_builder if controller.respond_to?(:default_form_builder) + end + end + + def logger + controller.logger if controller.respond_to?(:logger) + end + + def respond_to?(method_name, include_private = false) + return controller.respond_to?(method_name) if CONTROLLER_DELEGATES.include?(method_name.to_sym) + super + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/csp_helper.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/csp_helper.rb new file mode 100644 index 00000000..e2e065c2 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/csp_helper.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module ActionView + # = Action View CSP Helper + module Helpers #:nodoc: + module CspHelper + # Returns a meta tag "csp-nonce" with the per-session nonce value + # for allowing inline + # + # +html_options+ may be a hash of attributes for the \ + # + # Instead of passing the content as an argument, you can also use a block + # in which case, you pass your +html_options+ as the first parameter. + # + # <%= javascript_tag defer: 'defer' do -%> + # alert('All is good') + # <% end -%> + # + # If you have a content security policy enabled then you can add an automatic + # nonce value by passing +nonce: true+ as part of +html_options+. Example: + # + # <%= javascript_tag nonce: true do -%> + # alert('All is good') + # <% end -%> + def javascript_tag(content_or_options_with_block = nil, html_options = {}, &block) + content = + if block_given? + html_options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash) + capture(&block) + else + content_or_options_with_block + end + + if html_options[:nonce] == true + html_options[:nonce] = content_security_policy_nonce + end + + content_tag("script".freeze, javascript_cdata_section(content), html_options) + end + + def javascript_cdata_section(content) #:nodoc: + "\n//#{cdata_section("\n#{content}\n//")}\n".html_safe + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/number_helper.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/number_helper.rb new file mode 100644 index 00000000..4b53b8fe --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/number_helper.rb @@ -0,0 +1,451 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/string/output_safety" +require "active_support/number_helper" + +module ActionView + # = Action View Number Helpers + module Helpers #:nodoc: + # Provides methods for converting numbers into formatted strings. + # Methods are provided for phone numbers, currency, percentage, + # precision, positional notation, file size and pretty printing. + # + # Most methods expect a +number+ argument, and will return it + # unchanged if can't be converted into a valid number. + module NumberHelper + # Raised when argument +number+ param given to the helpers is invalid and + # the option :raise is set to +true+. + class InvalidNumberError < StandardError + attr_accessor :number + def initialize(number) + @number = number + end + end + + # Formats a +number+ into a phone number (US by default e.g., (555) + # 123-9876). You can customize the format in the +options+ hash. + # + # ==== Options + # + # * :area_code - Adds parentheses around the area code. + # * :delimiter - Specifies the delimiter to use + # (defaults to "-"). + # * :extension - Specifies an extension to add to the + # end of the generated number. + # * :country_code - Sets the country code for the phone + # number. + # * :pattern - Specifies how the number is divided into three + # groups with the custom regexp to override the default format. + # * :raise - If true, raises +InvalidNumberError+ when + # the argument is invalid. + # + # ==== Examples + # + # number_to_phone(5551234) # => 555-1234 + # number_to_phone("5551234") # => 555-1234 + # number_to_phone(1235551234) # => 123-555-1234 + # number_to_phone(1235551234, area_code: true) # => (123) 555-1234 + # number_to_phone(1235551234, delimiter: " ") # => 123 555 1234 + # number_to_phone(1235551234, area_code: true, extension: 555) # => (123) 555-1234 x 555 + # number_to_phone(1235551234, country_code: 1) # => +1-123-555-1234 + # number_to_phone("123a456") # => 123a456 + # number_to_phone("1234a567", raise: true) # => InvalidNumberError + # + # number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: ".") + # # => +1.123.555.1234 x 1343 + # + # number_to_phone(75561234567, pattern: /(\d{1,4})(\d{4})(\d{4})$/, area_code: true) + # # => "(755) 6123-4567" + # number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/)) + # # => "133-1234-5678" + def number_to_phone(number, options = {}) + return unless number + options = options.symbolize_keys + + parse_float(number, true) if options.delete(:raise) + ERB::Util.html_escape(ActiveSupport::NumberHelper.number_to_phone(number, options)) + end + + # Formats a +number+ into a currency string (e.g., $13.65). You + # can customize the format in the +options+ hash. + # + # The currency unit and number formatting of the current locale will be used + # unless otherwise specified in the provided options. No currency conversion + # is performed. If the user is given a way to change their locale, they will + # also be able to change the relative value of the currency displayed with + # this helper. If your application will ever support multiple locales, you + # may want to specify a constant :locale option or consider + # using a library capable of currency conversion. + # + # ==== Options + # + # * :locale - Sets the locale to be used for formatting + # (defaults to current locale). + # * :precision - Sets the level of precision (defaults + # to 2). + # * :unit - Sets the denomination of the currency + # (defaults to "$"). + # * :separator - Sets the separator between the units + # (defaults to "."). + # * :delimiter - Sets the thousands delimiter (defaults + # to ","). + # * :format - Sets the format for non-negative numbers + # (defaults to "%u%n"). Fields are %u for the + # currency, and %n for the number. + # * :negative_format - Sets the format for negative + # numbers (defaults to prepending a hyphen to the formatted + # number given by :format). Accepts the same fields + # than :format, except %n is here the + # absolute value of the number. + # * :raise - If true, raises +InvalidNumberError+ when + # the argument is invalid. + # + # ==== Examples + # + # number_to_currency(1234567890.50) # => $1,234,567,890.50 + # number_to_currency(1234567890.506) # => $1,234,567,890.51 + # number_to_currency(1234567890.506, precision: 3) # => $1,234,567,890.506 + # number_to_currency(1234567890.506, locale: :fr) # => 1 234 567 890,51 € + # number_to_currency("123a456") # => $123a456 + # + # number_to_currency("123a456", raise: true) # => InvalidNumberError + # + # number_to_currency(-1234567890.50, negative_format: "(%u%n)") + # # => ($1,234,567,890.50) + # number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "") + # # => R$1234567890,50 + # number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "", format: "%n %u") + # # => 1234567890,50 R$ + def number_to_currency(number, options = {}) + delegate_number_helper_method(:number_to_currency, number, options) + end + + # Formats a +number+ as a percentage string (e.g., 65%). You can + # customize the format in the +options+ hash. + # + # ==== Options + # + # * :locale - Sets the locale to be used for formatting + # (defaults to current locale). + # * :precision - Sets the precision of the number + # (defaults to 3). + # * :significant - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional + # digits (defaults to +false+). + # * :separator - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * :delimiter - Sets the thousands delimiter (defaults + # to ""). + # * :strip_insignificant_zeros - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +false+). + # * :format - Specifies the format of the percentage + # string The number field is %n (defaults to "%n%"). + # * :raise - If true, raises +InvalidNumberError+ when + # the argument is invalid. + # + # ==== Examples + # + # number_to_percentage(100) # => 100.000% + # number_to_percentage("98") # => 98.000% + # number_to_percentage(100, precision: 0) # => 100% + # number_to_percentage(1000, delimiter: '.', separator: ',') # => 1.000,000% + # number_to_percentage(302.24398923423, precision: 5) # => 302.24399% + # number_to_percentage(1000, locale: :fr) # => 1 000,000% + # number_to_percentage("98a") # => 98a% + # number_to_percentage(100, format: "%n %") # => 100.000 % + # + # number_to_percentage("98a", raise: true) # => InvalidNumberError + def number_to_percentage(number, options = {}) + delegate_number_helper_method(:number_to_percentage, number, options) + end + + # Formats a +number+ with grouped thousands using +delimiter+ + # (e.g., 12,324). You can customize the format in the +options+ + # hash. + # + # ==== Options + # + # * :locale - Sets the locale to be used for formatting + # (defaults to current locale). + # * :delimiter - Sets the thousands delimiter (defaults + # to ","). + # * :separator - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * :delimiter_pattern - Sets a custom regular expression used for + # deriving the placement of delimiter. Helpful when using currency formats + # like INR. + # * :raise - If true, raises +InvalidNumberError+ when + # the argument is invalid. + # + # ==== Examples + # + # number_with_delimiter(12345678) # => 12,345,678 + # number_with_delimiter("123456") # => 123,456 + # number_with_delimiter(12345678.05) # => 12,345,678.05 + # number_with_delimiter(12345678, delimiter: ".") # => 12.345.678 + # number_with_delimiter(12345678, delimiter: ",") # => 12,345,678 + # number_with_delimiter(12345678.05, separator: " ") # => 12,345,678 05 + # number_with_delimiter(12345678.05, locale: :fr) # => 12 345 678,05 + # number_with_delimiter("112a") # => 112a + # number_with_delimiter(98765432.98, delimiter: " ", separator: ",") + # # => 98 765 432,98 + # + # number_with_delimiter("123456.78", + # delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/) # => "1,23,456.78" + # + # number_with_delimiter("112a", raise: true) # => raise InvalidNumberError + def number_with_delimiter(number, options = {}) + delegate_number_helper_method(:number_to_delimited, number, options) + end + + # Formats a +number+ with the specified level of + # :precision (e.g., 112.32 has a precision of 2 if + # +:significant+ is +false+, and 5 if +:significant+ is +true+). + # You can customize the format in the +options+ hash. + # + # ==== Options + # + # * :locale - Sets the locale to be used for formatting + # (defaults to current locale). + # * :precision - Sets the precision of the number + # (defaults to 3). + # * :significant - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional + # digits (defaults to +false+). + # * :separator - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * :delimiter - Sets the thousands delimiter (defaults + # to ""). + # * :strip_insignificant_zeros - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +false+). + # * :raise - If true, raises +InvalidNumberError+ when + # the argument is invalid. + # + # ==== Examples + # + # number_with_precision(111.2345) # => 111.235 + # number_with_precision(111.2345, precision: 2) # => 111.23 + # number_with_precision(13, precision: 5) # => 13.00000 + # number_with_precision(389.32314, precision: 0) # => 389 + # number_with_precision(111.2345, significant: true) # => 111 + # number_with_precision(111.2345, precision: 1, significant: true) # => 100 + # number_with_precision(13, precision: 5, significant: true) # => 13.000 + # number_with_precision(111.234, locale: :fr) # => 111,234 + # + # number_with_precision(13, precision: 5, significant: true, strip_insignificant_zeros: true) + # # => 13 + # + # number_with_precision(389.32314, precision: 4, significant: true) # => 389.3 + # number_with_precision(1111.2345, precision: 2, separator: ',', delimiter: '.') + # # => 1.111,23 + def number_with_precision(number, options = {}) + delegate_number_helper_method(:number_to_rounded, number, options) + end + + # Formats the bytes in +number+ into a more understandable + # representation (e.g., giving it 1500 yields 1.5 KB). This + # method is useful for reporting file sizes to users. You can + # customize the format in the +options+ hash. + # + # See number_to_human if you want to pretty-print a + # generic number. + # + # ==== Options + # + # * :locale - Sets the locale to be used for formatting + # (defaults to current locale). + # * :precision - Sets the precision of the number + # (defaults to 3). + # * :significant - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional + # digits (defaults to +true+) + # * :separator - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * :delimiter - Sets the thousands delimiter (defaults + # to ""). + # * :strip_insignificant_zeros - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +true+) + # * :raise - If true, raises +InvalidNumberError+ when + # the argument is invalid. + # + # ==== Examples + # + # number_to_human_size(123) # => 123 Bytes + # number_to_human_size(1234) # => 1.21 KB + # number_to_human_size(12345) # => 12.1 KB + # number_to_human_size(1234567) # => 1.18 MB + # number_to_human_size(1234567890) # => 1.15 GB + # number_to_human_size(1234567890123) # => 1.12 TB + # number_to_human_size(1234567890123456) # => 1.1 PB + # number_to_human_size(1234567890123456789) # => 1.07 EB + # number_to_human_size(1234567, precision: 2) # => 1.2 MB + # number_to_human_size(483989, precision: 2) # => 470 KB + # number_to_human_size(1234567, precision: 2, separator: ',') # => 1,2 MB + # number_to_human_size(1234567890123, precision: 5) # => "1.1228 TB" + # number_to_human_size(524288000, precision: 5) # => "500 MB" + def number_to_human_size(number, options = {}) + delegate_number_helper_method(:number_to_human_size, number, options) + end + + # Pretty prints (formats and approximates) a number in a way it + # is more readable by humans (eg.: 1200000000 becomes "1.2 + # Billion"). This is useful for numbers that can get very large + # (and too hard to read). + # + # See number_to_human_size if you want to print a file + # size. + # + # You can also define your own unit-quantifier names if you want + # to use other decimal units (eg.: 1500 becomes "1.5 + # kilometers", 0.150 becomes "150 milliliters", etc). You may + # define a wide range of unit quantifiers, even fractional ones + # (centi, deci, mili, etc). + # + # ==== Options + # + # * :locale - Sets the locale to be used for formatting + # (defaults to current locale). + # * :precision - Sets the precision of the number + # (defaults to 3). + # * :significant - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional + # digits (defaults to +true+) + # * :separator - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * :delimiter - Sets the thousands delimiter (defaults + # to ""). + # * :strip_insignificant_zeros - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +true+) + # * :units - A Hash of unit quantifier names. Or a + # string containing an i18n scope where to find this hash. It + # might have the following keys: + # * *integers*: :unit, :ten, + # :hundred, :thousand, :million, + # :billion, :trillion, + # :quadrillion + # * *fractionals*: :deci, :centi, + # :mili, :micro, :nano, + # :pico, :femto + # * :format - Sets the format of the output string + # (defaults to "%n %u"). The field types are: + # * %u - The quantifier (ex.: 'thousand') + # * %n - The number + # * :raise - If true, raises +InvalidNumberError+ when + # the argument is invalid. + # + # ==== Examples + # + # number_to_human(123) # => "123" + # number_to_human(1234) # => "1.23 Thousand" + # number_to_human(12345) # => "12.3 Thousand" + # number_to_human(1234567) # => "1.23 Million" + # number_to_human(1234567890) # => "1.23 Billion" + # number_to_human(1234567890123) # => "1.23 Trillion" + # number_to_human(1234567890123456) # => "1.23 Quadrillion" + # number_to_human(1234567890123456789) # => "1230 Quadrillion" + # number_to_human(489939, precision: 2) # => "490 Thousand" + # number_to_human(489939, precision: 4) # => "489.9 Thousand" + # number_to_human(1234567, precision: 4, + # significant: false) # => "1.2346 Million" + # number_to_human(1234567, precision: 1, + # separator: ',', + # significant: false) # => "1,2 Million" + # + # number_to_human(500000000, precision: 5) # => "500 Million" + # number_to_human(12345012345, significant: false) # => "12.345 Billion" + # + # Non-significant zeros after the decimal separator are stripped + # out by default (set :strip_insignificant_zeros to + # +false+ to change that): + # + # number_to_human(12.00001) # => "12" + # number_to_human(12.00001, strip_insignificant_zeros: false) # => "12.0" + # + # ==== Custom Unit Quantifiers + # + # You can also use your own custom unit quantifiers: + # number_to_human(500000, units: {unit: "ml", thousand: "lt"}) # => "500 lt" + # + # If in your I18n locale you have: + # distance: + # centi: + # one: "centimeter" + # other: "centimeters" + # unit: + # one: "meter" + # other: "meters" + # thousand: + # one: "kilometer" + # other: "kilometers" + # billion: "gazillion-distance" + # + # Then you could do: + # + # number_to_human(543934, units: :distance) # => "544 kilometers" + # number_to_human(54393498, units: :distance) # => "54400 kilometers" + # number_to_human(54393498000, units: :distance) # => "54.4 gazillion-distance" + # number_to_human(343, units: :distance, precision: 1) # => "300 meters" + # number_to_human(1, units: :distance) # => "1 meter" + # number_to_human(0.34, units: :distance) # => "34 centimeters" + # + def number_to_human(number, options = {}) + delegate_number_helper_method(:number_to_human, number, options) + end + + private + + def delegate_number_helper_method(method, number, options) + return unless number + options = escape_unsafe_options(options.symbolize_keys) + + wrap_with_output_safety_handling(number, options.delete(:raise)) { + ActiveSupport::NumberHelper.public_send(method, number, options) + } + end + + def escape_unsafe_options(options) + options[:format] = ERB::Util.html_escape(options[:format]) if options[:format] + options[:negative_format] = ERB::Util.html_escape(options[:negative_format]) if options[:negative_format] + options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator] + options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter] + options[:unit] = ERB::Util.html_escape(options[:unit]) if options[:unit] && !options[:unit].html_safe? + options[:units] = escape_units(options[:units]) if options[:units] && Hash === options[:units] + options + end + + def escape_units(units) + Hash[units.map do |k, v| + [k, ERB::Util.html_escape(v)] + end] + end + + def wrap_with_output_safety_handling(number, raise_on_invalid, &block) + valid_float = valid_float?(number) + raise InvalidNumberError, number if raise_on_invalid && !valid_float + + formatted_number = yield + + if valid_float || number.html_safe? + formatted_number.html_safe + else + formatted_number + end + end + + def valid_float?(number) + !parse_float(number, false).nil? + end + + def parse_float(number, raise_error) + Float(number) + rescue ArgumentError, TypeError + raise InvalidNumberError, number if raise_error + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/output_safety_helper.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/output_safety_helper.rb new file mode 100644 index 00000000..279cde5e --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/output_safety_helper.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/output_safety" + +module ActionView #:nodoc: + # = Action View Raw Output Helper + module Helpers #:nodoc: + module OutputSafetyHelper + # This method outputs without escaping a string. Since escaping tags is + # now default, this can be used when you don't want Rails to automatically + # escape tags. This is not recommended if the data is coming from the user's + # input. + # + # For example: + # + # raw @user.name + # # => 'Jimmy Tables' + def raw(stringish) + stringish.to_s.html_safe + end + + # This method returns an HTML safe string similar to what Array#join + # would return. The array is flattened, and all items, including + # the supplied separator, are HTML escaped unless they are HTML + # safe, and the returned string is marked as HTML safe. + # + # safe_join([raw("

    foo

    "), "

    bar

    "], "
    ") + # # => "

    foo

    <br /><p>bar</p>" + # + # safe_join([raw("

    foo

    "), raw("

    bar

    ")], raw("
    ")) + # # => "

    foo


    bar

    " + # + def safe_join(array, sep = $,) + sep = ERB::Util.unwrapped_html_escape(sep) + + array.flatten.map! { |i| ERB::Util.unwrapped_html_escape(i) }.join(sep).html_safe + end + + # Converts the array to a comma-separated sentence where the last element is + # joined by the connector word. This is the html_safe-aware version of + # ActiveSupport's {Array#to_sentence}[http://api.rubyonrails.org/classes/Array.html#method-i-to_sentence]. + # + def to_sentence(array, options = {}) + options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale) + + default_connectors = { + words_connector: ", ", + two_words_connector: " and ", + last_word_connector: ", and " + } + if defined?(I18n) + i18n_connectors = I18n.translate(:'support.array', locale: options[:locale], default: {}) + default_connectors.merge!(i18n_connectors) + end + options = default_connectors.merge!(options) + + case array.length + when 0 + "".html_safe + when 1 + ERB::Util.html_escape(array[0]) + when 2 + safe_join([array[0], array[1]], options[:two_words_connector]) + else + safe_join([safe_join(array[0...-1], options[:words_connector]), options[:last_word_connector], array[-1]], nil) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/record_tag_helper.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/record_tag_helper.rb new file mode 100644 index 00000000..a6953ee9 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/record_tag_helper.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module ActionView + module Helpers #:nodoc: + module RecordTagHelper + def div_for(*) # :nodoc: + raise NoMethodError, "The `div_for` method has been removed from " \ + "Rails. To continue using it, add the `record_tag_helper` gem to " \ + "your Gemfile:\n" \ + " gem 'record_tag_helper', '~> 1.0'\n" \ + "Consult the Rails upgrade guide for details." + end + + def content_tag_for(*) # :nodoc: + raise NoMethodError, "The `content_tag_for` method has been removed from " \ + "Rails. To continue using it, add the `record_tag_helper` gem to " \ + "your Gemfile:\n" \ + " gem 'record_tag_helper', '~> 1.0'\n" \ + "Consult the Rails upgrade guide for details." + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/rendering_helper.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/rendering_helper.rb new file mode 100644 index 00000000..1e12aa27 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/rendering_helper.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +module ActionView + module Helpers #:nodoc: + # = Action View Rendering + # + # Implements methods that allow rendering from a view context. + # In order to use this module, all you need is to implement + # view_renderer that returns an ActionView::Renderer object. + module RenderingHelper + # Returns the result of a render that's dictated by the options hash. The primary options are: + # + # * :partial - See ActionView::PartialRenderer. + # * :file - Renders an explicit template file (this used to be the old default), add :locals to pass in those. + # * :inline - Renders an inline template similar to how it's done in the controller. + # * :plain - Renders the text passed in out. Setting the content + # type as text/plain. + # * :html - Renders the HTML safe string passed in out, otherwise + # performs HTML escape on the string first. Setting the content type as + # text/html. + # * :body - Renders the text passed in, and inherits the content + # type of text/plain from ActionDispatch::Response + # object. + # + # If no options hash is passed or :update specified, the default is to render a partial and use the second parameter + # as the locals hash. + def render(options = {}, locals = {}, &block) + case options + when Hash + if block_given? + view_renderer.render_partial(self, options.merge(partial: options[:layout]), &block) + else + view_renderer.render(self, options) + end + else + view_renderer.render_partial(self, partial: options, locals: locals, &block) + end + end + + # Overwrites _layout_for in the context object so it supports the case a block is + # passed to a partial. Returns the contents that are yielded to a layout, given a + # name or a block. + # + # You can think of a layout as a method that is called with a block. If the user calls + # yield :some_name, the block, by default, returns content_for(:some_name). + # If the user calls simply +yield+, the default block returns content_for(:layout). + # + # The user can override this default by passing a block to the layout: + # + # # The template + # <%= render layout: "my_layout" do %> + # Content + # <% end %> + # + # # The layout + # + # <%= yield %> + # + # + # In this case, instead of the default block, which would return content_for(:layout), + # this method returns the block that was passed in to render :layout, and the response + # would be + # + # + # Content + # + # + # Finally, the block can take block arguments, which can be passed in by +yield+: + # + # # The template + # <%= render layout: "my_layout" do |customer| %> + # Hello <%= customer.name %> + # <% end %> + # + # # The layout + # + # <%= yield Struct.new(:name).new("David") %> + # + # + # In this case, the layout would receive the block passed into render :layout, + # and the struct specified would be passed into the block as an argument. The result + # would be + # + # + # Hello David + # + # + def _layout_for(*args, &block) + name = args.first + + if block && !name.is_a?(Symbol) + capture(*args, &block) + else + super + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/sanitize_helper.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/sanitize_helper.rb new file mode 100644 index 00000000..275a2dff --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/sanitize_helper.rb @@ -0,0 +1,177 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/try" +require "rails-html-sanitizer" + +module ActionView + # = Action View Sanitize Helpers + module Helpers #:nodoc: + # The SanitizeHelper module provides a set of methods for scrubbing text of undesired HTML elements. + # These helper methods extend Action View making them callable within your template files. + module SanitizeHelper + extend ActiveSupport::Concern + # Sanitizes HTML input, stripping all tags and attributes that aren't whitelisted. + # + # It also strips href/src attributes with unsafe protocols like + # javascript:, while also protecting against attempts to use Unicode, + # ASCII, and hex character references to work around these protocol filters. + # All special characters will be escaped. + # + # The default sanitizer is Rails::Html::WhiteListSanitizer. See {Rails HTML + # Sanitizers}[https://github.com/rails/rails-html-sanitizer] for more information. + # + # Custom sanitization rules can also be provided. + # + # Please note that sanitizing user-provided text does not guarantee that the + # resulting markup is valid or even well-formed. + # + # ==== Options + # + # * :tags - An array of allowed tags. + # * :attributes - An array of allowed attributes. + # * :scrubber - A {Rails::Html scrubber}[https://github.com/rails/rails-html-sanitizer] + # or {Loofah::Scrubber}[https://github.com/flavorjones/loofah] object that + # defines custom sanitization rules. A custom scrubber takes precedence over + # custom tags and attributes. + # + # ==== Examples + # + # Normal use: + # + # <%= sanitize @comment.body %> + # + # Providing custom whitelisted tags and attributes: + # + # <%= sanitize @comment.body, tags: %w(strong em a), attributes: %w(href) %> + # + # Providing a custom Rails::Html scrubber: + # + # class CommentScrubber < Rails::Html::PermitScrubber + # def initialize + # super + # self.tags = %w( form script comment blockquote ) + # self.attributes = %w( style ) + # end + # + # def skip_node?(node) + # node.text? + # end + # end + # + # <%= sanitize @comment.body, scrubber: CommentScrubber.new %> + # + # See {Rails HTML Sanitizer}[https://github.com/rails/rails-html-sanitizer] for + # documentation about Rails::Html scrubbers. + # + # Providing a custom Loofah::Scrubber: + # + # scrubber = Loofah::Scrubber.new do |node| + # node.remove if node.name == 'script' + # end + # + # <%= sanitize @comment.body, scrubber: scrubber %> + # + # See {Loofah's documentation}[https://github.com/flavorjones/loofah] for more + # information about defining custom Loofah::Scrubber objects. + # + # To set the default allowed tags or attributes across your application: + # + # # In config/application.rb + # config.action_view.sanitized_allowed_tags = ['strong', 'em', 'a'] + # config.action_view.sanitized_allowed_attributes = ['href', 'title'] + def sanitize(html, options = {}) + self.class.white_list_sanitizer.sanitize(html, options).try(:html_safe) + end + + # Sanitizes a block of CSS code. Used by +sanitize+ when it comes across a style attribute. + def sanitize_css(style) + self.class.white_list_sanitizer.sanitize_css(style) + end + + # Strips all HTML tags from +html+, including comments and special characters. + # + # strip_tags("Strip these tags!") + # # => Strip these tags! + # + # strip_tags("Bold no more! See more here...") + # # => Bold no more! See more here... + # + # strip_tags("
    Welcome to my website!
    ") + # # => Welcome to my website! + # + # strip_tags("> A quote from Smith & Wesson") + # # => > A quote from Smith & Wesson + def strip_tags(html) + self.class.full_sanitizer.sanitize(html) + end + + # Strips all link tags from +html+ leaving just the link text. + # + # strip_links('Ruby on Rails') + # # => Ruby on Rails + # + # strip_links('Please e-mail me at me@email.com.') + # # => Please e-mail me at me@email.com. + # + # strip_links('Blog: Visit.') + # # => Blog: Visit. + # + # strip_links('<malformed & link') + # # => <malformed & link + def strip_links(html) + self.class.link_sanitizer.sanitize(html) + end + + module ClassMethods #:nodoc: + attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer + + # Vendors the full, link and white list sanitizers. + # Provided strictly for compatibility and can be removed in Rails 5.1. + def sanitizer_vendor + Rails::Html::Sanitizer + end + + def sanitized_allowed_tags + sanitizer_vendor.white_list_sanitizer.allowed_tags + end + + def sanitized_allowed_attributes + sanitizer_vendor.white_list_sanitizer.allowed_attributes + end + + # Gets the Rails::Html::FullSanitizer instance used by +strip_tags+. Replace with + # any object that responds to +sanitize+. + # + # class Application < Rails::Application + # config.action_view.full_sanitizer = MySpecialSanitizer.new + # end + # + def full_sanitizer + @full_sanitizer ||= sanitizer_vendor.full_sanitizer.new + end + + # Gets the Rails::Html::LinkSanitizer instance used by +strip_links+. + # Replace with any object that responds to +sanitize+. + # + # class Application < Rails::Application + # config.action_view.link_sanitizer = MySpecialSanitizer.new + # end + # + def link_sanitizer + @link_sanitizer ||= sanitizer_vendor.link_sanitizer.new + end + + # Gets the Rails::Html::WhiteListSanitizer instance used by sanitize and +sanitize_css+. + # Replace with any object that responds to +sanitize+. + # + # class Application < Rails::Application + # config.action_view.white_list_sanitizer = MySpecialSanitizer.new + # end + # + def white_list_sanitizer + @white_list_sanitizer ||= sanitizer_vendor.white_list_sanitizer.new + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tag_helper.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tag_helper.rb new file mode 100644 index 00000000..a6cec3f6 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tag_helper.rb @@ -0,0 +1,313 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/output_safety" +require "set" + +module ActionView + # = Action View Tag Helpers + module Helpers #:nodoc: + # Provides methods to generate HTML tags programmatically both as a modern + # HTML5 compliant builder style and legacy XHTML compliant tags. + module TagHelper + extend ActiveSupport::Concern + include CaptureHelper + include OutputSafetyHelper + + BOOLEAN_ATTRIBUTES = %w(allowfullscreen async autofocus autoplay checked + compact controls declare default defaultchecked + defaultmuted defaultselected defer disabled + enabled formnovalidate hidden indeterminate inert + ismap itemscope loop multiple muted nohref + noresize noshade novalidate nowrap open + pauseonexit readonly required reversed scoped + seamless selected sortable truespeed typemustmatch + visible).to_set + + BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym)) + + TAG_PREFIXES = ["aria", "data", :aria, :data].to_set + + PRE_CONTENT_STRINGS = Hash.new { "" } + PRE_CONTENT_STRINGS[:textarea] = "\n" + PRE_CONTENT_STRINGS["textarea"] = "\n" + + class TagBuilder #:nodoc: + include CaptureHelper + include OutputSafetyHelper + + VOID_ELEMENTS = %i(area base br col embed hr img input keygen link meta param source track wbr).to_set + + def initialize(view_context) + @view_context = view_context + end + + def tag_string(name, content = nil, escape_attributes: true, **options, &block) + content = @view_context.capture(self, &block) if block_given? + if VOID_ELEMENTS.include?(name) && content.nil? + "<#{name.to_s.dasherize}#{tag_options(options, escape_attributes)}>".html_safe + else + content_tag_string(name.to_s.dasherize, content || "", options, escape_attributes) + end + end + + def content_tag_string(name, content, options, escape = true) + tag_options = tag_options(options, escape) if options + content = ERB::Util.unwrapped_html_escape(content) if escape + "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}".html_safe + end + + def tag_options(options, escape = true) + return if options.blank? + output = "".dup + sep = " " + options.each_pair do |key, value| + if TAG_PREFIXES.include?(key) && value.is_a?(Hash) + value.each_pair do |k, v| + next if v.nil? + output << sep + output << prefix_tag_option(key, k, v, escape) + end + elsif BOOLEAN_ATTRIBUTES.include?(key) + if value + output << sep + output << boolean_tag_option(key) + end + elsif !value.nil? + output << sep + output << tag_option(key, value, escape) + end + end + output unless output.empty? + end + + def boolean_tag_option(key) + %(#{key}="#{key}") + end + + def tag_option(key, value, escape) + if value.is_a?(Array) + value = escape ? safe_join(value, " ".freeze) : value.join(" ".freeze) + else + value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s + end + %(#{key}="#{value.gsub('"'.freeze, '"'.freeze)}") + end + + private + def prefix_tag_option(prefix, key, value, escape) + key = "#{prefix}-#{key.to_s.dasherize}" + unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal) + value = value.to_json + end + tag_option(key, value, escape) + end + + def respond_to_missing?(*args) + true + end + + def method_missing(called, *args, &block) + tag_string(called, *args, &block) + end + end + + # Returns an HTML tag. + # + # === Building HTML tags + # + # Builds HTML5 compliant tags with a tag proxy. Every tag can be built with: + # + # tag.(optional content, options) + # + # where tag name can be e.g. br, div, section, article, or any tag really. + # + # ==== Passing content + # + # Tags can pass content to embed within it: + # + # tag.h1 'All titles fit to print' # =>

    All titles fit to print

    + # + # tag.div tag.p('Hello world!') # =>

    Hello world!

    + # + # Content can also be captured with a block, which is useful in templates: + # + # <%= tag.p do %> + # The next great American novel starts here. + # <% end %> + # # =>

    The next great American novel starts here.

    + # + # ==== Options + # + # Use symbol keyed options to add attributes to the generated tag. + # + # tag.section class: %w( kitties puppies ) + # # =>
    + # + # tag.section id: dom_id(@post) + # # =>
    + # + # Pass +true+ for any attributes that can render with no values, like +disabled+ and +readonly+. + # + # tag.input type: 'text', disabled: true + # # => + # + # HTML5 data-* attributes can be set with a single +data+ key + # pointing to a hash of sub-attributes. + # + # To play nicely with JavaScript conventions, sub-attributes are dasherized. + # + # tag.article data: { user_id: 123 } + # # =>
    + # + # Thus data-user-id can be accessed as dataset.userId. + # + # Data attribute values are encoded to JSON, with the exception of strings, symbols and + # BigDecimals. + # This may come in handy when using jQuery's HTML5-aware .data() + # from 1.4.3. + # + # tag.div data: { city_state: %w( Chicago IL ) } + # # =>
    + # + # The generated attributes are escaped by default. This can be disabled using + # +escape_attributes+. + # + # tag.img src: 'open & shut.png' + # # => + # + # tag.img src: 'open & shut.png', escape_attributes: false + # # => + # + # The tag builder respects + # {HTML5 void elements}[https://www.w3.org/TR/html5/syntax.html#void-elements] + # if no content is passed, and omits closing tags for those elements. + # + # # A standard element: + # tag.div # =>
    + # + # # A void element: + # tag.br # =>
    + # + # === Legacy syntax + # + # The following format is for legacy syntax support. It will be deprecated in future versions of Rails. + # + # tag(name, options = nil, open = false, escape = true) + # + # It returns an empty HTML tag of type +name+ which by default is XHTML + # compliant. Set +open+ to true to create an open tag compatible + # with HTML 4.0 and below. Add HTML attributes by passing an attributes + # hash to +options+. Set +escape+ to false to disable attribute value + # escaping. + # + # ==== Options + # + # You can use symbols or strings for the attribute names. + # + # Use +true+ with boolean attributes that can render with no value, like + # +disabled+ and +readonly+. + # + # HTML5 data-* attributes can be set with a single +data+ key + # pointing to a hash of sub-attributes. + # + # ==== Examples + # + # tag("br") + # # =>
    + # + # tag("br", nil, true) + # # =>
    + # + # tag("input", type: 'text', disabled: true) + # # => + # + # tag("input", type: 'text', class: ["strong", "highlight"]) + # # => + # + # tag("img", src: "open & shut.png") + # # => + # + # tag("img", {src: "open & shut.png"}, false, false) + # # => + # + # tag("div", data: {name: 'Stephen', city_state: %w(Chicago IL)}) + # # =>
    + def tag(name = nil, options = nil, open = false, escape = true) + if name.nil? + tag_builder + else + "<#{name}#{tag_builder.tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe + end + end + + # Returns an HTML block tag of type +name+ surrounding the +content+. Add + # HTML attributes by passing an attributes hash to +options+. + # Instead of passing the content as an argument, you can also use a block + # in which case, you pass your +options+ as the second parameter. + # Set escape to false to disable attribute value escaping. + # Note: this is legacy syntax, see +tag+ method description for details. + # + # ==== Options + # The +options+ hash can be used with attributes with no value like (disabled and + # readonly), which you can give a value of true in the +options+ hash. You can use + # symbols or strings for the attribute names. + # + # ==== Examples + # content_tag(:p, "Hello world!") + # # =>

    Hello world!

    + # content_tag(:div, content_tag(:p, "Hello world!"), class: "strong") + # # =>

    Hello world!

    + # content_tag(:div, "Hello world!", class: ["strong", "highlight"]) + # # =>
    Hello world!
    + # content_tag("select", options, multiple: true) + # # => + # + # <%= content_tag :div, class: "strong" do -%> + # Hello world! + # <% end -%> + # # =>
    Hello world!
    + def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block) + if block_given? + options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash) + tag_builder.content_tag_string(name, capture(&block), options, escape) + else + tag_builder.content_tag_string(name, content_or_options_with_block, options, escape) + end + end + + # Returns a CDATA section with the given +content+. CDATA sections + # are used to escape blocks of text containing characters which would + # otherwise be recognized as markup. CDATA sections begin with the string + # and end with (and may not contain) the string ]]>. + # + # cdata_section("") + # # => ]]> + # + # cdata_section(File.read("hello_world.txt")) + # # => + # + # cdata_section("hello]]>world") + # # => world]]> + def cdata_section(content) + splitted = content.to_s.gsub(/\]\]\>/, "]]]]>") + "".html_safe + end + + # Returns an escaped version of +html+ without affecting existing escaped entities. + # + # escape_once("1 < 2 & 3") + # # => "1 < 2 & 3" + # + # escape_once("<< Accept & Checkout") + # # => "<< Accept & Checkout" + def escape_once(html) + ERB::Util.html_escape_once(html) + end + + private + def tag_builder + @tag_builder ||= TagBuilder.new(self) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags.rb new file mode 100644 index 00000000..566668b9 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module ActionView + module Helpers #:nodoc: + module Tags #:nodoc: + extend ActiveSupport::Autoload + + eager_autoload do + autoload :Base + autoload :Translator + autoload :CheckBox + autoload :CollectionCheckBoxes + autoload :CollectionRadioButtons + autoload :CollectionSelect + autoload :ColorField + autoload :DateField + autoload :DateSelect + autoload :DatetimeField + autoload :DatetimeLocalField + autoload :DatetimeSelect + autoload :EmailField + autoload :FileField + autoload :GroupedCollectionSelect + autoload :HiddenField + autoload :Label + autoload :MonthField + autoload :NumberField + autoload :PasswordField + autoload :RadioButton + autoload :RangeField + autoload :SearchField + autoload :Select + autoload :TelField + autoload :TextArea + autoload :TextField + autoload :TimeField + autoload :TimeSelect + autoload :TimeZoneSelect + autoload :UrlField + autoload :WeekField + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/base.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/base.rb new file mode 100644 index 00000000..fed908fc --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/base.rb @@ -0,0 +1,192 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class Base # :nodoc: + include Helpers::ActiveModelInstanceTag, Helpers::TagHelper, Helpers::FormTagHelper + include FormOptionsHelper + + attr_reader :object + + def initialize(object_name, method_name, template_object, options = {}) + @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup + @template_object = template_object + + @object_name.sub!(/\[\]$/, "") || @object_name.sub!(/\[\]\]$/, "]") + @object = retrieve_object(options.delete(:object)) + @skip_default_ids = options.delete(:skip_default_ids) + @allow_method_names_outside_object = options.delete(:allow_method_names_outside_object) + @options = options + + if Regexp.last_match + @generate_indexed_names = true + @auto_index = retrieve_autoindex(Regexp.last_match.pre_match) + else + @generate_indexed_names = false + @auto_index = nil + end + end + + # This is what child classes implement. + def render + raise NotImplementedError, "Subclasses must implement a render method" + end + + private + + def value + if @allow_method_names_outside_object + object.public_send @method_name if object && object.respond_to?(@method_name) + else + object.public_send @method_name if object + end + end + + def value_before_type_cast + unless object.nil? + method_before_type_cast = @method_name + "_before_type_cast" + + if value_came_from_user? && object.respond_to?(method_before_type_cast) + object.public_send(method_before_type_cast) + else + value + end + end + end + + def value_came_from_user? + method_name = "#{@method_name}_came_from_user?" + !object.respond_to?(method_name) || object.public_send(method_name) + end + + def retrieve_object(object) + if object + object + elsif @template_object.instance_variable_defined?("@#{@object_name}") + @template_object.instance_variable_get("@#{@object_name}") + end + rescue NameError + # As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil. + nil + end + + def retrieve_autoindex(pre_match) + object = self.object || @template_object.instance_variable_get("@#{pre_match}") + if object && object.respond_to?(:to_param) + object.to_param + else + raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}" + end + end + + def add_default_name_and_id_for_value(tag_value, options) + if tag_value.nil? + add_default_name_and_id(options) + else + specified_id = options["id"] + add_default_name_and_id(options) + + if specified_id.blank? && options["id"].present? + options["id"] += "_#{sanitized_value(tag_value)}" + end + end + end + + def add_default_name_and_id(options) + index = name_and_id_index(options) + options["name"] = options.fetch("name") { tag_name(options["multiple"], index) } + + if generate_ids? + options["id"] = options.fetch("id") { tag_id(index) } + if namespace = options.delete("namespace") + options["id"] = options["id"] ? "#{namespace}_#{options['id']}" : namespace + end + end + end + + def tag_name(multiple = false, index = nil) + # a little duplication to construct less strings + case + when @object_name.empty? + "#{sanitized_method_name}#{"[]" if multiple}" + when index + "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}" + else + "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}" + end + end + + def tag_id(index = nil) + # a little duplication to construct less strings + case + when @object_name.empty? + sanitized_method_name.dup + when index + "#{sanitized_object_name}_#{index}_#{sanitized_method_name}" + else + "#{sanitized_object_name}_#{sanitized_method_name}" + end + end + + def sanitized_object_name + @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "") + end + + def sanitized_method_name + @sanitized_method_name ||= @method_name.sub(/\?$/, "") + end + + def sanitized_value(value) + value.to_s.gsub(/\s/, "_").gsub(/[^-[[:word:]]]/, "").mb_chars.downcase.to_s + end + + def select_content_tag(option_tags, options, html_options) + html_options = html_options.stringify_keys + add_default_name_and_id(html_options) + + if placeholder_required?(html_options) + raise ArgumentError, "include_blank cannot be false for a required field." if options[:include_blank] == false + options[:include_blank] ||= true unless options[:prompt] + end + + value = options.fetch(:selected) { value() } + select = content_tag("select", add_options(option_tags, options, value), html_options) + + if html_options["multiple"] && options.fetch(:include_hidden, true) + tag("input", disabled: html_options["disabled"], name: html_options["name"], type: "hidden", value: "") + select + else + select + end + end + + def placeholder_required?(html_options) + # See https://html.spec.whatwg.org/multipage/forms.html#attr-select-required + html_options["required"] && !html_options["multiple"] && html_options.fetch("size", 1).to_i == 1 + end + + def add_options(option_tags, options, value = nil) + if options[:include_blank] + option_tags = tag_builder.content_tag_string("option", options[:include_blank].kind_of?(String) ? options[:include_blank] : nil, value: "") + "\n" + option_tags + end + if value.blank? && options[:prompt] + option_tags = tag_builder.content_tag_string("option", prompt_text(options[:prompt]), value: "") + "\n" + option_tags + end + option_tags + end + + def name_and_id_index(options) + if options.key?("index") + options.delete("index") || "" + elsif @generate_indexed_names + @auto_index || "" + end + end + + def generate_ids? + !@skip_default_ids + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/check_box.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/check_box.rb new file mode 100644 index 00000000..4327e07c --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/check_box.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require "action_view/helpers/tags/checkable" + +module ActionView + module Helpers + module Tags # :nodoc: + class CheckBox < Base #:nodoc: + include Checkable + + def initialize(object_name, method_name, template_object, checked_value, unchecked_value, options) + @checked_value = checked_value + @unchecked_value = unchecked_value + super(object_name, method_name, template_object, options) + end + + def render + options = @options.stringify_keys + options["type"] = "checkbox" + options["value"] = @checked_value + options["checked"] = "checked" if input_checked?(options) + + if options["multiple"] + add_default_name_and_id_for_value(@checked_value, options) + options.delete("multiple") + else + add_default_name_and_id(options) + end + + include_hidden = options.delete("include_hidden") { true } + checkbox = tag("input", options) + + if include_hidden + hidden = hidden_field_for_checkbox(options) + hidden + checkbox + else + checkbox + end + end + + private + + def checked?(value) + case value + when TrueClass, FalseClass + value == !!@checked_value + when NilClass + false + when String + value == @checked_value + else + if value.respond_to?(:include?) + value.include?(@checked_value) + else + value.to_i == @checked_value.to_i + end + end + end + + def hidden_field_for_checkbox(options) + @unchecked_value ? tag("input", options.slice("name", "disabled", "form").merge!("type" => "hidden", "value" => @unchecked_value)) : "".html_safe + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/checkable.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/checkable.rb new file mode 100644 index 00000000..776fefe7 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/checkable.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + module Checkable # :nodoc: + def input_checked?(options) + if options.has_key?("checked") + checked = options.delete "checked" + checked == true || checked == "checked" + else + checked?(value) + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/collection_check_boxes.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/collection_check_boxes.rb new file mode 100644 index 00000000..45544217 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/collection_check_boxes.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require "action_view/helpers/tags/collection_helpers" + +module ActionView + module Helpers + module Tags # :nodoc: + class CollectionCheckBoxes < Base # :nodoc: + include CollectionHelpers + + class CheckBoxBuilder < Builder # :nodoc: + def check_box(extra_html_options = {}) + html_options = extra_html_options.merge(@input_html_options) + html_options[:multiple] = true + html_options[:skip_default_ids] = false + @template_object.check_box(@object_name, @method_name, html_options, @value, nil) + end + end + + def render(&block) + render_collection_for(CheckBoxBuilder, &block) + end + + private + + def render_component(builder) + builder.check_box + builder.label + end + + def hidden_field_name + "#{super}[]" + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/collection_helpers.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/collection_helpers.rb new file mode 100644 index 00000000..e1ad11bf --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/collection_helpers.rb @@ -0,0 +1,119 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + module CollectionHelpers # :nodoc: + class Builder # :nodoc: + attr_reader :object, :text, :value + + def initialize(template_object, object_name, method_name, object, + sanitized_attribute_name, text, value, input_html_options) + @template_object = template_object + @object_name = object_name + @method_name = method_name + @object = object + @sanitized_attribute_name = sanitized_attribute_name + @text = text + @value = value + @input_html_options = input_html_options + end + + def label(label_html_options = {}, &block) + html_options = @input_html_options.slice(:index, :namespace).merge(label_html_options) + html_options[:for] ||= @input_html_options[:id] if @input_html_options[:id] + + @template_object.label(@object_name, @sanitized_attribute_name, @text, html_options, &block) + end + end + + def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options) + @collection = collection + @value_method = value_method + @text_method = text_method + @html_options = html_options + + super(object_name, method_name, template_object, options) + end + + private + + def instantiate_builder(builder_class, item, value, text, html_options) + builder_class.new(@template_object, @object_name, @method_name, item, + sanitize_attribute_name(value), text, value, html_options) + end + + # Generate default options for collection helpers, such as :checked and + # :disabled. + def default_html_options_for_collection(item, value) + html_options = @html_options.dup + + [:checked, :selected, :disabled, :readonly].each do |option| + current_value = @options[option] + next if current_value.nil? + + accept = if current_value.respond_to?(:call) + current_value.call(item) + else + Array(current_value).map(&:to_s).include?(value.to_s) + end + + if accept + html_options[option] = true + elsif option == :checked + html_options[option] = false + end + end + + html_options[:object] = @object + html_options + end + + def sanitize_attribute_name(value) + "#{sanitized_method_name}_#{sanitized_value(value)}" + end + + def render_collection + @collection.map do |item| + value = value_for_collection(item, @value_method) + text = value_for_collection(item, @text_method) + default_html_options = default_html_options_for_collection(item, value) + additional_html_options = option_html_attributes(item) + + yield item, value, text, default_html_options.merge(additional_html_options) + end.join.html_safe + end + + def render_collection_for(builder_class, &block) + options = @options.stringify_keys + rendered_collection = render_collection do |item, value, text, default_html_options| + builder = instantiate_builder(builder_class, item, value, text, default_html_options) + + if block_given? + @template_object.capture(builder, &block) + else + render_component(builder) + end + end + + # Prepend a hidden field to make sure something will be sent back to the + # server if all radio buttons are unchecked. + if options.fetch("include_hidden", true) + hidden_field + rendered_collection + else + rendered_collection + end + end + + def hidden_field + hidden_name = @html_options[:name] || hidden_field_name + @template_object.hidden_field_tag(hidden_name, "", id: nil) + end + + def hidden_field_name + "#{tag_name(false, @options[:index])}" + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/collection_radio_buttons.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/collection_radio_buttons.rb new file mode 100644 index 00000000..16d37134 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/collection_radio_buttons.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require "action_view/helpers/tags/collection_helpers" + +module ActionView + module Helpers + module Tags # :nodoc: + class CollectionRadioButtons < Base # :nodoc: + include CollectionHelpers + + class RadioButtonBuilder < Builder # :nodoc: + def radio_button(extra_html_options = {}) + html_options = extra_html_options.merge(@input_html_options) + html_options[:skip_default_ids] = false + @template_object.radio_button(@object_name, @method_name, @value, html_options) + end + end + + def render(&block) + render_collection_for(RadioButtonBuilder, &block) + end + + private + + def render_component(builder) + builder.radio_button + builder.label + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/collection_select.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/collection_select.rb new file mode 100644 index 00000000..6a3af1b2 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/collection_select.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class CollectionSelect < Base #:nodoc: + def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options) + @collection = collection + @value_method = value_method + @text_method = text_method + @html_options = html_options + + super(object_name, method_name, template_object, options) + end + + def render + option_tags_options = { + selected: @options.fetch(:selected) { value }, + disabled: @options[:disabled] + } + + select_content_tag( + options_from_collection_for_select(@collection, @value_method, @text_method, option_tags_options), + @options, @html_options + ) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/color_field.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/color_field.rb new file mode 100644 index 00000000..c5f0bb6b --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/color_field.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class ColorField < TextField # :nodoc: + def render + options = @options.stringify_keys + options["value"] ||= validate_color_string(value) + @options = options + super + end + + private + + def validate_color_string(string) + regex = /#[0-9a-fA-F]{6}/ + if regex.match(string) + string.downcase + else + "#000000" + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/date_field.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/date_field.rb new file mode 100644 index 00000000..b17a9076 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/date_field.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class DateField < DatetimeField # :nodoc: + private + + def format_date(value) + value.try(:strftime, "%Y-%m-%d") + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/date_select.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/date_select.rb new file mode 100644 index 00000000..fe4e3914 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/date_select.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require "active_support/core_ext/time/calculations" + +module ActionView + module Helpers + module Tags # :nodoc: + class DateSelect < Base # :nodoc: + def initialize(object_name, method_name, template_object, options, html_options) + @html_options = html_options + + super(object_name, method_name, template_object, options) + end + + def render + error_wrapping(datetime_selector(@options, @html_options).send("select_#{select_type}").html_safe) + end + + class << self + def select_type + @select_type ||= name.split("::").last.sub("Select", "").downcase + end + end + + private + + def select_type + self.class.select_type + end + + def datetime_selector(options, html_options) + datetime = options.fetch(:selected) { value || default_datetime(options) } + @auto_index ||= nil + + options = options.dup + options[:field_name] = @method_name + options[:include_position] = true + options[:prefix] ||= @object_name + options[:index] = @auto_index if @auto_index && !options.has_key?(:index) + + DateTimeSelector.new(datetime, options, html_options) + end + + def default_datetime(options) + return if options[:include_blank] || options[:prompt] + + case options[:default] + when nil + Time.current + when Date, Time + options[:default] + else + default = options[:default].dup + + # Rename :minute and :second to :min and :sec + default[:min] ||= default[:minute] + default[:sec] ||= default[:second] + + time = Time.current + + [:year, :month, :day, :hour, :min, :sec].each do |key| + default[key] ||= time.send(key) + end + + Time.utc( + default[:year], default[:month], default[:day], + default[:hour], default[:min], default[:sec] + ) + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/datetime_field.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/datetime_field.rb new file mode 100644 index 00000000..5d9b639b --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/datetime_field.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class DatetimeField < TextField # :nodoc: + def render + options = @options.stringify_keys + options["value"] ||= format_date(value) + options["min"] = format_date(datetime_value(options["min"])) + options["max"] = format_date(datetime_value(options["max"])) + @options = options + super + end + + private + + def format_date(value) + raise NotImplementedError + end + + def datetime_value(value) + if value.is_a? String + DateTime.parse(value) rescue nil + else + value + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/datetime_local_field.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/datetime_local_field.rb new file mode 100644 index 00000000..d8f8fd00 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/datetime_local_field.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class DatetimeLocalField < DatetimeField # :nodoc: + class << self + def field_type + @field_type ||= "datetime-local" + end + end + + private + + def format_date(value) + value.try(:strftime, "%Y-%m-%dT%T") + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/datetime_select.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/datetime_select.rb new file mode 100644 index 00000000..dc557093 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/datetime_select.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class DatetimeSelect < DateSelect # :nodoc: + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/email_field.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/email_field.rb new file mode 100644 index 00000000..0c3b9224 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/email_field.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class EmailField < TextField # :nodoc: + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/file_field.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/file_field.rb new file mode 100644 index 00000000..0b1d9bb7 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/file_field.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class FileField < TextField # :nodoc: + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/grouped_collection_select.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/grouped_collection_select.rb new file mode 100644 index 00000000..f24cb4be --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/grouped_collection_select.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class GroupedCollectionSelect < Base # :nodoc: + def initialize(object_name, method_name, template_object, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options) + @collection = collection + @group_method = group_method + @group_label_method = group_label_method + @option_key_method = option_key_method + @option_value_method = option_value_method + @html_options = html_options + + super(object_name, method_name, template_object, options) + end + + def render + option_tags_options = { + selected: @options.fetch(:selected) { value }, + disabled: @options[:disabled] + } + + select_content_tag( + option_groups_from_collection_for_select(@collection, @group_method, @group_label_method, @option_key_method, @option_value_method, option_tags_options), @options, @html_options + ) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/hidden_field.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/hidden_field.rb new file mode 100644 index 00000000..e014bd3a --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/hidden_field.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class HiddenField < TextField # :nodoc: + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/label.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/label.rb new file mode 100644 index 00000000..02bd0997 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/label.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class Label < Base # :nodoc: + class LabelBuilder # :nodoc: + attr_reader :object + + def initialize(template_object, object_name, method_name, object, tag_value) + @template_object = template_object + @object_name = object_name + @method_name = method_name + @object = object + @tag_value = tag_value + end + + def translation + method_and_value = @tag_value.present? ? "#{@method_name}.#{@tag_value}" : @method_name + + content ||= Translator + .new(object, @object_name, method_and_value, scope: "helpers.label") + .translate + content ||= @method_name.humanize + + content + end + end + + def initialize(object_name, method_name, template_object, content_or_options = nil, options = nil) + options ||= {} + + content_is_options = content_or_options.is_a?(Hash) + if content_is_options + options.merge! content_or_options + @content = nil + else + @content = content_or_options + end + + super(object_name, method_name, template_object, options) + end + + def render(&block) + options = @options.stringify_keys + tag_value = options.delete("value") + name_and_id = options.dup + + if name_and_id["for"] + name_and_id["id"] = name_and_id["for"] + else + name_and_id.delete("id") + end + + add_default_name_and_id_for_value(tag_value, name_and_id) + options.delete("index") + options.delete("namespace") + options["for"] = name_and_id["id"] unless options.key?("for") + + builder = LabelBuilder.new(@template_object, @object_name, @method_name, @object, tag_value) + + content = if block_given? + @template_object.capture(builder, &block) + elsif @content.present? + @content.to_s + else + render_component(builder) + end + + label_tag(name_and_id["id"], content, options) + end + + private + + def render_component(builder) + builder.translation + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/month_field.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/month_field.rb new file mode 100644 index 00000000..93b2bf11 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/month_field.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class MonthField < DatetimeField # :nodoc: + private + + def format_date(value) + value.try(:strftime, "%Y-%m") + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/number_field.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/number_field.rb new file mode 100644 index 00000000..41c69642 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/number_field.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class NumberField < TextField # :nodoc: + def render + options = @options.stringify_keys + + if range = options.delete("in") || options.delete("within") + options.update("min" => range.min, "max" => range.max) + end + + @options = options + super + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/password_field.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/password_field.rb new file mode 100644 index 00000000..9f10f523 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/password_field.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class PasswordField < TextField # :nodoc: + def render + @options = { value: nil }.merge!(@options) + super + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/placeholderable.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/placeholderable.rb new file mode 100644 index 00000000..e9f7601e --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/placeholderable.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + module Placeholderable # :nodoc: + def initialize(*) + super + + if tag_value = @options[:placeholder] + placeholder = tag_value if tag_value.is_a?(String) + method_and_value = tag_value.is_a?(TrueClass) ? @method_name : "#{@method_name}.#{tag_value}" + + placeholder ||= Tags::Translator + .new(object, @object_name, method_and_value, scope: "helpers.placeholder") + .translate + placeholder ||= @method_name.humanize + @options[:placeholder] = placeholder + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/radio_button.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/radio_button.rb new file mode 100644 index 00000000..621db2b1 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/radio_button.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require "action_view/helpers/tags/checkable" + +module ActionView + module Helpers + module Tags # :nodoc: + class RadioButton < Base # :nodoc: + include Checkable + + def initialize(object_name, method_name, template_object, tag_value, options) + @tag_value = tag_value + super(object_name, method_name, template_object, options) + end + + def render + options = @options.stringify_keys + options["type"] = "radio" + options["value"] = @tag_value + options["checked"] = "checked" if input_checked?(options) + add_default_name_and_id_for_value(@tag_value, options) + tag("input", options) + end + + private + + def checked?(value) + value.to_s == @tag_value.to_s + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/range_field.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/range_field.rb new file mode 100644 index 00000000..66d1bbac --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/range_field.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class RangeField < NumberField # :nodoc: + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/search_field.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/search_field.rb new file mode 100644 index 00000000..f2093489 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/search_field.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class SearchField < TextField # :nodoc: + def render + options = @options.stringify_keys + + if options["autosave"] + if options["autosave"] == true + options["autosave"] = request.host.split(".").reverse.join(".") + end + options["results"] ||= 10 + end + + if options["onsearch"] + options["incremental"] = true unless options.has_key?("incremental") + end + + @options = options + super + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/select.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/select.rb new file mode 100644 index 00000000..790721a0 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/select.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class Select < Base # :nodoc: + def initialize(object_name, method_name, template_object, choices, options, html_options) + @choices = block_given? ? template_object.capture { yield || "" } : choices + @choices = @choices.to_a if @choices.is_a?(Range) + + @html_options = html_options + + super(object_name, method_name, template_object, options) + end + + def render + option_tags_options = { + selected: @options.fetch(:selected) { value }, + disabled: @options[:disabled] + } + + option_tags = if grouped_choices? + grouped_options_for_select(@choices, option_tags_options) + else + options_for_select(@choices, option_tags_options) + end + + select_content_tag(option_tags, @options, @html_options) + end + + private + + # Grouped choices look like this: + # + # [nil, []] + # { nil => [] } + def grouped_choices? + !@choices.blank? && @choices.first.respond_to?(:last) && Array === @choices.first.last + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/tel_field.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/tel_field.rb new file mode 100644 index 00000000..ab1caaac --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/tel_field.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class TelField < TextField # :nodoc: + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/text_area.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/text_area.rb new file mode 100644 index 00000000..4519082f --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/text_area.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "action_view/helpers/tags/placeholderable" + +module ActionView + module Helpers + module Tags # :nodoc: + class TextArea < Base # :nodoc: + include Placeholderable + + def render + options = @options.stringify_keys + add_default_name_and_id(options) + + if size = options.delete("size") + options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split) + end + + content_tag("textarea", options.delete("value") { value_before_type_cast }, options) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/text_field.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/text_field.rb new file mode 100644 index 00000000..d92967e2 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/text_field.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "action_view/helpers/tags/placeholderable" + +module ActionView + module Helpers + module Tags # :nodoc: + class TextField < Base # :nodoc: + include Placeholderable + + def render + options = @options.stringify_keys + options["size"] = options["maxlength"] unless options.key?("size") + options["type"] ||= field_type + options["value"] = options.fetch("value") { value_before_type_cast } unless field_type == "file" + add_default_name_and_id(options) + tag("input", options) + end + + class << self + def field_type + @field_type ||= name.split("::").last.sub("Field", "").downcase + end + end + + private + + def field_type + self.class.field_type + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/time_field.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/time_field.rb new file mode 100644 index 00000000..9384a83a --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/time_field.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class TimeField < DatetimeField # :nodoc: + private + + def format_date(value) + value.try(:strftime, "%T.%L") + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/time_select.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/time_select.rb new file mode 100644 index 00000000..ba3dcb64 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/time_select.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class TimeSelect < DateSelect # :nodoc: + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/time_zone_select.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/time_zone_select.rb new file mode 100644 index 00000000..1d060960 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/time_zone_select.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class TimeZoneSelect < Base # :nodoc: + def initialize(object_name, method_name, template_object, priority_zones, options, html_options) + @priority_zones = priority_zones + @html_options = html_options + + super(object_name, method_name, template_object, options) + end + + def render + select_content_tag( + time_zone_options_for_select(value || @options[:default], @priority_zones, @options[:model] || ActiveSupport::TimeZone), @options, @html_options + ) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/translator.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/translator.rb new file mode 100644 index 00000000..fcf96d2c --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/translator.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class Translator # :nodoc: + def initialize(object, object_name, method_and_value, scope:) + @object_name = object_name.gsub(/\[(.*)_attributes\]\[\d+\]/, '.\1') + @method_and_value = method_and_value + @scope = scope + @model = object.respond_to?(:to_model) ? object.to_model : nil + end + + def translate + translated_attribute = I18n.t("#{object_name}.#{method_and_value}", default: i18n_default, scope: scope).presence + translated_attribute || human_attribute_name + end + + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected + + attr_reader :object_name, :method_and_value, :scope, :model + + private + + def i18n_default + if model + key = model.model_name.i18n_key + ["#{key}.#{method_and_value}".to_sym, ""] + else + "" + end + end + + def human_attribute_name + if model && model.class.respond_to?(:human_attribute_name) + model.class.human_attribute_name(method_and_value) + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/url_field.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/url_field.rb new file mode 100644 index 00000000..395fec67 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/url_field.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class UrlField < TextField # :nodoc: + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/week_field.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/week_field.rb new file mode 100644 index 00000000..572535d1 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/tags/week_field.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActionView + module Helpers + module Tags # :nodoc: + class WeekField < DatetimeField # :nodoc: + private + + def format_date(value) + value.try(:strftime, "%Y-W%V") + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/text_helper.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/text_helper.rb new file mode 100644 index 00000000..28a5fd54 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/text_helper.rb @@ -0,0 +1,486 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/filters" +require "active_support/core_ext/array/extract_options" + +module ActionView + # = Action View Text Helpers + module Helpers #:nodoc: + # The TextHelper module provides a set of methods for filtering, formatting + # and transforming strings, which can reduce the amount of inline Ruby code in + # your views. These helper methods extend Action View making them callable + # within your template files. + # + # ==== Sanitization + # + # Most text helpers that generate HTML output sanitize the given input by default, + # but do not escape it. This means HTML tags will appear in the page but all malicious + # code will be removed. Let's look at some examples using the +simple_format+ method: + # + # simple_format('Example') + # # => "

    Example

    " + # + # simple_format('Example') + # # => "

    Example

    " + # + # If you want to escape all content, you should invoke the +h+ method before + # calling the text helper. + # + # simple_format h('Example') + # # => "

    <a href=\"http://example.com/\">Example</a>

    " + module TextHelper + extend ActiveSupport::Concern + + include SanitizeHelper + include TagHelper + include OutputSafetyHelper + + # The preferred method of outputting text in your views is to use the + # <%= "text" %> eRuby syntax. The regular _puts_ and _print_ methods + # do not operate as expected in an eRuby code block. If you absolutely must + # output text within a non-output code block (i.e., <% %>), you can use the concat method. + # + # <% + # concat "hello" + # # is the equivalent of <%= "hello" %> + # + # if logged_in + # concat "Logged in!" + # else + # concat link_to('login', action: :login) + # end + # # will either display "Logged in!" or a login link + # %> + def concat(string) + output_buffer << string + end + + def safe_concat(string) + output_buffer.respond_to?(:safe_concat) ? output_buffer.safe_concat(string) : concat(string) + end + + # Truncates a given +text+ after a given :length if +text+ is longer than :length + # (defaults to 30). The last characters will be replaced with the :omission (defaults to "...") + # for a total length not exceeding :length. + # + # Pass a :separator to truncate +text+ at a natural break. + # + # Pass a block if you want to show extra content when the text is truncated. + # + # The result is marked as HTML-safe, but it is escaped by default, unless :escape is + # +false+. Care should be taken if +text+ contains HTML tags or entities, because truncation + # may produce invalid HTML (such as unbalanced or incomplete tags). + # + # truncate("Once upon a time in a world far far away") + # # => "Once upon a time in a world..." + # + # truncate("Once upon a time in a world far far away", length: 17) + # # => "Once upon a ti..." + # + # truncate("Once upon a time in a world far far away", length: 17, separator: ' ') + # # => "Once upon a..." + # + # truncate("And they found that many people were sleeping better.", length: 25, omission: '... (continued)') + # # => "And they f... (continued)" + # + # truncate("

    Once upon a time in a world far far away

    ") + # # => "<p>Once upon a time in a wo..." + # + # truncate("

    Once upon a time in a world far far away

    ", escape: false) + # # => "

    Once upon a time in a wo..." + # + # truncate("Once upon a time in a world far far away") { link_to "Continue", "#" } + # # => "Once upon a time in a wo...Continue" + def truncate(text, options = {}, &block) + if text + length = options.fetch(:length, 30) + + content = text.truncate(length, options) + content = options[:escape] == false ? content.html_safe : ERB::Util.html_escape(content) + content << capture(&block) if block_given? && text.length > length + content + end + end + + # Highlights one or more +phrases+ everywhere in +text+ by inserting it into + # a :highlighter string. The highlighter can be specialized by passing :highlighter + # as a single-quoted string with \1 where the phrase is to be inserted (defaults to + # '\1') or passing a block that receives each matched term. By default +text+ + # is sanitized to prevent possible XSS attacks. If the input is trustworthy, passing false + # for :sanitize will turn sanitizing off. + # + # highlight('You searched for: rails', 'rails') + # # => You searched for: rails + # + # highlight('You searched for: rails', /for|rails/) + # # => You searched for: rails + # + # highlight('You searched for: ruby, rails, dhh', 'actionpack') + # # => You searched for: ruby, rails, dhh + # + # highlight('You searched for: rails', ['for', 'rails'], highlighter: '\1') + # # => You searched for: rails + # + # highlight('You searched for: rails', 'rails', highlighter: '\1') + # # => You searched for: rails + # + # highlight('You searched for: rails', 'rails') { |match| link_to(search_path(q: match, match)) } + # # => You searched for: rails + # + # highlight('ruby on rails', 'rails', sanitize: false) + # # => ruby on rails + def highlight(text, phrases, options = {}) + text = sanitize(text) if options.fetch(:sanitize, true) + + if text.blank? || phrases.blank? + text || "" + else + match = Array(phrases).map do |p| + Regexp === p ? p.to_s : Regexp.escape(p) + end.join("|") + + if block_given? + text.gsub(/(#{match})(?![^<]*?>)/i) { |found| yield found } + else + highlighter = options.fetch(:highlighter, '\1') + text.gsub(/(#{match})(?![^<]*?>)/i, highlighter) + end + end.html_safe + end + + # Extracts an excerpt from +text+ that matches the first instance of +phrase+. + # The :radius option expands the excerpt on each side of the first occurrence of +phrase+ by the number of characters + # defined in :radius (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+, + # then the :omission option (which defaults to "...") will be prepended/appended accordingly. Use the + # :separator option to choose the delimitation. The resulting string will be stripped in any case. If the +phrase+ + # isn't found, +nil+ is returned. + # + # excerpt('This is an example', 'an', radius: 5) + # # => ...s is an exam... + # + # excerpt('This is an example', 'is', radius: 5) + # # => This is a... + # + # excerpt('This is an example', 'is') + # # => This is an example + # + # excerpt('This next thing is an example', 'ex', radius: 2) + # # => ...next... + # + # excerpt('This is also an example', 'an', radius: 8, omission: ' ') + # # => is also an example + # + # excerpt('This is a very beautiful morning', 'very', separator: ' ', radius: 1) + # # => ...a very beautiful... + def excerpt(text, phrase, options = {}) + return unless text && phrase + + separator = options.fetch(:separator, nil) || "" + case phrase + when Regexp + regex = phrase + else + regex = /#{Regexp.escape(phrase)}/i + end + + return unless matches = text.match(regex) + phrase = matches[0] + + unless separator.empty? + text.split(separator).each do |value| + if value.match(regex) + phrase = value + break + end + end + end + + first_part, second_part = text.split(phrase, 2) + + prefix, first_part = cut_excerpt_part(:first, first_part, separator, options) + postfix, second_part = cut_excerpt_part(:second, second_part, separator, options) + + affix = [first_part, separator, phrase, separator, second_part].join.strip + [prefix, affix, postfix].join + end + + # Attempts to pluralize the +singular+ word unless +count+ is 1. If + # +plural+ is supplied, it will use that when count is > 1, otherwise + # it will use the Inflector to determine the plural form for the given locale, + # which defaults to I18n.locale + # + # The word will be pluralized using rules defined for the locale + # (you must define your own inflection rules for languages other than English). + # See ActiveSupport::Inflector.pluralize + # + # pluralize(1, 'person') + # # => 1 person + # + # pluralize(2, 'person') + # # => 2 people + # + # pluralize(3, 'person', plural: 'users') + # # => 3 users + # + # pluralize(0, 'person') + # # => 0 people + # + # pluralize(2, 'Person', locale: :de) + # # => 2 Personen + def pluralize(count, singular, plural_arg = nil, plural: plural_arg, locale: I18n.locale) + word = if (count == 1 || count.to_s =~ /^1(\.0+)?$/) + singular + else + plural || singular.pluralize(locale) + end + + "#{count || 0} #{word}" + end + + # Wraps the +text+ into lines no longer than +line_width+ width. This method + # breaks on the first whitespace character that does not exceed +line_width+ + # (which is 80 by default). + # + # word_wrap('Once upon a time') + # # => Once upon a time + # + # word_wrap('Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding a successor to the throne turned out to be more trouble than anyone could have imagined...') + # # => Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding\na successor to the throne turned out to be more trouble than anyone could have\nimagined... + # + # word_wrap('Once upon a time', line_width: 8) + # # => Once\nupon a\ntime + # + # word_wrap('Once upon a time', line_width: 1) + # # => Once\nupon\na\ntime + # + # You can also specify a custom +break_sequence+ ("\n" by default) + # + # word_wrap('Once upon a time', line_width: 1, break_sequence: "\r\n") + # # => Once\r\nupon\r\na\r\ntime + def word_wrap(text, line_width: 80, break_sequence: "\n") + text.split("\n").collect! do |line| + line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1#{break_sequence}").strip : line + end * break_sequence + end + + # Returns +text+ transformed into HTML using simple formatting rules. + # Two or more consecutive newlines(\n\n or \r\n\r\n) are + # considered a paragraph and wrapped in

    tags. One newline + # (\n or \r\n) is considered a linebreak and a + #
    tag is appended. This method does not remove the + # newlines from the +text+. + # + # You can pass any HTML attributes into html_options. These + # will be added to all created paragraphs. + # + # ==== Options + # * :sanitize - If +false+, does not sanitize +text+. + # * :wrapper_tag - String representing the wrapper tag, defaults to "p" + # + # ==== Examples + # my_text = "Here is some basic text...\n...with a line break." + # + # simple_format(my_text) + # # => "

    Here is some basic text...\n
    ...with a line break.

    " + # + # simple_format(my_text, {}, wrapper_tag: "div") + # # => "
    Here is some basic text...\n
    ...with a line break.
    " + # + # more_text = "We want to put a paragraph...\n\n...right there." + # + # simple_format(more_text) + # # => "

    We want to put a paragraph...

    \n\n

    ...right there.

    " + # + # simple_format("Look ma! A class!", class: 'description') + # # => "

    Look ma! A class!

    " + # + # simple_format("Unblinkable.") + # # => "

    Unblinkable.

    " + # + # simple_format("Blinkable! It's true.", {}, sanitize: false) + # # => "

    Blinkable! It's true.

    " + def simple_format(text, html_options = {}, options = {}) + wrapper_tag = options.fetch(:wrapper_tag, :p) + + text = sanitize(text) if options.fetch(:sanitize, true) + paragraphs = split_paragraphs(text) + + if paragraphs.empty? + content_tag(wrapper_tag, nil, html_options) + else + paragraphs.map! { |paragraph| + content_tag(wrapper_tag, raw(paragraph), html_options) + }.join("\n\n").html_safe + end + end + + # Creates a Cycle object whose _to_s_ method cycles through elements of an + # array every time it is called. This can be used for example, to alternate + # classes for table rows. You can use named cycles to allow nesting in loops. + # Passing a Hash as the last parameter with a :name key will create a + # named cycle. The default name for a cycle without a +:name+ key is + # "default". You can manually reset a cycle by calling reset_cycle + # and passing the name of the cycle. The current cycle string can be obtained + # anytime using the current_cycle method. + # + # # Alternate CSS classes for even and odd numbers... + # @items = [1,2,3,4] + # + # <% @items.each do |item| %> + # "> + # + # + # <% end %> + #
    <%= item %>
    + # + # + # # Cycle CSS classes for rows, and text colors for values within each row + # @items = x = [{first: 'Robert', middle: 'Daniel', last: 'James'}, + # {first: 'Emily', middle: 'Shannon', maiden: 'Pike', last: 'Hicks'}, + # {first: 'June', middle: 'Dae', last: 'Jones'}] + # <% @items.each do |item| %> + # "> + # + # <% item.values.each do |value| %> + # <%# Create a named cycle "colors" %> + # "> + # <%= value %> + # + # <% end %> + # <% reset_cycle("colors") %> + # + # + # <% end %> + def cycle(first_value, *values) + options = values.extract_options! + name = options.fetch(:name, "default") + + values.unshift(*first_value) + + cycle = get_cycle(name) + unless cycle && cycle.values == values + cycle = set_cycle(name, Cycle.new(*values)) + end + cycle.to_s + end + + # Returns the current cycle string after a cycle has been started. Useful + # for complex table highlighting or any other design need which requires + # the current cycle string in more than one place. + # + # # Alternate background colors + # @items = [1,2,3,4] + # <% @items.each do |item| %> + #
    "> + # <%= item %> + #
    + # <% end %> + def current_cycle(name = "default") + cycle = get_cycle(name) + cycle.current_value if cycle + end + + # Resets a cycle so that it starts from the first element the next time + # it is called. Pass in +name+ to reset a named cycle. + # + # # Alternate CSS classes for even and odd numbers... + # @items = [[1,2,3,4], [5,6,3], [3,4,5,6,7,4]] + # + # <% @items.each do |item| %> + # "> + # <% item.each do |value| %> + # "> + # <%= value %> + # + # <% end %> + # + # <% reset_cycle("colors") %> + # + # <% end %> + #
    + def reset_cycle(name = "default") + cycle = get_cycle(name) + cycle.reset if cycle + end + + class Cycle #:nodoc: + attr_reader :values + + def initialize(first_value, *values) + @values = values.unshift(first_value) + reset + end + + def reset + @index = 0 + end + + def current_value + @values[previous_index].to_s + end + + def to_s + value = @values[@index].to_s + @index = next_index + value + end + + private + + def next_index + step_index(1) + end + + def previous_index + step_index(-1) + end + + def step_index(n) + (@index + n) % @values.size + end + end + + private + # The cycle helpers need to store the cycles in a place that is + # guaranteed to be reset every time a page is rendered, so it + # uses an instance variable of ActionView::Base. + def get_cycle(name) + @_cycles = Hash.new unless defined?(@_cycles) + @_cycles[name] + end + + def set_cycle(name, cycle_object) + @_cycles = Hash.new unless defined?(@_cycles) + @_cycles[name] = cycle_object + end + + def split_paragraphs(text) + return [] if text.blank? + + text.to_str.gsub(/\r\n?/, "\n").split(/\n\n+/).map! do |t| + t.gsub!(/([^\n]\n)(?=[^\n])/, '\1
    ') || t + end + end + + def cut_excerpt_part(part_position, part, separator, options) + return "", "" unless part + + radius = options.fetch(:radius, 100) + omission = options.fetch(:omission, "...") + + part = part.split(separator) + part.delete("") + affix = part.size > radius ? omission : "" + + part = if part_position == :first + drop_index = [part.length - radius, 0].max + part.drop(drop_index) + else + part.first(radius) + end + + return affix, part.join(separator) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/translation_helper.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/translation_helper.rb new file mode 100644 index 00000000..1860bc47 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/translation_helper.rb @@ -0,0 +1,141 @@ +# frozen_string_literal: true + +require "action_view/helpers/tag_helper" +require "active_support/core_ext/string/access" +require "i18n/exceptions" + +module ActionView + # = Action View Translation Helpers + module Helpers #:nodoc: + module TranslationHelper + extend ActiveSupport::Concern + + include TagHelper + + included do + mattr_accessor :debug_missing_translation, default: true + end + + # Delegates to I18n#translate but also performs three additional + # functions. + # + # First, it will ensure that any thrown +MissingTranslation+ messages will + # be rendered as inline spans that: + # + # * Have a translation-missing class applied + # * Contain the missing key as the value of the +title+ attribute + # * Have a titleized version of the last key segment as text + # + # For example, the value returned for the missing translation key + # "blog.post.title" will be: + # + # Title + # + # This allows for views to display rather reasonable strings while still + # giving developers a way to find missing translations. + # + # If you would prefer missing translations to raise an error, you can + # opt out of span-wrapping behavior globally by setting + # ActionView::Base.raise_on_missing_translations = true or + # individually by passing raise: true as an option to + # translate. + # + # Second, if the key starts with a period translate will scope + # the key by the current partial. Calling translate(".foo") from + # the people/index.html.erb template is equivalent to calling + # translate("people.index.foo"). This makes it less + # repetitive to translate many keys within the same partial and provides + # a convention to scope keys consistently. + # + # Third, the translation will be marked as html_safe if the key + # has the suffix "_html" or the last element of the key is "html". Calling + # translate("footer_html") or translate("footer.html") + # will return an HTML safe string that won't be escaped by other HTML + # helper methods. This naming convention helps to identify translations + # that include HTML tags so that you know what kind of output to expect + # when you call translate in a template and translators know which keys + # they can provide HTML values for. + def translate(key, options = {}) + options = options.dup + has_default = options.has_key?(:default) + remaining_defaults = Array(options.delete(:default)).compact + + if has_default && !remaining_defaults.first.kind_of?(Symbol) + options[:default] = remaining_defaults + end + + # If the user has explicitly decided to NOT raise errors, pass that option to I18n. + # Otherwise, tell I18n to raise an exception, which we rescue further in this method. + # Note: `raise_error` refers to us re-raising the error in this method. I18n is forced to raise by default. + if options[:raise] == false + raise_error = false + i18n_raise = false + else + raise_error = options[:raise] || ActionView::Base.raise_on_missing_translations + i18n_raise = true + end + + if html_safe_translation_key?(key) + html_safe_options = options.dup + options.except(*I18n::RESERVED_KEYS).each do |name, value| + unless name == :count && value.is_a?(Numeric) + html_safe_options[name] = ERB::Util.html_escape(value.to_s) + end + end + translation = I18n.translate(scope_key_by_partial(key), html_safe_options.merge(raise: i18n_raise)) + + translation.respond_to?(:html_safe) ? translation.html_safe : translation + else + I18n.translate(scope_key_by_partial(key), options.merge(raise: i18n_raise)) + end + rescue I18n::MissingTranslationData => e + if remaining_defaults.present? + translate remaining_defaults.shift, options.merge(default: remaining_defaults) + else + raise e if raise_error + + keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope]) + title = "translation missing: #{keys.join('.')}".dup + + interpolations = options.except(:default, :scope) + if interpolations.any? + title << ", " << interpolations.map { |k, v| "#{k}: #{ERB::Util.html_escape(v)}" }.join(", ") + end + + return title unless ActionView::Base.debug_missing_translation + + content_tag("span", keys.last.to_s.titleize, class: "translation_missing", title: title) + end + end + alias :t :translate + + # Delegates to I18n.localize with no additional functionality. + # + # See http://rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize + # for more information. + def localize(*args) + I18n.localize(*args) + end + alias :l :localize + + private + def scope_key_by_partial(key) + if key.to_s.first == "." + if @virtual_path + @virtual_path.gsub(%r{/_?}, ".") + key.to_s + else + raise "Cannot use t(#{key.inspect}) shortcut because path is not available" + end + else + key + end + end + + def html_safe_translation_key?(key) + /(\b|_|\.)html$/.match?(key.to_s) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/url_helper.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/url_helper.rb new file mode 100644 index 00000000..bff0cc39 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/helpers/url_helper.rb @@ -0,0 +1,676 @@ +# frozen_string_literal: true + +require "action_view/helpers/javascript_helper" +require "active_support/core_ext/array/access" +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/string/output_safety" + +module ActionView + # = Action View URL Helpers + module Helpers #:nodoc: + # Provides a set of methods for making links and getting URLs that + # depend on the routing subsystem (see ActionDispatch::Routing). + # This allows you to use the same format for links in views + # and controllers. + module UrlHelper + # This helper may be included in any class that includes the + # URL helpers of a routes (routes.url_helpers). Some methods + # provided here will only work in the context of a request + # (link_to_unless_current, for instance), which must be provided + # as a method called #request on the context. + BUTTON_TAG_METHOD_VERBS = %w{patch put delete} + extend ActiveSupport::Concern + + include TagHelper + + module ClassMethods + def _url_for_modules + ActionView::RoutingUrlFor + end + end + + # Basic implementation of url_for to allow use helpers without routes existence + def url_for(options = nil) # :nodoc: + case options + when String + options + when :back + _back_url + else + raise ArgumentError, "arguments passed to url_for can't be handled. Please require " \ + "routes or provide your own implementation" + end + end + + def _back_url # :nodoc: + _filtered_referrer || "javascript:history.back()" + end + protected :_back_url + + def _filtered_referrer # :nodoc: + if controller.respond_to?(:request) + referrer = controller.request.env["HTTP_REFERER"] + if referrer && URI(referrer).scheme != "javascript" + referrer + end + end + rescue URI::InvalidURIError + end + protected :_filtered_referrer + + # Creates an anchor element of the given +name+ using a URL created by the set of +options+. + # See the valid options in the documentation for +url_for+. It's also possible to + # pass a String instead of an options hash, which generates an anchor element that uses the + # value of the String as the href for the link. Using a :back Symbol instead + # of an options hash will generate a link to the referrer (a JavaScript back link + # will be used in place of a referrer if none exists). If +nil+ is passed as the name + # the value of the link itself will become the name. + # + # ==== Signatures + # + # link_to(body, url, html_options = {}) + # # url is a String; you can use URL helpers like + # # posts_path + # + # link_to(body, url_options = {}, html_options = {}) + # # url_options, except :method, is passed to url_for + # + # link_to(options = {}, html_options = {}) do + # # name + # end + # + # link_to(url, html_options = {}) do + # # name + # end + # + # ==== Options + # * :data - This option can be used to add custom data attributes. + # * method: symbol of HTTP verb - This modifier will dynamically + # create an HTML form and immediately submit the form for processing using + # the HTTP verb specified. Useful for having links perform a POST operation + # in dangerous actions like deleting a record (which search bots can follow + # while spidering your site). Supported verbs are :post, :delete, :patch, and :put. + # Note that if the user has JavaScript disabled, the request will fall back + # to using GET. If href: '#' is used and the user has JavaScript + # disabled clicking the link will have no effect. If you are relying on the + # POST behavior, you should check for it in your controller's action by using + # the request object's methods for post?, delete?, patch?, or put?. + # * remote: true - This will allow the unobtrusive JavaScript + # driver to make an Ajax request to the URL in question instead of following + # the link. The drivers each provide mechanisms for listening for the + # completion of the Ajax request and performing JavaScript operations once + # they're complete + # + # ==== Data attributes + # + # * confirm: 'question?' - This will allow the unobtrusive JavaScript + # driver to prompt with the question specified (in this case, the + # resulting text would be question?. If the user accepts, the + # link is processed normally, otherwise no action is taken. + # * :disable_with - Value of this parameter will be used as the + # name for a disabled version of the link. This feature is provided by + # the unobtrusive JavaScript driver. + # + # ==== Examples + # Because it relies on +url_for+, +link_to+ supports both older-style controller/action/id arguments + # and newer RESTful routes. Current Rails style favors RESTful routes whenever possible, so base + # your application on resources and use + # + # link_to "Profile", profile_path(@profile) + # # => Profile + # + # or the even pithier + # + # link_to "Profile", @profile + # # => Profile + # + # in place of the older more verbose, non-resource-oriented + # + # link_to "Profile", controller: "profiles", action: "show", id: @profile + # # => Profile + # + # Similarly, + # + # link_to "Profiles", profiles_path + # # => Profiles + # + # is better than + # + # link_to "Profiles", controller: "profiles" + # # => Profiles + # + # When name is +nil+ the href is presented instead + # + # link_to nil, "http://example.com" + # # => http://www.example.com + # + # You can use a block as well if your link target is hard to fit into the name parameter. ERB example: + # + # <%= link_to(@profile) do %> + # <%= @profile.name %> -- Check it out! + # <% end %> + # # => + # David -- Check it out! + # + # + # Classes and ids for CSS are easy to produce: + # + # link_to "Articles", articles_path, id: "news", class: "article" + # # => Articles + # + # Be careful when using the older argument style, as an extra literal hash is needed: + # + # link_to "Articles", { controller: "articles" }, id: "news", class: "article" + # # => Articles + # + # Leaving the hash off gives the wrong link: + # + # link_to "WRONG!", controller: "articles", id: "news", class: "article" + # # => WRONG! + # + # +link_to+ can also produce links with anchors or query strings: + # + # link_to "Comment wall", profile_path(@profile, anchor: "wall") + # # => Comment wall + # + # link_to "Ruby on Rails search", controller: "searches", query: "ruby on rails" + # # => Ruby on Rails search + # + # link_to "Nonsense search", searches_path(foo: "bar", baz: "quux") + # # => Nonsense search + # + # The only option specific to +link_to+ (:method) is used as follows: + # + # link_to("Destroy", "http://www.example.com", method: :delete) + # # => Destroy + # + # You can also use custom data attributes using the :data option: + # + # link_to "Visit Other Site", "http://www.rubyonrails.org/", data: { confirm: "Are you sure?" } + # # => Visit Other Site + # + # Also you can set any link attributes such as target, rel, type: + # + # link_to "External link", "http://www.rubyonrails.org/", target: "_blank", rel: "nofollow" + # # => External link + def link_to(name = nil, options = nil, html_options = nil, &block) + html_options, options, name = options, name, block if block_given? + options ||= {} + + html_options = convert_options_to_data_attributes(options, html_options) + + url = url_for(options) + html_options["href".freeze] ||= url + + content_tag("a".freeze, name || url, html_options, &block) + end + + # Generates a form containing a single button that submits to the URL created + # by the set of +options+. This is the safest method to ensure links that + # cause changes to your data are not triggered by search bots or accelerators. + # If the HTML button does not work with your layout, you can also consider + # using the +link_to+ method with the :method modifier as described in + # the +link_to+ documentation. + # + # By default, the generated form element has a class name of button_to + # to allow styling of the form itself and its children. This can be changed + # using the :form_class modifier within +html_options+. You can control + # the form submission and input element behavior using +html_options+. + # This method accepts the :method modifier described in the +link_to+ documentation. + # If no :method modifier is given, it will default to performing a POST operation. + # You can also disable the button by passing disabled: true in +html_options+. + # If you are using RESTful routes, you can pass the :method + # to change the HTTP verb used to submit the form. + # + # ==== Options + # The +options+ hash accepts the same options as +url_for+. + # + # There are a few special +html_options+: + # * :method - Symbol of HTTP verb. Supported verbs are :post, :get, + # :delete, :patch, and :put. By default it will be :post. + # * :disabled - If set to true, it will generate a disabled button. + # * :data - This option can be used to add custom data attributes. + # * :remote - If set to true, will allow the Unobtrusive JavaScript drivers to control the + # submit behavior. By default this behavior is an ajax submit. + # * :form - This hash will be form attributes + # * :form_class - This controls the class of the form within which the submit button will + # be placed + # * :params - Hash of parameters to be rendered as hidden fields within the form. + # + # ==== Data attributes + # + # * :confirm - This will use the unobtrusive JavaScript driver to + # prompt with the question specified. If the user accepts, the link is + # processed normally, otherwise no action is taken. + # * :disable_with - Value of this parameter will be + # used as the value for a disabled version of the submit + # button when the form is submitted. This feature is provided + # by the unobtrusive JavaScript driver. + # + # ==== Examples + # <%= button_to "New", action: "new" %> + # # => "
    + # # + # #
    " + # + # <%= button_to "New", new_articles_path %> + # # => "
    + # # + # #
    " + # + # <%= button_to [:make_happy, @user] do %> + # Make happy <%= @user.name %> + # <% end %> + # # => "
    + # # + # #
    " + # + # <%= button_to "New", { action: "new" }, form_class: "new-thing" %> + # # => "
    + # # + # #
    " + # + # + # <%= button_to "Create", { action: "create" }, remote: true, form: { "data-type" => "json" } %> + # # => "
    + # # + # # + # #
    " + # + # + # <%= button_to "Delete Image", { action: "delete", id: @image.id }, + # method: :delete, data: { confirm: "Are you sure?" } %> + # # => "
    + # # + # # + # # + # #
    " + # + # + # <%= button_to('Destroy', 'http://www.example.com', + # method: "delete", remote: true, data: { confirm: 'Are you sure?', disable_with: 'loading...' }) %> + # # => "
    + # # + # # + # # + # #
    " + # # + def button_to(name = nil, options = nil, html_options = nil, &block) + html_options, options = options, name if block_given? + options ||= {} + html_options ||= {} + html_options = html_options.stringify_keys + + url = options.is_a?(String) ? options : url_for(options) + remote = html_options.delete("remote") + params = html_options.delete("params") + + method = html_options.delete("method").to_s + method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : "".freeze.html_safe + + form_method = method == "get" ? "get" : "post" + form_options = html_options.delete("form") || {} + form_options[:class] ||= html_options.delete("form_class") || "button_to" + form_options[:method] = form_method + form_options[:action] = url + form_options[:'data-remote'] = true if remote + + request_token_tag = if form_method == "post" + request_method = method.empty? ? "post" : method + token_tag(nil, form_options: { action: url, method: request_method }) + else + "".freeze + end + + html_options = convert_options_to_data_attributes(options, html_options) + html_options["type"] = "submit" + + button = if block_given? + content_tag("button", html_options, &block) + else + html_options["value"] = name || url + tag("input", html_options) + end + + inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag) + if params + to_form_params(params).each do |param| + inner_tags.safe_concat tag(:input, type: "hidden", name: param[:name], value: param[:value]) + end + end + content_tag("form", inner_tags, form_options) + end + + # Creates a link tag of the given +name+ using a URL created by the set of + # +options+ unless the current request URI is the same as the links, in + # which case only the name is returned (or the given block is yielded, if + # one exists). You can give +link_to_unless_current+ a block which will + # specialize the default behavior (e.g., show a "Start Here" link rather + # than the link's text). + # + # ==== Examples + # Let's say you have a navigation menu... + # + # + # + # If in the "about" action, it will render... + # + # + # + # ...but if in the "index" action, it will render: + # + # + # + # The implicit block given to +link_to_unless_current+ is evaluated if the current + # action is the action given. So, if we had a comments page and wanted to render a + # "Go Back" link instead of a link to the comments page, we could do something like this... + # + # <%= + # link_to_unless_current("Comment", { controller: "comments", action: "new" }) do + # link_to("Go back", { controller: "posts", action: "index" }) + # end + # %> + def link_to_unless_current(name, options = {}, html_options = {}, &block) + link_to_unless current_page?(options), name, options, html_options, &block + end + + # Creates a link tag of the given +name+ using a URL created by the set of + # +options+ unless +condition+ is true, in which case only the name is + # returned. To specialize the default behavior (i.e., show a login link rather + # than just the plaintext link text), you can pass a block that + # accepts the name or the full argument list for +link_to_unless+. + # + # ==== Examples + # <%= link_to_unless(@current_user.nil?, "Reply", { action: "reply" }) %> + # # If the user is logged in... + # # => Reply + # + # <%= + # link_to_unless(@current_user.nil?, "Reply", { action: "reply" }) do |name| + # link_to(name, { controller: "accounts", action: "signup" }) + # end + # %> + # # If the user is logged in... + # # => Reply + # # If not... + # # => Reply + def link_to_unless(condition, name, options = {}, html_options = {}, &block) + link_to_if !condition, name, options, html_options, &block + end + + # Creates a link tag of the given +name+ using a URL created by the set of + # +options+ if +condition+ is true, otherwise only the name is + # returned. To specialize the default behavior, you can pass a block that + # accepts the name or the full argument list for +link_to_unless+ (see the examples + # in +link_to_unless+). + # + # ==== Examples + # <%= link_to_if(@current_user.nil?, "Login", { controller: "sessions", action: "new" }) %> + # # If the user isn't logged in... + # # => Login + # + # <%= + # link_to_if(@current_user.nil?, "Login", { controller: "sessions", action: "new" }) do + # link_to(@current_user.login, { controller: "accounts", action: "show", id: @current_user }) + # end + # %> + # # If the user isn't logged in... + # # => Login + # # If they are logged in... + # # => my_username + def link_to_if(condition, name, options = {}, html_options = {}, &block) + if condition + link_to(name, options, html_options) + else + if block_given? + block.arity <= 1 ? capture(name, &block) : capture(name, options, html_options, &block) + else + ERB::Util.html_escape(name) + end + end + end + + # Creates a mailto link tag to the specified +email_address+, which is + # also used as the name of the link unless +name+ is specified. Additional + # HTML attributes for the link can be passed in +html_options+. + # + # +mail_to+ has several methods for customizing the email itself by + # passing special keys to +html_options+. + # + # ==== Options + # * :subject - Preset the subject line of the email. + # * :body - Preset the body of the email. + # * :cc - Carbon Copy additional recipients on the email. + # * :bcc - Blind Carbon Copy additional recipients on the email. + # * :reply_to - Preset the Reply-To field of the email. + # + # ==== Obfuscation + # Prior to Rails 4.0, +mail_to+ provided options for encoding the address + # in order to hinder email harvesters. To take advantage of these options, + # install the +actionview-encoded_mail_to+ gem. + # + # ==== Examples + # mail_to "me@domain.com" + # # => me@domain.com + # + # mail_to "me@domain.com", "My email" + # # => My email + # + # mail_to "me@domain.com", "My email", cc: "ccaddress@domain.com", + # subject: "This is an example email" + # # => My email + # + # You can use a block as well if your link target is hard to fit into the name parameter. ERB example: + # + # <%= mail_to "me@domain.com" do %> + # Email me: me@domain.com + # <% end %> + # # => + # Email me: me@domain.com + # + def mail_to(email_address, name = nil, html_options = {}, &block) + html_options, name = name, nil if block_given? + html_options = (html_options || {}).stringify_keys + + extras = %w{ cc bcc body subject reply_to }.map! { |item| + option = html_options.delete(item).presence || next + "#{item.dasherize}=#{ERB::Util.url_encode(option)}" + }.compact + extras = extras.empty? ? "".freeze : "?" + extras.join("&") + + encoded_email_address = ERB::Util.url_encode(email_address).gsub("%40", "@") + html_options["href"] = "mailto:#{encoded_email_address}#{extras}" + + content_tag("a".freeze, name || email_address, html_options, &block) + end + + # True if the current request URI was generated by the given +options+. + # + # ==== Examples + # Let's say we're in the http://www.example.com/shop/checkout?order=desc&page=1 action. + # + # current_page?(action: 'process') + # # => false + # + # current_page?(action: 'checkout') + # # => true + # + # current_page?(controller: 'library', action: 'checkout') + # # => false + # + # current_page?(controller: 'shop', action: 'checkout') + # # => true + # + # current_page?(controller: 'shop', action: 'checkout', order: 'asc') + # # => false + # + # current_page?(controller: 'shop', action: 'checkout', order: 'desc', page: '1') + # # => true + # + # current_page?(controller: 'shop', action: 'checkout', order: 'desc', page: '2') + # # => false + # + # current_page?('http://www.example.com/shop/checkout') + # # => true + # + # current_page?('http://www.example.com/shop/checkout', check_parameters: true) + # # => false + # + # current_page?('/shop/checkout') + # # => true + # + # current_page?('http://www.example.com/shop/checkout?order=desc&page=1') + # # => true + # + # Let's say we're in the http://www.example.com/products action with method POST in case of invalid product. + # + # current_page?(controller: 'product', action: 'index') + # # => false + # + # We can also pass in the symbol arguments instead of strings. + # + def current_page?(options, check_parameters: false) + unless request + raise "You cannot use helpers that need to determine the current " \ + "page unless your view context provides a Request object " \ + "in a #request method" + end + + return false unless request.get? || request.head? + + check_parameters ||= options.is_a?(Hash) && options.delete(:check_parameters) + url_string = URI.parser.unescape(url_for(options)).force_encoding(Encoding::BINARY) + + # We ignore any extra parameters in the request_uri if the + # submitted url doesn't have any either. This lets the function + # work with things like ?order=asc + # the behaviour can be disabled with check_parameters: true + request_uri = url_string.index("?") || check_parameters ? request.fullpath : request.path + request_uri = URI.parser.unescape(request_uri).force_encoding(Encoding::BINARY) + + if url_string.start_with?("/") && url_string != "/" + url_string.chomp!("/") + request_uri.chomp!("/") + end + + if %r{^\w+://}.match?(url_string) + url_string == "#{request.protocol}#{request.host_with_port}#{request_uri}" + else + url_string == request_uri + end + end + + private + def convert_options_to_data_attributes(options, html_options) + if html_options + html_options = html_options.stringify_keys + html_options["data-remote"] = "true".freeze if link_to_remote_options?(options) || link_to_remote_options?(html_options) + + method = html_options.delete("method".freeze) + + add_method_to_attributes!(html_options, method) if method + + html_options + else + link_to_remote_options?(options) ? { "data-remote" => "true".freeze } : {} + end + end + + def link_to_remote_options?(options) + if options.is_a?(Hash) + options.delete("remote".freeze) || options.delete(:remote) + end + end + + def add_method_to_attributes!(html_options, method) + if method_not_get_method?(method) && html_options["rel"] !~ /nofollow/ + if html_options["rel"].blank? + html_options["rel"] = "nofollow" + else + html_options["rel"] = "#{html_options["rel"]} nofollow" + end + end + html_options["data-method"] = method + end + + STRINGIFIED_COMMON_METHODS = { + get: "get", + delete: "delete", + patch: "patch", + post: "post", + put: "put", + }.freeze + + def method_not_get_method?(method) + return false unless method + (STRINGIFIED_COMMON_METHODS[method] || method.to_s.downcase) != "get" + end + + def token_tag(token = nil, form_options: {}) + if token != false && protect_against_forgery? + token ||= form_authenticity_token(form_options: form_options) + tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: token) + else + "".freeze + end + end + + def method_tag(method) + tag("input", type: "hidden", name: "_method", value: method.to_s) + end + + # Returns an array of hashes each containing :name and :value keys + # suitable for use as the names and values of form input fields: + # + # to_form_params(name: 'David', nationality: 'Danish') + # # => [{name: 'name', value: 'David'}, {name: 'nationality', value: 'Danish'}] + # + # to_form_params(country: {name: 'Denmark'}) + # # => [{name: 'country[name]', value: 'Denmark'}] + # + # to_form_params(countries: ['Denmark', 'Sweden']}) + # # => [{name: 'countries[]', value: 'Denmark'}, {name: 'countries[]', value: 'Sweden'}] + # + # An optional namespace can be passed to enclose key names: + # + # to_form_params({ name: 'Denmark' }, 'country') + # # => [{name: 'country[name]', value: 'Denmark'}] + def to_form_params(attribute, namespace = nil) + attribute = if attribute.respond_to?(:permitted?) + attribute.to_h + else + attribute + end + + params = [] + case attribute + when Hash + attribute.each do |key, value| + prefix = namespace ? "#{namespace}[#{key}]" : key + params.push(*to_form_params(value, prefix)) + end + when Array + array_prefix = "#{namespace}[]" + attribute.each do |value| + params.push(*to_form_params(value, array_prefix)) + end + else + params << { name: namespace.to_s, value: attribute.to_param } + end + + params.sort_by { |pair| pair[:name] } + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/layouts.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/layouts.rb new file mode 100644 index 00000000..3e6d352c --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/layouts.rb @@ -0,0 +1,433 @@ +# frozen_string_literal: true + +require "action_view/rendering" +require "active_support/core_ext/module/redefine_method" + +module ActionView + # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in + # repeated setups. The inclusion pattern has pages that look like this: + # + # <%= render "shared/header" %> + # Hello World + # <%= render "shared/footer" %> + # + # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose + # and if you ever want to change the structure of these two includes, you'll have to change all the templates. + # + # With layouts, you can flip it around and have the common structure know where to insert changing content. This means + # that the header and footer are only mentioned in one place, like this: + # + # // The header part of this layout + # <%= yield %> + # // The footer part of this layout + # + # And then you have content pages that look like this: + # + # hello world + # + # At rendering time, the content page is computed and then inserted in the layout, like this: + # + # // The header part of this layout + # hello world + # // The footer part of this layout + # + # == Accessing shared variables + # + # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with + # references that won't materialize before rendering time: + # + #

    <%= @page_title %>

    + # <%= yield %> + # + # ...and content pages that fulfill these references _at_ rendering time: + # + # <% @page_title = "Welcome" %> + # Off-world colonies offers you a chance to start a new life + # + # The result after rendering is: + # + #

    Welcome

    + # Off-world colonies offers you a chance to start a new life + # + # == Layout assignment + # + # You can either specify a layout declaratively (using the #layout class method) or give + # it the same name as your controller, and place it in app/views/layouts. + # If a subclass does not have a layout specified, it inherits its layout using normal Ruby inheritance. + # + # For instance, if you have PostsController and a template named app/views/layouts/posts.html.erb, + # that template will be used for all actions in PostsController and controllers inheriting + # from PostsController. + # + # If you use a module, for instance Weblog::PostsController, you will need a template named + # app/views/layouts/weblog/posts.html.erb. + # + # Since all your controllers inherit from ApplicationController, they will use + # app/views/layouts/application.html.erb if no other layout is specified + # or provided. + # + # == Inheritance Examples + # + # class BankController < ActionController::Base + # # bank.html.erb exists + # + # class ExchangeController < BankController + # # exchange.html.erb exists + # + # class CurrencyController < BankController + # + # class InformationController < BankController + # layout "information" + # + # class TellerController < InformationController + # # teller.html.erb exists + # + # class EmployeeController < InformationController + # # employee.html.erb exists + # layout nil + # + # class VaultController < BankController + # layout :access_level_layout + # + # class TillController < BankController + # layout false + # + # In these examples, we have three implicit lookup scenarios: + # * The +BankController+ uses the "bank" layout. + # * The +ExchangeController+ uses the "exchange" layout. + # * The +CurrencyController+ inherits the layout from BankController. + # + # However, when a layout is explicitly set, the explicitly set layout wins: + # * The +InformationController+ uses the "information" layout, explicitly set. + # * The +TellerController+ also uses the "information" layout, because the parent explicitly set it. + # * The +EmployeeController+ uses the "employee" layout, because it set the layout to +nil+, resetting the parent configuration. + # * The +VaultController+ chooses a layout dynamically by calling the access_level_layout method. + # * The +TillController+ does not use a layout at all. + # + # == Types of layouts + # + # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes + # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can + # be done either by specifying a method reference as a symbol or using an inline method (as a proc). + # + # The method reference is the preferred approach to variable layouts and is used like this: + # + # class WeblogController < ActionController::Base + # layout :writers_and_readers + # + # def index + # # fetching posts + # end + # + # private + # def writers_and_readers + # logged_in? ? "writer_layout" : "reader_layout" + # end + # end + # + # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing + # is logged in or not. + # + # If you want to use an inline method, such as a proc, do something like this: + # + # class WeblogController < ActionController::Base + # layout proc { |controller| controller.logged_in? ? "writer_layout" : "reader_layout" } + # end + # + # If an argument isn't given to the proc, it's evaluated in the context of + # the current controller anyway. + # + # class WeblogController < ActionController::Base + # layout proc { logged_in? ? "writer_layout" : "reader_layout" } + # end + # + # Of course, the most common way of specifying a layout is still just as a plain template name: + # + # class WeblogController < ActionController::Base + # layout "weblog_standard" + # end + # + # The template will be looked always in app/views/layouts/ folder. But you can point + # layouts folder direct also. layout "layouts/demo" is the same as layout "demo". + # + # Setting the layout to +nil+ forces it to be looked up in the filesystem and fallbacks to the parent behavior if none exists. + # Setting it to +nil+ is useful to re-enable template lookup overriding a previous configuration set in the parent: + # + # class ApplicationController < ActionController::Base + # layout "application" + # end + # + # class PostsController < ApplicationController + # # Will use "application" layout + # end + # + # class CommentsController < ApplicationController + # # Will search for "comments" layout and fallback "application" layout + # layout nil + # end + # + # == Conditional layouts + # + # If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering + # a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The + # :only and :except options can be passed to the layout call. For example: + # + # class WeblogController < ActionController::Base + # layout "weblog_standard", except: :rss + # + # # ... + # + # end + # + # This will assign "weblog_standard" as the WeblogController's layout for all actions except for the +rss+ action, which will + # be rendered directly, without wrapping a layout around the rendered view. + # + # Both the :only and :except condition can accept an arbitrary number of method references, so + # #except: [ :rss, :text_only ] is valid, as is except: :rss. + # + # == Using a different layout in the action render call + # + # If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above. + # Sometimes you'll have exceptions where one action wants to use a different layout than the rest of the controller. + # You can do this by passing a :layout option to the render call. For example: + # + # class WeblogController < ActionController::Base + # layout "weblog_standard" + # + # def help + # render action: "help", layout: "help" + # end + # end + # + # This will override the controller-wide "weblog_standard" layout, and will render the help action with the "help" layout instead. + module Layouts + extend ActiveSupport::Concern + + include ActionView::Rendering + + included do + class_attribute :_layout, instance_accessor: false + class_attribute :_layout_conditions, instance_accessor: false, default: {} + + _write_layout_method + end + + delegate :_layout_conditions, to: :class + + module ClassMethods + def inherited(klass) # :nodoc: + super + klass._write_layout_method + end + + # This module is mixed in if layout conditions are provided. This means + # that if no layout conditions are used, this method is not used + module LayoutConditions # :nodoc: + private + + # Determines whether the current action has a layout definition by + # checking the action name against the :only and :except conditions + # set by the layout method. + # + # ==== Returns + # * Boolean - True if the action has a layout definition, false otherwise. + def _conditional_layout? + return unless super + + conditions = _layout_conditions + + if only = conditions[:only] + only.include?(action_name) + elsif except = conditions[:except] + !except.include?(action_name) + else + true + end + end + end + + # Specify the layout to use for this class. + # + # If the specified layout is a: + # String:: the String is the template name + # Symbol:: call the method specified by the symbol + # Proc:: call the passed Proc + # false:: There is no layout + # true:: raise an ArgumentError + # nil:: Force default layout behavior with inheritance + # + # Return value of +Proc+ and +Symbol+ arguments should be +String+, +false+, +true+ or +nil+ + # with the same meaning as described above. + # ==== Parameters + # * layout - The layout to use. + # + # ==== Options (conditions) + # * :only - A list of actions to apply this layout to. + # * :except - Apply this layout to all actions but this one. + def layout(layout, conditions = {}) + include LayoutConditions unless conditions.empty? + + conditions.each { |k, v| conditions[k] = Array(v).map(&:to_s) } + self._layout_conditions = conditions + + self._layout = layout + _write_layout_method + end + + # Creates a _layout method to be called by _default_layout . + # + # If a layout is not explicitly mentioned then look for a layout with the controller's name. + # if nothing is found then try same procedure to find super class's layout. + def _write_layout_method # :nodoc: + silence_redefinition_of_method(:_layout) + + prefixes = /\blayouts/.match?(_implied_layout_name) ? [] : ["layouts"] + default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}, false, [], { formats: formats }).first || super" + name_clause = if name + default_behavior + else + <<-RUBY + super + RUBY + end + + layout_definition = \ + case _layout + when String + _layout.inspect + when Symbol + <<-RUBY + #{_layout}.tap do |layout| + return #{default_behavior} if layout.nil? + unless layout.is_a?(String) || !layout + raise ArgumentError, "Your layout method :#{_layout} returned \#{layout}. It " \ + "should have returned a String, false, or nil" + end + end + RUBY + when Proc + define_method :_layout_from_proc, &_layout + protected :_layout_from_proc + <<-RUBY + result = _layout_from_proc(#{_layout.arity == 0 ? '' : 'self'}) + return #{default_behavior} if result.nil? + result + RUBY + when false + nil + when true + raise ArgumentError, "Layouts must be specified as a String, Symbol, Proc, false, or nil" + when nil + name_clause + end + + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def _layout(formats) + if _conditional_layout? + #{layout_definition} + else + #{name_clause} + end + end + private :_layout + RUBY + end + + private + + # If no layout is supplied, look for a template named the return + # value of this method. + # + # ==== Returns + # * String - A template name + def _implied_layout_name + controller_path + end + end + + def _normalize_options(options) # :nodoc: + super + + if _include_layout?(options) + layout = options.delete(:layout) { :default } + options[:layout] = _layout_for_option(layout) + end + end + + attr_internal_writer :action_has_layout + + def initialize(*) # :nodoc: + @_action_has_layout = true + super + end + + # Controls whether an action should be rendered using a layout. + # If you want to disable any layout settings for the + # current action so that it is rendered without a layout then + # either override this method in your controller to return false + # for that action or set the action_has_layout attribute + # to false before rendering. + def action_has_layout? + @_action_has_layout + end + + private + + def _conditional_layout? + true + end + + # This will be overwritten by _write_layout_method + def _layout(*); end + + # Determine the layout for a given name, taking into account the name type. + # + # ==== Parameters + # * name - The name of the template + def _layout_for_option(name) + case name + when String then _normalize_layout(name) + when Proc then name + when true then Proc.new { |formats| _default_layout(formats, true) } + when :default then Proc.new { |formats| _default_layout(formats, false) } + when false, nil then nil + else + raise ArgumentError, + "String, Proc, :default, true, or false, expected for `layout'; you passed #{name.inspect}" + end + end + + def _normalize_layout(value) + value.is_a?(String) && value !~ /\blayouts/ ? "layouts/#{value}" : value + end + + # Returns the default layout for this controller. + # Optionally raises an exception if the layout could not be found. + # + # ==== Parameters + # * formats - The formats accepted to this layout + # * require_layout - If set to +true+ and layout is not found, + # an +ArgumentError+ exception is raised (defaults to +false+) + # + # ==== Returns + # * template - The template object for the default layout (or +nil+) + def _default_layout(formats, require_layout = false) + begin + value = _layout(formats) if action_has_layout? + rescue NameError => e + raise e, "Could not render layout: #{e.message}" + end + + if require_layout && action_has_layout? && !value + raise ArgumentError, + "There was no default layout for #{self.class} in #{view_paths.inspect}" + end + + _normalize_layout(value) + end + + def _include_layout?(options) + (options.keys & [:body, :plain, :html, :inline, :partial]).empty? || options.key?(:layout) + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/locale/en.yml b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/locale/en.yml new file mode 100644 index 00000000..8a56f147 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/locale/en.yml @@ -0,0 +1,56 @@ +"en": + # Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words() + datetime: + distance_in_words: + half_a_minute: "half a minute" + less_than_x_seconds: + one: "less than 1 second" + other: "less than %{count} seconds" + x_seconds: + one: "1 second" + other: "%{count} seconds" + less_than_x_minutes: + one: "less than a minute" + other: "less than %{count} minutes" + x_minutes: + one: "1 minute" + other: "%{count} minutes" + about_x_hours: + one: "about 1 hour" + other: "about %{count} hours" + x_days: + one: "1 day" + other: "%{count} days" + about_x_months: + one: "about 1 month" + other: "about %{count} months" + x_months: + one: "1 month" + other: "%{count} months" + about_x_years: + one: "about 1 year" + other: "about %{count} years" + over_x_years: + one: "over 1 year" + other: "over %{count} years" + almost_x_years: + one: "almost 1 year" + other: "almost %{count} years" + prompts: + year: "Year" + month: "Month" + day: "Day" + hour: "Hour" + minute: "Minute" + second: "Seconds" + + helpers: + select: + # Default value for :prompt => true in FormOptionsHelper + prompt: "Please select" + + # Default translation keys for submit and button FormHelper + submit: + create: 'Create %{model}' + update: 'Update %{model}' + submit: 'Save %{model}' diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/log_subscriber.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/log_subscriber.rb new file mode 100644 index 00000000..d4ac77e1 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/log_subscriber.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +require "active_support/log_subscriber" + +module ActionView + # = Action View Log Subscriber + # + # Provides functionality so that Rails can output logs from Action View. + class LogSubscriber < ActiveSupport::LogSubscriber + VIEWS_PATTERN = /^app\/views\// + + def initialize + @root = nil + super + end + + def render_template(event) + info do + message = " Rendered #{from_rails_root(event.payload[:identifier])}".dup + message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout] + message << " (#{event.duration.round(1)}ms)" + end + end + + def render_partial(event) + info do + message = " Rendered #{from_rails_root(event.payload[:identifier])}".dup + message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout] + message << " (#{event.duration.round(1)}ms)" + message << " #{cache_message(event.payload)}" unless event.payload[:cache_hit].nil? + message + end + end + + def render_collection(event) + identifier = event.payload[:identifier] || "templates" + + info do + " Rendered collection of #{from_rails_root(identifier)}" \ + " #{render_count(event.payload)} (#{event.duration.round(1)}ms)" + end + end + + def start(name, id, payload) + if name == "render_template.action_view" + log_rendering_start(payload) + end + + super + end + + def logger + ActionView::Base.logger + end + + private + + EMPTY = "" + def from_rails_root(string) # :doc: + string = string.sub(rails_root, EMPTY) + string.sub!(VIEWS_PATTERN, EMPTY) + string + end + + def rails_root # :doc: + @root ||= "#{Rails.root}/" + end + + def render_count(payload) # :doc: + if payload[:cache_hits] + "[#{payload[:cache_hits]} / #{payload[:count]} cache hits]" + else + "[#{payload[:count]} times]" + end + end + + def cache_message(payload) # :doc: + case payload[:cache_hit] + when :hit + "[cache hit]" + when :miss + "[cache miss]" + end + end + + def log_rendering_start(payload) + info do + message = " Rendering #{from_rails_root(payload[:identifier])}".dup + message << " within #{from_rails_root(payload[:layout])}" if payload[:layout] + message + end + end + end +end + +ActionView::LogSubscriber.attach_to :action_view diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/lookup_context.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/lookup_context.rb new file mode 100644 index 00000000..0e56eca3 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/lookup_context.rb @@ -0,0 +1,274 @@ +# frozen_string_literal: true + +require "concurrent/map" +require "active_support/core_ext/module/remove_method" +require "active_support/core_ext/module/attribute_accessors" +require "action_view/template/resolver" + +module ActionView + # = Action View Lookup Context + # + # LookupContext is the object responsible for holding all information + # required for looking up templates, i.e. view paths and details. + # LookupContext is also responsible for generating a key, given to + # view paths, used in the resolver cache lookup. Since this key is generated + # only once during the request, it speeds up all cache accesses. + class LookupContext #:nodoc: + attr_accessor :prefixes, :rendered_format + + mattr_accessor :fallbacks, default: FallbackFileSystemResolver.instances + + mattr_accessor :registered_details, default: [] + + def self.register_detail(name, &block) + registered_details << name + Accessors::DEFAULT_PROCS[name] = block + + Accessors.send :define_method, :"default_#{name}", &block + Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1 + def #{name} + @details.fetch(:#{name}, []) + end + + def #{name}=(value) + value = value.present? ? Array(value) : default_#{name} + _set_detail(:#{name}, value) if value != @details[:#{name}] + end + METHOD + end + + # Holds accessors for the registered details. + module Accessors #:nodoc: + DEFAULT_PROCS = {} + end + + register_detail(:locale) do + locales = [I18n.locale] + locales.concat(I18n.fallbacks[I18n.locale]) if I18n.respond_to? :fallbacks + locales << I18n.default_locale + locales.uniq! + locales + end + register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json] } + register_detail(:variants) { [] } + register_detail(:handlers) { Template::Handlers.extensions } + + class DetailsKey #:nodoc: + alias :eql? :equal? + + @details_keys = Concurrent::Map.new + + def self.get(details) + if details[:formats] + details = details.dup + details[:formats] &= Template::Types.symbols + end + @details_keys[details] ||= Concurrent::Map.new + end + + def self.clear + @details_keys.clear + end + + def self.digest_caches + @details_keys.values + end + end + + # Add caching behavior on top of Details. + module DetailsCache + attr_accessor :cache + + # Calculate the details key. Remove the handlers from calculation to improve performance + # since the user cannot modify it explicitly. + def details_key #:nodoc: + @details_key ||= DetailsKey.get(@details) if @cache + end + + # Temporary skip passing the details_key forward. + def disable_cache + old_value, @cache = @cache, false + yield + ensure + @cache = old_value + end + + private + + def _set_detail(key, value) # :doc: + @details = @details.dup if @details_key + @details_key = nil + @details[key] = value + end + end + + # Helpers related to template lookup using the lookup context information. + module ViewPaths + attr_reader :view_paths, :html_fallback_for_js + + # Whenever setting view paths, makes a copy so that we can manipulate them in + # instance objects as we wish. + def view_paths=(paths) + @view_paths = ActionView::PathSet.new(Array(paths)) + end + + def find(name, prefixes = [], partial = false, keys = [], options = {}) + @view_paths.find(*args_for_lookup(name, prefixes, partial, keys, options)) + end + alias :find_template :find + + def find_file(name, prefixes = [], partial = false, keys = [], options = {}) + @view_paths.find_file(*args_for_lookup(name, prefixes, partial, keys, options)) + end + + def find_all(name, prefixes = [], partial = false, keys = [], options = {}) + @view_paths.find_all(*args_for_lookup(name, prefixes, partial, keys, options)) + end + + def exists?(name, prefixes = [], partial = false, keys = [], **options) + @view_paths.exists?(*args_for_lookup(name, prefixes, partial, keys, options)) + end + alias :template_exists? :exists? + + def any?(name, prefixes = [], partial = false) + @view_paths.exists?(*args_for_any(name, prefixes, partial)) + end + alias :any_templates? :any? + + # Adds fallbacks to the view paths. Useful in cases when you are rendering + # a :file. + def with_fallbacks + added_resolvers = 0 + self.class.fallbacks.each do |resolver| + next if view_paths.include?(resolver) + view_paths.push(resolver) + added_resolvers += 1 + end + yield + ensure + added_resolvers.times { view_paths.pop } + end + + private + + def args_for_lookup(name, prefixes, partial, keys, details_options) + name, prefixes = normalize_name(name, prefixes) + details, details_key = detail_args_for(details_options) + [name, prefixes, partial || false, details, details_key, keys] + end + + # Compute details hash and key according to user options (e.g. passed from #render). + def detail_args_for(options) # :doc: + return @details, details_key if options.empty? # most common path. + user_details = @details.merge(options) + + if @cache + details_key = DetailsKey.get(user_details) + else + details_key = nil + end + + [user_details, details_key] + end + + def args_for_any(name, prefixes, partial) + name, prefixes = normalize_name(name, prefixes) + details, details_key = detail_args_for_any + [name, prefixes, partial || false, details, details_key] + end + + def detail_args_for_any + @detail_args_for_any ||= begin + details = {} + + registered_details.each do |k| + if k == :variants + details[k] = :any + else + details[k] = Accessors::DEFAULT_PROCS[k].call + end + end + + if @cache + [details, DetailsKey.get(details)] + else + [details, nil] + end + end + end + + # Support legacy foo.erb names even though we now ignore .erb + # as well as incorrectly putting part of the path in the template + # name instead of the prefix. + def normalize_name(name, prefixes) + prefixes = prefixes.presence + parts = name.to_s.split("/".freeze) + parts.shift if parts.first.empty? + name = parts.pop + + return name, prefixes || [""] if parts.empty? + + parts = parts.join("/".freeze) + prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts] + + return name, prefixes + end + end + + include Accessors + include DetailsCache + include ViewPaths + + def initialize(view_paths, details = {}, prefixes = []) + @details_key = nil + @cache = true + @prefixes = prefixes + @rendered_format = nil + + @details = initialize_details({}, details) + self.view_paths = view_paths + end + + def digest_cache + details_key + end + + def initialize_details(target, details) + registered_details.each do |k| + target[k] = details[k] || Accessors::DEFAULT_PROCS[k].call + end + target + end + private :initialize_details + + # Override formats= to expand ["*/*"] values and automatically + # add :html as fallback to :js. + def formats=(values) + if values + values.concat(default_formats) if values.delete "*/*".freeze + if values == [:js] + values << :html + @html_fallback_for_js = true + end + end + super(values) + end + + # Override locale to return a symbol instead of array. + def locale + @details[:locale].first + end + + # Overload locale= to also set the I18n.locale. If the current I18n.config object responds + # to original_config, it means that it has a copy of the original I18n configuration and it's + # acting as proxy, which we need to skip. + def locale=(value) + if value + config = I18n.config.respond_to?(:original_config) ? I18n.config.original_config : I18n.config + config.locale = value + end + + super(default_locale) + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/model_naming.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/model_naming.rb new file mode 100644 index 00000000..23cca8d6 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/model_naming.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module ActionView + module ModelNaming #:nodoc: + # Converts the given object to an ActiveModel compliant one. + def convert_to_model(object) + object.respond_to?(:to_model) ? object.to_model : object + end + + def model_name_from_record_or_class(record_or_class) + convert_to_model(record_or_class).model_name + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/path_set.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/path_set.rb new file mode 100644 index 00000000..691b53e2 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/path_set.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +module ActionView #:nodoc: + # = Action View PathSet + # + # This class is used to store and access paths in Action View. A number of + # operations are defined so that you can search among the paths in this + # set and also perform operations on other +PathSet+ objects. + # + # A +LookupContext+ will use a +PathSet+ to store the paths in its context. + class PathSet #:nodoc: + include Enumerable + + attr_reader :paths + + delegate :[], :include?, :pop, :size, :each, to: :paths + + def initialize(paths = []) + @paths = typecast paths + end + + def initialize_copy(other) + @paths = other.paths.dup + self + end + + def to_ary + paths.dup + end + + def compact + PathSet.new paths.compact + end + + def +(array) + PathSet.new(paths + array) + end + + %w(<< concat push insert unshift).each do |method| + class_eval <<-METHOD, __FILE__, __LINE__ + 1 + def #{method}(*args) + paths.#{method}(*typecast(args)) + end + METHOD + end + + def find(*args) + find_all(*args).first || raise(MissingTemplate.new(self, *args)) + end + + def find_file(path, prefixes = [], *args) + _find_all(path, prefixes, args, true).first || raise(MissingTemplate.new(self, path, prefixes, *args)) + end + + def find_all(path, prefixes = [], *args) + _find_all path, prefixes, args, false + end + + def exists?(path, prefixes, *args) + find_all(path, prefixes, *args).any? + end + + def find_all_with_query(query) # :nodoc: + paths.each do |resolver| + templates = resolver.find_all_with_query(query) + return templates unless templates.empty? + end + + [] + end + + private + + def _find_all(path, prefixes, args, outside_app) + prefixes = [prefixes] if String === prefixes + prefixes.each do |prefix| + paths.each do |resolver| + if outside_app + templates = resolver.find_all_anywhere(path, prefix, *args) + else + templates = resolver.find_all(path, prefix, *args) + end + return templates unless templates.empty? + end + end + [] + end + + def typecast(paths) + paths.map do |path| + case path + when Pathname, String + OptimizedFileSystemResolver.new path.to_s + else + path + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/railtie.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/railtie.rb new file mode 100644 index 00000000..73dfb267 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/railtie.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require "action_view" +require "rails" + +module ActionView + # = Action View Railtie + class Railtie < Rails::Engine # :nodoc: + config.action_view = ActiveSupport::OrderedOptions.new + config.action_view.embed_authenticity_token_in_remote_forms = nil + config.action_view.debug_missing_translation = true + + config.eager_load_namespaces << ActionView + + initializer "action_view.embed_authenticity_token_in_remote_forms" do |app| + ActiveSupport.on_load(:action_view) do + ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = + app.config.action_view.delete(:embed_authenticity_token_in_remote_forms) + end + end + + initializer "action_view.form_with_generates_remote_forms" do |app| + ActiveSupport.on_load(:action_view) do + form_with_generates_remote_forms = app.config.action_view.delete(:form_with_generates_remote_forms) + ActionView::Helpers::FormHelper.form_with_generates_remote_forms = form_with_generates_remote_forms + end + end + + initializer "action_view.form_with_generates_ids" do |app| + ActiveSupport.on_load(:action_view) do + form_with_generates_ids = app.config.action_view.delete(:form_with_generates_ids) + unless form_with_generates_ids.nil? + ActionView::Helpers::FormHelper.form_with_generates_ids = form_with_generates_ids + end + end + end + + initializer "action_view.logger" do + ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger } + end + + initializer "action_view.set_configs" do |app| + ActiveSupport.on_load(:action_view) do + app.config.action_view.each do |k, v| + send "#{k}=", v + end + end + end + + initializer "action_view.caching" do |app| + ActiveSupport.on_load(:action_view) do + if app.config.action_view.cache_template_loading.nil? + ActionView::Resolver.caching = app.config.cache_classes + end + end + end + + initializer "action_view.per_request_digest_cache" do |app| + ActiveSupport.on_load(:action_view) do + unless ActionView::Resolver.caching? + app.executor.to_run ActionView::Digestor::PerExecutionDigestCacheExpiry + end + end + end + + initializer "action_view.setup_action_pack" do |app| + ActiveSupport.on_load(:action_controller) do + ActionView::RoutingUrlFor.include(ActionDispatch::Routing::UrlFor) + end + end + + initializer "action_view.collection_caching", after: "action_controller.set_configs" do |app| + PartialRenderer.collection_cache = app.config.action_controller.cache_store + end + + rake_tasks do |app| + unless app.config.api_only + load "action_view/tasks/cache_digests.rake" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/record_identifier.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/record_identifier.rb new file mode 100644 index 00000000..1310a1ce --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/record_identifier.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module" +require "action_view/model_naming" + +module ActionView + # RecordIdentifier encapsulates methods used by various ActionView helpers + # to associate records with DOM elements. + # + # Consider for example the following code that form of post: + # + # <%= form_for(post) do |f| %> + # <%= f.text_field :body %> + # <% end %> + # + # When +post+ is a new, unsaved ActiveRecord::Base instance, the resulting HTML + # is: + # + #
    + # + #
    + # + # When +post+ is a persisted ActiveRecord::Base instance, the resulting HTML + # is: + # + #
    + # + #
    + # + # In both cases, the +id+ and +class+ of the wrapping DOM element are + # automatically generated, following naming conventions encapsulated by the + # RecordIdentifier methods #dom_id and #dom_class: + # + # dom_id(Post.new) # => "new_post" + # dom_class(Post.new) # => "post" + # dom_id(Post.find 42) # => "post_42" + # dom_class(Post.find 42) # => "post" + # + # Note that these methods do not strictly require +Post+ to be a subclass of + # ActiveRecord::Base. + # Any +Post+ class will work as long as its instances respond to +to_key+ + # and +model_name+, given that +model_name+ responds to +param_key+. + # For instance: + # + # class Post + # attr_accessor :to_key + # + # def model_name + # OpenStruct.new param_key: 'post' + # end + # + # def self.find(id) + # new.tap { |post| post.to_key = [id] } + # end + # end + module RecordIdentifier + extend self + extend ModelNaming + + include ModelNaming + + JOIN = "_".freeze + NEW = "new".freeze + + # The DOM class convention is to use the singular form of an object or class. + # + # dom_class(post) # => "post" + # dom_class(Person) # => "person" + # + # If you need to address multiple instances of the same class in the same view, you can prefix the dom_class: + # + # dom_class(post, :edit) # => "edit_post" + # dom_class(Person, :edit) # => "edit_person" + def dom_class(record_or_class, prefix = nil) + singular = model_name_from_record_or_class(record_or_class).param_key + prefix ? "#{prefix}#{JOIN}#{singular}" : singular + end + + # The DOM id convention is to use the singular form of an object or class with the id following an underscore. + # If no id is found, prefix with "new_" instead. + # + # dom_id(Post.find(45)) # => "post_45" + # dom_id(Post.new) # => "new_post" + # + # If you need to address multiple instances of the same class in the same view, you can prefix the dom_id: + # + # dom_id(Post.find(45), :edit) # => "edit_post_45" + # dom_id(Post.new, :custom) # => "custom_post" + def dom_id(record, prefix = nil) + if record_id = record_key_for_dom_id(record) + "#{dom_class(record, prefix)}#{JOIN}#{record_id}" + else + dom_class(record, prefix || NEW) + end + end + + private + + # Returns a string representation of the key attribute(s) that is suitable for use in an HTML DOM id. + # This can be overwritten to customize the default generated string representation if desired. + # If you need to read back a key from a dom_id in order to query for the underlying database record, + # you should write a helper like 'person_record_from_dom_id' that will extract the key either based + # on the default implementation (which just joins all key attributes with '_') or on your own + # overwritten version of the method. By default, this implementation passes the key string through a + # method that replaces all characters that are invalid inside DOM ids, with valid ones. You need to + # make sure yourself that your dom ids are valid, in case you overwrite this method. + def record_key_for_dom_id(record) # :doc: + key = convert_to_model(record).to_key + key ? key.join(JOIN) : key + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/renderer/abstract_renderer.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/renderer/abstract_renderer.rb new file mode 100644 index 00000000..20b2523c --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/renderer/abstract_renderer.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module ActionView + # This class defines the interface for a renderer. Each class that + # subclasses +AbstractRenderer+ is used by the base +Renderer+ class to + # render a specific type of object. + # + # The base +Renderer+ class uses its +render+ method to delegate to the + # renderers. These currently consist of + # + # PartialRenderer - Used for rendering partials + # TemplateRenderer - Used for rendering other types of templates + # StreamingTemplateRenderer - Used for streaming + # + # Whenever the +render+ method is called on the base +Renderer+ class, a new + # renderer object of the correct type is created, and the +render+ method on + # that new object is called in turn. This abstracts the setup and rendering + # into a separate classes for partials and templates. + class AbstractRenderer #:nodoc: + delegate :find_template, :find_file, :template_exists?, :any_templates?, :with_fallbacks, :with_layout_format, :formats, to: :@lookup_context + + def initialize(lookup_context) + @lookup_context = lookup_context + end + + def render + raise NotImplementedError + end + + private + + def extract_details(options) # :doc: + @lookup_context.registered_details.each_with_object({}) do |key, details| + value = options[key] + + details[key] = Array(value) if value + end + end + + def instrument(name, **options) # :doc: + options[:identifier] ||= (@template && @template.identifier) || @path + + ActiveSupport::Notifications.instrument("render_#{name}.action_view", options) do |payload| + yield payload + end + end + + def prepend_formats(formats) # :doc: + formats = Array(formats) + return if formats.empty? || @lookup_context.html_fallback_for_js + + @lookup_context.formats = formats | @lookup_context.formats + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/renderer/partial_renderer.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/renderer/partial_renderer.rb new file mode 100644 index 00000000..5b40af4f --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/renderer/partial_renderer.rb @@ -0,0 +1,552 @@ +# frozen_string_literal: true + +require "concurrent/map" +require "action_view/renderer/partial_renderer/collection_caching" + +module ActionView + class PartialIteration + # The number of iterations that will be done by the partial. + attr_reader :size + + # The current iteration of the partial. + attr_reader :index + + def initialize(size) + @size = size + @index = 0 + end + + # Check if this is the first iteration of the partial. + def first? + index == 0 + end + + # Check if this is the last iteration of the partial. + def last? + index == size - 1 + end + + def iterate! # :nodoc: + @index += 1 + end + end + + # = Action View Partials + # + # There's also a convenience method for rendering sub templates within the current controller that depends on a + # single object (we call this kind of sub templates for partials). It relies on the fact that partials should + # follow the naming convention of being prefixed with an underscore -- as to separate them from regular + # templates that could be rendered on their own. + # + # In a template for Advertiser#account: + # + # <%= render partial: "account" %> + # + # This would render "advertiser/_account.html.erb". + # + # In another template for Advertiser#buy, we could have: + # + # <%= render partial: "account", locals: { account: @buyer } %> + # + # <% @advertisements.each do |ad| %> + # <%= render partial: "ad", locals: { ad: ad } %> + # <% end %> + # + # This would first render advertiser/_account.html.erb with @buyer passed in as the local variable +account+, then + # render advertiser/_ad.html.erb and pass the local variable +ad+ to the template for display. + # + # == The :as and :object options + # + # By default ActionView::PartialRenderer doesn't have any local variables. + # The :object option can be used to pass an object to the partial. For instance: + # + # <%= render partial: "account", object: @buyer %> + # + # would provide the @buyer object to the partial, available under the local variable +account+ and is + # equivalent to: + # + # <%= render partial: "account", locals: { account: @buyer } %> + # + # With the :as option we can specify a different name for said local variable. For example, if we + # wanted it to be +user+ instead of +account+ we'd do: + # + # <%= render partial: "account", object: @buyer, as: 'user' %> + # + # This is equivalent to + # + # <%= render partial: "account", locals: { user: @buyer } %> + # + # == \Rendering a collection of partials + # + # The example of partial use describes a familiar pattern where a template needs to iterate over an array and + # render a sub template for each of the elements. This pattern has been implemented as a single method that + # accepts an array and renders a partial by the same name as the elements contained within. So the three-lined + # example in "Using partials" can be rewritten with a single line: + # + # <%= render partial: "ad", collection: @advertisements %> + # + # This will render advertiser/_ad.html.erb and pass the local variable +ad+ to the template for display. An + # iteration object will automatically be made available to the template with a name of the form + # +partial_name_iteration+. The iteration object has knowledge about which index the current object has in + # the collection and the total size of the collection. The iteration object also has two convenience methods, + # +first?+ and +last?+. In the case of the example above, the template would be fed +ad_iteration+. + # For backwards compatibility the +partial_name_counter+ is still present and is mapped to the iteration's + # +index+ method. + # + # The :as option may be used when rendering partials. + # + # You can specify a partial to be rendered between elements via the :spacer_template option. + # The following example will render advertiser/_ad_divider.html.erb between each ad partial: + # + # <%= render partial: "ad", collection: @advertisements, spacer_template: "ad_divider" %> + # + # If the given :collection is +nil+ or empty, render will return +nil+. This will allow you + # to specify a text which will be displayed instead by using this form: + # + # <%= render(partial: "ad", collection: @advertisements) || "There's no ad to be displayed" %> + # + # NOTE: Due to backwards compatibility concerns, the collection can't be one of hashes. Normally you'd also + # just keep domain objects, like Active Records, in there. + # + # == \Rendering shared partials + # + # Two controllers can share a set of partials and render them like this: + # + # <%= render partial: "advertisement/ad", locals: { ad: @advertisement } %> + # + # This will render the partial advertisement/_ad.html.erb regardless of which controller this is being called from. + # + # == \Rendering objects that respond to +to_partial_path+ + # + # Instead of explicitly naming the location of a partial, you can also let PartialRenderer do the work + # and pick the proper path by checking +to_partial_path+ method. + # + # # @account.to_partial_path returns 'accounts/account', so it can be used to replace: + # # <%= render partial: "accounts/account", locals: { account: @account} %> + # <%= render partial: @account %> + # + # # @posts is an array of Post instances, so every post record returns 'posts/post' on +to_partial_path+, + # # that's why we can replace: + # # <%= render partial: "posts/post", collection: @posts %> + # <%= render partial: @posts %> + # + # == \Rendering the default case + # + # If you're not going to be using any of the options like collections or layouts, you can also use the short-hand + # defaults of render to render partials. Examples: + # + # # Instead of <%= render partial: "account" %> + # <%= render "account" %> + # + # # Instead of <%= render partial: "account", locals: { account: @buyer } %> + # <%= render "account", account: @buyer %> + # + # # @account.to_partial_path returns 'accounts/account', so it can be used to replace: + # # <%= render partial: "accounts/account", locals: { account: @account} %> + # <%= render @account %> + # + # # @posts is an array of Post instances, so every post record returns 'posts/post' on +to_partial_path+, + # # that's why we can replace: + # # <%= render partial: "posts/post", collection: @posts %> + # <%= render @posts %> + # + # == \Rendering partials with layouts + # + # Partials can have their own layouts applied to them. These layouts are different than the ones that are + # specified globally for the entire action, but they work in a similar fashion. Imagine a list with two types + # of users: + # + # <%# app/views/users/index.html.erb %> + # Here's the administrator: + # <%= render partial: "user", layout: "administrator", locals: { user: administrator } %> + # + # Here's the editor: + # <%= render partial: "user", layout: "editor", locals: { user: editor } %> + # + # <%# app/views/users/_user.html.erb %> + # Name: <%= user.name %> + # + # <%# app/views/users/_administrator.html.erb %> + #
    + # Budget: $<%= user.budget %> + # <%= yield %> + #
    + # + # <%# app/views/users/_editor.html.erb %> + #
    + # Deadline: <%= user.deadline %> + # <%= yield %> + #
    + # + # ...this will return: + # + # Here's the administrator: + #
    + # Budget: $<%= user.budget %> + # Name: <%= user.name %> + #
    + # + # Here's the editor: + #
    + # Deadline: <%= user.deadline %> + # Name: <%= user.name %> + #
    + # + # If a collection is given, the layout will be rendered once for each item in + # the collection. For example, these two snippets have the same output: + # + # <%# app/views/users/_user.html.erb %> + # Name: <%= user.name %> + # + # <%# app/views/users/index.html.erb %> + # <%# This does not use layouts %> + #
      + # <% users.each do |user| -%> + #
    • + # <%= render partial: "user", locals: { user: user } %> + #
    • + # <% end -%> + #
    + # + # <%# app/views/users/_li_layout.html.erb %> + #
  • + # <%= yield %> + #
  • + # + # <%# app/views/users/index.html.erb %> + #
      + # <%= render partial: "user", layout: "li_layout", collection: users %> + #
    + # + # Given two users whose names are Alice and Bob, these snippets return: + # + #
      + #
    • + # Name: Alice + #
    • + #
    • + # Name: Bob + #
    • + #
    + # + # The current object being rendered, as well as the object_counter, will be + # available as local variables inside the layout template under the same names + # as available in the partial. + # + # You can also apply a layout to a block within any template: + # + # <%# app/views/users/_chief.html.erb %> + # <%= render(layout: "administrator", locals: { user: chief }) do %> + # Title: <%= chief.title %> + # <% end %> + # + # ...this will return: + # + #
    + # Budget: $<%= user.budget %> + # Title: <%= chief.name %> + #
    + # + # As you can see, the :locals hash is shared between both the partial and its layout. + # + # If you pass arguments to "yield" then this will be passed to the block. One way to use this is to pass + # an array to layout and treat it as an enumerable. + # + # <%# app/views/users/_user.html.erb %> + #
    + # Budget: $<%= user.budget %> + # <%= yield user %> + #
    + # + # <%# app/views/users/index.html.erb %> + # <%= render layout: @users do |user| %> + # Title: <%= user.title %> + # <% end %> + # + # This will render the layout for each user and yield to the block, passing the user, each time. + # + # You can also yield multiple times in one layout and use block arguments to differentiate the sections. + # + # <%# app/views/users/_user.html.erb %> + #
    + # <%= yield user, :header %> + # Budget: $<%= user.budget %> + # <%= yield user, :footer %> + #
    + # + # <%# app/views/users/index.html.erb %> + # <%= render layout: @users do |user, section| %> + # <%- case section when :header -%> + # Title: <%= user.title %> + # <%- when :footer -%> + # Deadline: <%= user.deadline %> + # <%- end -%> + # <% end %> + class PartialRenderer < AbstractRenderer + include CollectionCaching + + PREFIXED_PARTIAL_NAMES = Concurrent::Map.new do |h, k| + h[k] = Concurrent::Map.new + end + + def initialize(*) + super + @context_prefix = @lookup_context.prefixes.first + end + + def render(context, options, block) + setup(context, options, block) + @template = find_partial + + @lookup_context.rendered_format ||= begin + if @template && @template.formats.present? + @template.formats.first + else + formats.first + end + end + + if @collection + render_collection + else + render_partial + end + end + + private + + def render_collection + instrument(:collection, count: @collection.size) do |payload| + return nil if @collection.blank? + + if @options.key?(:spacer_template) + spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals) + end + + cache_collection_render(payload) do + @template ? collection_with_template : collection_without_template + end.join(spacer).html_safe + end + end + + def render_partial + instrument(:partial) do |payload| + view, locals, block = @view, @locals, @block + object, as = @object, @variable + + if !block && (layout = @options[:layout]) + layout = find_template(layout.to_s, @template_keys) + end + + object = locals[as] if object.nil? # Respect object when object is false + locals[as] = object if @has_object + + content = @template.render(view, locals) do |*name| + view._layout_for(*name, &block) + end + + content = layout.render(view, locals) { content } if layout + payload[:cache_hit] = view.view_renderer.cache_hits[@template.virtual_path] + content + end + end + + # Sets up instance variables needed for rendering a partial. This method + # finds the options and details and extracts them. The method also contains + # logic that handles the type of object passed in as the partial. + # + # If +options[:partial]+ is a string, then the @path instance variable is + # set to that string. Otherwise, the +options[:partial]+ object must + # respond to +to_partial_path+ in order to setup the path. + def setup(context, options, block) + @view = context + @options = options + @block = block + + @locals = options[:locals] || {} + @details = extract_details(options) + + prepend_formats(options[:formats]) + + partial = options[:partial] + + if String === partial + @has_object = options.key?(:object) + @object = options[:object] + @collection = collection_from_options + @path = partial + else + @has_object = true + @object = partial + @collection = collection_from_object || collection_from_options + + if @collection + paths = @collection_data = @collection.map { |o| partial_path(o) } + @path = paths.uniq.one? ? paths.first : nil + else + @path = partial_path + end + end + + if as = options[:as] + raise_invalid_option_as(as) unless /\A[a-z_]\w*\z/.match?(as.to_s) + as = as.to_sym + end + + if @path + @variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as) + @template_keys = retrieve_template_keys + else + paths.map! { |path| retrieve_variable(path, as).unshift(path) } + end + + self + end + + def collection_from_options + if @options.key?(:collection) + collection = @options[:collection] + collection ? collection.to_a : [] + end + end + + def collection_from_object + @object.to_ary if @object.respond_to?(:to_ary) + end + + def find_partial + find_template(@path, @template_keys) if @path + end + + def find_template(path, locals) + prefixes = path.include?(?/) ? [] : @lookup_context.prefixes + @lookup_context.find_template(path, prefixes, true, locals, @details) + end + + def collection_with_template + view, locals, template = @view, @locals, @template + as, counter, iteration = @variable, @variable_counter, @variable_iteration + + if layout = @options[:layout] + layout = find_template(layout, @template_keys) + end + + partial_iteration = PartialIteration.new(@collection.size) + locals[iteration] = partial_iteration + + @collection.map do |object| + locals[as] = object + locals[counter] = partial_iteration.index + + content = template.render(view, locals) + content = layout.render(view, locals) { content } if layout + partial_iteration.iterate! + content + end + end + + def collection_without_template + view, locals, collection_data = @view, @locals, @collection_data + cache = {} + keys = @locals.keys + + partial_iteration = PartialIteration.new(@collection.size) + + @collection.map do |object| + index = partial_iteration.index + path, as, counter, iteration = collection_data[index] + + locals[as] = object + locals[counter] = index + locals[iteration] = partial_iteration + + template = (cache[path] ||= find_template(path, keys + [as, counter, iteration])) + content = template.render(view, locals) + partial_iteration.iterate! + content + end + end + + # Obtains the path to where the object's partial is located. If the object + # responds to +to_partial_path+, then +to_partial_path+ will be called and + # will provide the path. If the object does not respond to +to_partial_path+, + # then an +ArgumentError+ is raised. + # + # If +prefix_partial_path_with_controller_namespace+ is true, then this + # method will prefix the partial paths with a namespace. + def partial_path(object = @object) + object = object.to_model if object.respond_to?(:to_model) + + path = if object.respond_to?(:to_partial_path) + object.to_partial_path + else + raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.") + end + + if @view.prefix_partial_path_with_controller_namespace + prefixed_partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup) + else + path + end + end + + def prefixed_partial_names + @prefixed_partial_names ||= PREFIXED_PARTIAL_NAMES[@context_prefix] + end + + def merge_prefix_into_object_path(prefix, object_path) + if prefix.include?(?/) && object_path.include?(?/) + prefixes = [] + prefix_array = File.dirname(prefix).split("/") + object_path_array = object_path.split("/")[0..-3] # skip model dir & partial + + prefix_array.each_with_index do |dir, index| + break if dir == object_path_array[index] + prefixes << dir + end + + (prefixes << object_path).join("/") + else + object_path + end + end + + def retrieve_template_keys + keys = @locals.keys + keys << @variable if @has_object || @collection + if @collection + keys << @variable_counter + keys << @variable_iteration + end + keys + end + + def retrieve_variable(path, as) + variable = as || begin + base = path[-1] == "/".freeze ? "".freeze : File.basename(path) + raise_invalid_identifier(path) unless base =~ /\A_?(.*?)(?:\.\w+)*\z/ + $1.to_sym + end + if @collection + variable_counter = :"#{variable}_counter" + variable_iteration = :"#{variable}_iteration" + end + [variable, variable_counter, variable_iteration] + end + + IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " \ + "make sure your partial name starts with underscore." + + OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " \ + "make sure it starts with lowercase letter, " \ + "and is followed by any combination of letters, numbers and underscores." + + def raise_invalid_identifier(path) + raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path)) + end + + def raise_invalid_option_as(as) + raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as)) + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/renderer/partial_renderer/collection_caching.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/renderer/partial_renderer/collection_caching.rb new file mode 100644 index 00000000..db52919e --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/renderer/partial_renderer/collection_caching.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module ActionView + module CollectionCaching # :nodoc: + extend ActiveSupport::Concern + + included do + # Fallback cache store if Action View is used without Rails. + # Otherwise overridden in Railtie to use Rails.cache. + mattr_accessor :collection_cache, default: ActiveSupport::Cache::MemoryStore.new + end + + private + def cache_collection_render(instrumentation_payload) + return yield unless @options[:cached] + + keyed_collection = collection_by_cache_keys + cached_partials = collection_cache.read_multi(*keyed_collection.keys) + instrumentation_payload[:cache_hits] = cached_partials.size + + @collection = keyed_collection.reject { |key, _| cached_partials.key?(key) }.values + rendered_partials = @collection.empty? ? [] : yield + + index = 0 + fetch_or_cache_partial(cached_partials, order_by: keyed_collection.each_key) do + rendered_partials[index].tap { index += 1 } + end + end + + def callable_cache_key? + @options[:cached].respond_to?(:call) + end + + def collection_by_cache_keys + seed = callable_cache_key? ? @options[:cached] : ->(i) { i } + + @collection.each_with_object({}) do |item, hash| + hash[expanded_cache_key(seed.call(item))] = item + end + end + + def expanded_cache_key(key) + key = @view.combined_fragment_cache_key(@view.cache_fragment_name(key, virtual_path: @template.virtual_path)) + key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0. + end + + def fetch_or_cache_partial(cached_partials, order_by:) + order_by.map do |cache_key| + cached_partials.fetch(cache_key) do + yield.tap do |rendered_partial| + collection_cache.write(cache_key, rendered_partial) + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/renderer/renderer.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/renderer/renderer.rb new file mode 100644 index 00000000..3f3a9752 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/renderer/renderer.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module ActionView + # This is the main entry point for rendering. It basically delegates + # to other objects like TemplateRenderer and PartialRenderer which + # actually renders the template. + # + # The Renderer will parse the options from the +render+ or +render_body+ + # method and render a partial or a template based on the options. The + # +TemplateRenderer+ and +PartialRenderer+ objects are wrappers which do all + # the setup and logic necessary to render a view and a new object is created + # each time +render+ is called. + class Renderer + attr_accessor :lookup_context + + def initialize(lookup_context) + @lookup_context = lookup_context + end + + # Main render entry point shared by Action View and Action Controller. + def render(context, options) + if options.key?(:partial) + render_partial(context, options) + else + render_template(context, options) + end + end + + # Render but returns a valid Rack body. If fibers are defined, we return + # a streaming body that renders the template piece by piece. + # + # Note that partials are not supported to be rendered with streaming, + # so in such cases, we just wrap them in an array. + def render_body(context, options) + if options.key?(:partial) + [render_partial(context, options)] + else + StreamingTemplateRenderer.new(@lookup_context).render(context, options) + end + end + + # Direct access to template rendering. + def render_template(context, options) #:nodoc: + TemplateRenderer.new(@lookup_context).render(context, options) + end + + # Direct access to partial rendering. + def render_partial(context, options, &block) #:nodoc: + PartialRenderer.new(@lookup_context).render(context, options, block) + end + + def cache_hits # :nodoc: + @cache_hits ||= {} + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/renderer/streaming_template_renderer.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/renderer/streaming_template_renderer.rb new file mode 100644 index 00000000..276a28ce --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/renderer/streaming_template_renderer.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +require "fiber" + +module ActionView + # == TODO + # + # * Support streaming from child templates, partials and so on. + # * Rack::Cache needs to support streaming bodies + class StreamingTemplateRenderer < TemplateRenderer #:nodoc: + # A valid Rack::Body (i.e. it responds to each). + # It is initialized with a block that, when called, starts + # rendering the template. + class Body #:nodoc: + def initialize(&start) + @start = start + end + + def each(&block) + begin + @start.call(block) + rescue Exception => exception + log_error(exception) + block.call ActionView::Base.streaming_completion_on_exception + end + self + end + + private + + # This is the same logging logic as in ShowExceptions middleware. + def log_error(exception) + logger = ActionView::Base.logger + return unless logger + + message = "\n#{exception.class} (#{exception.message}):\n".dup + message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code) + message << " " << exception.backtrace.join("\n ") + logger.fatal("#{message}\n\n") + end + end + + # For streaming, instead of rendering a given a template, we return a Body + # object that responds to each. This object is initialized with a block + # that knows how to render the template. + def render_template(template, layout_name = nil, locals = {}) #:nodoc: + return [super] unless layout_name && template.supports_streaming? + + locals ||= {} + layout = layout_name && find_layout(layout_name, locals.keys, [formats.first]) + + Body.new do |buffer| + delayed_render(buffer, template, layout, @view, locals) + end + end + + private + + def delayed_render(buffer, template, layout, view, locals) + # Wrap the given buffer in the StreamingBuffer and pass it to the + # underlying template handler. Now, every time something is concatenated + # to the buffer, it is not appended to an array, but streamed straight + # to the client. + output = ActionView::StreamingBuffer.new(buffer) + yielder = lambda { |*name| view._layout_for(*name) } + + instrument(:template, identifier: template.identifier, layout: layout.try(:virtual_path)) do + outer_config = I18n.config + fiber = Fiber.new do + I18n.config = outer_config + if layout + layout.render(view, locals, output, &yielder) + else + # If you don't have a layout, just render the thing + # and concatenate the final result. This is the same + # as a layout with just <%= yield %> + output.safe_concat view._layout_for + end + end + + # Set the view flow to support streaming. It will be aware + # when to stop rendering the layout because it needs to search + # something in the template and vice-versa. + view.view_flow = StreamingFlow.new(view, fiber) + + # Yo! Start the fiber! + fiber.resume + + # If the fiber is still alive, it means we need something + # from the template, so start rendering it. If not, it means + # the layout exited without requiring anything from the template. + if fiber.alive? + content = template.render(view, locals, &yielder) + + # Once rendering the template is done, sets its content in the :layout key. + view.view_flow.set(:layout, content) + + # In case the layout continues yielding, we need to resume + # the fiber until all yields are handled. + fiber.resume while fiber.alive? + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/renderer/template_renderer.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/renderer/template_renderer.rb new file mode 100644 index 00000000..ce890892 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/renderer/template_renderer.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/try" + +module ActionView + class TemplateRenderer < AbstractRenderer #:nodoc: + def render(context, options) + @view = context + @details = extract_details(options) + template = determine_template(options) + + prepend_formats(template.formats) + + @lookup_context.rendered_format ||= (template.formats.first || formats.first) + + render_template(template, options[:layout], options[:locals]) + end + + private + + # Determine the template to be rendered using the given options. + def determine_template(options) + keys = options.has_key?(:locals) ? options[:locals].keys : [] + + if options.key?(:body) + Template::Text.new(options[:body]) + elsif options.key?(:plain) + Template::Text.new(options[:plain]) + elsif options.key?(:html) + Template::HTML.new(options[:html], formats.first) + elsif options.key?(:file) + with_fallbacks { find_file(options[:file], nil, false, keys, @details) } + elsif options.key?(:inline) + handler = Template.handler_for_extension(options[:type] || "erb") + Template.new(options[:inline], "inline template", handler, locals: keys) + elsif options.key?(:template) + if options[:template].respond_to?(:render) + options[:template] + else + find_template(options[:template], options[:prefixes], false, keys, @details) + end + else + raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :html or :body option." + end + end + + # Renders the given template. A string representing the layout can be + # supplied as well. + def render_template(template, layout_name = nil, locals = nil) + view, locals = @view, locals || {} + + render_with_layout(layout_name, locals) do |layout| + instrument(:template, identifier: template.identifier, layout: layout.try(:virtual_path)) do + template.render(view, locals) { |*name| view._layout_for(*name) } + end + end + end + + def render_with_layout(path, locals) + layout = path && find_layout(path, locals.keys, [formats.first]) + content = yield(layout) + + if layout + view = @view + view.view_flow.set(:layout, content) + layout.render(view, locals) { |*name| view._layout_for(*name) } + else + content + end + end + + # This is the method which actually finds the layout using details in the lookup + # context object. If no layout is found, it checks if at least a layout with + # the given name exists across all details before raising the error. + def find_layout(layout, keys, formats) + resolve_layout(layout, keys, formats) + end + + def resolve_layout(layout, keys, formats) + details = @details.dup + details[:formats] = formats + + case layout + when String + begin + if layout.start_with?("/") + with_fallbacks { find_template(layout, nil, false, [], details) } + else + find_template(layout, nil, false, [], details) + end + rescue ActionView::MissingTemplate + all_details = @details.merge(formats: @lookup_context.default_formats) + raise unless template_exists?(layout, nil, false, [], all_details) + end + when Proc + resolve_layout(layout.call(formats), keys, formats) + else + layout + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/rendering.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/rendering.rb new file mode 100644 index 00000000..4e5fdfbb --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/rendering.rb @@ -0,0 +1,151 @@ +# frozen_string_literal: true + +require "action_view/view_paths" + +module ActionView + # This is a class to fix I18n global state. Whenever you provide I18n.locale during a request, + # it will trigger the lookup_context and consequently expire the cache. + class I18nProxy < ::I18n::Config #:nodoc: + attr_reader :original_config, :lookup_context + + def initialize(original_config, lookup_context) + original_config = original_config.original_config if original_config.respond_to?(:original_config) + @original_config, @lookup_context = original_config, lookup_context + end + + def locale + @original_config.locale + end + + def locale=(value) + @lookup_context.locale = value + end + end + + module Rendering + extend ActiveSupport::Concern + include ActionView::ViewPaths + + # Overwrite process to setup I18n proxy. + def process(*) #:nodoc: + old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context) + super + ensure + I18n.config = old_config + end + + module ClassMethods + def view_context_class + @view_context_class ||= begin + supports_path = supports_path? + routes = respond_to?(:_routes) && _routes + helpers = respond_to?(:_helpers) && _helpers + + Class.new(ActionView::Base) do + if routes + include routes.url_helpers(supports_path) + include routes.mounted_helpers + end + + if helpers + include helpers + end + end + end + end + end + + attr_internal_writer :view_context_class + + def view_context_class + @_view_context_class ||= self.class.view_context_class + end + + # An instance of a view class. The default view class is ActionView::Base. + # + # The view class must have the following methods: + # View.new[lookup_context, assigns, controller] + # Create a new ActionView instance for a controller and we can also pass the arguments. + # View#render(option) + # Returns String with the rendered template + # + # Override this method in a module to change the default behavior. + def view_context + view_context_class.new(view_renderer, view_assigns, self) + end + + # Returns an object that is able to render templates. + def view_renderer # :nodoc: + @_view_renderer ||= ActionView::Renderer.new(lookup_context) + end + + def render_to_body(options = {}) + _process_options(options) + _render_template(options) + end + + def rendered_format + Template::Types[lookup_context.rendered_format] + end + + private + + # Find and render a template based on the options given. + def _render_template(options) + variant = options.delete(:variant) + assigns = options.delete(:assigns) + context = view_context + + context.assign assigns if assigns + lookup_context.rendered_format = nil if options[:formats] + lookup_context.variants = variant if variant + + view_renderer.render(context, options) + end + + # Assign the rendered format to look up context. + def _process_format(format) + super + lookup_context.formats = [format.to_sym] + lookup_context.rendered_format = lookup_context.formats.first + end + + # Normalize args by converting render "foo" to render :action => "foo" and + # render "foo/bar" to render :template => "foo/bar". + def _normalize_args(action = nil, options = {}) + options = super(action, options) + case action + when NilClass + when Hash + options = action + when String, Symbol + action = action.to_s + key = action.include?(?/) ? :template : :action + options[key] = action + else + if action.respond_to?(:permitted?) && action.permitted? + options = action + else + options[:partial] = action + end + end + + options + end + + # Normalize options. + def _normalize_options(options) + options = super(options) + if options[:partial] == true + options[:partial] = action_name + end + + if (options.keys & [:partial, :file, :template]).empty? + options[:prefixes] ||= _prefixes + end + + options[:template] ||= (options[:action] || action_name).to_s + options + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/routing_url_for.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/routing_url_for.rb new file mode 100644 index 00000000..fd563f34 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/routing_url_for.rb @@ -0,0 +1,145 @@ +# frozen_string_literal: true + +require "action_dispatch/routing/polymorphic_routes" + +module ActionView + module RoutingUrlFor + # Returns the URL for the set of +options+ provided. This takes the + # same options as +url_for+ in Action Controller (see the + # documentation for ActionController::Base#url_for). Note that by default + # :only_path is true so you'll get the relative "/controller/action" + # instead of the fully qualified URL like "http://example.com/controller/action". + # + # ==== Options + # * :anchor - Specifies the anchor name to be appended to the path. + # * :only_path - If true, returns the relative URL (omitting the protocol, host name, and port) (true by default unless :host is specified). + # * :trailing_slash - If true, adds a trailing slash, as in "/archive/2005/". Note that this + # is currently not recommended since it breaks caching. + # * :host - Overrides the default (current) host if provided. + # * :protocol - Overrides the default (current) protocol if provided. + # * :user - Inline HTTP authentication (only plucked out if :password is also present). + # * :password - Inline HTTP authentication (only plucked out if :user is also present). + # + # ==== Relying on named routes + # + # Passing a record (like an Active Record) instead of a hash as the options parameter will + # trigger the named route for that record. The lookup will happen on the name of the class. So passing a + # Workshop object will attempt to use the +workshop_path+ route. If you have a nested route, such as + # +admin_workshop_path+ you'll have to call that explicitly (it's impossible for +url_for+ to guess that route). + # + # ==== Implicit Controller Namespacing + # + # Controllers passed in using the +:controller+ option will retain their namespace unless it is an absolute one. + # + # ==== Examples + # <%= url_for(action: 'index') %> + # # => /blogs/ + # + # <%= url_for(action: 'find', controller: 'books') %> + # # => /books/find + # + # <%= url_for(action: 'login', controller: 'members', only_path: false, protocol: 'https') %> + # # => https://www.example.com/members/login/ + # + # <%= url_for(action: 'play', anchor: 'player') %> + # # => /messages/play/#player + # + # <%= url_for(action: 'jump', anchor: 'tax&ship') %> + # # => /testing/jump/#tax&ship + # + # <%= url_for(Workshop.new) %> + # # relies on Workshop answering a persisted? call (and in this case returning false) + # # => /workshops + # + # <%= url_for(@workshop) %> + # # calls @workshop.to_param which by default returns the id + # # => /workshops/5 + # + # # to_param can be re-defined in a model to provide different URL names: + # # => /workshops/1-workshop-name + # + # <%= url_for("http://www.example.com") %> + # # => http://www.example.com + # + # <%= url_for(:back) %> + # # if request.env["HTTP_REFERER"] is set to "http://www.example.com" + # # => http://www.example.com + # + # <%= url_for(:back) %> + # # if request.env["HTTP_REFERER"] is not set or is blank + # # => javascript:history.back() + # + # <%= url_for(action: 'index', controller: 'users') %> + # # Assuming an "admin" namespace + # # => /admin/users + # + # <%= url_for(action: 'index', controller: '/users') %> + # # Specify absolute path with beginning slash + # # => /users + def url_for(options = nil) + case options + when String + options + when nil + super(only_path: _generate_paths_by_default) + when Hash + options = options.symbolize_keys + unless options.key?(:only_path) + options[:only_path] = only_path?(options[:host]) + end + + super(options) + when ActionController::Parameters + unless options.key?(:only_path) + options[:only_path] = only_path?(options[:host]) + end + + super(options) + when :back + _back_url + when Array + components = options.dup + if _generate_paths_by_default + polymorphic_path(components, components.extract_options!) + else + polymorphic_url(components, components.extract_options!) + end + else + method = _generate_paths_by_default ? :path : :url + builder = ActionDispatch::Routing::PolymorphicRoutes::HelperMethodBuilder.send(method) + + case options + when Symbol + builder.handle_string_call(self, options) + when Class + builder.handle_class_call(self, options) + else + builder.handle_model_call(self, options) + end + end + end + + def url_options #:nodoc: + return super unless controller.respond_to?(:url_options) + controller.url_options + end + + private + def _routes_context + controller + end + + def optimize_routes_generation? + controller.respond_to?(:optimize_routes_generation?, true) ? + controller.optimize_routes_generation? : super + end + + def _generate_paths_by_default + true + end + + def only_path?(host) + _generate_paths_by_default unless host + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/tasks/cache_digests.rake b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/tasks/cache_digests.rake new file mode 100644 index 00000000..dd8e94bd --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/tasks/cache_digests.rake @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +namespace :cache_digests do + desc "Lookup nested dependencies for TEMPLATE (like messages/show or comments/_comment.html)" + task nested_dependencies: :environment do + abort "You must provide TEMPLATE for the task to run" unless ENV["TEMPLATE"].present? + puts JSON.pretty_generate ActionView::Digestor.tree(CacheDigests.template_name, CacheDigests.finder).children.map(&:to_dep_map) + end + + desc "Lookup first-level dependencies for TEMPLATE (like messages/show or comments/_comment.html)" + task dependencies: :environment do + abort "You must provide TEMPLATE for the task to run" unless ENV["TEMPLATE"].present? + puts JSON.pretty_generate ActionView::Digestor.tree(CacheDigests.template_name, CacheDigests.finder).children.map(&:name) + end + + class CacheDigests + def self.template_name + ENV["TEMPLATE"].split(".", 2).first + end + + def self.finder + ApplicationController.new.lookup_context + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template.rb new file mode 100644 index 00000000..0c4bb73a --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template.rb @@ -0,0 +1,361 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/try" +require "active_support/core_ext/kernel/singleton_class" +require "thread" + +module ActionView + # = Action View Template + class Template + extend ActiveSupport::Autoload + + # === Encodings in ActionView::Template + # + # ActionView::Template is one of a few sources of potential + # encoding issues in Rails. This is because the source for + # templates are usually read from disk, and Ruby (like most + # encoding-aware programming languages) assumes that the + # String retrieved through File IO is encoded in the + # default_external encoding. In Rails, the default + # default_external encoding is UTF-8. + # + # As a result, if a user saves their template as ISO-8859-1 + # (for instance, using a non-Unicode-aware text editor), + # and uses characters outside of the ASCII range, their + # users will see diamonds with question marks in them in + # the browser. + # + # For the rest of this documentation, when we say "UTF-8", + # we mean "UTF-8 or whatever the default_internal encoding + # is set to". By default, it will be UTF-8. + # + # To mitigate this problem, we use a few strategies: + # 1. If the source is not valid UTF-8, we raise an exception + # when the template is compiled to alert the user + # to the problem. + # 2. The user can specify the encoding using Ruby-style + # encoding comments in any template engine. If such + # a comment is supplied, Rails will apply that encoding + # to the resulting compiled source returned by the + # template handler. + # 3. In all cases, we transcode the resulting String to + # the UTF-8. + # + # This means that other parts of Rails can always assume + # that templates are encoded in UTF-8, even if the original + # source of the template was not UTF-8. + # + # From a user's perspective, the easiest thing to do is + # to save your templates as UTF-8. If you do this, you + # do not need to do anything else for things to "just work". + # + # === Instructions for template handlers + # + # The easiest thing for you to do is to simply ignore + # encodings. Rails will hand you the template source + # as the default_internal (generally UTF-8), raising + # an exception for the user before sending the template + # to you if it could not determine the original encoding. + # + # For the greatest simplicity, you can support only + # UTF-8 as the default_internal. This means + # that from the perspective of your handler, the + # entire pipeline is just UTF-8. + # + # === Advanced: Handlers with alternate metadata sources + # + # If you want to provide an alternate mechanism for + # specifying encodings (like ERB does via <%# encoding: ... %>), + # you may indicate that you will handle encodings yourself + # by implementing handles_encoding? on your handler. + # + # If you do, Rails will not try to encode the String + # into the default_internal, passing you the unaltered + # bytes tagged with the assumed encoding (from + # default_external). + # + # In this case, make sure you return a String from + # your handler encoded in the default_internal. Since + # you are handling out-of-band metadata, you are + # also responsible for alerting the user to any + # problems with converting the user's data to + # the default_internal. + # + # To do so, simply raise +WrongEncodingError+ as follows: + # + # raise WrongEncodingError.new( + # problematic_string, + # expected_encoding + # ) + + ## + # :method: local_assigns + # + # Returns a hash with the defined local variables. + # + # Given this sub template rendering: + # + # <%= render "shared/header", { headline: "Welcome", person: person } %> + # + # You can use +local_assigns+ in the sub templates to access the local variables: + # + # local_assigns[:headline] # => "Welcome" + + eager_autoload do + autoload :Error + autoload :Handlers + autoload :HTML + autoload :Text + autoload :Types + end + + extend Template::Handlers + + attr_accessor :locals, :formats, :variants, :virtual_path + + attr_reader :source, :identifier, :handler, :original_encoding, :updated_at + + # This finalizer is needed (and exactly with a proc inside another proc) + # otherwise templates leak in development. + Finalizer = proc do |method_name, mod| # :nodoc: + proc do + mod.module_eval do + remove_possible_method method_name + end + end + end + + def initialize(source, identifier, handler, details) + format = details[:format] || (handler.default_format if handler.respond_to?(:default_format)) + + @source = source + @identifier = identifier + @handler = handler + @compiled = false + @original_encoding = nil + @locals = details[:locals] || [] + @virtual_path = details[:virtual_path] + @updated_at = details[:updated_at] || Time.now + @formats = Array(format).map { |f| f.respond_to?(:ref) ? f.ref : f } + @variants = [details[:variant]] + @compile_mutex = Mutex.new + end + + # Returns whether the underlying handler supports streaming. If so, + # a streaming buffer *may* be passed when it starts rendering. + def supports_streaming? + handler.respond_to?(:supports_streaming?) && handler.supports_streaming? + end + + # Render a template. If the template was not compiled yet, it is done + # exactly before rendering. + # + # This method is instrumented as "!render_template.action_view". Notice that + # we use a bang in this instrumentation because you don't want to + # consume this in production. This is only slow if it's being listened to. + def render(view, locals, buffer = nil, &block) + instrument_render_template do + compile!(view) + view.send(method_name, locals, buffer, &block) + end + rescue => e + handle_render_error(view, e) + end + + def type + @type ||= Types[@formats.first] if @formats.first + end + + # Receives a view object and return a template similar to self by using @virtual_path. + # + # This method is useful if you have a template object but it does not contain its source + # anymore since it was already compiled. In such cases, all you need to do is to call + # refresh passing in the view object. + # + # Notice this method raises an error if the template to be refreshed does not have a + # virtual path set (true just for inline templates). + def refresh(view) + raise "A template needs to have a virtual path in order to be refreshed" unless @virtual_path + lookup = view.lookup_context + pieces = @virtual_path.split("/") + name = pieces.pop + partial = !!name.sub!(/^_/, "") + lookup.disable_cache do + lookup.find_template(name, [ pieces.join("/") ], partial, @locals) + end + end + + def inspect + @inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", "".freeze) : identifier + end + + # This method is responsible for properly setting the encoding of the + # source. Until this point, we assume that the source is BINARY data. + # If no additional information is supplied, we assume the encoding is + # the same as Encoding.default_external. + # + # The user can also specify the encoding via a comment on the first + # line of the template (# encoding: NAME-OF-ENCODING). This will work + # with any template engine, as we process out the encoding comment + # before passing the source on to the template engine, leaving a + # blank line in its stead. + def encode! + return unless source.encoding == Encoding::BINARY + + # Look for # encoding: *. If we find one, we'll encode the + # String in that encoding, otherwise, we'll use the + # default external encoding. + if source.sub!(/\A#{ENCODING_FLAG}/, "") + encoding = magic_encoding = $1 + else + encoding = Encoding.default_external + end + + # Tag the source with the default external encoding + # or the encoding specified in the file + source.force_encoding(encoding) + + # If the user didn't specify an encoding, and the handler + # handles encodings, we simply pass the String as is to + # the handler (with the default_external tag) + if !magic_encoding && @handler.respond_to?(:handles_encoding?) && @handler.handles_encoding? + source + # Otherwise, if the String is valid in the encoding, + # encode immediately to default_internal. This means + # that if a handler doesn't handle encodings, it will + # always get Strings in the default_internal + elsif source.valid_encoding? + source.encode! + # Otherwise, since the String is invalid in the encoding + # specified, raise an exception + else + raise WrongEncodingError.new(source, encoding) + end + end + + private + + # Compile a template. This method ensures a template is compiled + # just once and removes the source after it is compiled. + def compile!(view) + return if @compiled + + # Templates can be used concurrently in threaded environments + # so compilation and any instance variable modification must + # be synchronized + @compile_mutex.synchronize do + # Any thread holding this lock will be compiling the template needed + # by the threads waiting. So re-check the @compiled flag to avoid + # re-compilation + return if @compiled + + if view.is_a?(ActionView::CompiledTemplates) + mod = ActionView::CompiledTemplates + else + mod = view.singleton_class + end + + instrument("!compile_template") do + compile(mod) + end + + # Just discard the source if we have a virtual path. This + # means we can get the template back. + @source = nil if @virtual_path + @compiled = true + end + end + + # Among other things, this method is responsible for properly setting + # the encoding of the compiled template. + # + # If the template engine handles encodings, we send the encoded + # String to the engine without further processing. This allows + # the template engine to support additional mechanisms for + # specifying the encoding. For instance, ERB supports <%# encoding: %> + # + # Otherwise, after we figure out the correct encoding, we then + # encode the source into Encoding.default_internal. + # In general, this means that templates will be UTF-8 inside of Rails, + # regardless of the original source encoding. + def compile(mod) + encode! + code = @handler.call(self) + + # Make sure that the resulting String to be eval'd is in the + # encoding of the code + source = <<-end_src.dup + def #{method_name}(local_assigns, output_buffer) + _old_virtual_path, @virtual_path = @virtual_path, #{@virtual_path.inspect};_old_output_buffer = @output_buffer;#{locals_code};#{code} + ensure + @virtual_path, @output_buffer = _old_virtual_path, _old_output_buffer + end + end_src + + # Make sure the source is in the encoding of the returned code + source.force_encoding(code.encoding) + + # In case we get back a String from a handler that is not in + # BINARY or the default_internal, encode it to the default_internal + source.encode! + + # Now, validate that the source we got back from the template + # handler is valid in the default_internal. This is for handlers + # that handle encoding but screw up + unless source.valid_encoding? + raise WrongEncodingError.new(@source, Encoding.default_internal) + end + + mod.module_eval(source, identifier, 0) + ObjectSpace.define_finalizer(self, Finalizer[method_name, mod]) + end + + def handle_render_error(view, e) + if e.is_a?(Template::Error) + e.sub_template_of(self) + raise e + else + template = self + unless template.source + template = refresh(view) + template.encode! + end + raise Template::Error.new(template) + end + end + + def locals_code + # Only locals with valid variable names get set directly. Others will + # still be available in local_assigns. + locals = @locals - Module::RUBY_RESERVED_KEYWORDS + locals = locals.grep(/\A@?(?![A-Z0-9])(?:[[:alnum:]_]|[^\0-\177])+\z/) + + # Assign for the same variable is to suppress unused variable warning + locals.each_with_object("".dup) { |key, code| code << "#{key} = local_assigns[:#{key}]; #{key} = #{key};" } + end + + def method_name + @method_name ||= begin + m = "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}".dup + m.tr!("-".freeze, "_".freeze) + m + end + end + + def identifier_method_name + inspect.tr("^a-z_".freeze, "_".freeze) + end + + def instrument(action, &block) # :doc: + ActiveSupport::Notifications.instrument("#{action}.action_view", instrument_payload, &block) + end + + def instrument_render_template(&block) + ActiveSupport::Notifications.instrument("!render_template.action_view".freeze, instrument_payload, &block) + end + + def instrument_payload + { virtual_path: @virtual_path, identifier: @identifier } + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/error.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/error.rb new file mode 100644 index 00000000..4e3c02e0 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/error.rb @@ -0,0 +1,141 @@ +# frozen_string_literal: true + +require "active_support/core_ext/enumerable" + +module ActionView + # = Action View Errors + class ActionViewError < StandardError #:nodoc: + end + + class EncodingError < StandardError #:nodoc: + end + + class WrongEncodingError < EncodingError #:nodoc: + def initialize(string, encoding) + @string, @encoding = string, encoding + end + + def message + @string.force_encoding(Encoding::ASCII_8BIT) + "Your template was not saved as valid #{@encoding}. Please " \ + "either specify #{@encoding} as the encoding for your template " \ + "in your text editor, or mark the template with its " \ + "encoding by inserting the following as the first line " \ + "of the template:\n\n# encoding: .\n\n" \ + "The source of your template was:\n\n#{@string}" + end + end + + class MissingTemplate < ActionViewError #:nodoc: + attr_reader :path + + def initialize(paths, path, prefixes, partial, details, *) + @path = path + prefixes = Array(prefixes) + template_type = if partial + "partial" + elsif /layouts/i.match?(path) + "layout" + else + "template" + end + + if partial && path.present? + path = path.sub(%r{([^/]+)$}, "_\\1") + end + searched_paths = prefixes.map { |prefix| [prefix, path].join("/") } + + out = "Missing #{template_type} #{searched_paths.join(", ")} with #{details.inspect}. Searched in:\n" + out += paths.compact.map { |p| " * #{p.to_s.inspect}\n" }.join + super out + end + end + + class Template + # The Template::Error exception is raised when the compilation or rendering of the template + # fails. This exception then gathers a bunch of intimate details and uses it to report a + # precise exception message. + class Error < ActionViewError #:nodoc: + SOURCE_CODE_RADIUS = 3 + + # Override to prevent #cause resetting during re-raise. + attr_reader :cause + + def initialize(template) + super($!.message) + set_backtrace($!.backtrace) + @cause = $! + @template, @sub_templates = template, nil + end + + def file_name + @template.identifier + end + + def sub_template_message + if @sub_templates + "Trace of template inclusion: " + + @sub_templates.collect(&:inspect).join(", ") + else + "" + end + end + + def source_extract(indentation = 0, output = :console) + return unless num = line_number + num = num.to_i + + source_code = @template.source.split("\n") + + start_on_line = [ num - SOURCE_CODE_RADIUS - 1, 0 ].max + end_on_line = [ num + SOURCE_CODE_RADIUS - 1, source_code.length].min + + indent = end_on_line.to_s.size + indentation + return unless source_code = source_code[start_on_line..end_on_line] + + formatted_code_for(source_code, start_on_line, indent, output) + end + + def sub_template_of(template_path) + @sub_templates ||= [] + @sub_templates << template_path + end + + def line_number + @line_number ||= + if file_name + regexp = /#{Regexp.escape File.basename(file_name)}:(\d+)/ + $1 if message =~ regexp || backtrace.find { |line| line =~ regexp } + end + end + + def annoted_source_code + source_extract(4) + end + + private + + def source_location + if line_number + "on line ##{line_number} of " + else + "in " + end + file_name + end + + def formatted_code_for(source_code, line_counter, indent, output) + start_value = (output == :html) ? {} : [] + source_code.inject(start_value) do |result, line| + line_counter += 1 + if output == :html + result.update(line_counter.to_s => "%#{indent}s %s\n" % ["", line]) + else + result << "%#{indent}s: %s" % [line_counter, line] + end + end + end + end + end + + TemplateError = Template::Error +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/handlers.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/handlers.rb new file mode 100644 index 00000000..7ec76dcc --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/handlers.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +module ActionView #:nodoc: + # = Action View Template Handlers + class Template #:nodoc: + module Handlers #:nodoc: + autoload :Raw, "action_view/template/handlers/raw" + autoload :ERB, "action_view/template/handlers/erb" + autoload :Html, "action_view/template/handlers/html" + autoload :Builder, "action_view/template/handlers/builder" + + def self.extended(base) + base.register_default_template_handler :raw, Raw.new + base.register_template_handler :erb, ERB.new + base.register_template_handler :html, Html.new + base.register_template_handler :builder, Builder.new + base.register_template_handler :ruby, :source.to_proc + end + + @@template_handlers = {} + @@default_template_handlers = nil + + def self.extensions + @@template_extensions ||= @@template_handlers.keys + end + + # Register an object that knows how to handle template files with the given + # extensions. This can be used to implement new template types. + # The handler must respond to +:call+, which will be passed the template + # and should return the rendered template as a String. + def register_template_handler(*extensions, handler) + raise(ArgumentError, "Extension is required") if extensions.empty? + extensions.each do |extension| + @@template_handlers[extension.to_sym] = handler + end + @@template_extensions = nil + end + + # Opposite to register_template_handler. + def unregister_template_handler(*extensions) + extensions.each do |extension| + handler = @@template_handlers.delete extension.to_sym + @@default_template_handlers = nil if @@default_template_handlers == handler + end + @@template_extensions = nil + end + + def template_handler_extensions + @@template_handlers.keys.map(&:to_s).sort + end + + def registered_template_handler(extension) + extension && @@template_handlers[extension.to_sym] + end + + def register_default_template_handler(extension, klass) + register_template_handler(extension, klass) + @@default_template_handlers = klass + end + + def handler_for_extension(extension) + registered_template_handler(extension) || @@default_template_handlers + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/handlers/builder.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/handlers/builder.rb new file mode 100644 index 00000000..61492ce4 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/handlers/builder.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module ActionView + module Template::Handlers + class Builder + class_attribute :default_format, default: :xml + + def call(template) + require_engine + "xml = ::Builder::XmlMarkup.new(:indent => 2);" \ + "self.output_buffer = xml.target!;" + + template.source + + ";xml.target!;" + end + + private + def require_engine # :doc: + @required ||= begin + require "builder" + true + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/handlers/erb.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/handlers/erb.rb new file mode 100644 index 00000000..b7b749f9 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/handlers/erb.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +module ActionView + class Template + module Handlers + class ERB + autoload :Erubi, "action_view/template/handlers/erb/erubi" + + # Specify trim mode for the ERB compiler. Defaults to '-'. + # See ERB documentation for suitable values. + class_attribute :erb_trim_mode, default: "-" + + # Default implementation used. + class_attribute :erb_implementation, default: Erubi + + # Do not escape templates of these mime types. + class_attribute :escape_whitelist, default: ["text/plain"] + + ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*") + + def self.call(template) + new.call(template) + end + + def supports_streaming? + true + end + + def handles_encoding? + true + end + + def call(template) + # First, convert to BINARY, so in case the encoding is + # wrong, we can still find an encoding tag + # (<%# encoding %>) inside the String using a regular + # expression + template_source = template.source.dup.force_encoding(Encoding::ASCII_8BIT) + + erb = template_source.gsub(ENCODING_TAG, "") + encoding = $2 + + erb.force_encoding valid_encoding(template.source.dup, encoding) + + # Always make sure we return a String in the default_internal + erb.encode! + + self.class.erb_implementation.new( + erb, + escape: (self.class.escape_whitelist.include? template.type), + trim: (self.class.erb_trim_mode == "-") + ).src + end + + private + + def valid_encoding(string, encoding) + # If a magic encoding comment was found, tag the + # String with this encoding. This is for a case + # where the original String was assumed to be, + # for instance, UTF-8, but a magic comment + # proved otherwise + string.force_encoding(encoding) if encoding + + # If the String is valid, return the encoding we found + return string.encoding if string.valid_encoding? + + # Otherwise, raise an exception + raise WrongEncodingError.new(string, string.encoding) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/handlers/erb/erubi.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/handlers/erb/erubi.rb new file mode 100644 index 00000000..db75f028 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/handlers/erb/erubi.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require "erubi" + +module ActionView + class Template + module Handlers + class ERB + class Erubi < ::Erubi::Engine + # :nodoc: all + def initialize(input, properties = {}) + @newline_pending = 0 + + # Dup properties so that we don't modify argument + properties = Hash[properties] + properties[:preamble] = "@output_buffer = output_buffer || ActionView::OutputBuffer.new;" + properties[:postamble] = "@output_buffer.to_s" + properties[:bufvar] = "@output_buffer" + properties[:escapefunc] = "" + + super + end + + def evaluate(action_view_erb_handler_context) + pr = eval("proc { #{@src} }", binding, @filename || "(erubi)") + action_view_erb_handler_context.instance_eval(&pr) + end + + private + def add_text(text) + return if text.empty? + + if text == "\n" + @newline_pending += 1 + else + src << "@output_buffer.safe_append='" + src << "\n" * @newline_pending if @newline_pending > 0 + src << text.gsub(/['\\]/, '\\\\\&') + src << "'.freeze;" + + @newline_pending = 0 + end + end + + BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/ + + def add_expression(indicator, code) + flush_newline_if_pending(src) + + if (indicator == "==") || @escape + src << "@output_buffer.safe_expr_append=" + else + src << "@output_buffer.append=" + end + + if BLOCK_EXPR.match?(code) + src << " " << code + else + src << "(" << code << ");" + end + end + + def add_code(code) + flush_newline_if_pending(src) + super + end + + def add_postamble(_) + flush_newline_if_pending(src) + super + end + + def flush_newline_if_pending(src) + if @newline_pending > 0 + src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;" + @newline_pending = 0 + end + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/handlers/html.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/handlers/html.rb new file mode 100644 index 00000000..27004a31 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/handlers/html.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module ActionView + module Template::Handlers + class Html < Raw + def call(template) + "ActionView::OutputBuffer.new #{super}" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/handlers/raw.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/handlers/raw.rb new file mode 100644 index 00000000..5cd23a00 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/handlers/raw.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module ActionView + module Template::Handlers + class Raw + def call(template) + "#{template.source.inspect}.html_safe;" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/html.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/html.rb new file mode 100644 index 00000000..a262c6d9 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/html.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module ActionView #:nodoc: + # = Action View HTML Template + class Template #:nodoc: + class HTML #:nodoc: + attr_accessor :type + + def initialize(string, type = nil) + @string = string.to_s + @type = Types[type] || type if type + @type ||= Types[:html] + end + + def identifier + "html template" + end + + alias_method :inspect, :identifier + + def to_str + ERB::Util.h(@string) + end + + def render(*args) + to_str + end + + def formats + [@type.respond_to?(:ref) ? @type.ref : @type.to_s] + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/resolver.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/resolver.rb new file mode 100644 index 00000000..5a86f109 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/resolver.rb @@ -0,0 +1,391 @@ +# frozen_string_literal: true + +require "pathname" +require "active_support/core_ext/class" +require "active_support/core_ext/module/attribute_accessors" +require "action_view/template" +require "thread" +require "concurrent/map" + +module ActionView + # = Action View Resolver + class Resolver + # Keeps all information about view path and builds virtual path. + class Path + attr_reader :name, :prefix, :partial, :virtual + alias_method :partial?, :partial + + def self.build(name, prefix, partial) + virtual = "".dup + virtual << "#{prefix}/" unless prefix.empty? + virtual << (partial ? "_#{name}" : name) + new name, prefix, partial, virtual + end + + def initialize(name, prefix, partial, virtual) + @name = name + @prefix = prefix + @partial = partial + @virtual = virtual + end + + def to_str + @virtual + end + alias :to_s :to_str + end + + # Threadsafe template cache + class Cache #:nodoc: + class SmallCache < Concurrent::Map + def initialize(options = {}) + super(options.merge(initial_capacity: 2)) + end + end + + # preallocate all the default blocks for performance/memory consumption reasons + PARTIAL_BLOCK = lambda { |cache, partial| cache[partial] = SmallCache.new } + PREFIX_BLOCK = lambda { |cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK) } + NAME_BLOCK = lambda { |cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK) } + KEY_BLOCK = lambda { |cache, key| cache[key] = SmallCache.new(&NAME_BLOCK) } + + # usually a majority of template look ups return nothing, use this canonical preallocated array to save memory + NO_TEMPLATES = [].freeze + + def initialize + @data = SmallCache.new(&KEY_BLOCK) + @query_cache = SmallCache.new + end + + def inspect + "#<#{self.class.name}:0x#{(object_id << 1).to_s(16)} keys=#{@data.size} queries=#{@query_cache.size}>" + end + + # Cache the templates returned by the block + def cache(key, name, prefix, partial, locals) + if Resolver.caching? + @data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield) + else + fresh_templates = yield + cached_templates = @data[key][name][prefix][partial][locals] + + if templates_have_changed?(cached_templates, fresh_templates) + @data[key][name][prefix][partial][locals] = canonical_no_templates(fresh_templates) + else + cached_templates || NO_TEMPLATES + end + end + end + + def cache_query(query) # :nodoc: + if Resolver.caching? + @query_cache[query] ||= canonical_no_templates(yield) + else + yield + end + end + + def clear + @data.clear + @query_cache.clear + end + + # Get the cache size. Do not call this + # method. This method is not guaranteed to be here ever. + def size # :nodoc: + size = 0 + @data.each_value do |v1| + v1.each_value do |v2| + v2.each_value do |v3| + v3.each_value do |v4| + size += v4.size + end + end + end + end + + size + @query_cache.size + end + + private + + def canonical_no_templates(templates) + templates.empty? ? NO_TEMPLATES : templates + end + + def templates_have_changed?(cached_templates, fresh_templates) + # if either the old or new template list is empty, we don't need to (and can't) + # compare modification times, and instead just check whether the lists are different + if cached_templates.blank? || fresh_templates.blank? + return fresh_templates.blank? != cached_templates.blank? + end + + cached_templates_max_updated_at = cached_templates.map(&:updated_at).max + + # if a template has changed, it will be now be newer than all the cached templates + fresh_templates.any? { |t| t.updated_at > cached_templates_max_updated_at } + end + end + + cattr_accessor :caching, default: true + + class << self + alias :caching? :caching + end + + def initialize + @cache = Cache.new + end + + def clear_cache + @cache.clear + end + + # Normalizes the arguments and passes it on to find_templates. + def find_all(name, prefix = nil, partial = false, details = {}, key = nil, locals = []) + cached(key, [name, prefix, partial], details, locals) do + find_templates(name, prefix, partial, details) + end + end + + def find_all_anywhere(name, prefix, partial = false, details = {}, key = nil, locals = []) + cached(key, [name, prefix, partial], details, locals) do + find_templates(name, prefix, partial, details, true) + end + end + + def find_all_with_query(query) # :nodoc: + @cache.cache_query(query) { find_template_paths(File.join(@path, query)) } + end + + private + + delegate :caching?, to: :class + + # This is what child classes implement. No defaults are needed + # because Resolver guarantees that the arguments are present and + # normalized. + def find_templates(name, prefix, partial, details, outside_app_allowed = false) + raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, outside_app_allowed = false) method" + end + + # Helpers that builds a path. Useful for building virtual paths. + def build_path(name, prefix, partial) + Path.build(name, prefix, partial) + end + + # Handles templates caching. If a key is given and caching is on + # always check the cache before hitting the resolver. Otherwise, + # it always hits the resolver but if the key is present, check if the + # resolver is fresher before returning it. + def cached(key, path_info, details, locals) + name, prefix, partial = path_info + locals = locals.map(&:to_s).sort! + + if key + @cache.cache(key, name, prefix, partial, locals) do + decorate(yield, path_info, details, locals) + end + else + decorate(yield, path_info, details, locals) + end + end + + # Ensures all the resolver information is set in the template. + def decorate(templates, path_info, details, locals) + cached = nil + templates.each do |t| + t.locals = locals + t.formats = details[:formats] || [:html] if t.formats.empty? + t.variants = details[:variants] || [] if t.variants.empty? + t.virtual_path ||= (cached ||= build_path(*path_info)) + end + end + end + + # An abstract class that implements a Resolver with path semantics. + class PathResolver < Resolver #:nodoc: + EXTENSIONS = { locale: ".", formats: ".", variants: "+", handlers: "." } + DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}" + + def initialize(pattern = nil) + @pattern = pattern || DEFAULT_PATTERN + super() + end + + private + + def find_templates(name, prefix, partial, details, outside_app_allowed = false) + path = Path.build(name, prefix, partial) + query(path, details, details[:formats], outside_app_allowed) + end + + def query(path, details, formats, outside_app_allowed) + query = build_query(path, details) + + template_paths = find_template_paths(query) + template_paths = reject_files_external_to_app(template_paths) unless outside_app_allowed + + template_paths.map do |template| + handler, format, variant = extract_handler_and_format_and_variant(template) + contents = File.binread(template) + + Template.new(contents, File.expand_path(template), handler, + virtual_path: path.virtual, + format: format, + variant: variant, + updated_at: mtime(template) + ) + end + end + + def reject_files_external_to_app(files) + files.reject { |filename| !inside_path?(@path, filename) } + end + + def find_template_paths(query) + Dir[query].uniq.reject do |filename| + File.directory?(filename) || + # deals with case-insensitive file systems. + !File.fnmatch(query, filename, File::FNM_EXTGLOB) + end + end + + def inside_path?(path, filename) + filename = File.expand_path(filename) + path = File.join(path, "") + filename.start_with?(path) + end + + # Helper for building query glob string based on resolver's pattern. + def build_query(path, details) + query = @pattern.dup + + prefix = path.prefix.empty? ? "" : "#{escape_entry(path.prefix)}\\1" + query.gsub!(/:prefix(\/)?/, prefix) + + partial = escape_entry(path.partial? ? "_#{path.name}" : path.name) + query.gsub!(/:action/, partial) + + details.each do |ext, candidates| + if ext == :variants && candidates == :any + query.gsub!(/:#{ext}/, "*") + else + query.gsub!(/:#{ext}/, "{#{candidates.compact.uniq.join(',')}}") + end + end + + File.expand_path(query, @path) + end + + def escape_entry(entry) + entry.gsub(/[*?{}\[\]]/, '\\\\\\&'.freeze) + end + + # Returns the file mtime from the filesystem. + def mtime(p) + File.mtime(p) + end + + # Extract handler, formats and variant from path. If a format cannot be found neither + # from the path, or the handler, we should return the array of formats given + # to the resolver. + def extract_handler_and_format_and_variant(path) + pieces = File.basename(path).split(".".freeze) + pieces.shift + + extension = pieces.pop + + handler = Template.handler_for_extension(extension) + format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last + format &&= Template::Types[format] + + [handler, format, variant] + end + end + + # A resolver that loads files from the filesystem. It allows setting your own + # resolving pattern. Such pattern can be a glob string supported by some variables. + # + # ==== Examples + # + # Default pattern, loads views the same way as previous versions of rails, eg. when you're + # looking for users/new it will produce query glob: users/new{.{en},}{.{html,js},}{.{erb,haml},} + # + # FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}") + # + # This one allows you to keep files with different formats in separate subdirectories, + # eg. users/new.html will be loaded from users/html/new.erb or users/new.html.erb, + # users/new.js from users/js/new.erb or users/new.js.erb, etc. + # + # FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}") + # + # If you don't specify a pattern then the default will be used. + # + # In order to use any of the customized resolvers above in a Rails application, you just need + # to configure ActionController::Base.view_paths in an initializer, for example: + # + # ActionController::Base.view_paths = FileSystemResolver.new( + # Rails.root.join("app/views"), + # ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}", + # ) + # + # ==== Pattern format and variables + # + # Pattern has to be a valid glob string, and it allows you to use the + # following variables: + # + # * :prefix - usually the controller path + # * :action - name of the action + # * :locale - possible locale versions + # * :formats - possible request formats (for example html, json, xml...) + # * :variants - possible request variants (for example phone, tablet...) + # * :handlers - possible handlers (for example erb, haml, builder...) + # + class FileSystemResolver < PathResolver + def initialize(path, pattern = nil) + raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver) + super(pattern) + @path = File.expand_path(path) + end + + def to_s + @path.to_s + end + alias :to_path :to_s + + def eql?(resolver) + self.class.equal?(resolver.class) && to_path == resolver.to_path + end + alias :== :eql? + end + + # An Optimized resolver for Rails' most common case. + class OptimizedFileSystemResolver < FileSystemResolver #:nodoc: + def build_query(path, details) + query = escape_entry(File.join(@path, path)) + + exts = EXTENSIONS.map do |ext, prefix| + if ext == :variants && details[ext] == :any + "{#{prefix}*,}" + else + "{#{details[ext].compact.uniq.map { |e| "#{prefix}#{e}," }.join}}" + end + end.join + + query + exts + end + end + + # The same as FileSystemResolver but does not allow templates to store + # a virtual path since it is invalid for such resolvers. + class FallbackFileSystemResolver < FileSystemResolver #:nodoc: + def self.instances + [new(""), new("/")] + end + + def decorate(*) + super.each { |t| t.virtual_path = nil } + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/text.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/text.rb new file mode 100644 index 00000000..f8d6c281 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/text.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module ActionView #:nodoc: + # = Action View Text Template + class Template #:nodoc: + class Text #:nodoc: + attr_accessor :type + + def initialize(string) + @string = string.to_s + @type = Types[:text] + end + + def identifier + "text template" + end + + alias_method :inspect, :identifier + + def to_str + @string + end + + def render(*args) + to_str + end + + def formats + [@type.ref] + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/types.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/types.rb new file mode 100644 index 00000000..67b7a62d --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/template/types.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attribute_accessors" + +module ActionView + class Template #:nodoc: + class Types + class Type + SET = Struct.new(:symbols).new([ :html, :text, :js, :css, :xml, :json ]) + + def self.[](type) + if type.is_a?(self) + type + else + new(type) + end + end + + attr_reader :symbol + + def initialize(symbol) + @symbol = symbol.to_sym + end + + def to_s + @symbol.to_s + end + alias to_str to_s + + def ref + @symbol + end + alias to_sym ref + + def ==(type) + @symbol == type.to_sym unless type.blank? + end + end + + cattr_accessor :type_klass + + def self.delegate_to(klass) + self.type_klass = klass + end + + delegate_to Type + + def self.[](type) + type_klass[type] + end + + def self.symbols + type_klass::SET.symbols + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/test_case.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/test_case.rb new file mode 100644 index 00000000..e1cbae58 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/test_case.rb @@ -0,0 +1,300 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/redefine_method" +require "action_controller" +require "action_controller/test_case" +require "action_view" + +require "rails-dom-testing" + +module ActionView + # = Action View Test Case + class TestCase < ActiveSupport::TestCase + class TestController < ActionController::Base + include ActionDispatch::TestProcess + + attr_accessor :request, :response, :params + + class << self + attr_writer :controller_path + end + + def controller_path=(path) + self.class.controller_path = (path) + end + + def initialize + super + self.class.controller_path = "" + @request = ActionController::TestRequest.create(self.class) + @response = ActionDispatch::TestResponse.new + + @request.env.delete("PATH_INFO") + @params = ActionController::Parameters.new + end + end + + module Behavior + extend ActiveSupport::Concern + + include ActionDispatch::Assertions, ActionDispatch::TestProcess + include Rails::Dom::Testing::Assertions + include ActionController::TemplateAssertions + include ActionView::Context + + include ActionDispatch::Routing::PolymorphicRoutes + + include AbstractController::Helpers + include ActionView::Helpers + include ActionView::RecordIdentifier + include ActionView::RoutingUrlFor + + include ActiveSupport::Testing::ConstantLookup + + delegate :lookup_context, to: :controller + attr_accessor :controller, :output_buffer, :rendered + + module ClassMethods + def tests(helper_class) + case helper_class + when String, Symbol + self.helper_class = "#{helper_class.to_s.underscore}_helper".camelize.safe_constantize + when Module + self.helper_class = helper_class + end + end + + def determine_default_helper_class(name) + determine_constant_from_test_name(name) do |constant| + Module === constant && !(Class === constant) + end + end + + def helper_method(*methods) + # Almost a duplicate from ActionController::Helpers + methods.flatten.each do |method| + _helpers.module_eval <<-end_eval, __FILE__, __LINE__ + 1 + def #{method}(*args, &block) # def current_user(*args, &block) + _test_case.send(%(#{method}), *args, &block) # _test_case.send(%(current_user), *args, &block) + end # end + end_eval + end + end + + attr_writer :helper_class + + def helper_class + @helper_class ||= determine_default_helper_class(name) + end + + def new(*) + include_helper_modules! + super + end + + private + + def include_helper_modules! + helper(helper_class) if helper_class + include _helpers + end + end + + def setup_with_controller + @controller = ActionView::TestCase::TestController.new + @request = @controller.request + @view_flow = ActionView::OutputFlow.new + # empty string ensures buffer has UTF-8 encoding as + # new without arguments returns ASCII-8BIT encoded buffer like String#new + @output_buffer = ActiveSupport::SafeBuffer.new "" + @rendered = "".dup + + make_test_case_available_to_view! + say_no_to_protect_against_forgery! + end + + def config + @controller.config if @controller.respond_to?(:config) + end + + def render(options = {}, local_assigns = {}, &block) + view.assign(view_assigns) + @rendered << output = view.render(options, local_assigns, &block) + output + end + + def rendered_views + @_rendered_views ||= RenderedViewsCollection.new + end + + def _routes + @controller._routes if @controller.respond_to?(:_routes) + end + + # Need to experiment if this priority is the best one: rendered => output_buffer + class RenderedViewsCollection + def initialize + @rendered_views ||= Hash.new { |hash, key| hash[key] = [] } + end + + def add(view, locals) + @rendered_views[view] ||= [] + @rendered_views[view] << locals + end + + def locals_for(view) + @rendered_views[view] + end + + def rendered_views + @rendered_views.keys + end + + def view_rendered?(view, expected_locals) + locals_for(view).any? do |actual_locals| + expected_locals.all? { |key, value| value == actual_locals[key] } + end + end + end + + included do + setup :setup_with_controller + ActiveSupport.run_load_hooks(:action_view_test_case, self) + end + + private + + # Need to experiment if this priority is the best one: rendered => output_buffer + def document_root_element + Nokogiri::HTML::Document.parse(@rendered.blank? ? @output_buffer : @rendered).root + end + + def say_no_to_protect_against_forgery! + _helpers.module_eval do + silence_redefinition_of_method :protect_against_forgery? + def protect_against_forgery? + false + end + end + end + + def make_test_case_available_to_view! + test_case_instance = self + _helpers.module_eval do + unless private_method_defined?(:_test_case) + define_method(:_test_case) { test_case_instance } + private :_test_case + end + end + end + + module Locals + attr_accessor :rendered_views + + def render(options = {}, local_assigns = {}) + case options + when Hash + if block_given? + rendered_views.add options[:layout], options[:locals] + elsif options.key?(:partial) + rendered_views.add options[:partial], options[:locals] + end + else + rendered_views.add options, local_assigns + end + + super + end + end + + # The instance of ActionView::Base that is used by +render+. + def view + @view ||= begin + view = @controller.view_context + view.singleton_class.include(_helpers) + view.extend(Locals) + view.rendered_views = rendered_views + view.output_buffer = output_buffer + view + end + end + + alias_method :_view, :view + + INTERNAL_IVARS = [ + :@NAME, + :@failures, + :@assertions, + :@__io__, + :@_assertion_wrapped, + :@_assertions, + :@_result, + :@_routes, + :@controller, + :@_layouts, + :@_files, + :@_rendered_views, + :@method_name, + :@output_buffer, + :@_partials, + :@passed, + :@rendered, + :@request, + :@routes, + :@tagged_logger, + :@_templates, + :@options, + :@test_passed, + :@view, + :@view_context_class, + :@view_flow, + :@_subscribers, + :@html_document + ] + + def _user_defined_ivars + instance_variables - INTERNAL_IVARS + end + + # Returns a Hash of instance variables and their values, as defined by + # the user in the test case, which are then assigned to the view being + # rendered. This is generally intended for internal use and extension + # frameworks. + def view_assigns + Hash[_user_defined_ivars.map do |ivar| + [ivar[1..-1].to_sym, instance_variable_get(ivar)] + end] + end + + def method_missing(selector, *args) + begin + routes = @controller.respond_to?(:_routes) && @controller._routes + rescue + # Don't call routes, if there is an error on _routes call + end + + if routes && + (routes.named_routes.route_defined?(selector) || + routes.mounted_helpers.method_defined?(selector)) + @controller.__send__(selector, *args) + else + super + end + end + + def respond_to_missing?(name, include_private = false) + begin + routes = @controller.respond_to?(:_routes) && @controller._routes + rescue + # Don't call routes, if there is an error on _routes call + end + + routes && + (routes.named_routes.route_defined?(name) || + routes.mounted_helpers.method_defined?(name)) + end + end + + include Behavior + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/testing/resolvers.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/testing/resolvers.rb new file mode 100644 index 00000000..68186c3b --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/testing/resolvers.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require "action_view/template/resolver" + +module ActionView #:nodoc: + # Use FixtureResolver in your tests to simulate the presence of files on the + # file system. This is used internally by Rails' own test suite, and is + # useful for testing extensions that have no way of knowing what the file + # system will look like at runtime. + class FixtureResolver < PathResolver + attr_reader :hash + + def initialize(hash = {}, pattern = nil) + super(pattern) + @hash = hash + end + + def to_s + @hash.keys.join(", ") + end + + private + + def query(path, exts, _, _) + query = "".dup + EXTENSIONS.each_key do |ext| + query << "(" << exts[ext].map { |e| e && Regexp.escape(".#{e}") }.join("|") << "|)" + end + query = /^(#{Regexp.escape(path)})#{query}$/ + + templates = [] + @hash.each do |_path, array| + source, updated_at = array + next unless query.match?(_path) + handler, format, variant = extract_handler_and_format_and_variant(_path) + templates << Template.new(source, _path, handler, + virtual_path: path.virtual, + format: format, + variant: variant, + updated_at: updated_at + ) + end + + templates.sort_by { |t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size } + end + end + + class NullResolver < PathResolver + def query(path, exts, _, _) + handler, format, variant = extract_handler_and_format_and_variant(path) + [ActionView::Template.new("Template generated by Null Resolver", path.virtual, handler, virtual_path: path.virtual, format: format, variant: variant)] + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/version.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/version.rb new file mode 100644 index 00000000..be53797a --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/version.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require_relative "gem_version" + +module ActionView + # Returns the version of the currently loaded ActionView as a Gem::Version + def self.version + gem_version + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/view_paths.rb b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/view_paths.rb new file mode 100644 index 00000000..d5694d77 --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/action_view/view_paths.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +module ActionView + module ViewPaths + extend ActiveSupport::Concern + + included do + class_attribute :_view_paths, default: ActionView::PathSet.new.freeze + end + + delegate :template_exists?, :any_templates?, :view_paths, :formats, :formats=, + :locale, :locale=, to: :lookup_context + + module ClassMethods + def _prefixes # :nodoc: + @_prefixes ||= begin + return local_prefixes if superclass.abstract? + + local_prefixes + superclass._prefixes + end + end + + private + + # Override this method in your controller if you want to change paths prefixes for finding views. + # Prefixes defined here will still be added to parents' ._prefixes. + def local_prefixes + [controller_path] + end + end + + # The prefixes used in render "foo" shortcuts. + def _prefixes # :nodoc: + self.class._prefixes + end + + # LookupContext is the object responsible for holding all + # information required for looking up templates, i.e. view paths and + # details. Check ActionView::LookupContext for more information. + def lookup_context + @_lookup_context ||= + ActionView::LookupContext.new(self.class._view_paths, details_for_lookup, _prefixes) + end + + def details_for_lookup + {} + end + + # Append a path to the list of view paths for the current LookupContext. + # + # ==== Parameters + # * path - If a String is provided, it gets converted into + # the default view path. You may also provide a custom view path + # (see ActionView::PathSet for more information) + def append_view_path(path) + lookup_context.view_paths.push(*path) + end + + # Prepend a path to the list of view paths for the current LookupContext. + # + # ==== Parameters + # * path - If a String is provided, it gets converted into + # the default view path. You may also provide a custom view path + # (see ActionView::PathSet for more information) + def prepend_view_path(path) + lookup_context.view_paths.unshift(*path) + end + + module ClassMethods + # Append a path to the list of view paths for this controller. + # + # ==== Parameters + # * path - If a String is provided, it gets converted into + # the default view path. You may also provide a custom view path + # (see ActionView::PathSet for more information) + def append_view_path(path) + self._view_paths = view_paths + Array(path) + end + + # Prepend a path to the list of view paths for this controller. + # + # ==== Parameters + # * path - If a String is provided, it gets converted into + # the default view path. You may also provide a custom view path + # (see ActionView::PathSet for more information) + def prepend_view_path(path) + self._view_paths = ActionView::PathSet.new(Array(path) + view_paths) + end + + # A list of all of the default view paths for this controller. + def view_paths + _view_paths + end + + # Set the view paths. + # + # ==== Parameters + # * paths - If a PathSet is provided, use that; + # otherwise, process the parameter into a PathSet. + def view_paths=(paths) + self._view_paths = ActionView::PathSet.new(Array(paths)) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/actionview-5.2.3/lib/assets/compiled/rails-ujs.js b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/assets/compiled/rails-ujs.js new file mode 100644 index 00000000..99f47f2d --- /dev/null +++ b/path/ruby/2.6.0/gems/actionview-5.2.3/lib/assets/compiled/rails-ujs.js @@ -0,0 +1,720 @@ +/* +Unobtrusive JavaScript +https://github.com/rails/rails/blob/master/actionview/app/assets/javascripts +Released under the MIT license + */ + +(function() { + var context = this; + + (function() { + (function() { + this.Rails = { + linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]', + buttonClickSelector: { + selector: 'button[data-remote]:not([form]), button[data-confirm]:not([form])', + exclude: 'form button' + }, + inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]', + formSubmitSelector: 'form', + formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type]), input[type=submit][form], input[type=image][form], button[type=submit][form], button[form]:not([type])', + formDisableSelector: 'input[data-disable-with]:enabled, button[data-disable-with]:enabled, textarea[data-disable-with]:enabled, input[data-disable]:enabled, button[data-disable]:enabled, textarea[data-disable]:enabled', + formEnableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled, input[data-disable]:disabled, button[data-disable]:disabled, textarea[data-disable]:disabled', + fileInputSelector: 'input[name][type=file]:not([disabled])', + linkDisableSelector: 'a[data-disable-with], a[data-disable]', + buttonDisableSelector: 'button[data-remote][data-disable-with], button[data-remote][data-disable]' + }; + + }).call(this); + }).call(context); + + var Rails = context.Rails; + + (function() { + (function() { + var nonce; + + nonce = null; + + Rails.loadCSPNonce = function() { + var ref; + return nonce = (ref = document.querySelector("meta[name=csp-nonce]")) != null ? ref.content : void 0; + }; + + Rails.cspNonce = function() { + return nonce != null ? nonce : Rails.loadCSPNonce(); + }; + + }).call(this); + (function() { + var expando, m; + + m = Element.prototype.matches || Element.prototype.matchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.webkitMatchesSelector; + + Rails.matches = function(element, selector) { + if (selector.exclude != null) { + return m.call(element, selector.selector) && !m.call(element, selector.exclude); + } else { + return m.call(element, selector); + } + }; + + expando = '_ujsData'; + + Rails.getData = function(element, key) { + var ref; + return (ref = element[expando]) != null ? ref[key] : void 0; + }; + + Rails.setData = function(element, key, value) { + if (element[expando] == null) { + element[expando] = {}; + } + return element[expando][key] = value; + }; + + Rails.$ = function(selector) { + return Array.prototype.slice.call(document.querySelectorAll(selector)); + }; + + }).call(this); + (function() { + var $, csrfParam, csrfToken; + + $ = Rails.$; + + csrfToken = Rails.csrfToken = function() { + var meta; + meta = document.querySelector('meta[name=csrf-token]'); + return meta && meta.content; + }; + + csrfParam = Rails.csrfParam = function() { + var meta; + meta = document.querySelector('meta[name=csrf-param]'); + return meta && meta.content; + }; + + Rails.CSRFProtection = function(xhr) { + var token; + token = csrfToken(); + if (token != null) { + return xhr.setRequestHeader('X-CSRF-Token', token); + } + }; + + Rails.refreshCSRFTokens = function() { + var param, token; + token = csrfToken(); + param = csrfParam(); + if ((token != null) && (param != null)) { + return $('form input[name="' + param + '"]').forEach(function(input) { + return input.value = token; + }); + } + }; + + }).call(this); + (function() { + var CustomEvent, fire, matches, preventDefault; + + matches = Rails.matches; + + CustomEvent = window.CustomEvent; + + if (typeof CustomEvent !== 'function') { + CustomEvent = function(event, params) { + var evt; + evt = document.createEvent('CustomEvent'); + evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); + return evt; + }; + CustomEvent.prototype = window.Event.prototype; + preventDefault = CustomEvent.prototype.preventDefault; + CustomEvent.prototype.preventDefault = function() { + var result; + result = preventDefault.call(this); + if (this.cancelable && !this.defaultPrevented) { + Object.defineProperty(this, 'defaultPrevented', { + get: function() { + return true; + } + }); + } + return result; + }; + } + + fire = Rails.fire = function(obj, name, data) { + var event; + event = new CustomEvent(name, { + bubbles: true, + cancelable: true, + detail: data + }); + obj.dispatchEvent(event); + return !event.defaultPrevented; + }; + + Rails.stopEverything = function(e) { + fire(e.target, 'ujs:everythingStopped'); + e.preventDefault(); + e.stopPropagation(); + return e.stopImmediatePropagation(); + }; + + Rails.delegate = function(element, selector, eventType, handler) { + return element.addEventListener(eventType, function(e) { + var target; + target = e.target; + while (!(!(target instanceof Element) || matches(target, selector))) { + target = target.parentNode; + } + if (target instanceof Element && handler.call(target, e) === false) { + e.preventDefault(); + return e.stopPropagation(); + } + }); + }; + + }).call(this); + (function() { + var AcceptHeaders, CSRFProtection, createXHR, cspNonce, fire, prepareOptions, processResponse; + + cspNonce = Rails.cspNonce, CSRFProtection = Rails.CSRFProtection, fire = Rails.fire; + + AcceptHeaders = { + '*': '*/*', + text: 'text/plain', + html: 'text/html', + xml: 'application/xml, text/xml', + json: 'application/json, text/javascript', + script: 'text/javascript, application/javascript, application/ecmascript, application/x-ecmascript' + }; + + Rails.ajax = function(options) { + var xhr; + options = prepareOptions(options); + xhr = createXHR(options, function() { + var ref, response; + response = processResponse((ref = xhr.response) != null ? ref : xhr.responseText, xhr.getResponseHeader('Content-Type')); + if (Math.floor(xhr.status / 100) === 2) { + if (typeof options.success === "function") { + options.success(response, xhr.statusText, xhr); + } + } else { + if (typeof options.error === "function") { + options.error(response, xhr.statusText, xhr); + } + } + return typeof options.complete === "function" ? options.complete(xhr, xhr.statusText) : void 0; + }); + if ((options.beforeSend != null) && !options.beforeSend(xhr, options)) { + return false; + } + if (xhr.readyState === XMLHttpRequest.OPENED) { + return xhr.send(options.data); + } + }; + + prepareOptions = function(options) { + options.url = options.url || location.href; + options.type = options.type.toUpperCase(); + if (options.type === 'GET' && options.data) { + if (options.url.indexOf('?') < 0) { + options.url += '?' + options.data; + } else { + options.url += '&' + options.data; + } + } + if (AcceptHeaders[options.dataType] == null) { + options.dataType = '*'; + } + options.accept = AcceptHeaders[options.dataType]; + if (options.dataType !== '*') { + options.accept += ', */*; q=0.01'; + } + return options; + }; + + createXHR = function(options, done) { + var xhr; + xhr = new XMLHttpRequest(); + xhr.open(options.type, options.url, true); + xhr.setRequestHeader('Accept', options.accept); + if (typeof options.data === 'string') { + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8'); + } + if (!options.crossDomain) { + xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + } + CSRFProtection(xhr); + xhr.withCredentials = !!options.withCredentials; + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE) { + return done(xhr); + } + }; + return xhr; + }; + + processResponse = function(response, type) { + var parser, script; + if (typeof response === 'string' && typeof type === 'string') { + if (type.match(/\bjson\b/)) { + try { + response = JSON.parse(response); + } catch (error) {} + } else if (type.match(/\b(?:java|ecma)script\b/)) { + script = document.createElement('script'); + script.setAttribute('nonce', cspNonce()); + script.text = response; + document.head.appendChild(script).parentNode.removeChild(script); + } else if (type.match(/\b(xml|html|svg)\b/)) { + parser = new DOMParser(); + type = type.replace(/;.+/, ''); + try { + response = parser.parseFromString(response, type); + } catch (error) {} + } + } + return response; + }; + + Rails.href = function(element) { + return element.href; + }; + + Rails.isCrossDomain = function(url) { + var e, originAnchor, urlAnchor; + originAnchor = document.createElement('a'); + originAnchor.href = location.href; + urlAnchor = document.createElement('a'); + try { + urlAnchor.href = url; + return !(((!urlAnchor.protocol || urlAnchor.protocol === ':') && !urlAnchor.host) || (originAnchor.protocol + '//' + originAnchor.host === urlAnchor.protocol + '//' + urlAnchor.host)); + } catch (error) { + e = error; + return true; + } + }; + + }).call(this); + (function() { + var matches, toArray; + + matches = Rails.matches; + + toArray = function(e) { + return Array.prototype.slice.call(e); + }; + + Rails.serializeElement = function(element, additionalParam) { + var inputs, params; + inputs = [element]; + if (matches(element, 'form')) { + inputs = toArray(element.elements); + } + params = []; + inputs.forEach(function(input) { + if (!input.name || input.disabled) { + return; + } + if (matches(input, 'select')) { + return toArray(input.options).forEach(function(option) { + if (option.selected) { + return params.push({ + name: input.name, + value: option.value + }); + } + }); + } else if (input.checked || ['radio', 'checkbox', 'submit'].indexOf(input.type) === -1) { + return params.push({ + name: input.name, + value: input.value + }); + } + }); + if (additionalParam) { + params.push(additionalParam); + } + return params.map(function(param) { + if (param.name != null) { + return (encodeURIComponent(param.name)) + "=" + (encodeURIComponent(param.value)); + } else { + return param; + } + }).join('&'); + }; + + Rails.formElements = function(form, selector) { + if (matches(form, 'form')) { + return toArray(form.elements).filter(function(el) { + return matches(el, selector); + }); + } else { + return toArray(form.querySelectorAll(selector)); + } + }; + + }).call(this); + (function() { + var allowAction, fire, stopEverything; + + fire = Rails.fire, stopEverything = Rails.stopEverything; + + Rails.handleConfirm = function(e) { + if (!allowAction(this)) { + return stopEverything(e); + } + }; + + allowAction = function(element) { + var answer, callback, message; + message = element.getAttribute('data-confirm'); + if (!message) { + return true; + } + answer = false; + if (fire(element, 'confirm')) { + try { + answer = confirm(message); + } catch (error) {} + callback = fire(element, 'confirm:complete', [answer]); + } + return answer && callback; + }; + + }).call(this); + (function() { + var disableFormElement, disableFormElements, disableLinkElement, enableFormElement, enableFormElements, enableLinkElement, formElements, getData, matches, setData, stopEverything; + + matches = Rails.matches, getData = Rails.getData, setData = Rails.setData, stopEverything = Rails.stopEverything, formElements = Rails.formElements; + + Rails.handleDisabledElement = function(e) { + var element; + element = this; + if (element.disabled) { + return stopEverything(e); + } + }; + + Rails.enableElement = function(e) { + var element; + element = e instanceof Event ? e.target : e; + if (matches(element, Rails.linkDisableSelector)) { + return enableLinkElement(element); + } else if (matches(element, Rails.buttonDisableSelector) || matches(element, Rails.formEnableSelector)) { + return enableFormElement(element); + } else if (matches(element, Rails.formSubmitSelector)) { + return enableFormElements(element); + } + }; + + Rails.disableElement = function(e) { + var element; + element = e instanceof Event ? e.target : e; + if (matches(element, Rails.linkDisableSelector)) { + return disableLinkElement(element); + } else if (matches(element, Rails.buttonDisableSelector) || matches(element, Rails.formDisableSelector)) { + return disableFormElement(element); + } else if (matches(element, Rails.formSubmitSelector)) { + return disableFormElements(element); + } + }; + + disableLinkElement = function(element) { + var replacement; + replacement = element.getAttribute('data-disable-with'); + if (replacement != null) { + setData(element, 'ujs:enable-with', element.innerHTML); + element.innerHTML = replacement; + } + element.addEventListener('click', stopEverything); + return setData(element, 'ujs:disabled', true); + }; + + enableLinkElement = function(element) { + var originalText; + originalText = getData(element, 'ujs:enable-with'); + if (originalText != null) { + element.innerHTML = originalText; + setData(element, 'ujs:enable-with', null); + } + element.removeEventListener('click', stopEverything); + return setData(element, 'ujs:disabled', null); + }; + + disableFormElements = function(form) { + return formElements(form, Rails.formDisableSelector).forEach(disableFormElement); + }; + + disableFormElement = function(element) { + var replacement; + replacement = element.getAttribute('data-disable-with'); + if (replacement != null) { + if (matches(element, 'button')) { + setData(element, 'ujs:enable-with', element.innerHTML); + element.innerHTML = replacement; + } else { + setData(element, 'ujs:enable-with', element.value); + element.value = replacement; + } + } + element.disabled = true; + return setData(element, 'ujs:disabled', true); + }; + + enableFormElements = function(form) { + return formElements(form, Rails.formEnableSelector).forEach(enableFormElement); + }; + + enableFormElement = function(element) { + var originalText; + originalText = getData(element, 'ujs:enable-with'); + if (originalText != null) { + if (matches(element, 'button')) { + element.innerHTML = originalText; + } else { + element.value = originalText; + } + setData(element, 'ujs:enable-with', null); + } + element.disabled = false; + return setData(element, 'ujs:disabled', null); + }; + + }).call(this); + (function() { + var stopEverything; + + stopEverything = Rails.stopEverything; + + Rails.handleMethod = function(e) { + var csrfParam, csrfToken, form, formContent, href, link, method; + link = this; + method = link.getAttribute('data-method'); + if (!method) { + return; + } + href = Rails.href(link); + csrfToken = Rails.csrfToken(); + csrfParam = Rails.csrfParam(); + form = document.createElement('form'); + formContent = ""; + if ((csrfParam != null) && (csrfToken != null) && !Rails.isCrossDomain(href)) { + formContent += ""; + } + formContent += ''; + form.method = 'post'; + form.action = href; + form.target = link.target; + form.innerHTML = formContent; + form.style.display = 'none'; + document.body.appendChild(form); + form.querySelector('[type="submit"]').click(); + return stopEverything(e); + }; + + }).call(this); + (function() { + var ajax, fire, getData, isCrossDomain, isRemote, matches, serializeElement, setData, stopEverything, + slice = [].slice; + + matches = Rails.matches, getData = Rails.getData, setData = Rails.setData, fire = Rails.fire, stopEverything = Rails.stopEverything, ajax = Rails.ajax, isCrossDomain = Rails.isCrossDomain, serializeElement = Rails.serializeElement; + + isRemote = function(element) { + var value; + value = element.getAttribute('data-remote'); + return (value != null) && value !== 'false'; + }; + + Rails.handleRemote = function(e) { + var button, data, dataType, element, method, url, withCredentials; + element = this; + if (!isRemote(element)) { + return true; + } + if (!fire(element, 'ajax:before')) { + fire(element, 'ajax:stopped'); + return false; + } + withCredentials = element.getAttribute('data-with-credentials'); + dataType = element.getAttribute('data-type') || 'script'; + if (matches(element, Rails.formSubmitSelector)) { + button = getData(element, 'ujs:submit-button'); + method = getData(element, 'ujs:submit-button-formmethod') || element.method; + url = getData(element, 'ujs:submit-button-formaction') || element.getAttribute('action') || location.href; + if (method.toUpperCase() === 'GET') { + url = url.replace(/\?.*$/, ''); + } + if (element.enctype === 'multipart/form-data') { + data = new FormData(element); + if (button != null) { + data.append(button.name, button.value); + } + } else { + data = serializeElement(element, button); + } + setData(element, 'ujs:submit-button', null); + setData(element, 'ujs:submit-button-formmethod', null); + setData(element, 'ujs:submit-button-formaction', null); + } else if (matches(element, Rails.buttonClickSelector) || matches(element, Rails.inputChangeSelector)) { + method = element.getAttribute('data-method'); + url = element.getAttribute('data-url'); + data = serializeElement(element, element.getAttribute('data-params')); + } else { + method = element.getAttribute('data-method'); + url = Rails.href(element); + data = element.getAttribute('data-params'); + } + ajax({ + type: method || 'GET', + url: url, + data: data, + dataType: dataType, + beforeSend: function(xhr, options) { + if (fire(element, 'ajax:beforeSend', [xhr, options])) { + return fire(element, 'ajax:send', [xhr]); + } else { + fire(element, 'ajax:stopped'); + return false; + } + }, + success: function() { + var args; + args = 1 <= arguments.length ? slice.call(arguments, 0) : []; + return fire(element, 'ajax:success', args); + }, + error: function() { + var args; + args = 1 <= arguments.length ? slice.call(arguments, 0) : []; + return fire(element, 'ajax:error', args); + }, + complete: function() { + var args; + args = 1 <= arguments.length ? slice.call(arguments, 0) : []; + return fire(element, 'ajax:complete', args); + }, + crossDomain: isCrossDomain(url), + withCredentials: (withCredentials != null) && withCredentials !== 'false' + }); + return stopEverything(e); + }; + + Rails.formSubmitButtonClick = function(e) { + var button, form; + button = this; + form = button.form; + if (!form) { + return; + } + if (button.name) { + setData(form, 'ujs:submit-button', { + name: button.name, + value: button.value + }); + } + setData(form, 'ujs:formnovalidate-button', button.formNoValidate); + setData(form, 'ujs:submit-button-formaction', button.getAttribute('formaction')); + return setData(form, 'ujs:submit-button-formmethod', button.getAttribute('formmethod')); + }; + + Rails.preventInsignificantClick = function(e) { + var data, insignificantMetaClick, link, metaClick, method, primaryMouseKey; + link = this; + method = (link.getAttribute('data-method') || 'GET').toUpperCase(); + data = link.getAttribute('data-params'); + metaClick = e.metaKey || e.ctrlKey; + insignificantMetaClick = metaClick && method === 'GET' && !data; + primaryMouseKey = e.button === 0; + if (!primaryMouseKey || insignificantMetaClick) { + return e.stopImmediatePropagation(); + } + }; + + }).call(this); + (function() { + var $, CSRFProtection, delegate, disableElement, enableElement, fire, formSubmitButtonClick, getData, handleConfirm, handleDisabledElement, handleMethod, handleRemote, loadCSPNonce, preventInsignificantClick, refreshCSRFTokens; + + fire = Rails.fire, delegate = Rails.delegate, getData = Rails.getData, $ = Rails.$, refreshCSRFTokens = Rails.refreshCSRFTokens, CSRFProtection = Rails.CSRFProtection, loadCSPNonce = Rails.loadCSPNonce, enableElement = Rails.enableElement, disableElement = Rails.disableElement, handleDisabledElement = Rails.handleDisabledElement, handleConfirm = Rails.handleConfirm, preventInsignificantClick = Rails.preventInsignificantClick, handleRemote = Rails.handleRemote, formSubmitButtonClick = Rails.formSubmitButtonClick, handleMethod = Rails.handleMethod; + + if ((typeof jQuery !== "undefined" && jQuery !== null) && (jQuery.ajax != null)) { + if (jQuery.rails) { + throw new Error('If you load both jquery_ujs and rails-ujs, use rails-ujs only.'); + } + jQuery.rails = Rails; + jQuery.ajaxPrefilter(function(options, originalOptions, xhr) { + if (!options.crossDomain) { + return CSRFProtection(xhr); + } + }); + } + + Rails.start = function() { + if (window._rails_loaded) { + throw new Error('rails-ujs has already been loaded!'); + } + window.addEventListener('pageshow', function() { + $(Rails.formEnableSelector).forEach(function(el) { + if (getData(el, 'ujs:disabled')) { + return enableElement(el); + } + }); + return $(Rails.linkDisableSelector).forEach(function(el) { + if (getData(el, 'ujs:disabled')) { + return enableElement(el); + } + }); + }); + delegate(document, Rails.linkDisableSelector, 'ajax:complete', enableElement); + delegate(document, Rails.linkDisableSelector, 'ajax:stopped', enableElement); + delegate(document, Rails.buttonDisableSelector, 'ajax:complete', enableElement); + delegate(document, Rails.buttonDisableSelector, 'ajax:stopped', enableElement); + delegate(document, Rails.linkClickSelector, 'click', preventInsignificantClick); + delegate(document, Rails.linkClickSelector, 'click', handleDisabledElement); + delegate(document, Rails.linkClickSelector, 'click', handleConfirm); + delegate(document, Rails.linkClickSelector, 'click', disableElement); + delegate(document, Rails.linkClickSelector, 'click', handleRemote); + delegate(document, Rails.linkClickSelector, 'click', handleMethod); + delegate(document, Rails.buttonClickSelector, 'click', preventInsignificantClick); + delegate(document, Rails.buttonClickSelector, 'click', handleDisabledElement); + delegate(document, Rails.buttonClickSelector, 'click', handleConfirm); + delegate(document, Rails.buttonClickSelector, 'click', disableElement); + delegate(document, Rails.buttonClickSelector, 'click', handleRemote); + delegate(document, Rails.inputChangeSelector, 'change', handleDisabledElement); + delegate(document, Rails.inputChangeSelector, 'change', handleConfirm); + delegate(document, Rails.inputChangeSelector, 'change', handleRemote); + delegate(document, Rails.formSubmitSelector, 'submit', handleDisabledElement); + delegate(document, Rails.formSubmitSelector, 'submit', handleConfirm); + delegate(document, Rails.formSubmitSelector, 'submit', handleRemote); + delegate(document, Rails.formSubmitSelector, 'submit', function(e) { + return setTimeout((function() { + return disableElement(e); + }), 13); + }); + delegate(document, Rails.formSubmitSelector, 'ajax:send', disableElement); + delegate(document, Rails.formSubmitSelector, 'ajax:complete', enableElement); + delegate(document, Rails.formInputClickSelector, 'click', preventInsignificantClick); + delegate(document, Rails.formInputClickSelector, 'click', handleDisabledElement); + delegate(document, Rails.formInputClickSelector, 'click', handleConfirm); + delegate(document, Rails.formInputClickSelector, 'click', formSubmitButtonClick); + document.addEventListener('DOMContentLoaded', refreshCSRFTokens); + document.addEventListener('DOMContentLoaded', loadCSPNonce); + return window._rails_loaded = true; + }; + + if (window.Rails === Rails && fire(document, 'rails:attachBindings')) { + Rails.start(); + } + + }).call(this); + }).call(this); + + if (typeof module === "object" && module.exports) { + module.exports = Rails; + } else if (typeof define === "function" && define.amd) { + define(Rails); + } +}).call(this); diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/CHANGELOG.md b/path/ruby/2.6.0/gems/activejob-5.2.3/CHANGELOG.md new file mode 100644 index 00000000..38154861 --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/CHANGELOG.md @@ -0,0 +1,80 @@ +## Rails 5.2.3 (March 27, 2019) ## + +* No changes. + + +## Rails 5.2.2.1 (March 11, 2019) ## + +* No changes. + + +## Rails 5.2.2 (December 04, 2018) ## + +* Make sure `assert_enqueued_with()` & `assert_performed_with()` work reliably with hash arguments. + + *Sharang Dashputre* + +* Restore `ActionController::Parameters` support to `ActiveJob::Arguments.serialize`. + + *Bernie Chiu* + +* Restore `HashWithIndifferentAccess` support to `ActiveJob::Arguments.deserialize`. + + *Gannon McGibbon* + +* Include deserialized arguments in job instances returned from + `assert_enqueued_with` and `assert_performed_with` + + *Alan Wu* + +* Increment execution count before deserialize arguments. + + Currently, the execution count increments after deserializes arguments. + Therefore, if an error occurs with deserialize, it retries indefinitely. + + *Yuji Yaginuma* + + +## Rails 5.2.1.1 (November 27, 2018) ## + +* Do not deserialize GlobalID objects that were not generated by Active Job. + + Trusting any GlobaID object when deserializing jobs can allow attackers to access + information that should not be accessible to them. + + Fix CVE-2018-16476. + + *Rafael Mendonça França* + + +## Rails 5.2.1 (August 07, 2018) ## + +* Pass the error instance as the second parameter of block executed by `discard_on`. + + Fixes #32853. + + *Yuji Yaginuma* + +## Rails 5.2.0 (April 09, 2018) ## + +* Allow block to be passed to `ActiveJob::Base.discard_on` to allow custom handling of discard jobs. + + Example: + + class RemoteServiceJob < ActiveJob::Base + discard_on(CustomAppException) do |job, exception| + ExceptionNotifier.caught(exception) + end + + def perform(*args) + # Might raise CustomAppException for something domain specific + end + end + + *Aidan Haran* + +* Support redis-rb 4.0. + + *Jeremy Daer* + +Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/activejob/CHANGELOG.md) for previous changes. diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/MIT-LICENSE b/path/ruby/2.6.0/gems/activejob-5.2.3/MIT-LICENSE new file mode 100644 index 00000000..274211f7 --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/MIT-LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2014-2018 David Heinemeier Hansson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/README.md b/path/ruby/2.6.0/gems/activejob-5.2.3/README.md new file mode 100644 index 00000000..0d37a472 --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/README.md @@ -0,0 +1,126 @@ +# Active Job -- Make work happen later + +Active Job is a framework for declaring jobs and making them run on a variety +of queueing backends. These jobs can be everything from regularly scheduled +clean-ups, to billing charges, to mailings. Anything that can be chopped up into +small units of work and run in parallel, really. + +It also serves as the backend for Action Mailer's #deliver_later functionality +that makes it easy to turn any mailing into a job for running later. That's +one of the most common jobs in a modern web application: sending emails outside +of the request-response cycle, so the user doesn't have to wait on it. + +The main point is to ensure that all Rails apps will have a job infrastructure +in place, even if it's in the form of an "immediate runner". We can then have +framework features and other gems build on top of that, without having to worry +about API differences between Delayed Job and Resque. Picking your queuing +backend becomes more of an operational concern, then. And you'll be able to +switch between them without having to rewrite your jobs. + + +## Usage + +To learn how to use your preferred queueing backend see its adapter +documentation at +[ActiveJob::QueueAdapters](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html). + +Declare a job like so: + +```ruby +class MyJob < ActiveJob::Base + queue_as :my_jobs + + def perform(record) + record.do_work + end +end +``` + +Enqueue a job like so: + +```ruby +MyJob.perform_later record # Enqueue a job to be performed as soon as the queueing system is free. +``` + +```ruby +MyJob.set(wait_until: Date.tomorrow.noon).perform_later(record) # Enqueue a job to be performed tomorrow at noon. +``` + +```ruby +MyJob.set(wait: 1.week).perform_later(record) # Enqueue a job to be performed 1 week from now. +``` + +That's it! + + +## GlobalID support + +Active Job supports [GlobalID serialization](https://github.com/rails/globalid/) for parameters. This makes it possible +to pass live Active Record objects to your job instead of class/id pairs, which +you then have to manually deserialize. Before, jobs would look like this: + +```ruby +class TrashableCleanupJob + def perform(trashable_class, trashable_id, depth) + trashable = trashable_class.constantize.find(trashable_id) + trashable.cleanup(depth) + end +end +``` + +Now you can simply do: + +```ruby +class TrashableCleanupJob + def perform(trashable, depth) + trashable.cleanup(depth) + end +end +``` + +This works with any class that mixes in GlobalID::Identification, which +by default has been mixed into Active Record classes. + + +## Supported queueing systems + +Active Job has built-in adapters for multiple queueing backends (Sidekiq, +Resque, Delayed Job and others). To get an up-to-date list of the adapters +see the API Documentation for [ActiveJob::QueueAdapters](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html). + +## Auxiliary gems + +* [activejob-stats](https://github.com/seuros/activejob-stats) + +## Download and installation + +The latest version of Active Job can be installed with RubyGems: + +``` + $ gem install activejob +``` + +Source code can be downloaded as part of the Rails project on GitHub: + +* https://github.com/rails/rails/tree/5-2-stable/activejob + +## License + +Active Job is released under the MIT license: + +* https://opensource.org/licenses/MIT + + +## Support + +API documentation is at: + +* http://api.rubyonrails.org + +Bug reports for the Ruby on Rails project can be filed here: + +* https://github.com/rails/rails/issues + +Feature requests should be discussed on the rails-core mailing list here: + +* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job.rb new file mode 100644 index 00000000..626abaa7 --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +#-- +# Copyright (c) 2014-2018 David Heinemeier Hansson +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ + +require "active_support" +require "active_support/rails" +require "active_job/version" +require "global_id" + +module ActiveJob + extend ActiveSupport::Autoload + + autoload :Base + autoload :QueueAdapters + autoload :ConfiguredJob + autoload :TestCase + autoload :TestHelper +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/arguments.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/arguments.rb new file mode 100644 index 00000000..fec4516a --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/arguments.rb @@ -0,0 +1,165 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash" + +module ActiveJob + # Raised when an exception is raised during job arguments deserialization. + # + # Wraps the original exception raised as +cause+. + class DeserializationError < StandardError + def initialize #:nodoc: + super("Error while trying to deserialize arguments: #{$!.message}") + set_backtrace $!.backtrace + end + end + + # Raised when an unsupported argument type is set as a job argument. We + # currently support NilClass, Integer, Fixnum, Float, String, TrueClass, FalseClass, + # Bignum, BigDecimal, and objects that can be represented as GlobalIDs (ex: Active Record). + # Raised if you set the key for a Hash something else than a string or + # a symbol. Also raised when trying to serialize an object which can't be + # identified with a Global ID - such as an unpersisted Active Record model. + class SerializationError < ArgumentError; end + + module Arguments + extend self + # :nodoc: + TYPE_WHITELIST = [ NilClass, String, Integer, Float, BigDecimal, TrueClass, FalseClass ] + TYPE_WHITELIST.push(Fixnum, Bignum) unless 1.class == Integer + + # Serializes a set of arguments. Whitelisted types are returned + # as-is. Arrays/Hashes are serialized element by element. + # All other types are serialized using GlobalID. + def serialize(arguments) + arguments.map { |argument| serialize_argument(argument) } + end + + # Deserializes a set of arguments. Whitelisted types are returned + # as-is. Arrays/Hashes are deserialized element by element. + # All other types are deserialized using GlobalID. + def deserialize(arguments) + arguments.map { |argument| deserialize_argument(argument) } + rescue + raise DeserializationError + end + + private + # :nodoc: + GLOBALID_KEY = "_aj_globalid".freeze + # :nodoc: + SYMBOL_KEYS_KEY = "_aj_symbol_keys".freeze + # :nodoc: + WITH_INDIFFERENT_ACCESS_KEY = "_aj_hash_with_indifferent_access".freeze + private_constant :GLOBALID_KEY, :SYMBOL_KEYS_KEY, :WITH_INDIFFERENT_ACCESS_KEY + + def serialize_argument(argument) + case argument + when *TYPE_WHITELIST + argument + when GlobalID::Identification + convert_to_global_id_hash(argument) + when Array + argument.map { |arg| serialize_argument(arg) } + when ActiveSupport::HashWithIndifferentAccess + serialize_indifferent_hash(argument) + when Hash + symbol_keys = argument.each_key.grep(Symbol).map(&:to_s) + result = serialize_hash(argument) + result[SYMBOL_KEYS_KEY] = symbol_keys + result + when -> (arg) { arg.respond_to?(:permitted?) } + serialize_indifferent_hash(argument.to_h) + else + raise SerializationError.new("Unsupported argument type: #{argument.class.name}") + end + end + + def deserialize_argument(argument) + case argument + when String + argument + when *TYPE_WHITELIST + argument + when Array + argument.map { |arg| deserialize_argument(arg) } + when Hash + if serialized_global_id?(argument) + deserialize_global_id argument + else + deserialize_hash(argument) + end + else + raise ArgumentError, "Can only deserialize primitive arguments: #{argument.inspect}" + end + end + + def serialized_global_id?(hash) + hash.size == 1 && hash.include?(GLOBALID_KEY) + end + + def deserialize_global_id(hash) + GlobalID::Locator.locate hash[GLOBALID_KEY] + end + + def serialize_hash(argument) + argument.each_with_object({}) do |(key, value), hash| + hash[serialize_hash_key(key)] = serialize_argument(value) + end + end + + def deserialize_hash(serialized_hash) + result = serialized_hash.transform_values { |v| deserialize_argument(v) } + if result.delete(WITH_INDIFFERENT_ACCESS_KEY) + result = result.with_indifferent_access + elsif symbol_keys = result.delete(SYMBOL_KEYS_KEY) + result = transform_symbol_keys(result, symbol_keys) + end + result + end + + # :nodoc: + RESERVED_KEYS = [ + GLOBALID_KEY, GLOBALID_KEY.to_sym, + SYMBOL_KEYS_KEY, SYMBOL_KEYS_KEY.to_sym, + WITH_INDIFFERENT_ACCESS_KEY, WITH_INDIFFERENT_ACCESS_KEY.to_sym, + ] + private_constant :RESERVED_KEYS + + def serialize_hash_key(key) + case key + when *RESERVED_KEYS + raise SerializationError.new("Can't serialize a Hash with reserved key #{key.inspect}") + when String, Symbol + key.to_s + else + raise SerializationError.new("Only string and symbol hash keys may be serialized as job arguments, but #{key.inspect} is a #{key.class}") + end + end + + def serialize_indifferent_hash(indifferent_hash) + result = serialize_hash(indifferent_hash) + result[WITH_INDIFFERENT_ACCESS_KEY] = serialize_argument(true) + result + end + + def transform_symbol_keys(hash, symbol_keys) + # NOTE: HashWithIndifferentAccess#transform_keys always + # returns stringified keys with indifferent access + # so we call #to_h here to ensure keys are symbolized. + hash.to_h.transform_keys do |key| + if symbol_keys.include?(key) + key.to_sym + else + key + end + end + end + + def convert_to_global_id_hash(argument) + { GLOBALID_KEY => argument.to_global_id.to_s } + rescue URI::GID::MissingModelIdError + raise SerializationError, "Unable to serialize #{argument.class} " \ + "without an id. (Maybe you forgot to call save?)" + end + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/base.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/base.rb new file mode 100644 index 00000000..ae112abb --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/base.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require "active_job/core" +require "active_job/queue_adapter" +require "active_job/queue_name" +require "active_job/queue_priority" +require "active_job/enqueuing" +require "active_job/execution" +require "active_job/callbacks" +require "active_job/exceptions" +require "active_job/logging" +require "active_job/translation" + +module ActiveJob #:nodoc: + # = Active Job + # + # Active Job objects can be configured to work with different backend + # queuing frameworks. To specify a queue adapter to use: + # + # ActiveJob::Base.queue_adapter = :inline + # + # A list of supported adapters can be found in QueueAdapters. + # + # Active Job objects can be defined by creating a class that inherits + # from the ActiveJob::Base class. The only necessary method to + # implement is the "perform" method. + # + # To define an Active Job object: + # + # class ProcessPhotoJob < ActiveJob::Base + # def perform(photo) + # photo.watermark!('Rails') + # photo.rotate!(90.degrees) + # photo.resize_to_fit!(300, 300) + # photo.upload! + # end + # end + # + # Records that are passed in are serialized/deserialized using Global + # ID. More information can be found in Arguments. + # + # To enqueue a job to be performed as soon as the queueing system is free: + # + # ProcessPhotoJob.perform_later(photo) + # + # To enqueue a job to be processed at some point in the future: + # + # ProcessPhotoJob.set(wait_until: Date.tomorrow.noon).perform_later(photo) + # + # More information can be found in ActiveJob::Core::ClassMethods#set + # + # A job can also be processed immediately without sending to the queue: + # + # ProcessPhotoJob.perform_now(photo) + # + # == Exceptions + # + # * DeserializationError - Error class for deserialization errors. + # * SerializationError - Error class for serialization errors. + class Base + include Core + include QueueAdapter + include QueueName + include QueuePriority + include Enqueuing + include Execution + include Callbacks + include Exceptions + include Logging + include Translation + + ActiveSupport.run_load_hooks(:active_job, self) + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/callbacks.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/callbacks.rb new file mode 100644 index 00000000..334b24fb --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/callbacks.rb @@ -0,0 +1,155 @@ +# frozen_string_literal: true + +require "active_support/callbacks" + +module ActiveJob + # = Active Job Callbacks + # + # Active Job provides hooks during the life cycle of a job. Callbacks allow you + # to trigger logic during this cycle. Available callbacks are: + # + # * before_enqueue + # * around_enqueue + # * after_enqueue + # * before_perform + # * around_perform + # * after_perform + # + # NOTE: Calling the same callback multiple times will overwrite previous callback definitions. + # + module Callbacks + extend ActiveSupport::Concern + include ActiveSupport::Callbacks + + class << self + include ActiveSupport::Callbacks + define_callbacks :execute + end + + included do + define_callbacks :perform + define_callbacks :enqueue + end + + # These methods will be included into any Active Job object, adding + # callbacks for +perform+ and +enqueue+ methods. + module ClassMethods + # Defines a callback that will get called right before the + # job's perform method is executed. + # + # class VideoProcessJob < ActiveJob::Base + # queue_as :default + # + # before_perform do |job| + # UserMailer.notify_video_started_processing(job.arguments.first) + # end + # + # def perform(video_id) + # Video.find(video_id).process + # end + # end + # + def before_perform(*filters, &blk) + set_callback(:perform, :before, *filters, &blk) + end + + # Defines a callback that will get called right after the + # job's perform method has finished. + # + # class VideoProcessJob < ActiveJob::Base + # queue_as :default + # + # after_perform do |job| + # UserMailer.notify_video_processed(job.arguments.first) + # end + # + # def perform(video_id) + # Video.find(video_id).process + # end + # end + # + def after_perform(*filters, &blk) + set_callback(:perform, :after, *filters, &blk) + end + + # Defines a callback that will get called around the job's perform method. + # + # class VideoProcessJob < ActiveJob::Base + # queue_as :default + # + # around_perform do |job, block| + # UserMailer.notify_video_started_processing(job.arguments.first) + # block.call + # UserMailer.notify_video_processed(job.arguments.first) + # end + # + # def perform(video_id) + # Video.find(video_id).process + # end + # end + # + def around_perform(*filters, &blk) + set_callback(:perform, :around, *filters, &blk) + end + + # Defines a callback that will get called right before the + # job is enqueued. + # + # class VideoProcessJob < ActiveJob::Base + # queue_as :default + # + # before_enqueue do |job| + # $statsd.increment "enqueue-video-job.try" + # end + # + # def perform(video_id) + # Video.find(video_id).process + # end + # end + # + def before_enqueue(*filters, &blk) + set_callback(:enqueue, :before, *filters, &blk) + end + + # Defines a callback that will get called right after the + # job is enqueued. + # + # class VideoProcessJob < ActiveJob::Base + # queue_as :default + # + # after_enqueue do |job| + # $statsd.increment "enqueue-video-job.success" + # end + # + # def perform(video_id) + # Video.find(video_id).process + # end + # end + # + def after_enqueue(*filters, &blk) + set_callback(:enqueue, :after, *filters, &blk) + end + + # Defines a callback that will get called around the enqueueing + # of the job. + # + # class VideoProcessJob < ActiveJob::Base + # queue_as :default + # + # around_enqueue do |job, block| + # $statsd.time "video-job.process" do + # block.call + # end + # end + # + # def perform(video_id) + # Video.find(video_id).process + # end + # end + # + def around_enqueue(*filters, &blk) + set_callback(:enqueue, :around, *filters, &blk) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/configured_job.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/configured_job.rb new file mode 100644 index 00000000..67daf48b --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/configured_job.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module ActiveJob + class ConfiguredJob #:nodoc: + def initialize(job_class, options = {}) + @options = options + @job_class = job_class + end + + def perform_now(*args) + @job_class.new(*args).perform_now + end + + def perform_later(*args) + @job_class.new(*args).enqueue @options + end + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/core.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/core.rb new file mode 100644 index 00000000..00c8ca92 --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/core.rb @@ -0,0 +1,158 @@ +# frozen_string_literal: true + +module ActiveJob + # Provides general behavior that will be included into every Active Job + # object that inherits from ActiveJob::Base. + module Core + extend ActiveSupport::Concern + + included do + # Job arguments + attr_accessor :arguments + attr_writer :serialized_arguments + + # Timestamp when the job should be performed + attr_accessor :scheduled_at + + # Job Identifier + attr_accessor :job_id + + # Queue in which the job will reside. + attr_writer :queue_name + + # Priority that the job will have (lower is more priority). + attr_writer :priority + + # ID optionally provided by adapter + attr_accessor :provider_job_id + + # Number of times this job has been executed (which increments on every retry, like after an exception). + attr_accessor :executions + + # I18n.locale to be used during the job. + attr_accessor :locale + end + + # These methods will be included into any Active Job object, adding + # helpers for de/serialization and creation of job instances. + module ClassMethods + # Creates a new job instance from a hash created with +serialize+ + def deserialize(job_data) + job = job_data["job_class"].constantize.new + job.deserialize(job_data) + job + end + + # Creates a job preconfigured with the given options. You can call + # perform_later with the job arguments to enqueue the job with the + # preconfigured options + # + # ==== Options + # * :wait - Enqueues the job with the specified delay + # * :wait_until - Enqueues the job at the time specified + # * :queue - Enqueues the job on the specified queue + # * :priority - Enqueues the job with the specified priority + # + # ==== Examples + # + # VideoJob.set(queue: :some_queue).perform_later(Video.last) + # VideoJob.set(wait: 5.minutes).perform_later(Video.last) + # VideoJob.set(wait_until: Time.now.tomorrow).perform_later(Video.last) + # VideoJob.set(queue: :some_queue, wait: 5.minutes).perform_later(Video.last) + # VideoJob.set(queue: :some_queue, wait_until: Time.now.tomorrow).perform_later(Video.last) + # VideoJob.set(queue: :some_queue, wait: 5.minutes, priority: 10).perform_later(Video.last) + def set(options = {}) + ConfiguredJob.new(self, options) + end + end + + # Creates a new job instance. Takes the arguments that will be + # passed to the perform method. + def initialize(*arguments) + @arguments = arguments + @job_id = SecureRandom.uuid + @queue_name = self.class.queue_name + @priority = self.class.priority + @executions = 0 + end + + # Returns a hash with the job data that can safely be passed to the + # queueing adapter. + def serialize + { + "job_class" => self.class.name, + "job_id" => job_id, + "provider_job_id" => provider_job_id, + "queue_name" => queue_name, + "priority" => priority, + "arguments" => serialize_arguments_if_needed(arguments), + "executions" => executions, + "locale" => I18n.locale.to_s + } + end + + # Attaches the stored job data to the current instance. Receives a hash + # returned from +serialize+ + # + # ==== Examples + # + # class DeliverWebhookJob < ActiveJob::Base + # attr_writer :attempt_number + # + # def attempt_number + # @attempt_number ||= 0 + # end + # + # def serialize + # super.merge('attempt_number' => attempt_number + 1) + # end + # + # def deserialize(job_data) + # super + # self.attempt_number = job_data['attempt_number'] + # end + # + # rescue_from(Timeout::Error) do |exception| + # raise exception if attempt_number > 5 + # retry_job(wait: 10) + # end + # end + def deserialize(job_data) + self.job_id = job_data["job_id"] + self.provider_job_id = job_data["provider_job_id"] + self.queue_name = job_data["queue_name"] + self.priority = job_data["priority"] + self.serialized_arguments = job_data["arguments"] + self.executions = job_data["executions"] + self.locale = job_data["locale"] || I18n.locale.to_s + end + + private + def serialize_arguments_if_needed(arguments) + if arguments_serialized? + @serialized_arguments + else + serialize_arguments(arguments) + end + end + + def deserialize_arguments_if_needed + if arguments_serialized? + @arguments = deserialize_arguments(@serialized_arguments) + @serialized_arguments = nil + end + end + + def serialize_arguments(arguments) + Arguments.serialize(arguments) + end + + def deserialize_arguments(serialized_args) + Arguments.deserialize(serialized_args) + end + + def arguments_serialized? + defined?(@serialized_arguments) && @serialized_arguments + end + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/enqueuing.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/enqueuing.rb new file mode 100644 index 00000000..53cb98fc --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/enqueuing.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require "active_job/arguments" + +module ActiveJob + # Provides behavior for enqueuing jobs. + module Enqueuing + extend ActiveSupport::Concern + + # Includes the +perform_later+ method for job initialization. + module ClassMethods + # Push a job onto the queue. The arguments must be legal JSON types + # (+string+, +int+, +float+, +nil+, +true+, +false+, +hash+ or +array+) or + # GlobalID::Identification instances. Arbitrary Ruby objects + # are not supported. + # + # Returns an instance of the job class queued with arguments available in + # Job#arguments. + def perform_later(*args) + job_or_instantiate(*args).enqueue + end + + private + def job_or_instantiate(*args) # :doc: + args.first.is_a?(self) ? args.first : new(*args) + end + end + + # Enqueues the job to be performed by the queue adapter. + # + # ==== Options + # * :wait - Enqueues the job with the specified delay + # * :wait_until - Enqueues the job at the time specified + # * :queue - Enqueues the job on the specified queue + # * :priority - Enqueues the job with the specified priority + # + # ==== Examples + # + # my_job_instance.enqueue + # my_job_instance.enqueue wait: 5.minutes + # my_job_instance.enqueue queue: :important + # my_job_instance.enqueue wait_until: Date.tomorrow.midnight + # my_job_instance.enqueue priority: 10 + def enqueue(options = {}) + self.scheduled_at = options[:wait].seconds.from_now.to_f if options[:wait] + self.scheduled_at = options[:wait_until].to_f if options[:wait_until] + self.queue_name = self.class.queue_name_from_part(options[:queue]) if options[:queue] + self.priority = options[:priority].to_i if options[:priority] + run_callbacks :enqueue do + if scheduled_at + self.class.queue_adapter.enqueue_at self, scheduled_at + else + self.class.queue_adapter.enqueue self + end + end + self + end + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/exceptions.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/exceptions.rb new file mode 100644 index 00000000..96b1e262 --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/exceptions.rb @@ -0,0 +1,134 @@ +# frozen_string_literal: true + +require "active_support/core_ext/numeric/time" + +module ActiveJob + # Provides behavior for retrying and discarding jobs on exceptions. + module Exceptions + extend ActiveSupport::Concern + + module ClassMethods + # Catch the exception and reschedule job for re-execution after so many seconds, for a specific number of attempts. + # If the exception keeps getting raised beyond the specified number of attempts, the exception is allowed to + # bubble up to the underlying queuing system, which may have its own retry mechanism or place it in a + # holding queue for inspection. + # + # You can also pass a block that'll be invoked if the retry attempts fail for custom logic rather than letting + # the exception bubble up. This block is yielded with the job instance as the first and the error instance as the second parameter. + # + # ==== Options + # * :wait - Re-enqueues the job with a delay specified either in seconds (default: 3 seconds), + # as a computing proc that the number of executions so far as an argument, or as a symbol reference of + # :exponentially_longer, which applies the wait algorithm of (executions ** 4) + 2 + # (first wait 3s, then 18s, then 83s, etc) + # * :attempts - Re-enqueues the job the specified number of times (default: 5 attempts) + # * :queue - Re-enqueues the job on a different queue + # * :priority - Re-enqueues the job with a different priority + # + # ==== Examples + # + # class RemoteServiceJob < ActiveJob::Base + # retry_on CustomAppException # defaults to 3s wait, 5 attempts + # retry_on AnotherCustomAppException, wait: ->(executions) { executions * 2 } + # retry_on(YetAnotherCustomAppException) do |job, error| + # ExceptionNotifier.caught(error) + # end + # retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3 + # retry_on Net::OpenTimeout, wait: :exponentially_longer, attempts: 10 + # + # def perform(*args) + # # Might raise CustomAppException, AnotherCustomAppException, or YetAnotherCustomAppException for something domain specific + # # Might raise ActiveRecord::Deadlocked when a local db deadlock is detected + # # Might raise Net::OpenTimeout when the remote service is down + # end + # end + def retry_on(exception, wait: 3.seconds, attempts: 5, queue: nil, priority: nil) + rescue_from exception do |error| + if executions < attempts + logger.error "Retrying #{self.class} in #{wait} seconds, due to a #{exception}. The original exception was #{error.cause.inspect}." + retry_job wait: determine_delay(wait), queue: queue, priority: priority + else + if block_given? + yield self, error + else + logger.error "Stopped retrying #{self.class} due to a #{exception}, which reoccurred on #{executions} attempts. The original exception was #{error.cause.inspect}." + raise error + end + end + end + end + + # Discard the job with no attempts to retry, if the exception is raised. This is useful when the subject of the job, + # like an Active Record, is no longer available, and the job is thus no longer relevant. + # + # You can also pass a block that'll be invoked. This block is yielded with the job instance as the first and the error instance as the second parameter. + # + # ==== Example + # + # class SearchIndexingJob < ActiveJob::Base + # discard_on ActiveJob::DeserializationError + # discard_on(CustomAppException) do |job, error| + # ExceptionNotifier.caught(error) + # end + # + # def perform(record) + # # Will raise ActiveJob::DeserializationError if the record can't be deserialized + # # Might raise CustomAppException for something domain specific + # end + # end + def discard_on(exception) + rescue_from exception do |error| + if block_given? + yield self, error + else + logger.error "Discarded #{self.class} due to a #{exception}. The original exception was #{error.cause.inspect}." + end + end + end + end + + # Reschedules the job to be re-executed. This is useful in combination + # with the +rescue_from+ option. When you rescue an exception from your job + # you can ask Active Job to retry performing your job. + # + # ==== Options + # * :wait - Enqueues the job with the specified delay in seconds + # * :wait_until - Enqueues the job at the time specified + # * :queue - Enqueues the job on the specified queue + # * :priority - Enqueues the job with the specified priority + # + # ==== Examples + # + # class SiteScraperJob < ActiveJob::Base + # rescue_from(ErrorLoadingSite) do + # retry_job queue: :low_priority + # end + # + # def perform(*args) + # # raise ErrorLoadingSite if cannot scrape + # end + # end + def retry_job(options = {}) + enqueue options + end + + private + def determine_delay(seconds_or_duration_or_algorithm) + case seconds_or_duration_or_algorithm + when :exponentially_longer + (executions**4) + 2 + when ActiveSupport::Duration + duration = seconds_or_duration_or_algorithm + duration.to_i + when Integer + seconds = seconds_or_duration_or_algorithm + seconds + when Proc + algorithm = seconds_or_duration_or_algorithm + algorithm.call(executions) + else + raise "Couldn't determine a delay based on #{seconds_or_duration_or_algorithm.inspect}" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/execution.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/execution.rb new file mode 100644 index 00000000..f5a34331 --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/execution.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require "active_support/rescuable" +require "active_job/arguments" + +module ActiveJob + module Execution + extend ActiveSupport::Concern + include ActiveSupport::Rescuable + + # Includes methods for executing and performing jobs instantly. + module ClassMethods + # Performs the job immediately. + # + # MyJob.perform_now("mike") + # + def perform_now(*args) + job_or_instantiate(*args).perform_now + end + + def execute(job_data) #:nodoc: + ActiveJob::Callbacks.run_callbacks(:execute) do + job = deserialize(job_data) + job.perform_now + end + end + end + + # Performs the job immediately. The job is not sent to the queueing adapter + # but directly executed by blocking the execution of others until it's finished. + # + # MyJob.new(*args).perform_now + def perform_now + # Guard against jobs that were persisted before we started counting executions by zeroing out nil counters + self.executions = (executions || 0) + 1 + + deserialize_arguments_if_needed + run_callbacks :perform do + perform(*arguments) + end + rescue => exception + rescue_with_handler(exception) || raise + end + + def perform(*) + fail NotImplementedError + end + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/gem_version.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/gem_version.rb new file mode 100644 index 00000000..7bf386a9 --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/gem_version.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActiveJob + # Returns the version of the currently loaded Active Job as a Gem::Version + def self.gem_version + Gem::Version.new VERSION::STRING + end + + module VERSION + MAJOR = 5 + MINOR = 2 + TINY = 3 + PRE = nil + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/logging.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/logging.rb new file mode 100644 index 00000000..f53b7eae --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/logging.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/transform_values" +require "active_support/core_ext/string/filters" +require "active_support/tagged_logging" +require "active_support/logger" + +module ActiveJob + module Logging #:nodoc: + extend ActiveSupport::Concern + + included do + cattr_accessor :logger, default: ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT)) + + around_enqueue do |_, block, _| + tag_logger do + block.call + end + end + + around_perform do |job, block, _| + tag_logger(job.class.name, job.job_id) do + payload = { adapter: job.class.queue_adapter, job: job } + ActiveSupport::Notifications.instrument("perform_start.active_job", payload.dup) + ActiveSupport::Notifications.instrument("perform.active_job", payload) do + block.call + end + end + end + + after_enqueue do |job| + if job.scheduled_at + ActiveSupport::Notifications.instrument "enqueue_at.active_job", + adapter: job.class.queue_adapter, job: job + else + ActiveSupport::Notifications.instrument "enqueue.active_job", + adapter: job.class.queue_adapter, job: job + end + end + end + + private + def tag_logger(*tags) + if logger.respond_to?(:tagged) + tags.unshift "ActiveJob" unless logger_tagged_by_active_job? + logger.tagged(*tags) { yield } + else + yield + end + end + + def logger_tagged_by_active_job? + logger.formatter.current_tags.include?("ActiveJob") + end + + class LogSubscriber < ActiveSupport::LogSubscriber #:nodoc: + def enqueue(event) + info do + job = event.payload[:job] + "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)}" + args_info(job) + end + end + + def enqueue_at(event) + info do + job = event.payload[:job] + "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)} at #{scheduled_at(event)}" + args_info(job) + end + end + + def perform_start(event) + info do + job = event.payload[:job] + "Performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)}" + args_info(job) + end + end + + def perform(event) + job = event.payload[:job] + ex = event.payload[:exception_object] + if ex + error do + "Error performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms: #{ex.class} (#{ex.message}):\n" + Array(ex.backtrace).join("\n") + end + else + info do + "Performed #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms" + end + end + end + + private + def queue_name(event) + event.payload[:adapter].class.name.demodulize.remove("Adapter") + "(#{event.payload[:job].queue_name})" + end + + def args_info(job) + if job.arguments.any? + " with arguments: " + + job.arguments.map { |arg| format(arg).inspect }.join(", ") + else + "" + end + end + + def format(arg) + case arg + when Hash + arg.transform_values { |value| format(value) } + when Array + arg.map { |value| format(value) } + when GlobalID::Identification + arg.to_global_id rescue arg + else + arg + end + end + + def scheduled_at(event) + Time.at(event.payload[:job].scheduled_at).utc + end + + def logger + ActiveJob::Base.logger + end + end + end +end + +ActiveJob::Logging::LogSubscriber.attach_to :active_job diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapter.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapter.rb new file mode 100644 index 00000000..006a683b --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapter.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/inflections" + +module ActiveJob + # The ActiveJob::QueueAdapter module is used to load the + # correct adapter. The default queue adapter is the +:async+ queue. + module QueueAdapter #:nodoc: + extend ActiveSupport::Concern + + included do + class_attribute :_queue_adapter_name, instance_accessor: false, instance_predicate: false + class_attribute :_queue_adapter, instance_accessor: false, instance_predicate: false + self.queue_adapter = :async + end + + # Includes the setter method for changing the active queue adapter. + module ClassMethods + # Returns the backend queue provider. The default queue adapter + # is the +:async+ queue. See QueueAdapters for more information. + def queue_adapter + _queue_adapter + end + + def queue_adapter_name + _queue_adapter_name + end + + # Specify the backend queue provider. The default queue adapter + # is the +:async+ queue. See QueueAdapters for more + # information. + def queue_adapter=(name_or_adapter) + case name_or_adapter + when Symbol, String + queue_adapter = ActiveJob::QueueAdapters.lookup(name_or_adapter).new + assign_adapter(name_or_adapter.to_s, queue_adapter) + else + if queue_adapter?(name_or_adapter) + adapter_name = "#{name_or_adapter.class.name.demodulize.remove('Adapter').underscore}" + assign_adapter(adapter_name, name_or_adapter) + else + raise ArgumentError + end + end + end + + private + def assign_adapter(adapter_name, queue_adapter) + self._queue_adapter_name = adapter_name + self._queue_adapter = queue_adapter + end + + QUEUE_ADAPTER_METHODS = [:enqueue, :enqueue_at].freeze + + def queue_adapter?(object) + QUEUE_ADAPTER_METHODS.all? { |meth| object.respond_to?(meth) } + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters.rb new file mode 100644 index 00000000..c1a1d3c5 --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters.rb @@ -0,0 +1,139 @@ +# frozen_string_literal: true + +module ActiveJob + # == Active Job adapters + # + # Active Job has adapters for the following queueing backends: + # + # * {Backburner}[https://github.com/nesquena/backburner] + # * {Delayed Job}[https://github.com/collectiveidea/delayed_job] + # * {Qu}[https://github.com/bkeepers/qu] + # * {Que}[https://github.com/chanks/que] + # * {queue_classic}[https://github.com/QueueClassic/queue_classic] + # * {Resque}[https://github.com/resque/resque] + # * {Sidekiq}[http://sidekiq.org] + # * {Sneakers}[https://github.com/jondot/sneakers] + # * {Sucker Punch}[https://github.com/brandonhilkert/sucker_punch] + # * {Active Job Async Job}[http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/AsyncAdapter.html] + # * {Active Job Inline}[http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/InlineAdapter.html] + # + # === Backends Features + # + # | | Async | Queues | Delayed | Priorities | Timeout | Retries | + # |-------------------|-------|--------|------------|------------|---------|---------| + # | Backburner | Yes | Yes | Yes | Yes | Job | Global | + # | Delayed Job | Yes | Yes | Yes | Job | Global | Global | + # | Qu | Yes | Yes | No | No | No | Global | + # | Que | Yes | Yes | Yes | Job | No | Job | + # | queue_classic | Yes | Yes | Yes* | No | No | No | + # | Resque | Yes | Yes | Yes (Gem) | Queue | Global | Yes | + # | Sidekiq | Yes | Yes | Yes | Queue | No | Job | + # | Sneakers | Yes | Yes | No | Queue | Queue | No | + # | Sucker Punch | Yes | Yes | Yes | No | No | No | + # | Active Job Async | Yes | Yes | Yes | No | No | No | + # | Active Job Inline | No | Yes | N/A | N/A | N/A | N/A | + # + # ==== Async + # + # Yes: The Queue Adapter has the ability to run the job in a non-blocking manner. + # It either runs on a separate or forked process, or on a different thread. + # + # No: The job is run in the same process. + # + # ==== Queues + # + # Yes: Jobs may set which queue they are run in with queue_as or by using the set + # method. + # + # ==== Delayed + # + # Yes: The adapter will run the job in the future through perform_later. + # + # (Gem): An additional gem is required to use perform_later with this adapter. + # + # No: The adapter will run jobs at the next opportunity and cannot use perform_later. + # + # N/A: The adapter does not support queueing. + # + # NOTE: + # queue_classic supports job scheduling since version 3.1. + # For older versions you can use the queue_classic-later gem. + # + # ==== Priorities + # + # The order in which jobs are processed can be configured differently depending + # on the adapter. + # + # Job: Any class inheriting from the adapter may set the priority on the job + # object relative to other jobs. + # + # Queue: The adapter can set the priority for job queues, when setting a queue + # with Active Job this will be respected. + # + # Yes: Allows the priority to be set on the job object, at the queue level or + # as default configuration option. + # + # No: Does not allow the priority of jobs to be configured. + # + # N/A: The adapter does not support queueing, and therefore sorting them. + # + # ==== Timeout + # + # When a job will stop after the allotted time. + # + # Job: The timeout can be set for each instance of the job class. + # + # Queue: The timeout is set for all jobs on the queue. + # + # Global: The adapter is configured that all jobs have a maximum run time. + # + # N/A: This adapter does not run in a separate process, and therefore timeout + # is unsupported. + # + # ==== Retries + # + # Job: The number of retries can be set per instance of the job class. + # + # Yes: The Number of retries can be configured globally, for each instance or + # on the queue. This adapter may also present failed instances of the job class + # that can be restarted. + # + # Global: The adapter has a global number of retries. + # + # N/A: The adapter does not run in a separate process, and therefore doesn't + # support retries. + # + # === Async and Inline Queue Adapters + # + # Active Job has two built-in queue adapters intended for development and + # testing: +:async+ and +:inline+. + module QueueAdapters + extend ActiveSupport::Autoload + + autoload :AsyncAdapter + autoload :InlineAdapter + autoload :BackburnerAdapter + autoload :DelayedJobAdapter + autoload :QuAdapter + autoload :QueAdapter + autoload :QueueClassicAdapter + autoload :ResqueAdapter + autoload :SidekiqAdapter + autoload :SneakersAdapter + autoload :SuckerPunchAdapter + autoload :TestAdapter + + ADAPTER = "Adapter".freeze + private_constant :ADAPTER + + class << self + # Returns adapter for specified name. + # + # ActiveJob::QueueAdapters.lookup(:sidekiq) + # # => ActiveJob::QueueAdapters::SidekiqAdapter + def lookup(name) + const_get(name.to_s.camelize << ADAPTER) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/async_adapter.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/async_adapter.rb new file mode 100644 index 00000000..ebf6f384 --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/async_adapter.rb @@ -0,0 +1,116 @@ +# frozen_string_literal: true + +require "securerandom" +require "concurrent/scheduled_task" +require "concurrent/executor/thread_pool_executor" +require "concurrent/utility/processor_counter" + +module ActiveJob + module QueueAdapters + # == Active Job Async adapter + # + # The Async adapter runs jobs with an in-process thread pool. + # + # This is the default queue adapter. It's well-suited for dev/test since + # it doesn't need an external infrastructure, but it's a poor fit for + # production since it drops pending jobs on restart. + # + # To use this adapter, set queue adapter to +:async+: + # + # config.active_job.queue_adapter = :async + # + # To configure the adapter's thread pool, instantiate the adapter and + # pass your own config: + # + # config.active_job.queue_adapter = ActiveJob::QueueAdapters::AsyncAdapter.new \ + # min_threads: 1, + # max_threads: 2 * Concurrent.processor_count, + # idletime: 600.seconds + # + # The adapter uses a {Concurrent Ruby}[https://github.com/ruby-concurrency/concurrent-ruby] thread pool to schedule and execute + # jobs. Since jobs share a single thread pool, long-running jobs will block + # short-lived jobs. Fine for dev/test; bad for production. + class AsyncAdapter + # See {Concurrent::ThreadPoolExecutor}[https://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadPoolExecutor.html] for executor options. + def initialize(**executor_options) + @scheduler = Scheduler.new(**executor_options) + end + + def enqueue(job) #:nodoc: + @scheduler.enqueue JobWrapper.new(job), queue_name: job.queue_name + end + + def enqueue_at(job, timestamp) #:nodoc: + @scheduler.enqueue_at JobWrapper.new(job), timestamp, queue_name: job.queue_name + end + + # Gracefully stop processing jobs. Finishes in-progress work and handles + # any new jobs following the executor's fallback policy (`caller_runs`). + # Waits for termination by default. Pass `wait: false` to continue. + def shutdown(wait: true) #:nodoc: + @scheduler.shutdown wait: wait + end + + # Used for our test suite. + def immediate=(immediate) #:nodoc: + @scheduler.immediate = immediate + end + + # Note that we don't actually need to serialize the jobs since we're + # performing them in-process, but we do so anyway for parity with other + # adapters and deployment environments. Otherwise, serialization bugs + # may creep in undetected. + class JobWrapper #:nodoc: + def initialize(job) + job.provider_job_id = SecureRandom.uuid + @job_data = job.serialize + end + + def perform + Base.execute @job_data + end + end + + class Scheduler #:nodoc: + DEFAULT_EXECUTOR_OPTIONS = { + min_threads: 0, + max_threads: Concurrent.processor_count, + auto_terminate: true, + idletime: 60, # 1 minute + max_queue: 0, # unlimited + fallback_policy: :caller_runs # shouldn't matter -- 0 max queue + }.freeze + + attr_accessor :immediate + + def initialize(**options) + self.immediate = false + @immediate_executor = Concurrent::ImmediateExecutor.new + @async_executor = Concurrent::ThreadPoolExecutor.new(DEFAULT_EXECUTOR_OPTIONS.merge(options)) + end + + def enqueue(job, queue_name:) + executor.post(job, &:perform) + end + + def enqueue_at(job, timestamp, queue_name:) + delay = timestamp - Time.current.to_f + if delay > 0 + Concurrent::ScheduledTask.execute(delay, args: [job], executor: executor, &:perform) + else + enqueue(job, queue_name: queue_name) + end + end + + def shutdown(wait: true) + @async_executor.shutdown + @async_executor.wait_for_termination if wait + end + + def executor + immediate ? @immediate_executor : @async_executor + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/backburner_adapter.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/backburner_adapter.rb new file mode 100644 index 00000000..0ba93c6e --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/backburner_adapter.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require "backburner" + +module ActiveJob + module QueueAdapters + # == Backburner adapter for Active Job + # + # Backburner is a beanstalkd-powered job queue that can handle a very + # high volume of jobs. You create background jobs and place them on + # multiple work queues to be processed later. Read more about + # Backburner {here}[https://github.com/nesquena/backburner]. + # + # To use Backburner set the queue_adapter config to +:backburner+. + # + # Rails.application.config.active_job.queue_adapter = :backburner + class BackburnerAdapter + def enqueue(job) #:nodoc: + Backburner::Worker.enqueue JobWrapper, [ job.serialize ], queue: job.queue_name + end + + def enqueue_at(job, timestamp) #:nodoc: + delay = timestamp - Time.current.to_f + Backburner::Worker.enqueue JobWrapper, [ job.serialize ], queue: job.queue_name, delay: delay + end + + class JobWrapper #:nodoc: + class << self + def perform(job_data) + Base.execute job_data + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/delayed_job_adapter.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/delayed_job_adapter.rb new file mode 100644 index 00000000..8eeef32b --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/delayed_job_adapter.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require "delayed_job" + +module ActiveJob + module QueueAdapters + # == Delayed Job adapter for Active Job + # + # Delayed::Job (or DJ) encapsulates the common pattern of asynchronously + # executing longer tasks in the background. Although DJ can have many + # storage backends, one of the most used is based on Active Record. + # Read more about Delayed Job {here}[https://github.com/collectiveidea/delayed_job]. + # + # To use Delayed Job, set the queue_adapter config to +:delayed_job+. + # + # Rails.application.config.active_job.queue_adapter = :delayed_job + class DelayedJobAdapter + def enqueue(job) #:nodoc: + delayed_job = Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name, priority: job.priority) + job.provider_job_id = delayed_job.id + delayed_job + end + + def enqueue_at(job, timestamp) #:nodoc: + delayed_job = Delayed::Job.enqueue(JobWrapper.new(job.serialize), queue: job.queue_name, priority: job.priority, run_at: Time.at(timestamp)) + job.provider_job_id = delayed_job.id + delayed_job + end + + class JobWrapper #:nodoc: + attr_accessor :job_data + + def initialize(job_data) + @job_data = job_data + end + + def display_name + "#{job_data['job_class']} [#{job_data['job_id']}] from DelayedJob(#{job_data['queue_name']}) with arguments: #{job_data['arguments']}" + end + + def perform + Base.execute(job_data) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/inline_adapter.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/inline_adapter.rb new file mode 100644 index 00000000..3d0b5902 --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/inline_adapter.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module ActiveJob + module QueueAdapters + # == Active Job Inline adapter + # + # When enqueuing jobs with the Inline adapter the job will be executed + # immediately. + # + # To use the Inline set the queue_adapter config to +:inline+. + # + # Rails.application.config.active_job.queue_adapter = :inline + class InlineAdapter + def enqueue(job) #:nodoc: + Base.execute(job.serialize) + end + + def enqueue_at(*) #:nodoc: + raise NotImplementedError, "Use a queueing backend to enqueue jobs in the future. Read more at http://guides.rubyonrails.org/active_job_basics.html" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/qu_adapter.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/qu_adapter.rb new file mode 100644 index 00000000..bd7003e1 --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/qu_adapter.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require "qu" + +module ActiveJob + module QueueAdapters + # == Qu adapter for Active Job + # + # Qu is a Ruby library for queuing and processing background jobs. It is + # heavily inspired by delayed_job and Resque. Qu was created to overcome + # some shortcomings in the existing queuing libraries. + # The advantages of Qu are: Multiple backends (redis, mongo), jobs are + # requeued when worker is killed, resque-like API. + # + # Read more about Qu {here}[https://github.com/bkeepers/qu]. + # + # To use Qu set the queue_adapter config to +:qu+. + # + # Rails.application.config.active_job.queue_adapter = :qu + class QuAdapter + def enqueue(job, *args) #:nodoc: + qu_job = Qu::Payload.new(klass: JobWrapper, args: [job.serialize]).tap do |payload| + payload.instance_variable_set(:@queue, job.queue_name) + end.push + + # qu_job can be nil depending on the configured backend + job.provider_job_id = qu_job.id unless qu_job.nil? + qu_job + end + + def enqueue_at(job, timestamp, *args) #:nodoc: + raise NotImplementedError, "This queueing backend does not support scheduling jobs. To see what features are supported go to http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html" + end + + class JobWrapper < Qu::Job #:nodoc: + def initialize(job_data) + @job_data = job_data + end + + def perform + Base.execute @job_data + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/que_adapter.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/que_adapter.rb new file mode 100644 index 00000000..86b5e077 --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/que_adapter.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require "que" + +module ActiveJob + module QueueAdapters + # == Que adapter for Active Job + # + # Que is a high-performance alternative to DelayedJob or QueueClassic that + # improves the reliability of your application by protecting your jobs with + # the same ACID guarantees as the rest of your data. Que is a queue for + # Ruby and PostgreSQL that manages jobs using advisory locks. + # + # Read more about Que {here}[https://github.com/chanks/que]. + # + # To use Que set the queue_adapter config to +:que+. + # + # Rails.application.config.active_job.queue_adapter = :que + class QueAdapter + def enqueue(job) #:nodoc: + que_job = JobWrapper.enqueue job.serialize, priority: job.priority + job.provider_job_id = que_job.attrs["job_id"] + que_job + end + + def enqueue_at(job, timestamp) #:nodoc: + que_job = JobWrapper.enqueue job.serialize, priority: job.priority, run_at: Time.at(timestamp) + job.provider_job_id = que_job.attrs["job_id"] + que_job + end + + class JobWrapper < Que::Job #:nodoc: + def run(job_data) + Base.execute job_data + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/queue_classic_adapter.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/queue_classic_adapter.rb new file mode 100644 index 00000000..ccc18810 --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/queue_classic_adapter.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require "queue_classic" + +module ActiveJob + module QueueAdapters + # == queue_classic adapter for Active Job + # + # queue_classic provides a simple interface to a PostgreSQL-backed message + # queue. queue_classic specializes in concurrent locking and minimizing + # database load while providing a simple, intuitive developer experience. + # queue_classic assumes that you are already using PostgreSQL in your + # production environment and that adding another dependency (e.g. redis, + # beanstalkd, 0mq) is undesirable. + # + # Read more about queue_classic {here}[https://github.com/QueueClassic/queue_classic]. + # + # To use queue_classic set the queue_adapter config to +:queue_classic+. + # + # Rails.application.config.active_job.queue_adapter = :queue_classic + class QueueClassicAdapter + def enqueue(job) #:nodoc: + qc_job = build_queue(job.queue_name).enqueue("#{JobWrapper.name}.perform", job.serialize) + job.provider_job_id = qc_job["id"] if qc_job.is_a?(Hash) + qc_job + end + + def enqueue_at(job, timestamp) #:nodoc: + queue = build_queue(job.queue_name) + unless queue.respond_to?(:enqueue_at) + raise NotImplementedError, "To be able to schedule jobs with queue_classic " \ + "the QC::Queue needs to respond to `enqueue_at(timestamp, method, *args)`. " \ + "You can implement this yourself or you can use the queue_classic-later gem." + end + qc_job = queue.enqueue_at(timestamp, "#{JobWrapper.name}.perform", job.serialize) + job.provider_job_id = qc_job["id"] if qc_job.is_a?(Hash) + qc_job + end + + # Builds a QC::Queue object to schedule jobs on. + # + # If you have a custom QC::Queue subclass you'll need to subclass + # ActiveJob::QueueAdapters::QueueClassicAdapter and override the + # build_queue method. + def build_queue(queue_name) + QC::Queue.new(queue_name) + end + + class JobWrapper #:nodoc: + class << self + def perform(job_data) + Base.execute job_data + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/resque_adapter.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/resque_adapter.rb new file mode 100644 index 00000000..590b4ee9 --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/resque_adapter.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require "resque" +require "active_support/core_ext/enumerable" +require "active_support/core_ext/array/access" + +begin + require "resque-scheduler" +rescue LoadError + begin + require "resque_scheduler" + rescue LoadError + false + end +end + +module ActiveJob + module QueueAdapters + # == Resque adapter for Active Job + # + # Resque (pronounced like "rescue") is a Redis-backed library for creating + # background jobs, placing those jobs on multiple queues, and processing + # them later. + # + # Read more about Resque {here}[https://github.com/resque/resque]. + # + # To use Resque set the queue_adapter config to +:resque+. + # + # Rails.application.config.active_job.queue_adapter = :resque + class ResqueAdapter + def enqueue(job) #:nodoc: + JobWrapper.instance_variable_set(:@queue, job.queue_name) + Resque.enqueue_to job.queue_name, JobWrapper, job.serialize + end + + def enqueue_at(job, timestamp) #:nodoc: + unless Resque.respond_to?(:enqueue_at_with_queue) + raise NotImplementedError, "To be able to schedule jobs with Resque you need the " \ + "resque-scheduler gem. Please add it to your Gemfile and run bundle install" + end + Resque.enqueue_at_with_queue job.queue_name, timestamp, JobWrapper, job.serialize + end + + class JobWrapper #:nodoc: + class << self + def perform(job_data) + Base.execute job_data + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/sidekiq_adapter.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/sidekiq_adapter.rb new file mode 100644 index 00000000..f726e6ad --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/sidekiq_adapter.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require "sidekiq" + +module ActiveJob + module QueueAdapters + # == Sidekiq adapter for Active Job + # + # Simple, efficient background processing for Ruby. Sidekiq uses threads to + # handle many jobs at the same time in the same process. It does not + # require Rails but will integrate tightly with it to make background + # processing dead simple. + # + # Read more about Sidekiq {here}[http://sidekiq.org]. + # + # To use Sidekiq set the queue_adapter config to +:sidekiq+. + # + # Rails.application.config.active_job.queue_adapter = :sidekiq + class SidekiqAdapter + def enqueue(job) #:nodoc: + # Sidekiq::Client does not support symbols as keys + job.provider_job_id = Sidekiq::Client.push \ + "class" => JobWrapper, + "wrapped" => job.class.to_s, + "queue" => job.queue_name, + "args" => [ job.serialize ] + end + + def enqueue_at(job, timestamp) #:nodoc: + job.provider_job_id = Sidekiq::Client.push \ + "class" => JobWrapper, + "wrapped" => job.class.to_s, + "queue" => job.queue_name, + "args" => [ job.serialize ], + "at" => timestamp + end + + class JobWrapper #:nodoc: + include Sidekiq::Worker + + def perform(job_data) + Base.execute job_data.merge("provider_job_id" => jid) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/sneakers_adapter.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/sneakers_adapter.rb new file mode 100644 index 00000000..de98a950 --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/sneakers_adapter.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require "sneakers" +require "monitor" + +module ActiveJob + module QueueAdapters + # == Sneakers adapter for Active Job + # + # A high-performance RabbitMQ background processing framework for Ruby. + # Sneakers is being used in production for both I/O and CPU intensive + # workloads, and have achieved the goals of high-performance and + # 0-maintenance, as designed. + # + # Read more about Sneakers {here}[https://github.com/jondot/sneakers]. + # + # To use Sneakers set the queue_adapter config to +:sneakers+. + # + # Rails.application.config.active_job.queue_adapter = :sneakers + class SneakersAdapter + def initialize + @monitor = Monitor.new + end + + def enqueue(job) #:nodoc: + @monitor.synchronize do + JobWrapper.from_queue job.queue_name + JobWrapper.enqueue ActiveSupport::JSON.encode(job.serialize) + end + end + + def enqueue_at(job, timestamp) #:nodoc: + raise NotImplementedError, "This queueing backend does not support scheduling jobs. To see what features are supported go to http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html" + end + + class JobWrapper #:nodoc: + include Sneakers::Worker + from_queue "default" + + def work(msg) + job_data = ActiveSupport::JSON.decode(msg) + Base.execute job_data + ack! + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/sucker_punch_adapter.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/sucker_punch_adapter.rb new file mode 100644 index 00000000..d09e1e91 --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/sucker_punch_adapter.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require "sucker_punch" + +module ActiveJob + module QueueAdapters + # == Sucker Punch adapter for Active Job + # + # Sucker Punch is a single-process Ruby asynchronous processing library. + # This reduces the cost of hosting on a service like Heroku along + # with the memory footprint of having to maintain additional jobs if + # hosting on a dedicated server. All queues can run within a + # single application (eg. Rails, Sinatra, etc.) process. + # + # Read more about Sucker Punch {here}[https://github.com/brandonhilkert/sucker_punch]. + # + # To use Sucker Punch set the queue_adapter config to +:sucker_punch+. + # + # Rails.application.config.active_job.queue_adapter = :sucker_punch + class SuckerPunchAdapter + def enqueue(job) #:nodoc: + if JobWrapper.respond_to?(:perform_async) + # sucker_punch 2.0 API + JobWrapper.perform_async job.serialize + else + # sucker_punch 1.0 API + JobWrapper.new.async.perform job.serialize + end + end + + def enqueue_at(job, timestamp) #:nodoc: + if JobWrapper.respond_to?(:perform_in) + delay = timestamp - Time.current.to_f + JobWrapper.perform_in delay, job.serialize + else + raise NotImplementedError, "sucker_punch 1.0 does not support `enqueued_at`. Please upgrade to version ~> 2.0.0 to enable this behavior." + end + end + + class JobWrapper #:nodoc: + include SuckerPunch::Job + + def perform(job_data) + Base.execute job_data + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/test_adapter.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/test_adapter.rb new file mode 100644 index 00000000..885f9ff0 --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_adapters/test_adapter.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +module ActiveJob + module QueueAdapters + # == Test adapter for Active Job + # + # The test adapter should be used only in testing. Along with + # ActiveJob::TestCase and ActiveJob::TestHelper + # it makes a great tool to test your Rails application. + # + # To use the test adapter set queue_adapter config to +:test+. + # + # Rails.application.config.active_job.queue_adapter = :test + class TestAdapter + attr_accessor(:perform_enqueued_jobs, :perform_enqueued_at_jobs, :filter, :reject) + attr_writer(:enqueued_jobs, :performed_jobs) + + # Provides a store of all the enqueued jobs with the TestAdapter so you can check them. + def enqueued_jobs + @enqueued_jobs ||= [] + end + + # Provides a store of all the performed jobs with the TestAdapter so you can check them. + def performed_jobs + @performed_jobs ||= [] + end + + def enqueue(job) #:nodoc: + return if filtered?(job) + + job_data = job_to_hash(job) + enqueue_or_perform(perform_enqueued_jobs, job, job_data) + end + + def enqueue_at(job, timestamp) #:nodoc: + return if filtered?(job) + + job_data = job_to_hash(job, at: timestamp) + enqueue_or_perform(perform_enqueued_at_jobs, job, job_data) + end + + private + def job_to_hash(job, extras = {}) + { job: job.class, args: job.serialize.fetch("arguments"), queue: job.queue_name }.merge!(extras) + end + + def enqueue_or_perform(perform, job, job_data) + if perform + performed_jobs << job_data + Base.execute job.serialize + else + enqueued_jobs << job_data + end + end + + def filtered?(job) + if filter + !Array(filter).include?(job.class) + elsif reject + Array(reject).include?(job.class) + else + false + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_name.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_name.rb new file mode 100644 index 00000000..9dc6bc7f --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_name.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module ActiveJob + module QueueName + extend ActiveSupport::Concern + + # Includes the ability to override the default queue name and prefix. + module ClassMethods + mattr_accessor :queue_name_prefix + mattr_accessor :default_queue_name, default: "default" + + # Specifies the name of the queue to process the job on. + # + # class PublishToFeedJob < ActiveJob::Base + # queue_as :feeds + # + # def perform(post) + # post.to_feed! + # end + # end + def queue_as(part_name = nil, &block) + if block_given? + self.queue_name = block + else + self.queue_name = queue_name_from_part(part_name) + end + end + + def queue_name_from_part(part_name) #:nodoc: + queue_name = part_name || default_queue_name + name_parts = [queue_name_prefix.presence, queue_name] + name_parts.compact.join(queue_name_delimiter) + end + end + + included do + class_attribute :queue_name, instance_accessor: false, default: default_queue_name + class_attribute :queue_name_delimiter, instance_accessor: false, default: "_" + end + + # Returns the name of the queue the job will be run on. + def queue_name + if @queue_name.is_a?(Proc) + @queue_name = self.class.queue_name_from_part(instance_exec(&@queue_name)) + end + @queue_name + end + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_priority.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_priority.rb new file mode 100644 index 00000000..063bccdb --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/queue_priority.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module ActiveJob + module QueuePriority + extend ActiveSupport::Concern + + # Includes the ability to override the default queue priority. + module ClassMethods + mattr_accessor :default_priority + + # Specifies the priority of the queue to create the job with. + # + # class PublishToFeedJob < ActiveJob::Base + # queue_with_priority 50 + # + # def perform(post) + # post.to_feed! + # end + # end + # + # Specify either an argument or a block. + def queue_with_priority(priority = nil, &block) + if block_given? + self.priority = block + else + self.priority = priority + end + end + end + + included do + class_attribute :priority, instance_accessor: false, default: default_priority + end + + # Returns the priority that the job will be created with + def priority + if @priority.is_a?(Proc) + @priority = instance_exec(&@priority) + end + @priority + end + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/railtie.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/railtie.rb new file mode 100644 index 00000000..7b0742a6 --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/railtie.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "global_id/railtie" +require "active_job" + +module ActiveJob + # = Active Job Railtie + class Railtie < Rails::Railtie # :nodoc: + config.active_job = ActiveSupport::OrderedOptions.new + + initializer "active_job.logger" do + ActiveSupport.on_load(:active_job) { self.logger = ::Rails.logger } + end + + initializer "active_job.set_configs" do |app| + options = app.config.active_job + options.queue_adapter ||= :async + + ActiveSupport.on_load(:active_job) do + options.each { |k, v| send("#{k}=", v) } + end + end + + initializer "active_job.set_reloader_hook" do |app| + ActiveSupport.on_load(:active_job) do + ActiveJob::Callbacks.singleton_class.set_callback(:execute, :around, prepend: true) do |_, inner| + app.reloader.wrap do + inner.call + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/test_case.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/test_case.rb new file mode 100644 index 00000000..49cd51bd --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/test_case.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require "active_support/test_case" + +module ActiveJob + class TestCase < ActiveSupport::TestCase + include ActiveJob::TestHelper + + ActiveSupport.run_load_hooks(:active_job_test_case, self) + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/test_helper.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/test_helper.rb new file mode 100644 index 00000000..81f46829 --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/test_helper.rb @@ -0,0 +1,456 @@ +# frozen_string_literal: true + +require "active_support/core_ext/class/subclasses" +require "active_support/core_ext/hash/keys" + +module ActiveJob + # Provides helper methods for testing Active Job + module TestHelper + delegate :enqueued_jobs, :enqueued_jobs=, + :performed_jobs, :performed_jobs=, + to: :queue_adapter + + module TestQueueAdapter + extend ActiveSupport::Concern + + included do + class_attribute :_test_adapter, instance_accessor: false, instance_predicate: false + end + + module ClassMethods + def queue_adapter + self._test_adapter.nil? ? super : self._test_adapter + end + + def disable_test_adapter + self._test_adapter = nil + end + + def enable_test_adapter(test_adapter) + self._test_adapter = test_adapter + end + end + end + + ActiveJob::Base.include(TestQueueAdapter) + + def before_setup # :nodoc: + test_adapter = queue_adapter_for_test + + queue_adapter_changed_jobs.each do |klass| + klass.enable_test_adapter(test_adapter) + end + + clear_enqueued_jobs + clear_performed_jobs + super + end + + def after_teardown # :nodoc: + super + + queue_adapter_changed_jobs.each { |klass| klass.disable_test_adapter } + end + + # Specifies the queue adapter to use with all active job test helpers. + # + # Returns an instance of the queue adapter and defaults to + # ActiveJob::QueueAdapters::TestAdapter. + # + # Note: The adapter provided by this method must provide some additional + # methods from those expected of a standard ActiveJob::QueueAdapter + # in order to be used with the active job test helpers. Refer to + # ActiveJob::QueueAdapters::TestAdapter. + def queue_adapter_for_test + ActiveJob::QueueAdapters::TestAdapter.new + end + + # Asserts that the number of enqueued jobs matches the given number. + # + # def test_jobs + # assert_enqueued_jobs 0 + # HelloJob.perform_later('david') + # assert_enqueued_jobs 1 + # HelloJob.perform_later('abdelkader') + # assert_enqueued_jobs 2 + # end + # + # If a block is passed, that block will cause the specified number of + # jobs to be enqueued. + # + # def test_jobs_again + # assert_enqueued_jobs 1 do + # HelloJob.perform_later('cristian') + # end + # + # assert_enqueued_jobs 2 do + # HelloJob.perform_later('aaron') + # HelloJob.perform_later('rafael') + # end + # end + # + # The number of times a specific job was enqueued can be asserted. + # + # def test_logging_job + # assert_enqueued_jobs 1, only: LoggingJob do + # LoggingJob.perform_later + # HelloJob.perform_later('jeremy') + # end + # end + # + # The number of times a job except specific class was enqueued can be asserted. + # + # def test_logging_job + # assert_enqueued_jobs 1, except: HelloJob do + # LoggingJob.perform_later + # HelloJob.perform_later('jeremy') + # end + # end + # + # The number of times a job is enqueued to a specific queue can also be asserted. + # + # def test_logging_job + # assert_enqueued_jobs 2, queue: 'default' do + # LoggingJob.perform_later + # HelloJob.perform_later('elfassy') + # end + # end + def assert_enqueued_jobs(number, only: nil, except: nil, queue: nil) + if block_given? + original_count = enqueued_jobs_size(only: only, except: except, queue: queue) + yield + new_count = enqueued_jobs_size(only: only, except: except, queue: queue) + assert_equal number, new_count - original_count, "#{number} jobs expected, but #{new_count - original_count} were enqueued" + else + actual_count = enqueued_jobs_size(only: only, except: except, queue: queue) + assert_equal number, actual_count, "#{number} jobs expected, but #{actual_count} were enqueued" + end + end + + # Asserts that no jobs have been enqueued. + # + # def test_jobs + # assert_no_enqueued_jobs + # HelloJob.perform_later('jeremy') + # assert_enqueued_jobs 1 + # end + # + # If a block is passed, that block should not cause any job to be enqueued. + # + # def test_jobs_again + # assert_no_enqueued_jobs do + # # No job should be enqueued from this block + # end + # end + # + # It can be asserted that no jobs of a specific kind are enqueued: + # + # def test_no_logging + # assert_no_enqueued_jobs only: LoggingJob do + # HelloJob.perform_later('jeremy') + # end + # end + # + # It can be asserted that no jobs except specific class are enqueued: + # + # def test_no_logging + # assert_no_enqueued_jobs except: HelloJob do + # HelloJob.perform_later('jeremy') + # end + # end + # + # Note: This assertion is simply a shortcut for: + # + # assert_enqueued_jobs 0, &block + def assert_no_enqueued_jobs(only: nil, except: nil, &block) + assert_enqueued_jobs 0, only: only, except: except, &block + end + + # Asserts that the number of performed jobs matches the given number. + # If no block is passed, perform_enqueued_jobs + # must be called around the job call. + # + # def test_jobs + # assert_performed_jobs 0 + # + # perform_enqueued_jobs do + # HelloJob.perform_later('xavier') + # end + # assert_performed_jobs 1 + # + # perform_enqueued_jobs do + # HelloJob.perform_later('yves') + # assert_performed_jobs 2 + # end + # end + # + # If a block is passed, that block should cause the specified number of + # jobs to be performed. + # + # def test_jobs_again + # assert_performed_jobs 1 do + # HelloJob.perform_later('robin') + # end + # + # assert_performed_jobs 2 do + # HelloJob.perform_later('carlos') + # HelloJob.perform_later('sean') + # end + # end + # + # The block form supports filtering. If the :only option is specified, + # then only the listed job(s) will be performed. + # + # def test_hello_job + # assert_performed_jobs 1, only: HelloJob do + # HelloJob.perform_later('jeremy') + # LoggingJob.perform_later + # end + # end + # + # Also if the :except option is specified, + # then the job(s) except specific class will be performed. + # + # def test_hello_job + # assert_performed_jobs 1, except: LoggingJob do + # HelloJob.perform_later('jeremy') + # LoggingJob.perform_later + # end + # end + # + # An array may also be specified, to support testing multiple jobs. + # + # def test_hello_and_logging_jobs + # assert_nothing_raised do + # assert_performed_jobs 2, only: [HelloJob, LoggingJob] do + # HelloJob.perform_later('jeremy') + # LoggingJob.perform_later('stewie') + # RescueJob.perform_later('david') + # end + # end + # end + def assert_performed_jobs(number, only: nil, except: nil) + if block_given? + original_count = performed_jobs.size + perform_enqueued_jobs(only: only, except: except) { yield } + new_count = performed_jobs.size + assert_equal number, new_count - original_count, + "#{number} jobs expected, but #{new_count - original_count} were performed" + else + performed_jobs_size = performed_jobs.size + assert_equal number, performed_jobs_size, "#{number} jobs expected, but #{performed_jobs_size} were performed" + end + end + + # Asserts that no jobs have been performed. + # + # def test_jobs + # assert_no_performed_jobs + # + # perform_enqueued_jobs do + # HelloJob.perform_later('matthew') + # assert_performed_jobs 1 + # end + # end + # + # If a block is passed, that block should not cause any job to be performed. + # + # def test_jobs_again + # assert_no_performed_jobs do + # # No job should be performed from this block + # end + # end + # + # The block form supports filtering. If the :only option is specified, + # then only the listed job(s) will not be performed. + # + # def test_no_logging + # assert_no_performed_jobs only: LoggingJob do + # HelloJob.perform_later('jeremy') + # end + # end + # + # Also if the :except option is specified, + # then the job(s) except specific class will not be performed. + # + # def test_no_logging + # assert_no_performed_jobs except: HelloJob do + # HelloJob.perform_later('jeremy') + # end + # end + # + # Note: This assertion is simply a shortcut for: + # + # assert_performed_jobs 0, &block + def assert_no_performed_jobs(only: nil, except: nil, &block) + assert_performed_jobs 0, only: only, except: except, &block + end + + # Asserts that the job passed in the block has been enqueued with the given arguments. + # + # def test_assert_enqueued_with + # assert_enqueued_with(job: MyJob, args: [1,2,3], queue: 'low') do + # MyJob.perform_later(1,2,3) + # end + # + # assert_enqueued_with(job: MyJob, at: Date.tomorrow.noon) do + # MyJob.set(wait_until: Date.tomorrow.noon).perform_later + # end + # end + def assert_enqueued_with(job: nil, args: nil, at: nil, queue: nil) + original_enqueued_jobs_count = enqueued_jobs.count + expected = { job: job, args: args, at: at, queue: queue }.compact + expected_args = prepare_args_for_assertion(expected) + yield + in_block_jobs = enqueued_jobs.drop(original_enqueued_jobs_count) + matching_job = in_block_jobs.find do |in_block_job| + deserialized_job = deserialize_args_for_assertion(in_block_job) + expected_args.all? { |key, value| value == deserialized_job[key] } + end + assert matching_job, "No enqueued job found with #{expected}" + instantiate_job(matching_job) + end + + # Asserts that the job passed in the block has been performed with the given arguments. + # + # def test_assert_performed_with + # assert_performed_with(job: MyJob, args: [1,2,3], queue: 'high') do + # MyJob.perform_later(1,2,3) + # end + # + # assert_performed_with(job: MyJob, at: Date.tomorrow.noon) do + # MyJob.set(wait_until: Date.tomorrow.noon).perform_later + # end + # end + def assert_performed_with(job: nil, args: nil, at: nil, queue: nil) + original_performed_jobs_count = performed_jobs.count + expected = { job: job, args: args, at: at, queue: queue }.compact + expected_args = prepare_args_for_assertion(expected) + perform_enqueued_jobs { yield } + in_block_jobs = performed_jobs.drop(original_performed_jobs_count) + matching_job = in_block_jobs.find do |in_block_job| + deserialized_job = deserialize_args_for_assertion(in_block_job) + expected_args.all? { |key, value| value == deserialized_job[key] } + end + assert matching_job, "No performed job found with #{expected}" + instantiate_job(matching_job) + end + + # Performs all enqueued jobs in the duration of the block. + # + # def test_perform_enqueued_jobs + # perform_enqueued_jobs do + # MyJob.perform_later(1, 2, 3) + # end + # assert_performed_jobs 1 + # end + # + # This method also supports filtering. If the +:only+ option is specified, + # then only the listed job(s) will be performed. + # + # def test_perform_enqueued_jobs_with_only + # perform_enqueued_jobs(only: MyJob) do + # MyJob.perform_later(1, 2, 3) # will be performed + # HelloJob.perform_later(1, 2, 3) # will not be performed + # end + # assert_performed_jobs 1 + # end + # + # Also if the +:except+ option is specified, + # then the job(s) except specific class will be performed. + # + # def test_perform_enqueued_jobs_with_except + # perform_enqueued_jobs(except: HelloJob) do + # MyJob.perform_later(1, 2, 3) # will be performed + # HelloJob.perform_later(1, 2, 3) # will not be performed + # end + # assert_performed_jobs 1 + # end + # + def perform_enqueued_jobs(only: nil, except: nil) + validate_option(only: only, except: except) + old_perform_enqueued_jobs = queue_adapter.perform_enqueued_jobs + old_perform_enqueued_at_jobs = queue_adapter.perform_enqueued_at_jobs + old_filter = queue_adapter.filter + old_reject = queue_adapter.reject + + begin + queue_adapter.perform_enqueued_jobs = true + queue_adapter.perform_enqueued_at_jobs = true + queue_adapter.filter = only + queue_adapter.reject = except + yield + ensure + queue_adapter.perform_enqueued_jobs = old_perform_enqueued_jobs + queue_adapter.perform_enqueued_at_jobs = old_perform_enqueued_at_jobs + queue_adapter.filter = old_filter + queue_adapter.reject = old_reject + end + end + + # Accesses the queue_adapter set by ActiveJob::Base. + # + # def test_assert_job_has_custom_queue_adapter_set + # assert_instance_of CustomQueueAdapter, HelloJob.queue_adapter + # end + def queue_adapter + ActiveJob::Base.queue_adapter + end + + private + def clear_enqueued_jobs + enqueued_jobs.clear + end + + def clear_performed_jobs + performed_jobs.clear + end + + def enqueued_jobs_size(only: nil, except: nil, queue: nil) + validate_option(only: only, except: except) + enqueued_jobs.count do |job| + job_class = job.fetch(:job) + if only + next false unless Array(only).include?(job_class) + elsif except + next false if Array(except).include?(job_class) + end + if queue + next false unless queue.to_s == job.fetch(:queue, job_class.queue_name) + end + true + end + end + + def prepare_args_for_assertion(args) + args.dup.tap do |arguments| + arguments[:at] = arguments[:at].to_f if arguments[:at] + end + end + + def deserialize_args_for_assertion(job) + job.dup.tap do |new_job| + new_job[:args] = ActiveJob::Arguments.deserialize(new_job[:args]) if new_job[:args] + end + end + + def instantiate_job(payload) + args = ActiveJob::Arguments.deserialize(payload[:args]) + job = payload[:job].new(*args) + job.scheduled_at = Time.at(payload[:at]) if payload.key?(:at) + job.queue_name = payload[:queue] + job + end + + def queue_adapter_changed_jobs + (ActiveJob::Base.descendants << ActiveJob::Base).select do |klass| + # only override explicitly set adapters, a quirk of `class_attribute` + klass.singleton_class.public_instance_methods(false).include?(:_queue_adapter) + end + end + + def validate_option(only: nil, except: nil) + raise ArgumentError, "Cannot specify both `:only` and `:except` options." if only && except + end + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/translation.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/translation.rb new file mode 100644 index 00000000..fb45c80d --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/translation.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module ActiveJob + module Translation #:nodoc: + extend ActiveSupport::Concern + + included do + around_perform do |job, block, _| + I18n.with_locale(job.locale, &block) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/version.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/version.rb new file mode 100644 index 00000000..eae7da4d --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/active_job/version.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require_relative "gem_version" + +module ActiveJob + # Returns the version of the currently loaded Active Job as a Gem::Version + def self.version + gem_version + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/rails/generators/job/job_generator.rb b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/rails/generators/job/job_generator.rb new file mode 100644 index 00000000..69b4fe7d --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/rails/generators/job/job_generator.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "rails/generators/named_base" + +module Rails # :nodoc: + module Generators # :nodoc: + class JobGenerator < Rails::Generators::NamedBase # :nodoc: + desc "This generator creates an active job file at app/jobs" + + class_option :queue, type: :string, default: "default", desc: "The queue name for the generated job" + + check_class_collision suffix: "Job" + + hook_for :test_framework + + def self.default_generator_root + __dir__ + end + + def create_job_file + template "job.rb", File.join("app/jobs", class_path, "#{file_name}_job.rb") + + in_root do + if behavior == :invoke && !File.exist?(application_job_file_name) + template "application_job.rb", application_job_file_name + end + end + end + + private + def application_job_file_name + @application_job_file_name ||= if mountable_engine? + "app/jobs/#{namespaced_path}/application_job.rb" + else + "app/jobs/application_job.rb" + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/rails/generators/job/templates/application_job.rb.tt b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/rails/generators/job/templates/application_job.rb.tt new file mode 100644 index 00000000..f93745a3 --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/rails/generators/job/templates/application_job.rb.tt @@ -0,0 +1,9 @@ +<% module_namespacing do -%> +class ApplicationJob < ActiveJob::Base + # Automatically retry jobs that encountered a deadlock + # retry_on ActiveRecord::Deadlocked + + # Most jobs are safe to ignore if the underlying records are no longer available + # discard_on ActiveJob::DeserializationError +end +<% end -%> diff --git a/path/ruby/2.6.0/gems/activejob-5.2.3/lib/rails/generators/job/templates/job.rb.tt b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/rails/generators/job/templates/job.rb.tt new file mode 100644 index 00000000..4ad2914a --- /dev/null +++ b/path/ruby/2.6.0/gems/activejob-5.2.3/lib/rails/generators/job/templates/job.rb.tt @@ -0,0 +1,9 @@ +<% module_namespacing do -%> +class <%= class_name %>Job < ApplicationJob + queue_as :<%= options[:queue] %> + + def perform(*args) + # Do something later + end +end +<% end -%> diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/CHANGELOG.md b/path/ruby/2.6.0/gems/activemodel-5.2.3/CHANGELOG.md new file mode 100644 index 00000000..4a5715ed --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/CHANGELOG.md @@ -0,0 +1,114 @@ +## Rails 5.2.3 (March 27, 2019) ## + +* Fix date value when casting a multiparameter date hash to not convert + from Gregorian date to Julian date. + + Before: + + Day.new({"day(1i)"=>"1", "day(2i)"=>"1", "day(3i)"=>"1"}) + => # + + After: + + Day.new({"day(1i)"=>"1", "day(2i)"=>"1", "day(3i)"=>"1"}) + => # + + Fixes #28521. + + *Sayan Chakraborty* + +* Fix numericality equality validation of `BigDecimal` and `Float` + by casting to `BigDecimal` on both ends of the validation. + + *Gannon McGibbon* + + +## Rails 5.2.2.1 (March 11, 2019) ## + +* No changes. + + +## Rails 5.2.2 (December 04, 2018) ## + +* Fix numericality validator to still use value before type cast except Active Record. + + Fixes #33651, #33686. + + *Ryuta Kamizono* + + +## Rails 5.2.1.1 (November 27, 2018) ## + +* No changes. + + +## Rails 5.2.1 (August 07, 2018) ## + +* No changes. + + +## Rails 5.2.0 (April 09, 2018) ## + +* Do not lose all multiple `:includes` with options in serialization. + + *Mike Mangino* + +* Models using the attributes API with a proc default can now be marshalled. + + Fixes #31216. + + *Sean Griffin* + +* Fix to working before/after validation callbacks on multiple contexts. + + *Yoshiyuki Hirano* + +* Execute `ConfirmationValidator` validation when `_confirmation`'s value is `false`. + + *bogdanvlviv* + +* Allow passing a Proc or Symbol to length validator options. + + *Matt Rohrer* + +* Add method `#merge!` for `ActiveModel::Errors`. + + *Jahfer Husain* + +* Fix regression in numericality validator when comparing Decimal and Float input + values with more scale than the schema. + + *Bradley Priest* + +* Fix methods `#keys`, `#values` in `ActiveModel::Errors`. + + Change `#keys` to only return the keys that don't have empty messages. + + Change `#values` to only return the not empty values. + + Example: + + # Before + person = Person.new + person.errors.keys # => [] + person.errors.values # => [] + person.errors.messages # => {} + person.errors[:name] # => [] + person.errors.messages # => {:name => []} + person.errors.keys # => [:name] + person.errors.values # => [[]] + + # After + person = Person.new + person.errors.keys # => [] + person.errors.values # => [] + person.errors.messages # => {} + person.errors[:name] # => [] + person.errors.messages # => {:name => []} + person.errors.keys # => [] + person.errors.values # => [] + + *bogdanvlviv* + + +Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/activemodel/CHANGELOG.md) for previous changes. diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/MIT-LICENSE b/path/ruby/2.6.0/gems/activemodel-5.2.3/MIT-LICENSE new file mode 100644 index 00000000..1cb3add0 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/MIT-LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2004-2018 David Heinemeier Hansson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/README.rdoc b/path/ruby/2.6.0/gems/activemodel-5.2.3/README.rdoc new file mode 100644 index 00000000..8d699946 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/README.rdoc @@ -0,0 +1,264 @@ += Active Model -- model interfaces for Rails + +Active Model provides a known set of interfaces for usage in model classes. +They allow for Action Pack helpers to interact with non-Active Record models, +for example. Active Model also helps with building custom ORMs for use outside of +the Rails framework. + +Prior to Rails 3.0, if a plugin or gem developer wanted to have an object +interact with Action Pack helpers, it was required to either copy chunks of +code from Rails, or monkey patch entire helpers to make them handle objects +that did not exactly conform to the Active Record interface. This would result +in code duplication and fragile applications that broke on upgrades. Active +Model solves this by defining an explicit API. You can read more about the +API in ActiveModel::Lint::Tests. + +Active Model provides a default module that implements the basic API required +to integrate with Action Pack out of the box: ActiveModel::Model. + + class Person + include ActiveModel::Model + + attr_accessor :name, :age + validates_presence_of :name + end + + person = Person.new(name: 'bob', age: '18') + person.name # => 'bob' + person.age # => '18' + person.valid? # => true + +It includes model name introspections, conversions, translations and +validations, resulting in a class suitable to be used with Action Pack. +See ActiveModel::Model for more examples. + +Active Model also provides the following functionality to have ORM-like +behavior out of the box: + +* Add attribute magic to objects + + class Person + include ActiveModel::AttributeMethods + + attribute_method_prefix 'clear_' + define_attribute_methods :name, :age + + attr_accessor :name, :age + + def clear_attribute(attr) + send("#{attr}=", nil) + end + end + + person = Person.new + person.clear_name + person.clear_age + + {Learn more}[link:classes/ActiveModel/AttributeMethods.html] + +* Callbacks for certain operations + + class Person + extend ActiveModel::Callbacks + define_model_callbacks :create + + def create + run_callbacks :create do + # Your create action methods here + end + end + end + + This generates +before_create+, +around_create+ and +after_create+ + class methods that wrap your create method. + + {Learn more}[link:classes/ActiveModel/Callbacks.html] + +* Tracking value changes + + class Person + include ActiveModel::Dirty + + define_attribute_methods :name + + def name + @name + end + + def name=(val) + name_will_change! unless val == @name + @name = val + end + + def save + # do persistence work + changes_applied + end + end + + person = Person.new + person.name # => nil + person.changed? # => false + person.name = 'bob' + person.changed? # => true + person.changed # => ['name'] + person.changes # => { 'name' => [nil, 'bob'] } + person.save + person.name = 'robert' + person.save + person.previous_changes # => {'name' => ['bob, 'robert']} + + {Learn more}[link:classes/ActiveModel/Dirty.html] + +* Adding +errors+ interface to objects + + Exposing error messages allows objects to interact with Action Pack + helpers seamlessly. + + class Person + + def initialize + @errors = ActiveModel::Errors.new(self) + end + + attr_accessor :name + attr_reader :errors + + def validate! + errors.add(:name, "cannot be nil") if name.nil? + end + + def self.human_attribute_name(attr, options = {}) + "Name" + end + end + + person = Person.new + person.name = nil + person.validate! + person.errors.full_messages + # => ["Name cannot be nil"] + + {Learn more}[link:classes/ActiveModel/Errors.html] + +* Model name introspection + + class NamedPerson + extend ActiveModel::Naming + end + + NamedPerson.model_name.name # => "NamedPerson" + NamedPerson.model_name.human # => "Named person" + + {Learn more}[link:classes/ActiveModel/Naming.html] + +* Making objects serializable + + ActiveModel::Serialization provides a standard interface for your object + to provide +to_json+ serialization. + + class SerialPerson + include ActiveModel::Serialization + + attr_accessor :name + + def attributes + {'name' => name} + end + end + + s = SerialPerson.new + s.serializable_hash # => {"name"=>nil} + + class SerialPerson + include ActiveModel::Serializers::JSON + end + + s = SerialPerson.new + s.to_json # => "{\"name\":null}" + + {Learn more}[link:classes/ActiveModel/Serialization.html] + +* Internationalization (i18n) support + + class Person + extend ActiveModel::Translation + end + + Person.human_attribute_name('my_attribute') + # => "My attribute" + + {Learn more}[link:classes/ActiveModel/Translation.html] + +* Validation support + + class Person + include ActiveModel::Validations + + attr_accessor :first_name, :last_name + + validates_each :first_name, :last_name do |record, attr, value| + record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z + end + end + + person = Person.new + person.first_name = 'zoolander' + person.valid? # => false + + {Learn more}[link:classes/ActiveModel/Validations.html] + +* Custom validators + + class HasNameValidator < ActiveModel::Validator + def validate(record) + record.errors.add(:name, "must exist") if record.name.blank? + end + end + + class ValidatorPerson + include ActiveModel::Validations + validates_with HasNameValidator + attr_accessor :name + end + + p = ValidatorPerson.new + p.valid? # => false + p.errors.full_messages # => ["Name must exist"] + p.name = "Bob" + p.valid? # => true + + {Learn more}[link:classes/ActiveModel/Validator.html] + + +== Download and installation + +The latest version of Active Model can be installed with RubyGems: + + $ gem install activemodel + +Source code can be downloaded as part of the Rails project on GitHub + +* https://github.com/rails/rails/tree/5-2-stable/activemodel + + +== License + +Active Model is released under the MIT license: + +* https://opensource.org/licenses/MIT + + +== Support + +API documentation is at: + +* http://api.rubyonrails.org + +Bug reports for the Ruby on Rails project can be filed here: + +* https://github.com/rails/rails/issues + +Feature requests should be discussed on the rails-core mailing list here: + +* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model.rb new file mode 100644 index 00000000..bc10d6b4 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +#-- +# Copyright (c) 2004-2018 David Heinemeier Hansson +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ + +require "active_support" +require "active_support/rails" +require "active_model/version" + +module ActiveModel + extend ActiveSupport::Autoload + + autoload :Attribute + autoload :Attributes + autoload :AttributeAssignment + autoload :AttributeMethods + autoload :BlockValidator, "active_model/validator" + autoload :Callbacks + autoload :Conversion + autoload :Dirty + autoload :EachValidator, "active_model/validator" + autoload :ForbiddenAttributesProtection + autoload :Lint + autoload :Model + autoload :Name, "active_model/naming" + autoload :Naming + autoload :SecurePassword + autoload :Serialization + autoload :Translation + autoload :Type + autoload :Validations + autoload :Validator + + eager_autoload do + autoload :Errors + autoload :RangeError, "active_model/errors" + autoload :StrictValidationFailed, "active_model/errors" + autoload :UnknownAttributeError, "active_model/errors" + end + + module Serializers + extend ActiveSupport::Autoload + + eager_autoload do + autoload :JSON + end + end + + def self.eager_load! + super + ActiveModel::Serializers.eager_load! + end +end + +ActiveSupport.on_load(:i18n) do + I18n.load_path << File.expand_path("active_model/locale/en.yml", __dir__) +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/attribute.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/attribute.rb new file mode 100644 index 00000000..6c35e90e --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/attribute.rb @@ -0,0 +1,248 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/duplicable" + +module ActiveModel + class Attribute # :nodoc: + class << self + def from_database(name, value, type) + FromDatabase.new(name, value, type) + end + + def from_user(name, value, type, original_attribute = nil) + FromUser.new(name, value, type, original_attribute) + end + + def with_cast_value(name, value, type) + WithCastValue.new(name, value, type) + end + + def null(name) + Null.new(name) + end + + def uninitialized(name, type) + Uninitialized.new(name, type) + end + end + + attr_reader :name, :value_before_type_cast, :type + + # This method should not be called directly. + # Use #from_database or #from_user + def initialize(name, value_before_type_cast, type, original_attribute = nil) + @name = name + @value_before_type_cast = value_before_type_cast + @type = type + @original_attribute = original_attribute + end + + def value + # `defined?` is cheaper than `||=` when we get back falsy values + @value = type_cast(value_before_type_cast) unless defined?(@value) + @value + end + + def original_value + if assigned? + original_attribute.original_value + else + type_cast(value_before_type_cast) + end + end + + def value_for_database + type.serialize(value) + end + + def changed? + changed_from_assignment? || changed_in_place? + end + + def changed_in_place? + has_been_read? && type.changed_in_place?(original_value_for_database, value) + end + + def forgetting_assignment + with_value_from_database(value_for_database) + end + + def with_value_from_user(value) + type.assert_valid_value(value) + self.class.from_user(name, value, type, original_attribute || self) + end + + def with_value_from_database(value) + self.class.from_database(name, value, type) + end + + def with_cast_value(value) + self.class.with_cast_value(name, value, type) + end + + def with_type(type) + if changed_in_place? + with_value_from_user(value).with_type(type) + else + self.class.new(name, value_before_type_cast, type, original_attribute) + end + end + + def type_cast(*) + raise NotImplementedError + end + + def initialized? + true + end + + def came_from_user? + false + end + + def has_been_read? + defined?(@value) + end + + def ==(other) + self.class == other.class && + name == other.name && + value_before_type_cast == other.value_before_type_cast && + type == other.type + end + alias eql? == + + def hash + [self.class, name, value_before_type_cast, type].hash + end + + def init_with(coder) + @name = coder["name"] + @value_before_type_cast = coder["value_before_type_cast"] + @type = coder["type"] + @original_attribute = coder["original_attribute"] + @value = coder["value"] if coder.map.key?("value") + end + + def encode_with(coder) + coder["name"] = name + coder["value_before_type_cast"] = value_before_type_cast unless value_before_type_cast.nil? + coder["type"] = type if type + coder["original_attribute"] = original_attribute if original_attribute + coder["value"] = value if defined?(@value) + end + + protected + + attr_reader :original_attribute + alias_method :assigned?, :original_attribute + + def original_value_for_database + if assigned? + original_attribute.original_value_for_database + else + _original_value_for_database + end + end + + private + def initialize_dup(other) + if defined?(@value) && @value.duplicable? + @value = @value.dup + end + end + + def changed_from_assignment? + assigned? && type.changed?(original_value, value, value_before_type_cast) + end + + def _original_value_for_database + type.serialize(original_value) + end + + class FromDatabase < Attribute # :nodoc: + def type_cast(value) + type.deserialize(value) + end + + def _original_value_for_database + value_before_type_cast + end + end + + class FromUser < Attribute # :nodoc: + def type_cast(value) + type.cast(value) + end + + def came_from_user? + !type.value_constructed_by_mass_assignment?(value_before_type_cast) + end + end + + class WithCastValue < Attribute # :nodoc: + def type_cast(value) + value + end + + def changed_in_place? + false + end + end + + class Null < Attribute # :nodoc: + def initialize(name) + super(name, nil, Type.default_value) + end + + def type_cast(*) + nil + end + + def with_type(type) + self.class.with_cast_value(name, nil, type) + end + + def with_value_from_database(value) + raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`" + end + alias_method :with_value_from_user, :with_value_from_database + alias_method :with_cast_value, :with_value_from_database + end + + class Uninitialized < Attribute # :nodoc: + UNINITIALIZED_ORIGINAL_VALUE = Object.new + + def initialize(name, type) + super(name, nil, type) + end + + def value + if block_given? + yield name + end + end + + def original_value + UNINITIALIZED_ORIGINAL_VALUE + end + + def value_for_database + end + + def initialized? + false + end + + def forgetting_assignment + dup + end + + def with_type(type) + self.class.new(name, type) + end + end + + private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/attribute/user_provided_default.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/attribute/user_provided_default.rb new file mode 100644 index 00000000..a5dc6188 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/attribute/user_provided_default.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require "active_model/attribute" + +module ActiveModel + class Attribute # :nodoc: + class UserProvidedDefault < FromUser # :nodoc: + def initialize(name, value, type, database_default) + @user_provided_value = value + super(name, value, type, database_default) + end + + def value_before_type_cast + if user_provided_value.is_a?(Proc) + @memoized_value_before_type_cast ||= user_provided_value.call + else + @user_provided_value + end + end + + def with_type(type) + self.class.new(name, user_provided_value, type, original_attribute) + end + + def marshal_dump + result = [ + name, + value_before_type_cast, + type, + original_attribute, + ] + result << value if defined?(@value) + result + end + + def marshal_load(values) + name, user_provided_value, type, original_attribute, value = values + @name = name + @user_provided_value = user_provided_value + @type = type + @original_attribute = original_attribute + if values.length == 5 + @value = value + end + end + + protected + + attr_reader :user_provided_value + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/attribute_assignment.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/attribute_assignment.rb new file mode 100644 index 00000000..217bf1ac --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/attribute_assignment.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/keys" + +module ActiveModel + module AttributeAssignment + include ActiveModel::ForbiddenAttributesProtection + + # Allows you to set all the attributes by passing in a hash of attributes with + # keys matching the attribute names. + # + # If the passed hash responds to permitted? method and the return value + # of this method is +false+ an ActiveModel::ForbiddenAttributesError + # exception is raised. + # + # class Cat + # include ActiveModel::AttributeAssignment + # attr_accessor :name, :status + # end + # + # cat = Cat.new + # cat.assign_attributes(name: "Gorby", status: "yawning") + # cat.name # => 'Gorby' + # cat.status # => 'yawning' + # cat.assign_attributes(status: "sleeping") + # cat.name # => 'Gorby' + # cat.status # => 'sleeping' + def assign_attributes(new_attributes) + if !new_attributes.respond_to?(:stringify_keys) + raise ArgumentError, "When assigning attributes, you must pass a hash as an argument." + end + return if new_attributes.empty? + + attributes = new_attributes.stringify_keys + _assign_attributes(sanitize_for_mass_assignment(attributes)) + end + + alias attributes= assign_attributes + + private + + def _assign_attributes(attributes) + attributes.each do |k, v| + _assign_attribute(k, v) + end + end + + def _assign_attribute(k, v) + setter = :"#{k}=" + if respond_to?(setter) + public_send(setter, v) + else + raise UnknownAttributeError.new(self, k) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/attribute_methods.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/attribute_methods.rb new file mode 100644 index 00000000..888a431e --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/attribute_methods.rb @@ -0,0 +1,478 @@ +# frozen_string_literal: true + +require "concurrent/map" + +module ActiveModel + # Raised when an attribute is not defined. + # + # class User < ActiveRecord::Base + # has_many :pets + # end + # + # user = User.first + # user.pets.select(:id).first.user_id + # # => ActiveModel::MissingAttributeError: missing attribute: user_id + class MissingAttributeError < NoMethodError + end + + # == Active \Model \Attribute \Methods + # + # Provides a way to add prefixes and suffixes to your methods as + # well as handling the creation of ActiveRecord::Base-like + # class methods such as +table_name+. + # + # The requirements to implement ActiveModel::AttributeMethods are to: + # + # * include ActiveModel::AttributeMethods in your class. + # * Call each of its methods you want to add, such as +attribute_method_suffix+ + # or +attribute_method_prefix+. + # * Call +define_attribute_methods+ after the other methods are called. + # * Define the various generic +_attribute+ methods that you have declared. + # * Define an +attributes+ method which returns a hash with each + # attribute name in your model as hash key and the attribute value as hash value. + # Hash keys must be strings. + # + # A minimal implementation could be: + # + # class Person + # include ActiveModel::AttributeMethods + # + # attribute_method_affix prefix: 'reset_', suffix: '_to_default!' + # attribute_method_suffix '_contrived?' + # attribute_method_prefix 'clear_' + # define_attribute_methods :name + # + # attr_accessor :name + # + # def attributes + # { 'name' => @name } + # end + # + # private + # + # def attribute_contrived?(attr) + # true + # end + # + # def clear_attribute(attr) + # send("#{attr}=", nil) + # end + # + # def reset_attribute_to_default!(attr) + # send("#{attr}=", 'Default Name') + # end + # end + module AttributeMethods + extend ActiveSupport::Concern + + NAME_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/ + CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/ + + included do + class_attribute :attribute_aliases, instance_writer: false, default: {} + class_attribute :attribute_method_matchers, instance_writer: false, default: [ ClassMethods::AttributeMethodMatcher.new ] + end + + module ClassMethods + # Declares a method available for all attributes with the given prefix. + # Uses +method_missing+ and respond_to? to rewrite the method. + # + # #{prefix}#{attr}(*args, &block) + # + # to + # + # #{prefix}attribute(#{attr}, *args, &block) + # + # An instance method #{prefix}attribute must exist and accept + # at least the +attr+ argument. + # + # class Person + # include ActiveModel::AttributeMethods + # + # attr_accessor :name + # attribute_method_prefix 'clear_' + # define_attribute_methods :name + # + # private + # + # def clear_attribute(attr) + # send("#{attr}=", nil) + # end + # end + # + # person = Person.new + # person.name = 'Bob' + # person.name # => "Bob" + # person.clear_name + # person.name # => nil + def attribute_method_prefix(*prefixes) + self.attribute_method_matchers += prefixes.map! { |prefix| AttributeMethodMatcher.new prefix: prefix } + undefine_attribute_methods + end + + # Declares a method available for all attributes with the given suffix. + # Uses +method_missing+ and respond_to? to rewrite the method. + # + # #{attr}#{suffix}(*args, &block) + # + # to + # + # attribute#{suffix}(#{attr}, *args, &block) + # + # An attribute#{suffix} instance method must exist and accept at + # least the +attr+ argument. + # + # class Person + # include ActiveModel::AttributeMethods + # + # attr_accessor :name + # attribute_method_suffix '_short?' + # define_attribute_methods :name + # + # private + # + # def attribute_short?(attr) + # send(attr).length < 5 + # end + # end + # + # person = Person.new + # person.name = 'Bob' + # person.name # => "Bob" + # person.name_short? # => true + def attribute_method_suffix(*suffixes) + self.attribute_method_matchers += suffixes.map! { |suffix| AttributeMethodMatcher.new suffix: suffix } + undefine_attribute_methods + end + + # Declares a method available for all attributes with the given prefix + # and suffix. Uses +method_missing+ and respond_to? to rewrite + # the method. + # + # #{prefix}#{attr}#{suffix}(*args, &block) + # + # to + # + # #{prefix}attribute#{suffix}(#{attr}, *args, &block) + # + # An #{prefix}attribute#{suffix} instance method must exist and + # accept at least the +attr+ argument. + # + # class Person + # include ActiveModel::AttributeMethods + # + # attr_accessor :name + # attribute_method_affix prefix: 'reset_', suffix: '_to_default!' + # define_attribute_methods :name + # + # private + # + # def reset_attribute_to_default!(attr) + # send("#{attr}=", 'Default Name') + # end + # end + # + # person = Person.new + # person.name # => 'Gem' + # person.reset_name_to_default! + # person.name # => 'Default Name' + def attribute_method_affix(*affixes) + self.attribute_method_matchers += affixes.map! { |affix| AttributeMethodMatcher.new prefix: affix[:prefix], suffix: affix[:suffix] } + undefine_attribute_methods + end + + # Allows you to make aliases for attributes. + # + # class Person + # include ActiveModel::AttributeMethods + # + # attr_accessor :name + # attribute_method_suffix '_short?' + # define_attribute_methods :name + # + # alias_attribute :nickname, :name + # + # private + # + # def attribute_short?(attr) + # send(attr).length < 5 + # end + # end + # + # person = Person.new + # person.name = 'Bob' + # person.name # => "Bob" + # person.nickname # => "Bob" + # person.name_short? # => true + # person.nickname_short? # => true + def alias_attribute(new_name, old_name) + self.attribute_aliases = attribute_aliases.merge(new_name.to_s => old_name.to_s) + attribute_method_matchers.each do |matcher| + matcher_new = matcher.method_name(new_name).to_s + matcher_old = matcher.method_name(old_name).to_s + define_proxy_call false, self, matcher_new, matcher_old + end + end + + # Is +new_name+ an alias? + def attribute_alias?(new_name) + attribute_aliases.key? new_name.to_s + end + + # Returns the original name for the alias +name+ + def attribute_alias(name) + attribute_aliases[name.to_s] + end + + # Declares the attributes that should be prefixed and suffixed by + # ActiveModel::AttributeMethods. + # + # To use, pass attribute names (as strings or symbols). Be sure to declare + # +define_attribute_methods+ after you define any prefix, suffix or affix + # methods, or they will not hook in. + # + # class Person + # include ActiveModel::AttributeMethods + # + # attr_accessor :name, :age, :address + # attribute_method_prefix 'clear_' + # + # # Call to define_attribute_methods must appear after the + # # attribute_method_prefix, attribute_method_suffix or + # # attribute_method_affix declarations. + # define_attribute_methods :name, :age, :address + # + # private + # + # def clear_attribute(attr) + # send("#{attr}=", nil) + # end + # end + def define_attribute_methods(*attr_names) + attr_names.flatten.each { |attr_name| define_attribute_method(attr_name) } + end + + # Declares an attribute that should be prefixed and suffixed by + # ActiveModel::AttributeMethods. + # + # To use, pass an attribute name (as string or symbol). Be sure to declare + # +define_attribute_method+ after you define any prefix, suffix or affix + # method, or they will not hook in. + # + # class Person + # include ActiveModel::AttributeMethods + # + # attr_accessor :name + # attribute_method_suffix '_short?' + # + # # Call to define_attribute_method must appear after the + # # attribute_method_prefix, attribute_method_suffix or + # # attribute_method_affix declarations. + # define_attribute_method :name + # + # private + # + # def attribute_short?(attr) + # send(attr).length < 5 + # end + # end + # + # person = Person.new + # person.name = 'Bob' + # person.name # => "Bob" + # person.name_short? # => true + def define_attribute_method(attr_name) + attribute_method_matchers.each do |matcher| + method_name = matcher.method_name(attr_name) + + unless instance_method_already_implemented?(method_name) + generate_method = "define_method_#{matcher.method_missing_target}" + + if respond_to?(generate_method, true) + send(generate_method, attr_name.to_s) + else + define_proxy_call true, generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s + end + end + end + attribute_method_matchers_cache.clear + end + + # Removes all the previously dynamically defined methods from the class. + # + # class Person + # include ActiveModel::AttributeMethods + # + # attr_accessor :name + # attribute_method_suffix '_short?' + # define_attribute_method :name + # + # private + # + # def attribute_short?(attr) + # send(attr).length < 5 + # end + # end + # + # person = Person.new + # person.name = 'Bob' + # person.name_short? # => true + # + # Person.undefine_attribute_methods + # + # person.name_short? # => NoMethodError + def undefine_attribute_methods + generated_attribute_methods.module_eval do + instance_methods.each { |m| undef_method(m) } + end + attribute_method_matchers_cache.clear + end + + private + def generated_attribute_methods + @generated_attribute_methods ||= Module.new.tap { |mod| include mod } + end + + def instance_method_already_implemented?(method_name) + generated_attribute_methods.method_defined?(method_name) + end + + # The methods +method_missing+ and +respond_to?+ of this module are + # invoked often in a typical rails, both of which invoke the method + # +matched_attribute_method+. The latter method iterates through an + # array doing regular expression matches, which results in a lot of + # object creations. Most of the time it returns a +nil+ match. As the + # match result is always the same given a +method_name+, this cache is + # used to alleviate the GC, which ultimately also speeds up the app + # significantly (in our case our test suite finishes 10% faster with + # this cache). + def attribute_method_matchers_cache + @attribute_method_matchers_cache ||= Concurrent::Map.new(initial_capacity: 4) + end + + def attribute_method_matchers_matching(method_name) + attribute_method_matchers_cache.compute_if_absent(method_name) do + # Must try to match prefixes/suffixes first, or else the matcher with no prefix/suffix + # will match every time. + matchers = attribute_method_matchers.partition(&:plain?).reverse.flatten(1) + matchers.map { |method| method.match(method_name) }.compact + end + end + + # Define a method `name` in `mod` that dispatches to `send` + # using the given `extra` args. This falls back on `define_method` + # and `send` if the given names cannot be compiled. + def define_proxy_call(include_private, mod, name, send, *extra) + defn = if NAME_COMPILABLE_REGEXP.match?(name) + "def #{name}(*args)" + else + "define_method(:'#{name}') do |*args|" + end + + extra = (extra.map!(&:inspect) << "*args").join(", ".freeze) + + target = if CALL_COMPILABLE_REGEXP.match?(send) + "#{"self." unless include_private}#{send}(#{extra})" + else + "send(:'#{send}', #{extra})" + end + + mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1 + #{defn} + #{target} + end + RUBY + end + + class AttributeMethodMatcher #:nodoc: + attr_reader :prefix, :suffix, :method_missing_target + + AttributeMethodMatch = Struct.new(:target, :attr_name, :method_name) + + def initialize(options = {}) + @prefix, @suffix = options.fetch(:prefix, ""), options.fetch(:suffix, "") + @regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/ + @method_missing_target = "#{@prefix}attribute#{@suffix}" + @method_name = "#{prefix}%s#{suffix}" + end + + def match(method_name) + if @regex =~ method_name + AttributeMethodMatch.new(method_missing_target, $1, method_name) + end + end + + def method_name(attr_name) + @method_name % attr_name + end + + def plain? + prefix.empty? && suffix.empty? + end + end + end + + # Allows access to the object attributes, which are held in the hash + # returned by attributes, as though they were first-class + # methods. So a +Person+ class with a +name+ attribute can for example use + # Person#name and Person#name= and never directly use + # the attributes hash -- except for multiple assignments with + # ActiveRecord::Base#attributes=. + # + # It's also possible to instantiate related objects, so a Client + # class belonging to the +clients+ table with a +master_id+ foreign key + # can instantiate master through Client#master. + def method_missing(method, *args, &block) + if respond_to_without_attributes?(method, true) + super + else + match = matched_attribute_method(method.to_s) + match ? attribute_missing(match, *args, &block) : super + end + end + + # +attribute_missing+ is like +method_missing+, but for attributes. When + # +method_missing+ is called we check to see if there is a matching + # attribute method. If so, we tell +attribute_missing+ to dispatch the + # attribute. This method can be overloaded to customize the behavior. + def attribute_missing(match, *args, &block) + __send__(match.target, match.attr_name, *args, &block) + end + + # A +Person+ instance with a +name+ attribute can ask + # person.respond_to?(:name), person.respond_to?(:name=), + # and person.respond_to?(:name?) which will all return +true+. + alias :respond_to_without_attributes? :respond_to? + def respond_to?(method, include_private_methods = false) + if super + true + elsif !include_private_methods && super(method, true) + # If we're here then we haven't found among non-private methods + # but found among all methods. Which means that the given method is private. + false + else + !matched_attribute_method(method.to_s).nil? + end + end + + private + def attribute_method?(attr_name) + respond_to_without_attributes?(:attributes) && attributes.include?(attr_name) + end + + # Returns a struct representing the matching attribute method. + # The struct's attributes are prefix, base and suffix. + def matched_attribute_method(method_name) + matches = self.class.send(:attribute_method_matchers_matching, method_name) + matches.detect { |match| attribute_method?(match.attr_name) } + end + + def missing_attribute(attr_name, stack) + raise ActiveModel::MissingAttributeError, "missing attribute: #{attr_name}", stack + end + + def _read_attribute(attr) + __send__(attr) + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/attribute_mutation_tracker.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/attribute_mutation_tracker.rb new file mode 100644 index 00000000..6d1af4e7 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/attribute_mutation_tracker.rb @@ -0,0 +1,124 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/indifferent_access" + +module ActiveModel + class AttributeMutationTracker # :nodoc: + OPTION_NOT_GIVEN = Object.new + + def initialize(attributes) + @attributes = attributes + @forced_changes = Set.new + end + + def changed_attribute_names + attr_names.select { |attr_name| changed?(attr_name) } + end + + def changed_values + attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result| + if changed?(attr_name) + result[attr_name] = attributes[attr_name].original_value + end + end + end + + def changes + attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result| + change = change_to_attribute(attr_name) + if change + result.merge!(attr_name => change) + end + end + end + + def change_to_attribute(attr_name) + attr_name = attr_name.to_s + if changed?(attr_name) + [attributes[attr_name].original_value, attributes.fetch_value(attr_name)] + end + end + + def any_changes? + attr_names.any? { |attr| changed?(attr) } + end + + def changed?(attr_name, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN) + attr_name = attr_name.to_s + forced_changes.include?(attr_name) || + attributes[attr_name].changed? && + (OPTION_NOT_GIVEN == from || attributes[attr_name].original_value == from) && + (OPTION_NOT_GIVEN == to || attributes[attr_name].value == to) + end + + def changed_in_place?(attr_name) + attributes[attr_name.to_s].changed_in_place? + end + + def forget_change(attr_name) + attr_name = attr_name.to_s + attributes[attr_name] = attributes[attr_name].forgetting_assignment + forced_changes.delete(attr_name) + end + + def original_value(attr_name) + attributes[attr_name.to_s].original_value + end + + def force_change(attr_name) + forced_changes << attr_name.to_s + end + + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected + + attr_reader :attributes, :forced_changes + + private + + def attr_names + attributes.keys + end + end + + class NullMutationTracker # :nodoc: + include Singleton + + def changed_attribute_names(*) + [] + end + + def changed_values(*) + {} + end + + def changes(*) + {} + end + + def change_to_attribute(attr_name) + end + + def any_changes?(*) + false + end + + def changed?(*) + false + end + + def changed_in_place?(*) + false + end + + def forget_change(*) + end + + def original_value(*) + end + + def force_change(*) + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/attribute_set.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/attribute_set.rb new file mode 100644 index 00000000..a890ee39 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/attribute_set.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/deep_dup" +require "active_model/attribute_set/builder" +require "active_model/attribute_set/yaml_encoder" + +module ActiveModel + class AttributeSet # :nodoc: + delegate :each_value, :fetch, :except, to: :attributes + + def initialize(attributes) + @attributes = attributes + end + + def [](name) + attributes[name] || Attribute.null(name) + end + + def []=(name, value) + attributes[name] = value + end + + def values_before_type_cast + attributes.transform_values(&:value_before_type_cast) + end + + def to_hash + initialized_attributes.transform_values(&:value) + end + alias_method :to_h, :to_hash + + def key?(name) + attributes.key?(name) && self[name].initialized? + end + + def keys + attributes.each_key.select { |name| self[name].initialized? } + end + + if defined?(JRUBY_VERSION) + # This form is significantly faster on JRuby, and this is one of our biggest hotspots. + # https://github.com/jruby/jruby/pull/2562 + def fetch_value(name, &block) + self[name].value(&block) + end + else + def fetch_value(name) + self[name].value { |n| yield n if block_given? } + end + end + + def write_from_database(name, value) + attributes[name] = self[name].with_value_from_database(value) + end + + def write_from_user(name, value) + attributes[name] = self[name].with_value_from_user(value) + end + + def write_cast_value(name, value) + attributes[name] = self[name].with_cast_value(value) + end + + def freeze + @attributes.freeze + super + end + + def deep_dup + self.class.allocate.tap do |copy| + copy.instance_variable_set(:@attributes, attributes.deep_dup) + end + end + + def initialize_dup(_) + @attributes = attributes.dup + super + end + + def initialize_clone(_) + @attributes = attributes.clone + super + end + + def reset(key) + if key?(key) + write_from_database(key, nil) + end + end + + def accessed + attributes.select { |_, attr| attr.has_been_read? }.keys + end + + def map(&block) + new_attributes = attributes.transform_values(&block) + AttributeSet.new(new_attributes) + end + + def ==(other) + attributes == other.attributes + end + + protected + + attr_reader :attributes + + private + + def initialized_attributes + attributes.select { |_, attr| attr.initialized? } + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/attribute_set/builder.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/attribute_set/builder.rb new file mode 100644 index 00000000..bf2d06b4 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/attribute_set/builder.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +require "active_model/attribute" + +module ActiveModel + class AttributeSet # :nodoc: + class Builder # :nodoc: + attr_reader :types, :default_attributes + + def initialize(types, default_attributes = {}) + @types = types + @default_attributes = default_attributes + end + + def build_from_database(values = {}, additional_types = {}) + attributes = LazyAttributeHash.new(types, values, additional_types, default_attributes) + AttributeSet.new(attributes) + end + end + end + + class LazyAttributeHash # :nodoc: + delegate :transform_values, :each_key, :each_value, :fetch, :except, to: :materialize + + def initialize(types, values, additional_types, default_attributes, delegate_hash = {}) + @types = types + @values = values + @additional_types = additional_types + @materialized = false + @delegate_hash = delegate_hash + @default_attributes = default_attributes + end + + def key?(key) + delegate_hash.key?(key) || values.key?(key) || types.key?(key) + end + + def [](key) + delegate_hash[key] || assign_default_value(key) + end + + def []=(key, value) + if frozen? + raise RuntimeError, "Can't modify frozen hash" + end + delegate_hash[key] = value + end + + def deep_dup + dup.tap do |copy| + copy.instance_variable_set(:@delegate_hash, delegate_hash.transform_values(&:dup)) + end + end + + def initialize_dup(_) + @delegate_hash = Hash[delegate_hash] + super + end + + def select + keys = types.keys | values.keys | delegate_hash.keys + keys.each_with_object({}) do |key, hash| + attribute = self[key] + if yield(key, attribute) + hash[key] = attribute + end + end + end + + def ==(other) + if other.is_a?(LazyAttributeHash) + materialize == other.materialize + else + materialize == other + end + end + + def marshal_dump + [@types, @values, @additional_types, @default_attributes, @delegate_hash] + end + + def marshal_load(values) + if values.is_a?(Hash) + empty_hash = {}.freeze + initialize(empty_hash, empty_hash, empty_hash, empty_hash, values) + @materialized = true + else + initialize(*values) + end + end + + protected + + attr_reader :types, :values, :additional_types, :delegate_hash, :default_attributes + + def materialize + unless @materialized + values.each_key { |key| self[key] } + types.each_key { |key| self[key] } + unless frozen? + @materialized = true + end + end + delegate_hash + end + + private + + def assign_default_value(name) + type = additional_types.fetch(name, types[name]) + value_present = true + value = values.fetch(name) { value_present = false } + + if value_present + delegate_hash[name] = Attribute.from_database(name, value, type) + elsif types.key?(name) + attr = default_attributes[name] + if attr + delegate_hash[name] = attr.dup + else + delegate_hash[name] = Attribute.uninitialized(name, type) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/attribute_set/yaml_encoder.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/attribute_set/yaml_encoder.rb new file mode 100644 index 00000000..4ea945b9 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/attribute_set/yaml_encoder.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module ActiveModel + class AttributeSet + # Attempts to do more intelligent YAML dumping of an + # ActiveModel::AttributeSet to reduce the size of the resulting string + class YAMLEncoder # :nodoc: + def initialize(default_types) + @default_types = default_types + end + + def encode(attribute_set, coder) + coder["concise_attributes"] = attribute_set.each_value.map do |attr| + if attr.type.equal?(default_types[attr.name]) + attr.with_type(nil) + else + attr + end + end + end + + def decode(coder) + if coder["attributes"] + coder["attributes"] + else + attributes_hash = Hash[coder["concise_attributes"].map do |attr| + if attr.type.nil? + attr = attr.with_type(default_types[attr.name]) + end + [attr.name, attr] + end] + AttributeSet.new(attributes_hash) + end + end + + protected + + attr_reader :default_types + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/attributes.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/attributes.rb new file mode 100644 index 00000000..4083bf82 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/attributes.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +require "active_model/attribute_set" +require "active_model/attribute/user_provided_default" + +module ActiveModel + module Attributes #:nodoc: + extend ActiveSupport::Concern + include ActiveModel::AttributeMethods + + included do + attribute_method_suffix "=" + class_attribute :attribute_types, :_default_attributes, instance_accessor: false + self.attribute_types = Hash.new(Type.default_value) + self._default_attributes = AttributeSet.new({}) + end + + module ClassMethods + def attribute(name, type = Type::Value.new, **options) + name = name.to_s + if type.is_a?(Symbol) + type = ActiveModel::Type.lookup(type, **options.except(:default)) + end + self.attribute_types = attribute_types.merge(name => type) + define_default_attribute(name, options.fetch(:default, NO_DEFAULT_PROVIDED), type) + define_attribute_method(name) + end + + private + + def define_method_attribute=(name) + safe_name = name.unpack("h*".freeze).first + ActiveModel::AttributeMethods::AttrNames.set_name_cache safe_name, name + + generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 + def __temp__#{safe_name}=(value) + name = ::ActiveModel::AttributeMethods::AttrNames::ATTR_#{safe_name} + write_attribute(name, value) + end + alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= + undef_method :__temp__#{safe_name}= + STR + end + + NO_DEFAULT_PROVIDED = Object.new # :nodoc: + private_constant :NO_DEFAULT_PROVIDED + + def define_default_attribute(name, value, type) + self._default_attributes = _default_attributes.deep_dup + if value == NO_DEFAULT_PROVIDED + default_attribute = _default_attributes[name].with_type(type) + else + default_attribute = Attribute::UserProvidedDefault.new( + name, + value, + type, + _default_attributes.fetch(name.to_s) { nil }, + ) + end + _default_attributes[name] = default_attribute + end + end + + def initialize(*) + @attributes = self.class._default_attributes.deep_dup + super + end + + def attributes + @attributes.to_hash + end + + private + + def write_attribute(attr_name, value) + name = if self.class.attribute_alias?(attr_name) + self.class.attribute_alias(attr_name).to_s + else + attr_name.to_s + end + + @attributes.write_from_user(name, value) + value + end + + def attribute(attr_name) + name = if self.class.attribute_alias?(attr_name) + self.class.attribute_alias(attr_name).to_s + else + attr_name.to_s + end + @attributes.fetch_value(name) + end + + # Handle *= for method_missing. + def attribute=(attribute_name, value) + write_attribute(attribute_name, value) + end + end + + module AttributeMethods #:nodoc: + AttrNames = Module.new { + def self.set_name_cache(name, value) + const_name = "ATTR_#{name}" + unless const_defined? const_name + const_set const_name, value.dup.freeze + end + end + } + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/callbacks.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/callbacks.rb new file mode 100644 index 00000000..8fa9680c --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/callbacks.rb @@ -0,0 +1,153 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array/extract_options" + +module ActiveModel + # == Active \Model \Callbacks + # + # Provides an interface for any class to have Active Record like callbacks. + # + # Like the Active Record methods, the callback chain is aborted as soon as + # one of the methods throws +:abort+. + # + # First, extend ActiveModel::Callbacks from the class you are creating: + # + # class MyModel + # extend ActiveModel::Callbacks + # end + # + # Then define a list of methods that you want callbacks attached to: + # + # define_model_callbacks :create, :update + # + # This will provide all three standard callbacks (before, around and after) + # for both the :create and :update methods. To implement, + # you need to wrap the methods you want callbacks on in a block so that the + # callbacks get a chance to fire: + # + # def create + # run_callbacks :create do + # # Your create action methods here + # end + # end + # + # Then in your class, you can use the +before_create+, +after_create+ and + # +around_create+ methods, just as you would in an Active Record model. + # + # before_create :action_before_create + # + # def action_before_create + # # Your code here + # end + # + # When defining an around callback remember to yield to the block, otherwise + # it won't be executed: + # + # around_create :log_status + # + # def log_status + # puts 'going to call the block...' + # yield + # puts 'block successfully called.' + # end + # + # You can choose to have only specific callbacks by passing a hash to the + # +define_model_callbacks+ method. + # + # define_model_callbacks :create, only: [:after, :before] + # + # Would only create the +after_create+ and +before_create+ callback methods in + # your class. + # + # NOTE: Calling the same callback multiple times will overwrite previous callback definitions. + # + module Callbacks + def self.extended(base) #:nodoc: + base.class_eval do + include ActiveSupport::Callbacks + end + end + + # define_model_callbacks accepts the same options +define_callbacks+ does, + # in case you want to overwrite a default. Besides that, it also accepts an + # :only option, where you can choose if you want all types (before, + # around or after) or just some. + # + # define_model_callbacks :initializer, only: :after + # + # Note, the only: hash will apply to all callbacks defined + # on that method call. To get around this you can call the define_model_callbacks + # method as many times as you need. + # + # define_model_callbacks :create, only: :after + # define_model_callbacks :update, only: :before + # define_model_callbacks :destroy, only: :around + # + # Would create +after_create+, +before_update+ and +around_destroy+ methods + # only. + # + # You can pass in a class to before_, after_ and around_, + # in which case the callback will call that class's _ method + # passing the object that the callback is being called on. + # + # class MyModel + # extend ActiveModel::Callbacks + # define_model_callbacks :create + # + # before_create AnotherClass + # end + # + # class AnotherClass + # def self.before_create( obj ) + # # obj is the MyModel instance that the callback is being called on + # end + # end + # + # NOTE: +method_name+ passed to define_model_callbacks must not end with + # !, ? or =. + def define_model_callbacks(*callbacks) + options = callbacks.extract_options! + options = { + skip_after_callbacks_if_terminated: true, + scope: [:kind, :name], + only: [:before, :around, :after] + }.merge!(options) + + types = Array(options.delete(:only)) + + callbacks.each do |callback| + define_callbacks(callback, options) + + types.each do |type| + send("_define_#{type}_model_callback", self, callback) + end + end + end + + private + + def _define_before_model_callback(klass, callback) + klass.define_singleton_method("before_#{callback}") do |*args, &block| + set_callback(:"#{callback}", :before, *args, &block) + end + end + + def _define_around_model_callback(klass, callback) + klass.define_singleton_method("around_#{callback}") do |*args, &block| + set_callback(:"#{callback}", :around, *args, &block) + end + end + + def _define_after_model_callback(klass, callback) + klass.define_singleton_method("after_#{callback}") do |*args, &block| + options = args.extract_options! + options[:prepend] = true + conditional = ActiveSupport::Callbacks::Conditionals::Value.new { |v| + v != false + } + options[:if] = Array(options[:if]) << conditional + set_callback(:"#{callback}", :after, *(args << options), &block) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/conversion.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/conversion.rb new file mode 100644 index 00000000..cdc12828 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/conversion.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +module ActiveModel + # == Active \Model \Conversion + # + # Handles default conversions: to_model, to_key, to_param, and to_partial_path. + # + # Let's take for example this non-persisted object. + # + # class ContactMessage + # include ActiveModel::Conversion + # + # # ContactMessage are never persisted in the DB + # def persisted? + # false + # end + # end + # + # cm = ContactMessage.new + # cm.to_model == cm # => true + # cm.to_key # => nil + # cm.to_param # => nil + # cm.to_partial_path # => "contact_messages/contact_message" + module Conversion + extend ActiveSupport::Concern + + # If your object is already designed to implement all of the \Active \Model + # you can use the default :to_model implementation, which simply + # returns +self+. + # + # class Person + # include ActiveModel::Conversion + # end + # + # person = Person.new + # person.to_model == person # => true + # + # If your model does not act like an \Active \Model object, then you should + # define :to_model yourself returning a proxy object that wraps + # your object with \Active \Model compliant methods. + def to_model + self + end + + # Returns an Array of all key attributes if any of the attributes is set, whether or not + # the object is persisted. Returns +nil+ if there are no key attributes. + # + # class Person + # include ActiveModel::Conversion + # attr_accessor :id + # + # def initialize(id) + # @id = id + # end + # end + # + # person = Person.new(1) + # person.to_key # => [1] + def to_key + key = respond_to?(:id) && id + key ? [key] : nil + end + + # Returns a +string+ representing the object's key suitable for use in URLs, + # or +nil+ if persisted? is +false+. + # + # class Person + # include ActiveModel::Conversion + # attr_accessor :id + # + # def initialize(id) + # @id = id + # end + # + # def persisted? + # true + # end + # end + # + # person = Person.new(1) + # person.to_param # => "1" + def to_param + (persisted? && key = to_key) ? key.join("-") : nil + end + + # Returns a +string+ identifying the path associated with the object. + # ActionPack uses this to find a suitable partial to represent the object. + # + # class Person + # include ActiveModel::Conversion + # end + # + # person = Person.new + # person.to_partial_path # => "people/person" + def to_partial_path + self.class._to_partial_path + end + + module ClassMethods #:nodoc: + # Provide a class level cache for #to_partial_path. This is an + # internal method and should not be accessed directly. + def _to_partial_path #:nodoc: + @_to_partial_path ||= begin + element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(name)) + collection = ActiveSupport::Inflector.tableize(name) + "#{collection}/#{element}".freeze + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/dirty.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/dirty.rb new file mode 100644 index 00000000..17bb79d0 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/dirty.rb @@ -0,0 +1,343 @@ +# frozen_string_literal: true + +require "active_support/hash_with_indifferent_access" +require "active_support/core_ext/object/duplicable" +require "active_model/attribute_mutation_tracker" + +module ActiveModel + # == Active \Model \Dirty + # + # Provides a way to track changes in your object in the same way as + # Active Record does. + # + # The requirements for implementing ActiveModel::Dirty are: + # + # * include ActiveModel::Dirty in your object. + # * Call define_attribute_methods passing each method you want to + # track. + # * Call [attr_name]_will_change! before each change to the tracked + # attribute. + # * Call changes_applied after the changes are persisted. + # * Call clear_changes_information when you want to reset the changes + # information. + # * Call restore_attributes when you want to restore previous data. + # + # A minimal implementation could be: + # + # class Person + # include ActiveModel::Dirty + # + # define_attribute_methods :name + # + # def initialize + # @name = nil + # end + # + # def name + # @name + # end + # + # def name=(val) + # name_will_change! unless val == @name + # @name = val + # end + # + # def save + # # do persistence work + # + # changes_applied + # end + # + # def reload! + # # get the values from the persistence layer + # + # clear_changes_information + # end + # + # def rollback! + # restore_attributes + # end + # end + # + # A newly instantiated +Person+ object is unchanged: + # + # person = Person.new + # person.changed? # => false + # + # Change the name: + # + # person.name = 'Bob' + # person.changed? # => true + # person.name_changed? # => true + # person.name_changed?(from: nil, to: "Bob") # => true + # person.name_was # => nil + # person.name_change # => [nil, "Bob"] + # person.name = 'Bill' + # person.name_change # => [nil, "Bill"] + # + # Save the changes: + # + # person.save + # person.changed? # => false + # person.name_changed? # => false + # + # Reset the changes: + # + # person.previous_changes # => {"name" => [nil, "Bill"]} + # person.name_previously_changed? # => true + # person.name_previous_change # => [nil, "Bill"] + # person.reload! + # person.previous_changes # => {} + # + # Rollback the changes: + # + # person.name = "Uncle Bob" + # person.rollback! + # person.name # => "Bill" + # person.name_changed? # => false + # + # Assigning the same value leaves the attribute unchanged: + # + # person.name = 'Bill' + # person.name_changed? # => false + # person.name_change # => nil + # + # Which attributes have changed? + # + # person.name = 'Bob' + # person.changed # => ["name"] + # person.changes # => {"name" => ["Bill", "Bob"]} + # + # If an attribute is modified in-place then make use of + # [attribute_name]_will_change! to mark that the attribute is changing. + # Otherwise \Active \Model can't track changes to in-place attributes. Note + # that Active Record can detect in-place modifications automatically. You do + # not need to call [attribute_name]_will_change! on Active Record models. + # + # person.name_will_change! + # person.name_change # => ["Bill", "Bill"] + # person.name << 'y' + # person.name_change # => ["Bill", "Billy"] + module Dirty + extend ActiveSupport::Concern + include ActiveModel::AttributeMethods + + OPTION_NOT_GIVEN = Object.new # :nodoc: + private_constant :OPTION_NOT_GIVEN + + included do + attribute_method_suffix "_changed?", "_change", "_will_change!", "_was" + attribute_method_suffix "_previously_changed?", "_previous_change" + attribute_method_affix prefix: "restore_", suffix: "!" + end + + def initialize_dup(other) # :nodoc: + super + if self.class.respond_to?(:_default_attributes) + @attributes = self.class._default_attributes.map do |attr| + attr.with_value_from_user(@attributes.fetch_value(attr.name)) + end + end + @mutations_from_database = nil + end + + # Clears dirty data and moves +changes+ to +previously_changed+ and + # +mutations_from_database+ to +mutations_before_last_save+ respectively. + def changes_applied + unless defined?(@attributes) + @previously_changed = changes + end + @mutations_before_last_save = mutations_from_database + @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new + forget_attribute_assignments + @mutations_from_database = nil + end + + # Returns +true+ if any of the attributes have unsaved changes, +false+ otherwise. + # + # person.changed? # => false + # person.name = 'bob' + # person.changed? # => true + def changed? + changed_attributes.present? + end + + # Returns an array with the name of the attributes with unsaved changes. + # + # person.changed # => [] + # person.name = 'bob' + # person.changed # => ["name"] + def changed + changed_attributes.keys + end + + # Handles *_changed? for +method_missing+. + def attribute_changed?(attr, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN) # :nodoc: + !!changes_include?(attr) && + (to == OPTION_NOT_GIVEN || to == _read_attribute(attr)) && + (from == OPTION_NOT_GIVEN || from == changed_attributes[attr]) + end + + # Handles *_was for +method_missing+. + def attribute_was(attr) # :nodoc: + attribute_changed?(attr) ? changed_attributes[attr] : _read_attribute(attr) + end + + # Handles *_previously_changed? for +method_missing+. + def attribute_previously_changed?(attr) #:nodoc: + previous_changes_include?(attr) + end + + # Restore all previous data of the provided attributes. + def restore_attributes(attributes = changed) + attributes.each { |attr| restore_attribute! attr } + end + + # Clears all dirty data: current changes and previous changes. + def clear_changes_information + @previously_changed = ActiveSupport::HashWithIndifferentAccess.new + @mutations_before_last_save = nil + @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new + forget_attribute_assignments + @mutations_from_database = nil + end + + def clear_attribute_changes(attr_names) + attributes_changed_by_setter.except!(*attr_names) + attr_names.each do |attr_name| + clear_attribute_change(attr_name) + end + end + + # Returns a hash of the attributes with unsaved changes indicating their original + # values like attr => original value. + # + # person.name # => "bob" + # person.name = 'robert' + # person.changed_attributes # => {"name" => "bob"} + def changed_attributes + # This should only be set by methods which will call changed_attributes + # multiple times when it is known that the computed value cannot change. + if defined?(@cached_changed_attributes) + @cached_changed_attributes + else + attributes_changed_by_setter.reverse_merge(mutations_from_database.changed_values).freeze + end + end + + # Returns a hash of changed attributes indicating their original + # and new values like attr => [original value, new value]. + # + # person.changes # => {} + # person.name = 'bob' + # person.changes # => { "name" => ["bill", "bob"] } + def changes + cache_changed_attributes do + ActiveSupport::HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }] + end + end + + # Returns a hash of attributes that were changed before the model was saved. + # + # person.name # => "bob" + # person.name = 'robert' + # person.save + # person.previous_changes # => {"name" => ["bob", "robert"]} + def previous_changes + @previously_changed ||= ActiveSupport::HashWithIndifferentAccess.new + @previously_changed.merge(mutations_before_last_save.changes) + end + + def attribute_changed_in_place?(attr_name) # :nodoc: + mutations_from_database.changed_in_place?(attr_name) + end + + private + def clear_attribute_change(attr_name) + mutations_from_database.forget_change(attr_name) + end + + def mutations_from_database + unless defined?(@mutations_from_database) + @mutations_from_database = nil + end + @mutations_from_database ||= if defined?(@attributes) + ActiveModel::AttributeMutationTracker.new(@attributes) + else + NullMutationTracker.instance + end + end + + def forget_attribute_assignments + @attributes = @attributes.map(&:forgetting_assignment) if defined?(@attributes) + end + + def mutations_before_last_save + @mutations_before_last_save ||= ActiveModel::NullMutationTracker.instance + end + + def cache_changed_attributes + @cached_changed_attributes = changed_attributes + yield + ensure + clear_changed_attributes_cache + end + + def clear_changed_attributes_cache + remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes) + end + + # Returns +true+ if attr_name is changed, +false+ otherwise. + def changes_include?(attr_name) + attributes_changed_by_setter.include?(attr_name) || mutations_from_database.changed?(attr_name) + end + alias attribute_changed_by_setter? changes_include? + + # Returns +true+ if attr_name were changed before the model was saved, + # +false+ otherwise. + def previous_changes_include?(attr_name) + previous_changes.include?(attr_name) + end + + # Handles *_change for +method_missing+. + def attribute_change(attr) + [changed_attributes[attr], _read_attribute(attr)] if attribute_changed?(attr) + end + + # Handles *_previous_change for +method_missing+. + def attribute_previous_change(attr) + previous_changes[attr] if attribute_previously_changed?(attr) + end + + # Handles *_will_change! for +method_missing+. + def attribute_will_change!(attr) + unless attribute_changed?(attr) + begin + value = _read_attribute(attr) + value = value.duplicable? ? value.clone : value + rescue TypeError, NoMethodError + end + + set_attribute_was(attr, value) + end + mutations_from_database.force_change(attr) + end + + # Handles restore_*! for +method_missing+. + def restore_attribute!(attr) + if attribute_changed?(attr) + __send__("#{attr}=", changed_attributes[attr]) + clear_attribute_changes([attr]) + end + end + + def attributes_changed_by_setter + @attributes_changed_by_setter ||= ActiveSupport::HashWithIndifferentAccess.new + end + + # Force an attribute to have a particular "before" value + def set_attribute_was(attr, old_value) + attributes_changed_by_setter[attr] = old_value + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/errors.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/errors.rb new file mode 100644 index 00000000..e7bf7698 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/errors.rb @@ -0,0 +1,517 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array/conversions" +require "active_support/core_ext/string/inflections" +require "active_support/core_ext/object/deep_dup" +require "active_support/core_ext/string/filters" + +module ActiveModel + # == Active \Model \Errors + # + # Provides a modified +Hash+ that you can include in your object + # for handling error messages and interacting with Action View helpers. + # + # A minimal implementation could be: + # + # class Person + # # Required dependency for ActiveModel::Errors + # extend ActiveModel::Naming + # + # def initialize + # @errors = ActiveModel::Errors.new(self) + # end + # + # attr_accessor :name + # attr_reader :errors + # + # def validate! + # errors.add(:name, :blank, message: "cannot be nil") if name.nil? + # end + # + # # The following methods are needed to be minimally implemented + # + # def read_attribute_for_validation(attr) + # send(attr) + # end + # + # def self.human_attribute_name(attr, options = {}) + # attr + # end + # + # def self.lookup_ancestors + # [self] + # end + # end + # + # The last three methods are required in your object for +Errors+ to be + # able to generate error messages correctly and also handle multiple + # languages. Of course, if you extend your object with ActiveModel::Translation + # you will not need to implement the last two. Likewise, using + # ActiveModel::Validations will handle the validation related methods + # for you. + # + # The above allows you to do: + # + # person = Person.new + # person.validate! # => ["cannot be nil"] + # person.errors.full_messages # => ["name cannot be nil"] + # # etc.. + class Errors + include Enumerable + + CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict] + MESSAGE_OPTIONS = [:message] + + attr_reader :messages, :details + + # Pass in the instance of the object that is using the errors object. + # + # class Person + # def initialize + # @errors = ActiveModel::Errors.new(self) + # end + # end + def initialize(base) + @base = base + @messages = apply_default_array({}) + @details = apply_default_array({}) + end + + def initialize_dup(other) # :nodoc: + @messages = other.messages.dup + @details = other.details.deep_dup + super + end + + # Copies the errors from other. + # + # other - The ActiveModel::Errors instance. + # + # Examples + # + # person.errors.copy!(other) + def copy!(other) # :nodoc: + @messages = other.messages.dup + @details = other.details.dup + end + + # Merges the errors from other. + # + # other - The ActiveModel::Errors instance. + # + # Examples + # + # person.errors.merge!(other) + def merge!(other) + @messages.merge!(other.messages) { |_, ary1, ary2| ary1 + ary2 } + @details.merge!(other.details) { |_, ary1, ary2| ary1 + ary2 } + end + + # Clear the error messages. + # + # person.errors.full_messages # => ["name cannot be nil"] + # person.errors.clear + # person.errors.full_messages # => [] + def clear + messages.clear + details.clear + end + + # Returns +true+ if the error messages include an error for the given key + # +attribute+, +false+ otherwise. + # + # person.errors.messages # => {:name=>["cannot be nil"]} + # person.errors.include?(:name) # => true + # person.errors.include?(:age) # => false + def include?(attribute) + attribute = attribute.to_sym + messages.key?(attribute) && messages[attribute].present? + end + alias :has_key? :include? + alias :key? :include? + + # Delete messages for +key+. Returns the deleted messages. + # + # person.errors[:name] # => ["cannot be nil"] + # person.errors.delete(:name) # => ["cannot be nil"] + # person.errors[:name] # => [] + def delete(key) + attribute = key.to_sym + details.delete(attribute) + messages.delete(attribute) + end + + # When passed a symbol or a name of a method, returns an array of errors + # for the method. + # + # person.errors[:name] # => ["cannot be nil"] + # person.errors['name'] # => ["cannot be nil"] + def [](attribute) + messages[attribute.to_sym] + end + + # Iterates through each error key, value pair in the error messages hash. + # Yields the attribute and the error for that attribute. If the attribute + # has more than one error message, yields once for each error message. + # + # person.errors.add(:name, :blank, message: "can't be blank") + # person.errors.each do |attribute, error| + # # Will yield :name and "can't be blank" + # end + # + # person.errors.add(:name, :not_specified, message: "must be specified") + # person.errors.each do |attribute, error| + # # Will yield :name and "can't be blank" + # # then yield :name and "must be specified" + # end + def each + messages.each_key do |attribute| + messages[attribute].each { |error| yield attribute, error } + end + end + + # Returns the number of error messages. + # + # person.errors.add(:name, :blank, message: "can't be blank") + # person.errors.size # => 1 + # person.errors.add(:name, :not_specified, message: "must be specified") + # person.errors.size # => 2 + def size + values.flatten.size + end + alias :count :size + + # Returns all message values. + # + # person.errors.messages # => {:name=>["cannot be nil", "must be specified"]} + # person.errors.values # => [["cannot be nil", "must be specified"]] + def values + messages.select do |key, value| + !value.empty? + end.values + end + + # Returns all message keys. + # + # person.errors.messages # => {:name=>["cannot be nil", "must be specified"]} + # person.errors.keys # => [:name] + def keys + messages.select do |key, value| + !value.empty? + end.keys + end + + # Returns +true+ if no errors are found, +false+ otherwise. + # If the error message is a string it can be empty. + # + # person.errors.full_messages # => ["name cannot be nil"] + # person.errors.empty? # => false + def empty? + size.zero? + end + alias :blank? :empty? + + # Returns an xml formatted representation of the Errors hash. + # + # person.errors.add(:name, :blank, message: "can't be blank") + # person.errors.add(:name, :not_specified, message: "must be specified") + # person.errors.to_xml + # # => + # # + # # + # # name can't be blank + # # name must be specified + # # + def to_xml(options = {}) + to_a.to_xml({ root: "errors", skip_types: true }.merge!(options)) + end + + # Returns a Hash that can be used as the JSON representation for this + # object. You can pass the :full_messages option. This determines + # if the json object should contain full messages or not (false by default). + # + # person.errors.as_json # => {:name=>["cannot be nil"]} + # person.errors.as_json(full_messages: true) # => {:name=>["name cannot be nil"]} + def as_json(options = nil) + to_hash(options && options[:full_messages]) + end + + # Returns a Hash of attributes with their error messages. If +full_messages+ + # is +true+, it will contain full messages (see +full_message+). + # + # person.errors.to_hash # => {:name=>["cannot be nil"]} + # person.errors.to_hash(true) # => {:name=>["name cannot be nil"]} + def to_hash(full_messages = false) + if full_messages + messages.each_with_object({}) do |(attribute, array), messages| + messages[attribute] = array.map { |message| full_message(attribute, message) } + end + else + without_default_proc(messages) + end + end + + # Adds +message+ to the error messages and used validator type to +details+ on +attribute+. + # More than one error can be added to the same +attribute+. + # If no +message+ is supplied, :invalid is assumed. + # + # person.errors.add(:name) + # # => ["is invalid"] + # person.errors.add(:name, :not_implemented, message: "must be implemented") + # # => ["is invalid", "must be implemented"] + # + # person.errors.messages + # # => {:name=>["is invalid", "must be implemented"]} + # + # person.errors.details + # # => {:name=>[{error: :not_implemented}, {error: :invalid}]} + # + # If +message+ is a symbol, it will be translated using the appropriate + # scope (see +generate_message+). + # + # If +message+ is a proc, it will be called, allowing for things like + # Time.now to be used within an error. + # + # If the :strict option is set to +true+, it will raise + # ActiveModel::StrictValidationFailed instead of adding the error. + # :strict option can also be set to any other exception. + # + # person.errors.add(:name, :invalid, strict: true) + # # => ActiveModel::StrictValidationFailed: Name is invalid + # person.errors.add(:name, :invalid, strict: NameIsInvalid) + # # => NameIsInvalid: Name is invalid + # + # person.errors.messages # => {} + # + # +attribute+ should be set to :base if the error is not + # directly associated with a single attribute. + # + # person.errors.add(:base, :name_or_email_blank, + # message: "either name or email must be present") + # person.errors.messages + # # => {:base=>["either name or email must be present"]} + # person.errors.details + # # => {:base=>[{error: :name_or_email_blank}]} + def add(attribute, message = :invalid, options = {}) + message = message.call if message.respond_to?(:call) + detail = normalize_detail(message, options) + message = normalize_message(attribute, message, options) + if exception = options[:strict] + exception = ActiveModel::StrictValidationFailed if exception == true + raise exception, full_message(attribute, message) + end + + details[attribute.to_sym] << detail + messages[attribute.to_sym] << message + end + + # Returns +true+ if an error on the attribute with the given message is + # present, or +false+ otherwise. +message+ is treated the same as for +add+. + # + # person.errors.add :name, :blank + # person.errors.added? :name, :blank # => true + # person.errors.added? :name, "can't be blank" # => true + # + # If the error message requires an option, then it returns +true+ with + # the correct option, or +false+ with an incorrect or missing option. + # + # person.errors.add :name, :too_long, { count: 25 } + # person.errors.added? :name, :too_long, count: 25 # => true + # person.errors.added? :name, "is too long (maximum is 25 characters)" # => true + # person.errors.added? :name, :too_long, count: 24 # => false + # person.errors.added? :name, :too_long # => false + # person.errors.added? :name, "is too long" # => false + def added?(attribute, message = :invalid, options = {}) + message = message.call if message.respond_to?(:call) + + if message.is_a? Symbol + details[attribute.to_sym].include? normalize_detail(message, options) + else + self[attribute].include? message + end + end + + # Returns all the full error messages in an array. + # + # class Person + # validates_presence_of :name, :address, :email + # validates_length_of :name, in: 5..30 + # end + # + # person = Person.create(address: '123 First St.') + # person.errors.full_messages + # # => ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"] + def full_messages + map { |attribute, message| full_message(attribute, message) } + end + alias :to_a :full_messages + + # Returns all the full error messages for a given attribute in an array. + # + # class Person + # validates_presence_of :name, :email + # validates_length_of :name, in: 5..30 + # end + # + # person = Person.create() + # person.errors.full_messages_for(:name) + # # => ["Name is too short (minimum is 5 characters)", "Name can't be blank"] + def full_messages_for(attribute) + attribute = attribute.to_sym + messages[attribute].map { |message| full_message(attribute, message) } + end + + # Returns a full message for a given attribute. + # + # person.errors.full_message(:name, 'is invalid') # => "Name is invalid" + def full_message(attribute, message) + return message if attribute == :base + attr_name = attribute.to_s.tr(".", "_").humanize + attr_name = @base.class.human_attribute_name(attribute, default: attr_name) + I18n.t(:"errors.format", + default: "%{attribute} %{message}", + attribute: attr_name, + message: message) + end + + # Translates an error message in its default scope + # (activemodel.errors.messages). + # + # Error messages are first looked up in activemodel.errors.models.MODEL.attributes.ATTRIBUTE.MESSAGE, + # if it's not there, it's looked up in activemodel.errors.models.MODEL.MESSAGE and if + # that is not there also, it returns the translation of the default message + # (e.g. activemodel.errors.messages.MESSAGE). The translated model + # name, translated attribute name and the value are available for + # interpolation. + # + # When using inheritance in your models, it will check all the inherited + # models too, but only if the model itself hasn't been found. Say you have + # class Admin < User; end and you wanted the translation for + # the :blank error message for the title attribute, + # it looks for these translations: + # + # * activemodel.errors.models.admin.attributes.title.blank + # * activemodel.errors.models.admin.blank + # * activemodel.errors.models.user.attributes.title.blank + # * activemodel.errors.models.user.blank + # * any default you provided through the +options+ hash (in the activemodel.errors scope) + # * activemodel.errors.messages.blank + # * errors.attributes.title.blank + # * errors.messages.blank + def generate_message(attribute, type = :invalid, options = {}) + type = options.delete(:message) if options[:message].is_a?(Symbol) + + if @base.class.respond_to?(:i18n_scope) + i18n_scope = @base.class.i18n_scope.to_s + defaults = @base.class.lookup_ancestors.flat_map do |klass| + [ :"#{i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}", + :"#{i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ] + end + defaults << :"#{i18n_scope}.errors.messages.#{type}" + else + defaults = [] + end + + defaults << :"errors.attributes.#{attribute}.#{type}" + defaults << :"errors.messages.#{type}" + + key = defaults.shift + defaults = options.delete(:message) if options[:message] + value = (attribute != :base ? @base.send(:read_attribute_for_validation, attribute) : nil) + + options = { + default: defaults, + model: @base.model_name.human, + attribute: @base.class.human_attribute_name(attribute), + value: value, + object: @base + }.merge!(options) + + I18n.translate(key, options) + end + + def marshal_dump # :nodoc: + [@base, without_default_proc(@messages), without_default_proc(@details)] + end + + def marshal_load(array) # :nodoc: + @base, @messages, @details = array + apply_default_array(@messages) + apply_default_array(@details) + end + + def init_with(coder) # :nodoc: + coder.map.each { |k, v| instance_variable_set(:"@#{k}", v) } + @details ||= {} + apply_default_array(@messages) + apply_default_array(@details) + end + + private + def normalize_message(attribute, message, options) + case message + when Symbol + generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS)) + else + message + end + end + + def normalize_detail(message, options) + { error: message }.merge(options.except(*CALLBACKS_OPTIONS + MESSAGE_OPTIONS)) + end + + def without_default_proc(hash) + hash.dup.tap do |new_h| + new_h.default_proc = nil + end + end + + def apply_default_array(hash) + hash.default_proc = proc { |h, key| h[key] = [] } + hash + end + end + + # Raised when a validation cannot be corrected by end users and are considered + # exceptional. + # + # class Person + # include ActiveModel::Validations + # + # attr_accessor :name + # + # validates_presence_of :name, strict: true + # end + # + # person = Person.new + # person.name = nil + # person.valid? + # # => ActiveModel::StrictValidationFailed: Name can't be blank + class StrictValidationFailed < StandardError + end + + # Raised when attribute values are out of range. + class RangeError < ::RangeError + end + + # Raised when unknown attributes are supplied via mass assignment. + # + # class Person + # include ActiveModel::AttributeAssignment + # include ActiveModel::Validations + # end + # + # person = Person.new + # person.assign_attributes(name: 'Gorby') + # # => ActiveModel::UnknownAttributeError: unknown attribute 'name' for Person. + class UnknownAttributeError < NoMethodError + attr_reader :record, :attribute + + def initialize(record, attribute) + @record = record + @attribute = attribute + super("unknown attribute '#{attribute}' for #{@record.class}.") + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/forbidden_attributes_protection.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/forbidden_attributes_protection.rb new file mode 100644 index 00000000..4b37f80c --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/forbidden_attributes_protection.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module ActiveModel + # Raised when forbidden attributes are used for mass assignment. + # + # class Person < ActiveRecord::Base + # end + # + # params = ActionController::Parameters.new(name: 'Bob') + # Person.new(params) + # # => ActiveModel::ForbiddenAttributesError + # + # params.permit! + # Person.new(params) + # # => # + class ForbiddenAttributesError < StandardError + end + + module ForbiddenAttributesProtection # :nodoc: + private + def sanitize_for_mass_assignment(attributes) + if attributes.respond_to?(:permitted?) + raise ActiveModel::ForbiddenAttributesError if !attributes.permitted? + attributes.to_h + else + attributes + end + end + alias :sanitize_forbidden_attributes :sanitize_for_mass_assignment + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/gem_version.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/gem_version.rb new file mode 100644 index 00000000..5d40a2c7 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/gem_version.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActiveModel + # Returns the version of the currently loaded \Active \Model as a Gem::Version + def self.gem_version + Gem::Version.new VERSION::STRING + end + + module VERSION + MAJOR = 5 + MINOR = 2 + TINY = 3 + PRE = nil + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/lint.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/lint.rb new file mode 100644 index 00000000..b7ceabb5 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/lint.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +module ActiveModel + module Lint + # == Active \Model \Lint \Tests + # + # You can test whether an object is compliant with the Active \Model API by + # including ActiveModel::Lint::Tests in your TestCase. It will + # include tests that tell you whether your object is fully compliant, + # or if not, which aspects of the API are not implemented. + # + # Note an object is not required to implement all APIs in order to work + # with Action Pack. This module only intends to provide guidance in case + # you want all features out of the box. + # + # These tests do not attempt to determine the semantic correctness of the + # returned values. For instance, you could implement valid? to + # always return +true+, and the tests would pass. It is up to you to ensure + # that the values are semantically meaningful. + # + # Objects you pass in are expected to return a compliant object from a call + # to to_model. It is perfectly fine for to_model to return + # +self+. + module Tests + # Passes if the object's model responds to to_key and if calling + # this method returns +nil+ when the object is not persisted. + # Fails otherwise. + # + # to_key returns an Enumerable of all (primary) key attributes + # of the model, and is used to a generate unique DOM id for the object. + def test_to_key + assert_respond_to model, :to_key + def model.persisted?() false end + assert model.to_key.nil?, "to_key should return nil when `persisted?` returns false" + end + + # Passes if the object's model responds to to_param and if + # calling this method returns +nil+ when the object is not persisted. + # Fails otherwise. + # + # to_param is used to represent the object's key in URLs. + # Implementers can decide to either raise an exception or provide a + # default in case the record uses a composite primary key. There are no + # tests for this behavior in lint because it doesn't make sense to force + # any of the possible implementation strategies on the implementer. + def test_to_param + assert_respond_to model, :to_param + def model.to_key() [1] end + def model.persisted?() false end + assert model.to_param.nil?, "to_param should return nil when `persisted?` returns false" + end + + # Passes if the object's model responds to to_partial_path and if + # calling this method returns a string. Fails otherwise. + # + # to_partial_path is used for looking up partials. For example, + # a BlogPost model might return "blog_posts/blog_post". + def test_to_partial_path + assert_respond_to model, :to_partial_path + assert_kind_of String, model.to_partial_path + end + + # Passes if the object's model responds to persisted? and if + # calling this method returns either +true+ or +false+. Fails otherwise. + # + # persisted? is used when calculating the URL for an object. + # If the object is not persisted, a form for that object, for instance, + # will route to the create action. If it is persisted, a form for the + # object will route to the update action. + def test_persisted? + assert_respond_to model, :persisted? + assert_boolean model.persisted?, "persisted?" + end + + # Passes if the object's model responds to model_name both as + # an instance method and as a class method, and if calling this method + # returns a string with some convenience methods: :human, + # :singular and :plural. + # + # Check ActiveModel::Naming for more information. + def test_model_naming + assert_respond_to model.class, :model_name + model_name = model.class.model_name + assert_respond_to model_name, :to_str + assert_respond_to model_name.human, :to_str + assert_respond_to model_name.singular, :to_str + assert_respond_to model_name.plural, :to_str + + assert_respond_to model, :model_name + assert_equal model.model_name, model.class.model_name + end + + # Passes if the object's model responds to errors and if calling + # [](attribute) on the result of this method returns an array. + # Fails otherwise. + # + # errors[attribute] is used to retrieve the errors of a model + # for a given attribute. If errors are present, the method should return + # an array of strings that are the errors for the attribute in question. + # If localization is used, the strings should be localized for the current + # locale. If no error is present, the method should return an empty array. + def test_errors_aref + assert_respond_to model, :errors + assert model.errors[:hello].is_a?(Array), "errors#[] should return an Array" + end + + private + def model + assert_respond_to @model, :to_model + @model.to_model + end + + def assert_boolean(result, name) + assert result == true || result == false, "#{name} should be a boolean" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/locale/en.yml b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/locale/en.yml new file mode 100644 index 00000000..061e35dd --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/locale/en.yml @@ -0,0 +1,36 @@ +en: + errors: + # The default format to use in full error messages. + format: "%{attribute} %{message}" + + # The values :model, :attribute and :value are always available for interpolation + # The value :count is available when applicable. Can be used for pluralization. + messages: + model_invalid: "Validation failed: %{errors}" + inclusion: "is not included in the list" + exclusion: "is reserved" + invalid: "is invalid" + confirmation: "doesn't match %{attribute}" + accepted: "must be accepted" + empty: "can't be empty" + blank: "can't be blank" + present: "must be blank" + too_long: + one: "is too long (maximum is 1 character)" + other: "is too long (maximum is %{count} characters)" + too_short: + one: "is too short (minimum is 1 character)" + other: "is too short (minimum is %{count} characters)" + wrong_length: + one: "is the wrong length (should be 1 character)" + other: "is the wrong length (should be %{count} characters)" + not_a_number: "is not a number" + not_an_integer: "must be an integer" + greater_than: "must be greater than %{count}" + greater_than_or_equal_to: "must be greater than or equal to %{count}" + equal_to: "must be equal to %{count}" + less_than: "must be less than %{count}" + less_than_or_equal_to: "must be less than or equal to %{count}" + other_than: "must be other than %{count}" + odd: "must be odd" + even: "must be even" diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/model.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/model.rb new file mode 100644 index 00000000..fc52cd4f --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/model.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +module ActiveModel + # == Active \Model \Basic \Model + # + # Includes the required interface for an object to interact with + # Action Pack and Action View, using different Active Model modules. + # It includes model name introspections, conversions, translations and + # validations. Besides that, it allows you to initialize the object with a + # hash of attributes, pretty much like Active Record does. + # + # A minimal implementation could be: + # + # class Person + # include ActiveModel::Model + # attr_accessor :name, :age + # end + # + # person = Person.new(name: 'bob', age: '18') + # person.name # => "bob" + # person.age # => "18" + # + # Note that, by default, ActiveModel::Model implements persisted? + # to return +false+, which is the most common case. You may want to override + # it in your class to simulate a different scenario: + # + # class Person + # include ActiveModel::Model + # attr_accessor :id, :name + # + # def persisted? + # self.id == 1 + # end + # end + # + # person = Person.new(id: 1, name: 'bob') + # person.persisted? # => true + # + # Also, if for some reason you need to run code on initialize, make + # sure you call +super+ if you want the attributes hash initialization to + # happen. + # + # class Person + # include ActiveModel::Model + # attr_accessor :id, :name, :omg + # + # def initialize(attributes={}) + # super + # @omg ||= true + # end + # end + # + # person = Person.new(id: 1, name: 'bob') + # person.omg # => true + # + # For more detailed information on other functionalities available, please + # refer to the specific modules included in ActiveModel::Model + # (see below). + module Model + extend ActiveSupport::Concern + include ActiveModel::AttributeAssignment + include ActiveModel::Validations + include ActiveModel::Conversion + + included do + extend ActiveModel::Naming + extend ActiveModel::Translation + end + + # Initializes a new model with the given +params+. + # + # class Person + # include ActiveModel::Model + # attr_accessor :name, :age + # end + # + # person = Person.new(name: 'bob', age: '18') + # person.name # => "bob" + # person.age # => "18" + def initialize(attributes = {}) + assign_attributes(attributes) if attributes + + super() + end + + # Indicates if the model is persisted. Default is +false+. + # + # class Person + # include ActiveModel::Model + # attr_accessor :id, :name + # end + # + # person = Person.new(id: 1, name: 'bob') + # person.persisted? # => false + def persisted? + false + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/naming.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/naming.rb new file mode 100644 index 00000000..dfccd03c --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/naming.rb @@ -0,0 +1,318 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/except" +require "active_support/core_ext/module/introspection" +require "active_support/core_ext/module/redefine_method" + +module ActiveModel + class Name + include Comparable + + attr_reader :singular, :plural, :element, :collection, + :singular_route_key, :route_key, :param_key, :i18n_key, + :name + + alias_method :cache_key, :collection + + ## + # :method: == + # + # :call-seq: + # ==(other) + # + # Equivalent to String#==. Returns +true+ if the class name and + # +other+ are equal, otherwise +false+. + # + # class BlogPost + # extend ActiveModel::Naming + # end + # + # BlogPost.model_name == 'BlogPost' # => true + # BlogPost.model_name == 'Blog Post' # => false + + ## + # :method: === + # + # :call-seq: + # ===(other) + # + # Equivalent to #==. + # + # class BlogPost + # extend ActiveModel::Naming + # end + # + # BlogPost.model_name === 'BlogPost' # => true + # BlogPost.model_name === 'Blog Post' # => false + + ## + # :method: <=> + # + # :call-seq: + # <=>(other) + # + # Equivalent to String#<=>. + # + # class BlogPost + # extend ActiveModel::Naming + # end + # + # BlogPost.model_name <=> 'BlogPost' # => 0 + # BlogPost.model_name <=> 'Blog' # => 1 + # BlogPost.model_name <=> 'BlogPosts' # => -1 + + ## + # :method: =~ + # + # :call-seq: + # =~(regexp) + # + # Equivalent to String#=~. Match the class name against the given + # regexp. Returns the position where the match starts or +nil+ if there is + # no match. + # + # class BlogPost + # extend ActiveModel::Naming + # end + # + # BlogPost.model_name =~ /Post/ # => 4 + # BlogPost.model_name =~ /\d/ # => nil + + ## + # :method: !~ + # + # :call-seq: + # !~(regexp) + # + # Equivalent to String#!~. Match the class name against the given + # regexp. Returns +true+ if there is no match, otherwise +false+. + # + # class BlogPost + # extend ActiveModel::Naming + # end + # + # BlogPost.model_name !~ /Post/ # => false + # BlogPost.model_name !~ /\d/ # => true + + ## + # :method: eql? + # + # :call-seq: + # eql?(other) + # + # Equivalent to String#eql?. Returns +true+ if the class name and + # +other+ have the same length and content, otherwise +false+. + # + # class BlogPost + # extend ActiveModel::Naming + # end + # + # BlogPost.model_name.eql?('BlogPost') # => true + # BlogPost.model_name.eql?('Blog Post') # => false + + ## + # :method: to_s + # + # :call-seq: + # to_s() + # + # Returns the class name. + # + # class BlogPost + # extend ActiveModel::Naming + # end + # + # BlogPost.model_name.to_s # => "BlogPost" + + ## + # :method: to_str + # + # :call-seq: + # to_str() + # + # Equivalent to +to_s+. + delegate :==, :===, :<=>, :=~, :"!~", :eql?, :to_s, + :to_str, :as_json, to: :name + + # Returns a new ActiveModel::Name instance. By default, the +namespace+ + # and +name+ option will take the namespace and name of the given class + # respectively. + # + # module Foo + # class Bar + # end + # end + # + # ActiveModel::Name.new(Foo::Bar).to_s + # # => "Foo::Bar" + def initialize(klass, namespace = nil, name = nil) + @name = name || klass.name + + raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if @name.blank? + + @unnamespaced = @name.sub(/^#{namespace.name}::/, "") if namespace + @klass = klass + @singular = _singularize(@name) + @plural = ActiveSupport::Inflector.pluralize(@singular) + @element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(@name)) + @human = ActiveSupport::Inflector.humanize(@element) + @collection = ActiveSupport::Inflector.tableize(@name) + @param_key = (namespace ? _singularize(@unnamespaced) : @singular) + @i18n_key = @name.underscore.to_sym + + @route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural.dup) + @singular_route_key = ActiveSupport::Inflector.singularize(@route_key) + @route_key << "_index" if @plural == @singular + end + + # Transform the model name into a more human format, using I18n. By default, + # it will underscore then humanize the class name. + # + # class BlogPost + # extend ActiveModel::Naming + # end + # + # BlogPost.model_name.human # => "Blog post" + # + # Specify +options+ with additional translating options. + def human(options = {}) + return @human unless @klass.respond_to?(:lookup_ancestors) && + @klass.respond_to?(:i18n_scope) + + defaults = @klass.lookup_ancestors.map do |klass| + klass.model_name.i18n_key + end + + defaults << options[:default] if options[:default] + defaults << @human + + options = { scope: [@klass.i18n_scope, :models], count: 1, default: defaults }.merge!(options.except(:default)) + I18n.translate(defaults.shift, options) + end + + private + + def _singularize(string) + ActiveSupport::Inflector.underscore(string).tr("/".freeze, "_".freeze) + end + end + + # == Active \Model \Naming + # + # Creates a +model_name+ method on your object. + # + # To implement, just extend ActiveModel::Naming in your object: + # + # class BookCover + # extend ActiveModel::Naming + # end + # + # BookCover.model_name.name # => "BookCover" + # BookCover.model_name.human # => "Book cover" + # + # BookCover.model_name.i18n_key # => :book_cover + # BookModule::BookCover.model_name.i18n_key # => :"book_module/book_cover" + # + # Providing the functionality that ActiveModel::Naming provides in your object + # is required to pass the \Active \Model Lint test. So either extending the + # provided method below, or rolling your own is required. + module Naming + def self.extended(base) #:nodoc: + base.silence_redefinition_of_method :model_name + base.delegate :model_name, to: :class + end + + # Returns an ActiveModel::Name object for module. It can be + # used to retrieve all kinds of naming-related information + # (See ActiveModel::Name for more information). + # + # class Person + # extend ActiveModel::Naming + # end + # + # Person.model_name.name # => "Person" + # Person.model_name.class # => ActiveModel::Name + # Person.model_name.singular # => "person" + # Person.model_name.plural # => "people" + def model_name + @_model_name ||= begin + namespace = parents.detect do |n| + n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming? + end + ActiveModel::Name.new(self, namespace) + end + end + + # Returns the plural class name of a record or class. + # + # ActiveModel::Naming.plural(post) # => "posts" + # ActiveModel::Naming.plural(Highrise::Person) # => "highrise_people" + def self.plural(record_or_class) + model_name_from_record_or_class(record_or_class).plural + end + + # Returns the singular class name of a record or class. + # + # ActiveModel::Naming.singular(post) # => "post" + # ActiveModel::Naming.singular(Highrise::Person) # => "highrise_person" + def self.singular(record_or_class) + model_name_from_record_or_class(record_or_class).singular + end + + # Identifies whether the class name of a record or class is uncountable. + # + # ActiveModel::Naming.uncountable?(Sheep) # => true + # ActiveModel::Naming.uncountable?(Post) # => false + def self.uncountable?(record_or_class) + plural(record_or_class) == singular(record_or_class) + end + + # Returns string to use while generating route names. It differs for + # namespaced models regarding whether it's inside isolated engine. + # + # # For isolated engine: + # ActiveModel::Naming.singular_route_key(Blog::Post) # => "post" + # + # # For shared engine: + # ActiveModel::Naming.singular_route_key(Blog::Post) # => "blog_post" + def self.singular_route_key(record_or_class) + model_name_from_record_or_class(record_or_class).singular_route_key + end + + # Returns string to use while generating route names. It differs for + # namespaced models regarding whether it's inside isolated engine. + # + # # For isolated engine: + # ActiveModel::Naming.route_key(Blog::Post) # => "posts" + # + # # For shared engine: + # ActiveModel::Naming.route_key(Blog::Post) # => "blog_posts" + # + # The route key also considers if the noun is uncountable and, in + # such cases, automatically appends _index. + def self.route_key(record_or_class) + model_name_from_record_or_class(record_or_class).route_key + end + + # Returns string to use for params names. It differs for + # namespaced models regarding whether it's inside isolated engine. + # + # # For isolated engine: + # ActiveModel::Naming.param_key(Blog::Post) # => "post" + # + # # For shared engine: + # ActiveModel::Naming.param_key(Blog::Post) # => "blog_post" + def self.param_key(record_or_class) + model_name_from_record_or_class(record_or_class).param_key + end + + def self.model_name_from_record_or_class(record_or_class) #:nodoc: + if record_or_class.respond_to?(:to_model) + record_or_class.to_model.model_name + else + record_or_class.model_name + end + end + private_class_method :model_name_from_record_or_class + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/railtie.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/railtie.rb new file mode 100644 index 00000000..a9cdabba --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/railtie.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "active_model" +require "rails" + +module ActiveModel + class Railtie < Rails::Railtie # :nodoc: + config.eager_load_namespaces << ActiveModel + + initializer "active_model.secure_password" do + ActiveModel::SecurePassword.min_cost = Rails.env.test? + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/secure_password.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/secure_password.rb new file mode 100644 index 00000000..86f051f5 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/secure_password.rb @@ -0,0 +1,129 @@ +# frozen_string_literal: true + +module ActiveModel + module SecurePassword + extend ActiveSupport::Concern + + # BCrypt hash function can handle maximum 72 bytes, and if we pass + # password of length more than 72 bytes it ignores extra characters. + # Hence need to put a restriction on password length. + MAX_PASSWORD_LENGTH_ALLOWED = 72 + + class << self + attr_accessor :min_cost # :nodoc: + end + self.min_cost = false + + module ClassMethods + # Adds methods to set and authenticate against a BCrypt password. + # This mechanism requires you to have a +password_digest+ attribute. + # + # The following validations are added automatically: + # * Password must be present on creation + # * Password length should be less than or equal to 72 bytes + # * Confirmation of password (using a +password_confirmation+ attribute) + # + # If password confirmation validation is not needed, simply leave out the + # value for +password_confirmation+ (i.e. don't provide a form field for + # it). When this attribute has a +nil+ value, the validation will not be + # triggered. + # + # For further customizability, it is possible to suppress the default + # validations by passing validations: false as an argument. + # + # Add bcrypt (~> 3.1.7) to Gemfile to use #has_secure_password: + # + # gem 'bcrypt', '~> 3.1.7' + # + # Example using Active Record (which automatically includes ActiveModel::SecurePassword): + # + # # Schema: User(name:string, password_digest:string) + # class User < ActiveRecord::Base + # has_secure_password + # end + # + # user = User.new(name: 'david', password: '', password_confirmation: 'nomatch') + # user.save # => false, password required + # user.password = 'mUc3m00RsqyRe' + # user.save # => false, confirmation doesn't match + # user.password_confirmation = 'mUc3m00RsqyRe' + # user.save # => true + # user.authenticate('notright') # => false + # user.authenticate('mUc3m00RsqyRe') # => user + # User.find_by(name: 'david').try(:authenticate, 'notright') # => false + # User.find_by(name: 'david').try(:authenticate, 'mUc3m00RsqyRe') # => user + def has_secure_password(options = {}) + # Load bcrypt gem only when has_secure_password is used. + # This is to avoid ActiveModel (and by extension the entire framework) + # being dependent on a binary library. + begin + require "bcrypt" + rescue LoadError + $stderr.puts "You don't have bcrypt installed in your application. Please add it to your Gemfile and run bundle install" + raise + end + + include InstanceMethodsOnActivation + + if options.fetch(:validations, true) + include ActiveModel::Validations + + # This ensures the model has a password by checking whether the password_digest + # is present, so that this works with both new and existing records. However, + # when there is an error, the message is added to the password attribute instead + # so that the error message will make sense to the end-user. + validate do |record| + record.errors.add(:password, :blank) unless record.password_digest.present? + end + + validates_length_of :password, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED + validates_confirmation_of :password, allow_blank: true + end + end + end + + module InstanceMethodsOnActivation + # Returns +self+ if the password is correct, otherwise +false+. + # + # class User < ActiveRecord::Base + # has_secure_password validations: false + # end + # + # user = User.new(name: 'david', password: 'mUc3m00RsqyRe') + # user.save + # user.authenticate('notright') # => false + # user.authenticate('mUc3m00RsqyRe') # => user + def authenticate(unencrypted_password) + BCrypt::Password.new(password_digest).is_password?(unencrypted_password) && self + end + + attr_reader :password + + # Encrypts the password into the +password_digest+ attribute, only if the + # new password is not empty. + # + # class User < ActiveRecord::Base + # has_secure_password validations: false + # end + # + # user = User.new + # user.password = nil + # user.password_digest # => nil + # user.password = 'mUc3m00RsqyRe' + # user.password_digest # => "$2a$10$4LEA7r4YmNHtvlAvHhsYAeZmk/xeUVtMTYqwIvYY76EW5GUqDiP4." + def password=(unencrypted_password) + if unencrypted_password.nil? + self.password_digest = nil + elsif !unencrypted_password.empty? + @password = unencrypted_password + cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost + self.password_digest = BCrypt::Password.create(unencrypted_password, cost: cost) + end + end + + def password_confirmation=(unencrypted_password) + @password_confirmation = unencrypted_password + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/serialization.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/serialization.rb new file mode 100644 index 00000000..c4b7b322 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/serialization.rb @@ -0,0 +1,192 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/except" +require "active_support/core_ext/hash/slice" + +module ActiveModel + # == Active \Model \Serialization + # + # Provides a basic serialization to a serializable_hash for your objects. + # + # A minimal implementation could be: + # + # class Person + # include ActiveModel::Serialization + # + # attr_accessor :name + # + # def attributes + # {'name' => nil} + # end + # end + # + # Which would provide you with: + # + # person = Person.new + # person.serializable_hash # => {"name"=>nil} + # person.name = "Bob" + # person.serializable_hash # => {"name"=>"Bob"} + # + # An +attributes+ hash must be defined and should contain any attributes you + # need to be serialized. Attributes must be strings, not symbols. + # When called, serializable hash will use instance methods that match the name + # of the attributes hash's keys. In order to override this behavior, take a look + # at the private method +read_attribute_for_serialization+. + # + # ActiveModel::Serializers::JSON module automatically includes + # the ActiveModel::Serialization module, so there is no need to + # explicitly include ActiveModel::Serialization. + # + # A minimal implementation including JSON would be: + # + # class Person + # include ActiveModel::Serializers::JSON + # + # attr_accessor :name + # + # def attributes + # {'name' => nil} + # end + # end + # + # Which would provide you with: + # + # person = Person.new + # person.serializable_hash # => {"name"=>nil} + # person.as_json # => {"name"=>nil} + # person.to_json # => "{\"name\":null}" + # + # person.name = "Bob" + # person.serializable_hash # => {"name"=>"Bob"} + # person.as_json # => {"name"=>"Bob"} + # person.to_json # => "{\"name\":\"Bob\"}" + # + # Valid options are :only, :except, :methods and + # :include. The following are all valid examples: + # + # person.serializable_hash(only: 'name') + # person.serializable_hash(include: :address) + # person.serializable_hash(include: { address: { only: 'city' }}) + module Serialization + # Returns a serialized hash of your object. + # + # class Person + # include ActiveModel::Serialization + # + # attr_accessor :name, :age + # + # def attributes + # {'name' => nil, 'age' => nil} + # end + # + # def capitalized_name + # name.capitalize + # end + # end + # + # person = Person.new + # person.name = 'bob' + # person.age = 22 + # person.serializable_hash # => {"name"=>"bob", "age"=>22} + # person.serializable_hash(only: :name) # => {"name"=>"bob"} + # person.serializable_hash(except: :name) # => {"age"=>22} + # person.serializable_hash(methods: :capitalized_name) + # # => {"name"=>"bob", "age"=>22, "capitalized_name"=>"Bob"} + # + # Example with :include option + # + # class User + # include ActiveModel::Serializers::JSON + # attr_accessor :name, :notes # Emulate has_many :notes + # def attributes + # {'name' => nil} + # end + # end + # + # class Note + # include ActiveModel::Serializers::JSON + # attr_accessor :title, :text + # def attributes + # {'title' => nil, 'text' => nil} + # end + # end + # + # note = Note.new + # note.title = 'Battle of Austerlitz' + # note.text = 'Some text here' + # + # user = User.new + # user.name = 'Napoleon' + # user.notes = [note] + # + # user.serializable_hash + # # => {"name" => "Napoleon"} + # user.serializable_hash(include: { notes: { only: 'title' }}) + # # => {"name" => "Napoleon", "notes" => [{"title"=>"Battle of Austerlitz"}]} + def serializable_hash(options = nil) + options ||= {} + + attribute_names = attributes.keys + if only = options[:only] + attribute_names &= Array(only).map(&:to_s) + elsif except = options[:except] + attribute_names -= Array(except).map(&:to_s) + end + + hash = {} + attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) } + + Array(options[:methods]).each { |m| hash[m.to_s] = send(m) } + + serializable_add_includes(options) do |association, records, opts| + hash[association.to_s] = if records.respond_to?(:to_ary) + records.to_ary.map { |a| a.serializable_hash(opts) } + else + records.serializable_hash(opts) + end + end + + hash + end + + private + + # Hook method defining how an attribute value should be retrieved for + # serialization. By default this is assumed to be an instance named after + # the attribute. Override this method in subclasses should you need to + # retrieve the value for a given attribute differently: + # + # class MyClass + # include ActiveModel::Serialization + # + # def initialize(data = {}) + # @data = data + # end + # + # def read_attribute_for_serialization(key) + # @data[key] + # end + # end + alias :read_attribute_for_serialization :send + + # Add associations specified via the :include option. + # + # Expects a block that takes as arguments: + # +association+ - name of the association + # +records+ - the association record(s) to be serialized + # +opts+ - options for the association records + def serializable_add_includes(options = {}) #:nodoc: + return unless includes = options[:include] + + unless includes.is_a?(Hash) + includes = Hash[Array(includes).flat_map { |n| n.is_a?(Hash) ? n.to_a : [[n, {}]] }] + end + + includes.each do |association, opts| + if records = send(association) + yield association, records, opts + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/serializers/json.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/serializers/json.rb new file mode 100644 index 00000000..25e1541d --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/serializers/json.rb @@ -0,0 +1,146 @@ +# frozen_string_literal: true + +require "active_support/json" + +module ActiveModel + module Serializers + # == Active \Model \JSON \Serializer + module JSON + extend ActiveSupport::Concern + include ActiveModel::Serialization + + included do + extend ActiveModel::Naming + + class_attribute :include_root_in_json, instance_writer: false, default: false + end + + # Returns a hash representing the model. Some configuration can be + # passed through +options+. + # + # The option include_root_in_json controls the top-level behavior + # of +as_json+. If +true+, +as_json+ will emit a single root node named + # after the object's type. The default value for include_root_in_json + # option is +false+. + # + # user = User.find(1) + # user.as_json + # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16, + # # "created_at" => "2006/08/01", "awesome" => true} + # + # ActiveRecord::Base.include_root_in_json = true + # + # user.as_json + # # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16, + # # "created_at" => "2006/08/01", "awesome" => true } } + # + # This behavior can also be achieved by setting the :root option + # to +true+ as in: + # + # user = User.find(1) + # user.as_json(root: true) + # # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16, + # # "created_at" => "2006/08/01", "awesome" => true } } + # + # Without any +options+, the returned Hash will include all the model's + # attributes. + # + # user = User.find(1) + # user.as_json + # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16, + # # "created_at" => "2006/08/01", "awesome" => true} + # + # The :only and :except options can be used to limit + # the attributes included, and work similar to the +attributes+ method. + # + # user.as_json(only: [:id, :name]) + # # => { "id" => 1, "name" => "Konata Izumi" } + # + # user.as_json(except: [:id, :created_at, :age]) + # # => { "name" => "Konata Izumi", "awesome" => true } + # + # To include the result of some method calls on the model use :methods: + # + # user.as_json(methods: :permalink) + # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16, + # # "created_at" => "2006/08/01", "awesome" => true, + # # "permalink" => "1-konata-izumi" } + # + # To include associations use :include: + # + # user.as_json(include: :posts) + # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16, + # # "created_at" => "2006/08/01", "awesome" => true, + # # "posts" => [ { "id" => 1, "author_id" => 1, "title" => "Welcome to the weblog" }, + # # { "id" => 2, "author_id" => 1, "title" => "So I was thinking" } ] } + # + # Second level and higher order associations work as well: + # + # user.as_json(include: { posts: { + # include: { comments: { + # only: :body } }, + # only: :title } }) + # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16, + # # "created_at" => "2006/08/01", "awesome" => true, + # # "posts" => [ { "comments" => [ { "body" => "1st post!" }, { "body" => "Second!" } ], + # # "title" => "Welcome to the weblog" }, + # # { "comments" => [ { "body" => "Don't think too hard" } ], + # # "title" => "So I was thinking" } ] } + def as_json(options = nil) + root = if options && options.key?(:root) + options[:root] + else + include_root_in_json + end + + if root + root = model_name.element if root == true + { root => serializable_hash(options) } + else + serializable_hash(options) + end + end + + # Sets the model +attributes+ from a JSON string. Returns +self+. + # + # class Person + # include ActiveModel::Serializers::JSON + # + # attr_accessor :name, :age, :awesome + # + # def attributes=(hash) + # hash.each do |key, value| + # send("#{key}=", value) + # end + # end + # + # def attributes + # instance_values + # end + # end + # + # json = { name: 'bob', age: 22, awesome:true }.to_json + # person = Person.new + # person.from_json(json) # => # + # person.name # => "bob" + # person.age # => 22 + # person.awesome # => true + # + # The default value for +include_root+ is +false+. You can change it to + # +true+ if the given JSON string includes a single root node. + # + # json = { person: { name: 'bob', age: 22, awesome:true } }.to_json + # person = Person.new + # person.from_json(json, true) # => # + # person.name # => "bob" + # person.age # => 22 + # person.awesome # => true + def from_json(json, include_root = include_root_in_json) + hash = ActiveSupport::JSON.decode(json) + hash = hash.values.first if include_root + self.attributes = hash + self + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/translation.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/translation.rb new file mode 100644 index 00000000..f3d0d3dc --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/translation.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module ActiveModel + # == Active \Model \Translation + # + # Provides integration between your object and the Rails internationalization + # (i18n) framework. + # + # A minimal implementation could be: + # + # class TranslatedPerson + # extend ActiveModel::Translation + # end + # + # TranslatedPerson.human_attribute_name('my_attribute') + # # => "My attribute" + # + # This also provides the required class methods for hooking into the + # Rails internationalization API, including being able to define a + # class based +i18n_scope+ and +lookup_ancestors+ to find translations in + # parent classes. + module Translation + include ActiveModel::Naming + + # Returns the +i18n_scope+ for the class. Overwrite if you want custom lookup. + def i18n_scope + :activemodel + end + + # When localizing a string, it goes through the lookup returned by this + # method, which is used in ActiveModel::Name#human, + # ActiveModel::Errors#full_messages and + # ActiveModel::Translation#human_attribute_name. + def lookup_ancestors + ancestors.select { |x| x.respond_to?(:model_name) } + end + + # Transforms attribute names into a more human format, such as "First name" + # instead of "first_name". + # + # Person.human_attribute_name("first_name") # => "First name" + # + # Specify +options+ with additional translating options. + def human_attribute_name(attribute, options = {}) + options = { count: 1 }.merge!(options) + parts = attribute.to_s.split(".") + attribute = parts.pop + namespace = parts.join("/") unless parts.empty? + attributes_scope = "#{i18n_scope}.attributes" + + if namespace + defaults = lookup_ancestors.map do |klass| + :"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.#{attribute}" + end + defaults << :"#{attributes_scope}.#{namespace}.#{attribute}" + else + defaults = lookup_ancestors.map do |klass| + :"#{attributes_scope}.#{klass.model_name.i18n_key}.#{attribute}" + end + end + + defaults << :"attributes.#{attribute}" + defaults << options.delete(:default) if options[:default] + defaults << attribute.humanize + + options[:default] = defaults + I18n.translate(defaults.shift, options) + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type.rb new file mode 100644 index 00000000..1d7a26ff --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require "active_model/type/helpers" +require "active_model/type/value" + +require "active_model/type/big_integer" +require "active_model/type/binary" +require "active_model/type/boolean" +require "active_model/type/date" +require "active_model/type/date_time" +require "active_model/type/decimal" +require "active_model/type/float" +require "active_model/type/immutable_string" +require "active_model/type/integer" +require "active_model/type/string" +require "active_model/type/time" + +require "active_model/type/registry" + +module ActiveModel + module Type + @registry = Registry.new + + class << self + attr_accessor :registry # :nodoc: + + # Add a new type to the registry, allowing it to be gotten through ActiveModel::Type#lookup + def register(type_name, klass = nil, **options, &block) + registry.register(type_name, klass, **options, &block) + end + + def lookup(*args, **kwargs) # :nodoc: + registry.lookup(*args, **kwargs) + end + + def default_value # :nodoc: + @default_value ||= Value.new + end + end + + register(:big_integer, Type::BigInteger) + register(:binary, Type::Binary) + register(:boolean, Type::Boolean) + register(:date, Type::Date) + register(:datetime, Type::DateTime) + register(:decimal, Type::Decimal) + register(:float, Type::Float) + register(:immutable_string, Type::ImmutableString) + register(:integer, Type::Integer) + register(:string, Type::String) + register(:time, Type::Time) + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/big_integer.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/big_integer.rb new file mode 100644 index 00000000..89e43bcc --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/big_integer.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require "active_model/type/integer" + +module ActiveModel + module Type + class BigInteger < Integer # :nodoc: + private + + def max_value + ::Float::INFINITY + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/binary.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/binary.rb new file mode 100644 index 00000000..dc2eca18 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/binary.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + class Binary < Value # :nodoc: + def type + :binary + end + + def binary? + true + end + + def cast(value) + if value.is_a?(Data) + value.to_s + else + super + end + end + + def serialize(value) + return if value.nil? + Data.new(super) + end + + def changed_in_place?(raw_old_value, value) + old_value = deserialize(raw_old_value) + old_value != value + end + + class Data # :nodoc: + def initialize(value) + @value = value.to_s + end + + def to_s + @value + end + alias_method :to_str, :to_s + + def hex + @value.unpack("H*")[0] + end + + def ==(other) + other == to_s || super + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/boolean.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/boolean.rb new file mode 100644 index 00000000..f6c6efbc --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/boolean.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + # == Active \Model \Type \Boolean + # + # A class that behaves like a boolean type, including rules for coercion of user input. + # + # === Coercion + # Values set from user input will first be coerced into the appropriate ruby type. + # Coercion behavior is roughly mapped to Ruby's boolean semantics. + # + # - "false", "f" , "0", +0+ or any other value in +FALSE_VALUES+ will be coerced to +false+ + # - Empty strings are coerced to +nil+ + # - All other values will be coerced to +true+ + class Boolean < Value + FALSE_VALUES = [false, 0, "0", "f", "F", "false", "FALSE", "off", "OFF"].to_set + + def type # :nodoc: + :boolean + end + + def serialize(value) # :nodoc: + cast(value) + end + + private + + def cast_value(value) + if value == "" + nil + else + !FALSE_VALUES.include?(value) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/date.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/date.rb new file mode 100644 index 00000000..4ba7095a --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/date.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + class Date < Value # :nodoc: + include Helpers::Timezone + include Helpers::AcceptsMultiparameterTime.new + + def type + :date + end + + def serialize(value) + cast(value) + end + + def type_cast_for_schema(value) + value.to_s(:db).inspect + end + + private + + def cast_value(value) + if value.is_a?(::String) + return if value.empty? + fast_string_to_date(value) || fallback_string_to_date(value) + elsif value.respond_to?(:to_date) + value.to_date + else + value + end + end + + ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/ + def fast_string_to_date(string) + if string =~ ISO_DATE + new_date $1.to_i, $2.to_i, $3.to_i + end + end + + def fallback_string_to_date(string) + new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday)) + end + + def new_date(year, mon, mday) + unless year.nil? || (year == 0 && mon == 0 && mday == 0) + ::Date.new(year, mon, mday) rescue nil + end + end + + def value_from_multiparameter_assignment(*) + time = super + time && new_date(time.year, time.mon, time.mday) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/date_time.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/date_time.rb new file mode 100644 index 00000000..50256287 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/date_time.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + class DateTime < Value # :nodoc: + include Helpers::Timezone + include Helpers::TimeValue + include Helpers::AcceptsMultiparameterTime.new( + defaults: { 4 => 0, 5 => 0 } + ) + + def type + :datetime + end + + def serialize(value) + super(cast(value)) + end + + private + + def cast_value(value) + return apply_seconds_precision(value) unless value.is_a?(::String) + return if value.empty? + + fast_string_to_time(value) || fallback_string_to_time(value) + end + + # '0.123456' -> 123456 + # '1.123456' -> 123456 + def microseconds(time) + time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0 + end + + def fallback_string_to_time(string) + time_hash = ::Date._parse(string) + time_hash[:sec_fraction] = microseconds(time_hash) + + new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset)) + end + + def value_from_multiparameter_assignment(values_hash) + missing_parameter = (1..3).detect { |key| !values_hash.key?(key) } + if missing_parameter + raise ArgumentError, missing_parameter + end + super + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/decimal.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/decimal.rb new file mode 100644 index 00000000..e8ee18c0 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/decimal.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require "bigdecimal/util" + +module ActiveModel + module Type + class Decimal < Value # :nodoc: + include Helpers::Numeric + BIGDECIMAL_PRECISION = 18 + + def type + :decimal + end + + def type_cast_for_schema(value) + value.to_s.inspect + end + + private + + def cast_value(value) + casted_value = \ + case value + when ::Float + convert_float_to_big_decimal(value) + when ::Numeric + BigDecimal(value, precision || BIGDECIMAL_PRECISION) + when ::String + begin + value.to_d + rescue ArgumentError + BigDecimal(0) + end + else + if value.respond_to?(:to_d) + value.to_d + else + cast_value(value.to_s) + end + end + + apply_scale(casted_value) + end + + def convert_float_to_big_decimal(value) + if precision + BigDecimal(apply_scale(value), float_precision) + else + value.to_d + end + end + + def float_precision + if precision.to_i > ::Float::DIG + 1 + ::Float::DIG + 1 + else + precision.to_i + end + end + + def apply_scale(value) + if scale + value.round(scale) + else + value + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/float.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/float.rb new file mode 100644 index 00000000..9dbe32e5 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/float.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + class Float < Value # :nodoc: + include Helpers::Numeric + + def type + :float + end + + def type_cast_for_schema(value) + return "::Float::NAN" if value.try(:nan?) + case value + when ::Float::INFINITY then "::Float::INFINITY" + when -::Float::INFINITY then "-::Float::INFINITY" + else super + end + end + + alias serialize cast + + private + + def cast_value(value) + case value + when ::Float then value + when "Infinity" then ::Float::INFINITY + when "-Infinity" then -::Float::INFINITY + when "NaN" then ::Float::NAN + else value.to_f + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/helpers.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/helpers.rb new file mode 100644 index 00000000..20145d5f --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/helpers.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "active_model/type/helpers/accepts_multiparameter_time" +require "active_model/type/helpers/numeric" +require "active_model/type/helpers/mutable" +require "active_model/type/helpers/time_value" +require "active_model/type/helpers/timezone" diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/helpers/accepts_multiparameter_time.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/helpers/accepts_multiparameter_time.rb new file mode 100644 index 00000000..ad891f84 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/helpers/accepts_multiparameter_time.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + module Helpers # :nodoc: all + class AcceptsMultiparameterTime < Module + def initialize(defaults: {}) + define_method(:cast) do |value| + if value.is_a?(Hash) + value_from_multiparameter_assignment(value) + else + super(value) + end + end + + define_method(:assert_valid_value) do |value| + if value.is_a?(Hash) + value_from_multiparameter_assignment(value) + else + super(value) + end + end + + define_method(:value_constructed_by_mass_assignment?) do |value| + value.is_a?(Hash) + end + + define_method(:value_from_multiparameter_assignment) do |values_hash| + defaults.each do |k, v| + values_hash[k] ||= v + end + return unless values_hash[1] && values_hash[2] && values_hash[3] + values = values_hash.sort.map(&:last) + ::Time.send(default_timezone, *values) + end + private :value_from_multiparameter_assignment + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/helpers/mutable.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/helpers/mutable.rb new file mode 100644 index 00000000..1cbea644 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/helpers/mutable.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + module Helpers # :nodoc: all + module Mutable + def cast(value) + deserialize(serialize(value)) + end + + # +raw_old_value+ will be the `_before_type_cast` version of the + # value (likely a string). +new_value+ will be the current, type + # cast value. + def changed_in_place?(raw_old_value, new_value) + raw_old_value != serialize(new_value) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/helpers/numeric.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/helpers/numeric.rb new file mode 100644 index 00000000..473cdb0c --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/helpers/numeric.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + module Helpers # :nodoc: all + module Numeric + def cast(value) + value = \ + case value + when true then 1 + when false then 0 + when ::String then value.presence + else value + end + super(value) + end + + def changed?(old_value, _new_value, new_value_before_type_cast) # :nodoc: + super || number_to_non_number?(old_value, new_value_before_type_cast) + end + + private + + def number_to_non_number?(old_value, new_value_before_type_cast) + old_value != nil && non_numeric_string?(new_value_before_type_cast) + end + + def non_numeric_string?(value) + # 'wibble'.to_i will give zero, we want to make sure + # that we aren't marking int zero to string zero as + # changed. + !/\A[-+]?\d+/.match?(value.to_s) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/helpers/time_value.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/helpers/time_value.rb new file mode 100644 index 00000000..7cbdb62f --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/helpers/time_value.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/zones" +require "active_support/core_ext/time/zones" + +module ActiveModel + module Type + module Helpers # :nodoc: all + module TimeValue + def serialize(value) + value = apply_seconds_precision(value) + + if value.acts_like?(:time) + zone_conversion_method = is_utc? ? :getutc : :getlocal + + if value.respond_to?(zone_conversion_method) + value = value.send(zone_conversion_method) + end + end + + value + end + + def apply_seconds_precision(value) + return value unless precision && value.respond_to?(:usec) + number_of_insignificant_digits = 6 - precision + round_power = 10**number_of_insignificant_digits + value.change(usec: value.usec - value.usec % round_power) + end + + def type_cast_for_schema(value) + value.to_s(:db).inspect + end + + def user_input_in_time_zone(value) + value.in_time_zone + end + + private + + def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil) + # Treat 0000-00-00 00:00:00 as nil. + return if year.nil? || (year == 0 && mon == 0 && mday == 0) + + if offset + time = ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil + return unless time + + time -= offset + is_utc? ? time : time.getlocal + else + ::Time.public_send(default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil + end + end + + ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/ + + # Doesn't handle time zones. + def fast_string_to_time(string) + if string =~ ISO_DATETIME + microsec = ($7.to_r * 1_000_000).to_i + new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/helpers/timezone.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/helpers/timezone.rb new file mode 100644 index 00000000..cf87b971 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/helpers/timezone.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require "active_support/core_ext/time/zones" + +module ActiveModel + module Type + module Helpers # :nodoc: all + module Timezone + def is_utc? + ::Time.zone_default.nil? || ::Time.zone_default =~ "UTC" + end + + def default_timezone + is_utc? ? :utc : :local + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/immutable_string.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/immutable_string.rb new file mode 100644 index 00000000..826bd703 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/immutable_string.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + class ImmutableString < Value # :nodoc: + def type + :string + end + + def serialize(value) + case value + when ::Numeric, ActiveSupport::Duration then value.to_s + when true then "t" + when false then "f" + else super + end + end + + private + + def cast_value(value) + result = \ + case value + when true then "t" + when false then "f" + else value.to_s + end + result.freeze + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/integer.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/integer.rb new file mode 100644 index 00000000..fe396998 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/integer.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + class Integer < Value # :nodoc: + include Helpers::Numeric + + # Column storage size in bytes. + # 4 bytes means an integer as opposed to smallint etc. + DEFAULT_LIMIT = 4 + + def initialize(*) + super + @range = min_value...max_value + end + + def type + :integer + end + + def deserialize(value) + return if value.nil? + value.to_i + end + + def serialize(value) + result = cast(value) + if result + ensure_in_range(result) + end + result + end + + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected + + attr_reader :range + + private + + def cast_value(value) + case value + when true then 1 + when false then 0 + else + value.to_i rescue nil + end + end + + def ensure_in_range(value) + unless range.cover?(value) + raise ActiveModel::RangeError, "#{value} is out of range for #{self.class} with limit #{_limit} bytes" + end + end + + def max_value + 1 << (_limit * 8 - 1) # 8 bits per byte with one bit for sign + end + + def min_value + -max_value + end + + def _limit + limit || DEFAULT_LIMIT + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/registry.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/registry.rb new file mode 100644 index 00000000..7272d7b0 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/registry.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module ActiveModel + # :stopdoc: + module Type + class Registry + def initialize + @registrations = [] + end + + def register(type_name, klass = nil, **options, &block) + block ||= proc { |_, *args| klass.new(*args) } + registrations << registration_klass.new(type_name, block, **options) + end + + def lookup(symbol, *args) + registration = find_registration(symbol, *args) + + if registration + registration.call(self, symbol, *args) + else + raise ArgumentError, "Unknown type #{symbol.inspect}" + end + end + + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected + + attr_reader :registrations + + private + + def registration_klass + Registration + end + + def find_registration(symbol, *args) + registrations.find { |r| r.matches?(symbol, *args) } + end + end + + class Registration + # Options must be taken because of https://bugs.ruby-lang.org/issues/10856 + def initialize(name, block, **) + @name = name + @block = block + end + + def call(_registry, *args, **kwargs) + if kwargs.any? # https://bugs.ruby-lang.org/issues/10856 + block.call(*args, **kwargs) + else + block.call(*args) + end + end + + def matches?(type_name, *args, **kwargs) + type_name == name + end + + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected + + attr_reader :name, :block + end + end + # :startdoc: +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/string.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/string.rb new file mode 100644 index 00000000..36f13945 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/string.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require "active_model/type/immutable_string" + +module ActiveModel + module Type + class String < ImmutableString # :nodoc: + def changed_in_place?(raw_old_value, new_value) + if new_value.is_a?(::String) + raw_old_value != new_value + end + end + + private + + def cast_value(value) + case value + when ::String then ::String.new(value) + when true then "t".freeze + when false then "f".freeze + else value.to_s + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/time.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/time.rb new file mode 100644 index 00000000..3438bd9b --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/time.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + class Time < Value # :nodoc: + include Helpers::Timezone + include Helpers::TimeValue + include Helpers::AcceptsMultiparameterTime.new( + defaults: { 1 => 1970, 2 => 1, 3 => 1, 4 => 0, 5 => 0 } + ) + + def type + :time + end + + def serialize(value) + super(cast(value)) + end + + def user_input_in_time_zone(value) + return unless value.present? + + case value + when ::String + value = "2000-01-01 #{value}" + time_hash = ::Date._parse(value) + return if time_hash[:hour].nil? + when ::Time + value = value.change(year: 2000, day: 1, month: 1) + end + + super(value) + end + + private + + def cast_value(value) + return apply_seconds_precision(value) unless value.is_a?(::String) + return if value.empty? + + dummy_time_value = value.sub(/\A(\d\d\d\d-\d\d-\d\d |)/, "2000-01-01 ") + + fast_string_to_time(dummy_time_value) || begin + time_hash = ::Date._parse(dummy_time_value) + return if time_hash[:hour].nil? + new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset)) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/value.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/value.rb new file mode 100644 index 00000000..b6914dd6 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/type/value.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +module ActiveModel + module Type + class Value + attr_reader :precision, :scale, :limit + + def initialize(precision: nil, limit: nil, scale: nil) + @precision = precision + @scale = scale + @limit = limit + end + + def type # :nodoc: + end + + # Converts a value from database input to the appropriate ruby type. The + # return value of this method will be returned from + # ActiveRecord::AttributeMethods::Read#read_attribute. The default + # implementation just calls Value#cast. + # + # +value+ The raw input, as provided from the database. + def deserialize(value) + cast(value) + end + + # Type casts a value from user input (e.g. from a setter). This value may + # be a string from the form builder, or a ruby object passed to a setter. + # There is currently no way to differentiate between which source it came + # from. + # + # The return value of this method will be returned from + # ActiveRecord::AttributeMethods::Read#read_attribute. See also: + # Value#cast_value. + # + # +value+ The raw input, as provided to the attribute setter. + def cast(value) + cast_value(value) unless value.nil? + end + + # Casts a value from the ruby type to a type that the database knows how + # to understand. The returned value from this method should be a + # +String+, +Numeric+, +Date+, +Time+, +Symbol+, +true+, +false+, or + # +nil+. + def serialize(value) + value + end + + # Type casts a value for schema dumping. This method is private, as we are + # hoping to remove it entirely. + def type_cast_for_schema(value) # :nodoc: + value.inspect + end + + # These predicates are not documented, as I need to look further into + # their use, and see if they can be removed entirely. + def binary? # :nodoc: + false + end + + # Determines whether a value has changed for dirty checking. +old_value+ + # and +new_value+ will always be type-cast. Types should not need to + # override this method. + def changed?(old_value, new_value, _new_value_before_type_cast) + old_value != new_value + end + + # Determines whether the mutable value has been modified since it was + # read. Returns +false+ by default. If your type returns an object + # which could be mutated, you should override this method. You will need + # to either: + # + # - pass +new_value+ to Value#serialize and compare it to + # +raw_old_value+ + # + # or + # + # - pass +raw_old_value+ to Value#deserialize and compare it to + # +new_value+ + # + # +raw_old_value+ The original value, before being passed to + # +deserialize+. + # + # +new_value+ The current value, after type casting. + def changed_in_place?(raw_old_value, new_value) + false + end + + def value_constructed_by_mass_assignment?(_value) # :nodoc: + false + end + + def force_equality?(_value) # :nodoc: + false + end + + def map(value) # :nodoc: + yield value + end + + def ==(other) + self.class == other.class && + precision == other.precision && + scale == other.scale && + limit == other.limit + end + alias eql? == + + def hash + [self.class, precision, scale, limit].hash + end + + def assert_valid_value(*) + end + + private + + # Convenience method for types which do not need separate type casting + # behavior for user and database inputs. Called by Value#cast for + # values except +nil+. + def cast_value(value) # :doc: + value + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations.rb new file mode 100644 index 00000000..7f14d102 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations.rb @@ -0,0 +1,439 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/hash/except" + +module ActiveModel + # == Active \Model \Validations + # + # Provides a full validation framework to your objects. + # + # A minimal implementation could be: + # + # class Person + # include ActiveModel::Validations + # + # attr_accessor :first_name, :last_name + # + # validates_each :first_name, :last_name do |record, attr, value| + # record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z + # end + # end + # + # Which provides you with the full standard validation stack that you + # know from Active Record: + # + # person = Person.new + # person.valid? # => true + # person.invalid? # => false + # + # person.first_name = 'zoolander' + # person.valid? # => false + # person.invalid? # => true + # person.errors.messages # => {first_name:["starts with z."]} + # + # Note that ActiveModel::Validations automatically adds an +errors+ + # method to your instances initialized with a new ActiveModel::Errors + # object, so there is no need for you to do this manually. + module Validations + extend ActiveSupport::Concern + + included do + extend ActiveModel::Naming + extend ActiveModel::Callbacks + extend ActiveModel::Translation + + extend HelperMethods + include HelperMethods + + attr_accessor :validation_context + private :validation_context= + define_callbacks :validate, scope: :name + + class_attribute :_validators, instance_writer: false, default: Hash.new { |h, k| h[k] = [] } + end + + module ClassMethods + # Validates each attribute against a block. + # + # class Person + # include ActiveModel::Validations + # + # attr_accessor :first_name, :last_name + # + # validates_each :first_name, :last_name, allow_blank: true do |record, attr, value| + # record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z + # end + # end + # + # Options: + # * :on - Specifies the contexts where this validation is active. + # Runs in all validation contexts by default +nil+. You can pass a symbol + # or an array of symbols. (e.g. on: :create or + # on: :custom_validation_context or + # on: [:create, :custom_validation_context]) + # * :allow_nil - Skip validation if attribute is +nil+. + # * :allow_blank - Skip validation if attribute is blank. + # * :if - Specifies a method, proc or string to call to determine + # if the validation should occur (e.g. if: :allow_validation, + # or if: Proc.new { |user| user.signup_step > 2 }). The method, + # proc or string should return or evaluate to a +true+ or +false+ value. + # * :unless - Specifies a method, proc or string to call to + # determine if the validation should not occur (e.g. unless: :skip_validation, + # or unless: Proc.new { |user| user.signup_step <= 2 }). The + # method, proc or string should return or evaluate to a +true+ or +false+ + # value. + def validates_each(*attr_names, &block) + validates_with BlockValidator, _merge_attributes(attr_names), &block + end + + VALID_OPTIONS_FOR_VALIDATE = [:on, :if, :unless, :prepend].freeze # :nodoc: + + # Adds a validation method or block to the class. This is useful when + # overriding the +validate+ instance method becomes too unwieldy and + # you're looking for more descriptive declaration of your validations. + # + # This can be done with a symbol pointing to a method: + # + # class Comment + # include ActiveModel::Validations + # + # validate :must_be_friends + # + # def must_be_friends + # errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee) + # end + # end + # + # With a block which is passed with the current record to be validated: + # + # class Comment + # include ActiveModel::Validations + # + # validate do |comment| + # comment.must_be_friends + # end + # + # def must_be_friends + # errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee) + # end + # end + # + # Or with a block where self points to the current record to be validated: + # + # class Comment + # include ActiveModel::Validations + # + # validate do + # errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee) + # end + # end + # + # Note that the return value of validation methods is not relevant. + # It's not possible to halt the validate callback chain. + # + # Options: + # * :on - Specifies the contexts where this validation is active. + # Runs in all validation contexts by default +nil+. You can pass a symbol + # or an array of symbols. (e.g. on: :create or + # on: :custom_validation_context or + # on: [:create, :custom_validation_context]) + # * :if - Specifies a method, proc or string to call to determine + # if the validation should occur (e.g. if: :allow_validation, + # or if: Proc.new { |user| user.signup_step > 2 }). The method, + # proc or string should return or evaluate to a +true+ or +false+ value. + # * :unless - Specifies a method, proc or string to call to + # determine if the validation should not occur (e.g. unless: :skip_validation, + # or unless: Proc.new { |user| user.signup_step <= 2 }). The + # method, proc or string should return or evaluate to a +true+ or +false+ + # value. + # + # NOTE: Calling +validate+ multiple times on the same method will overwrite previous definitions. + # + def validate(*args, &block) + options = args.extract_options! + + if args.all? { |arg| arg.is_a?(Symbol) } + options.each_key do |k| + unless VALID_OPTIONS_FOR_VALIDATE.include?(k) + raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{VALID_OPTIONS_FOR_VALIDATE.map(&:inspect).join(', ')}. Perhaps you meant to call `validates` instead of `validate`?") + end + end + end + + if options.key?(:on) + options = options.dup + options[:on] = Array(options[:on]) + options[:if] = Array(options[:if]) + options[:if].unshift ->(o) { + !(options[:on] & Array(o.validation_context)).empty? + } + end + + set_callback(:validate, *args, options, &block) + end + + # List all validators that are being used to validate the model using + # +validates_with+ method. + # + # class Person + # include ActiveModel::Validations + # + # validates_with MyValidator + # validates_with OtherValidator, on: :create + # validates_with StrictValidator, strict: true + # end + # + # Person.validators + # # => [ + # # #, + # # #, + # # # + # # ] + def validators + _validators.values.flatten.uniq + end + + # Clears all of the validators and validations. + # + # Note that this will clear anything that is being used to validate + # the model for both the +validates_with+ and +validate+ methods. + # It clears the validators that are created with an invocation of + # +validates_with+ and the callbacks that are set by an invocation + # of +validate+. + # + # class Person + # include ActiveModel::Validations + # + # validates_with MyValidator + # validates_with OtherValidator, on: :create + # validates_with StrictValidator, strict: true + # validate :cannot_be_robot + # + # def cannot_be_robot + # errors.add(:base, 'A person cannot be a robot') if person_is_robot + # end + # end + # + # Person.validators + # # => [ + # # #, + # # #, + # # # + # # ] + # + # If one runs Person.clear_validators! and then checks to see what + # validators this class has, you would obtain: + # + # Person.validators # => [] + # + # Also, the callback set by validate :cannot_be_robot will be erased + # so that: + # + # Person._validate_callbacks.empty? # => true + # + def clear_validators! + reset_callbacks(:validate) + _validators.clear + end + + # List all validators that are being used to validate a specific attribute. + # + # class Person + # include ActiveModel::Validations + # + # attr_accessor :name , :age + # + # validates_presence_of :name + # validates_inclusion_of :age, in: 0..99 + # end + # + # Person.validators_on(:name) + # # => [ + # # #, + # # ] + def validators_on(*attributes) + attributes.flat_map do |attribute| + _validators[attribute.to_sym] + end + end + + # Returns +true+ if +attribute+ is an attribute method, +false+ otherwise. + # + # class Person + # include ActiveModel::Validations + # + # attr_accessor :name + # end + # + # User.attribute_method?(:name) # => true + # User.attribute_method?(:age) # => false + def attribute_method?(attribute) + method_defined?(attribute) + end + + # Copy validators on inheritance. + def inherited(base) #:nodoc: + dup = _validators.dup + base._validators = dup.each { |k, v| dup[k] = v.dup } + super + end + end + + # Clean the +Errors+ object if instance is duped. + def initialize_dup(other) #:nodoc: + @errors = nil + super + end + + # Returns the +Errors+ object that holds all information about attribute + # error messages. + # + # class Person + # include ActiveModel::Validations + # + # attr_accessor :name + # validates_presence_of :name + # end + # + # person = Person.new + # person.valid? # => false + # person.errors # => # + def errors + @errors ||= Errors.new(self) + end + + # Runs all the specified validations and returns +true+ if no errors were + # added otherwise +false+. + # + # class Person + # include ActiveModel::Validations + # + # attr_accessor :name + # validates_presence_of :name + # end + # + # person = Person.new + # person.name = '' + # person.valid? # => false + # person.name = 'david' + # person.valid? # => true + # + # Context can optionally be supplied to define which callbacks to test + # against (the context is defined on the validations using :on). + # + # class Person + # include ActiveModel::Validations + # + # attr_accessor :name + # validates_presence_of :name, on: :new + # end + # + # person = Person.new + # person.valid? # => true + # person.valid?(:new) # => false + def valid?(context = nil) + current_context, self.validation_context = validation_context, context + errors.clear + run_validations! + ensure + self.validation_context = current_context + end + + alias_method :validate, :valid? + + # Performs the opposite of valid?. Returns +true+ if errors were + # added, +false+ otherwise. + # + # class Person + # include ActiveModel::Validations + # + # attr_accessor :name + # validates_presence_of :name + # end + # + # person = Person.new + # person.name = '' + # person.invalid? # => true + # person.name = 'david' + # person.invalid? # => false + # + # Context can optionally be supplied to define which callbacks to test + # against (the context is defined on the validations using :on). + # + # class Person + # include ActiveModel::Validations + # + # attr_accessor :name + # validates_presence_of :name, on: :new + # end + # + # person = Person.new + # person.invalid? # => false + # person.invalid?(:new) # => true + def invalid?(context = nil) + !valid?(context) + end + + # Runs all the validations within the specified context. Returns +true+ if + # no errors are found, raises +ValidationError+ otherwise. + # + # Validations with no :on option will run no matter the context. Validations with + # some :on option will only run in the specified context. + def validate!(context = nil) + valid?(context) || raise_validation_error + end + + # Hook method defining how an attribute value should be retrieved. By default + # this is assumed to be an instance named after the attribute. Override this + # method in subclasses should you need to retrieve the value for a given + # attribute differently: + # + # class MyClass + # include ActiveModel::Validations + # + # def initialize(data = {}) + # @data = data + # end + # + # def read_attribute_for_validation(key) + # @data[key] + # end + # end + alias :read_attribute_for_validation :send + + private + + def run_validations! + _run_validate_callbacks + errors.empty? + end + + def raise_validation_error # :doc: + raise(ValidationError.new(self)) + end + end + + # = Active Model ValidationError + # + # Raised by validate! when the model is invalid. Use the + # +model+ method to retrieve the record which did not validate. + # + # begin + # complex_operation_that_internally_calls_validate! + # rescue ActiveModel::ValidationError => invalid + # puts invalid.model.errors + # end + class ValidationError < StandardError + attr_reader :model + + def initialize(model) + @model = model + errors = @model.errors.full_messages.join(", ") + super(I18n.t(:"#{@model.class.i18n_scope}.errors.messages.model_invalid", errors: errors, default: :"errors.messages.model_invalid")) + end + end +end + +Dir[File.expand_path("validations/*.rb", __dir__)].each { |file| require file } diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/absence.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/absence.rb new file mode 100644 index 00000000..385d9f27 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/absence.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module ActiveModel + module Validations + # == \Active \Model Absence Validator + class AbsenceValidator < EachValidator #:nodoc: + def validate_each(record, attr_name, value) + record.errors.add(attr_name, :present, options) if value.present? + end + end + + module HelperMethods + # Validates that the specified attributes are blank (as defined by + # Object#blank?). Happens by default on save. + # + # class Person < ActiveRecord::Base + # validates_absence_of :first_name + # end + # + # The first_name attribute must be in the object and it must be blank. + # + # Configuration options: + # * :message - A custom error message (default is: "must be blank"). + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. + # See ActiveModel::Validations#validates for more information + def validates_absence_of(*attr_names) + validates_with AbsenceValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/acceptance.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/acceptance.rb new file mode 100644 index 00000000..f35e4dec --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/acceptance.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +module ActiveModel + module Validations + class AcceptanceValidator < EachValidator # :nodoc: + def initialize(options) + super({ allow_nil: true, accept: ["1", true] }.merge!(options)) + setup!(options[:class]) + end + + def validate_each(record, attribute, value) + unless acceptable_option?(value) + record.errors.add(attribute, :accepted, options.except(:accept, :allow_nil)) + end + end + + private + + def setup!(klass) + klass.include(LazilyDefineAttributes.new(AttributeDefinition.new(attributes))) + end + + def acceptable_option?(value) + Array(options[:accept]).include?(value) + end + + class LazilyDefineAttributes < Module + def initialize(attribute_definition) + define_method(:respond_to_missing?) do |method_name, include_private = false| + super(method_name, include_private) || attribute_definition.matches?(method_name) + end + + define_method(:method_missing) do |method_name, *args, &block| + if attribute_definition.matches?(method_name) + attribute_definition.define_on(self.class) + send(method_name, *args, &block) + else + super(method_name, *args, &block) + end + end + end + end + + class AttributeDefinition + def initialize(attributes) + @attributes = attributes.map(&:to_s) + end + + def matches?(method_name) + attr_name = convert_to_reader_name(method_name) + attributes.include?(attr_name) + end + + def define_on(klass) + attr_readers = attributes.reject { |name| klass.attribute_method?(name) } + attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") } + klass.send(:attr_reader, *attr_readers) + klass.send(:attr_writer, *attr_writers) + end + + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected + + attr_reader :attributes + + private + + def convert_to_reader_name(method_name) + method_name.to_s.chomp("=") + end + end + end + + module HelperMethods + # Encapsulates the pattern of wanting to validate the acceptance of a + # terms of service check box (or similar agreement). + # + # class Person < ActiveRecord::Base + # validates_acceptance_of :terms_of_service + # validates_acceptance_of :eula, message: 'must be abided' + # end + # + # If the database column does not exist, the +terms_of_service+ attribute + # is entirely virtual. This check is performed only if +terms_of_service+ + # is not +nil+ and by default on save. + # + # Configuration options: + # * :message - A custom error message (default is: "must be + # accepted"). + # * :accept - Specifies a value that is considered accepted. + # Also accepts an array of possible values. The default value is + # an array ["1", true], which makes it easy to relate to an HTML + # checkbox. This should be set to, or include, +true+ if you are validating + # a database column, since the attribute is typecast from "1" to +true+ + # before validation. + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. + # See ActiveModel::Validations#validates for more information. + def validates_acceptance_of(*attr_names) + validates_with AcceptanceValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/callbacks.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/callbacks.rb new file mode 100644 index 00000000..887d31ae --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/callbacks.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +module ActiveModel + module Validations + # == Active \Model \Validation \Callbacks + # + # Provides an interface for any class to have +before_validation+ and + # +after_validation+ callbacks. + # + # First, include ActiveModel::Validations::Callbacks from the class you are + # creating: + # + # class MyModel + # include ActiveModel::Validations::Callbacks + # + # before_validation :do_stuff_before_validation + # after_validation :do_stuff_after_validation + # end + # + # Like other before_* callbacks if +before_validation+ throws + # +:abort+ then valid? will not be called. + module Callbacks + extend ActiveSupport::Concern + + included do + include ActiveSupport::Callbacks + define_callbacks :validation, + skip_after_callbacks_if_terminated: true, + scope: [:kind, :name] + end + + module ClassMethods + # Defines a callback that will get called right before validation. + # + # class Person + # include ActiveModel::Validations + # include ActiveModel::Validations::Callbacks + # + # attr_accessor :name + # + # validates_length_of :name, maximum: 6 + # + # before_validation :remove_whitespaces + # + # private + # + # def remove_whitespaces + # name.strip! + # end + # end + # + # person = Person.new + # person.name = ' bob ' + # person.valid? # => true + # person.name # => "bob" + def before_validation(*args, &block) + options = args.extract_options! + + if options.key?(:on) + options = options.dup + options[:on] = Array(options[:on]) + options[:if] = Array(options[:if]) + options[:if].unshift ->(o) { + !(options[:on] & Array(o.validation_context)).empty? + } + end + + set_callback(:validation, :before, *args, options, &block) + end + + # Defines a callback that will get called right after validation. + # + # class Person + # include ActiveModel::Validations + # include ActiveModel::Validations::Callbacks + # + # attr_accessor :name, :status + # + # validates_presence_of :name + # + # after_validation :set_status + # + # private + # + # def set_status + # self.status = errors.empty? + # end + # end + # + # person = Person.new + # person.name = '' + # person.valid? # => false + # person.status # => false + # person.name = 'bob' + # person.valid? # => true + # person.status # => true + def after_validation(*args, &block) + options = args.extract_options! + options = options.dup + options[:prepend] = true + + if options.key?(:on) + options[:on] = Array(options[:on]) + options[:if] = Array(options[:if]) + options[:if].unshift ->(o) { + !(options[:on] & Array(o.validation_context)).empty? + } + end + + set_callback(:validation, :after, *args, options, &block) + end + end + + private + + # Overwrite run validations to include callbacks. + def run_validations! + _run_validation_callbacks { super } + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/clusivity.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/clusivity.rb new file mode 100644 index 00000000..0b9b5ce6 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/clusivity.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require "active_support/core_ext/range" + +module ActiveModel + module Validations + module Clusivity #:nodoc: + ERROR_MESSAGE = "An object with the method #include? or a proc, lambda or symbol is required, " \ + "and must be supplied as the :in (or :within) option of the configuration hash" + + def check_validity! + unless delimiter.respond_to?(:include?) || delimiter.respond_to?(:call) || delimiter.respond_to?(:to_sym) + raise ArgumentError, ERROR_MESSAGE + end + end + + private + + def include?(record, value) + members = if delimiter.respond_to?(:call) + delimiter.call(record) + elsif delimiter.respond_to?(:to_sym) + record.send(delimiter) + else + delimiter + end + + members.send(inclusion_method(members), value) + end + + def delimiter + @delimiter ||= options[:in] || options[:within] + end + + # In Ruby 2.2 Range#include? on non-number-or-time-ish ranges checks all + # possible values in the range for equality, which is slower but more accurate. + # Range#cover? uses the previous logic of comparing a value with the range + # endpoints, which is fast but is only accurate on Numeric, Time, Date, + # or DateTime ranges. + def inclusion_method(enumerable) + if enumerable.is_a? Range + case enumerable.first + when Numeric, Time, DateTime, Date + :cover? + else + :include? + end + else + :include? + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/confirmation.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/confirmation.rb new file mode 100644 index 00000000..1b5d5b09 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/confirmation.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +module ActiveModel + module Validations + class ConfirmationValidator < EachValidator # :nodoc: + def initialize(options) + super({ case_sensitive: true }.merge!(options)) + setup!(options[:class]) + end + + def validate_each(record, attribute, value) + unless (confirmed = record.send("#{attribute}_confirmation")).nil? + unless confirmation_value_equal?(record, attribute, value, confirmed) + human_attribute_name = record.class.human_attribute_name(attribute) + record.errors.add(:"#{attribute}_confirmation", :confirmation, options.except(:case_sensitive).merge!(attribute: human_attribute_name)) + end + end + end + + private + def setup!(klass) + klass.send(:attr_reader, *attributes.map do |attribute| + :"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation") + end.compact) + + klass.send(:attr_writer, *attributes.map do |attribute| + :"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation=") + end.compact) + end + + def confirmation_value_equal?(record, attribute, value, confirmed) + if !options[:case_sensitive] && value.is_a?(String) + value.casecmp(confirmed) == 0 + else + value == confirmed + end + end + end + + module HelperMethods + # Encapsulates the pattern of wanting to validate a password or email + # address field with a confirmation. + # + # Model: + # class Person < ActiveRecord::Base + # validates_confirmation_of :user_name, :password + # validates_confirmation_of :email_address, + # message: 'should match confirmation' + # end + # + # View: + # <%= password_field "person", "password" %> + # <%= password_field "person", "password_confirmation" %> + # + # The added +password_confirmation+ attribute is virtual; it exists only + # as an in-memory attribute for validating the password. To achieve this, + # the validation adds accessors to the model for the confirmation + # attribute. + # + # NOTE: This check is performed only if +password_confirmation+ is not + # +nil+. To require confirmation, make sure to add a presence check for + # the confirmation attribute: + # + # validates_presence_of :password_confirmation, if: :password_changed? + # + # Configuration options: + # * :message - A custom error message (default is: "doesn't match + # %{translated_attribute_name}"). + # * :case_sensitive - Looks for an exact match. Ignored by + # non-text columns (+true+ by default). + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. + # See ActiveModel::Validations#validates for more information + def validates_confirmation_of(*attr_names) + validates_with ConfirmationValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/exclusion.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/exclusion.rb new file mode 100644 index 00000000..3be7ab6b --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/exclusion.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require "active_model/validations/clusivity" + +module ActiveModel + module Validations + class ExclusionValidator < EachValidator # :nodoc: + include Clusivity + + def validate_each(record, attribute, value) + if include?(record, value) + record.errors.add(attribute, :exclusion, options.except(:in, :within).merge!(value: value)) + end + end + end + + module HelperMethods + # Validates that the value of the specified attribute is not in a + # particular enumerable object. + # + # class Person < ActiveRecord::Base + # validates_exclusion_of :username, in: %w( admin superuser ), message: "You don't belong here" + # validates_exclusion_of :age, in: 30..60, message: 'This site is only for under 30 and over 60' + # validates_exclusion_of :format, in: %w( mov avi ), message: "extension %{value} is not allowed" + # validates_exclusion_of :password, in: ->(person) { [person.username, person.first_name] }, + # message: 'should not be the same as your username or first name' + # validates_exclusion_of :karma, in: :reserved_karmas + # end + # + # Configuration options: + # * :in - An enumerable object of items that the value shouldn't + # be part of. This can be supplied as a proc, lambda or symbol which returns an + # enumerable. If the enumerable is a numerical, time or datetime range the test + # is performed with Range#cover?, otherwise with include?. When + # using a proc or lambda the instance under validation is passed as an argument. + # * :within - A synonym(or alias) for :in + # Range#cover?, otherwise with include?. + # * :message - Specifies a custom error message (default is: "is + # reserved"). + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. + # See ActiveModel::Validations#validates for more information + def validates_exclusion_of(*attr_names) + validates_with ExclusionValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/format.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/format.rb new file mode 100644 index 00000000..7c3f0914 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/format.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +module ActiveModel + module Validations + class FormatValidator < EachValidator # :nodoc: + def validate_each(record, attribute, value) + if options[:with] + regexp = option_call(record, :with) + record_error(record, attribute, :with, value) if value.to_s !~ regexp + elsif options[:without] + regexp = option_call(record, :without) + record_error(record, attribute, :without, value) if regexp.match?(value.to_s) + end + end + + def check_validity! + unless options.include?(:with) ^ options.include?(:without) # ^ == xor, or "exclusive or" + raise ArgumentError, "Either :with or :without must be supplied (but not both)" + end + + check_options_validity :with + check_options_validity :without + end + + private + + def option_call(record, name) + option = options[name] + option.respond_to?(:call) ? option.call(record) : option + end + + def record_error(record, attribute, name, value) + record.errors.add(attribute, :invalid, options.except(name).merge!(value: value)) + end + + def check_options_validity(name) + if option = options[name] + if option.is_a?(Regexp) + if options[:multiline] != true && regexp_using_multiline_anchors?(option) + raise ArgumentError, "The provided regular expression is using multiline anchors (^ or $), " \ + "which may present a security risk. Did you mean to use \\A and \\z, or forgot to add the " \ + ":multiline => true option?" + end + elsif !option.respond_to?(:call) + raise ArgumentError, "A regular expression or a proc or lambda must be supplied as :#{name}" + end + end + end + + def regexp_using_multiline_anchors?(regexp) + source = regexp.source + source.start_with?("^") || (source.end_with?("$") && !source.end_with?("\\$")) + end + end + + module HelperMethods + # Validates whether the value of the specified attribute is of the correct + # form, going by the regular expression provided. You can require that the + # attribute matches the regular expression: + # + # class Person < ActiveRecord::Base + # validates_format_of :email, with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create + # end + # + # Alternatively, you can require that the specified attribute does _not_ + # match the regular expression: + # + # class Person < ActiveRecord::Base + # validates_format_of :email, without: /NOSPAM/ + # end + # + # You can also provide a proc or lambda which will determine the regular + # expression that will be used to validate the attribute. + # + # class Person < ActiveRecord::Base + # # Admin can have number as a first letter in their screen name + # validates_format_of :screen_name, + # with: ->(person) { person.admin? ? /\A[a-z0-9][a-z0-9_\-]*\z/i : /\A[a-z][a-z0-9_\-]*\z/i } + # end + # + # Note: use \A and \z to match the start and end of the + # string, ^ and $ match the start/end of a line. + # + # Due to frequent misuse of ^ and $, you need to pass + # the multiline: true option in case you use any of these two + # anchors in the provided regular expression. In most cases, you should be + # using \A and \z. + # + # You must pass either :with or :without as an option. + # In addition, both must be a regular expression or a proc or lambda, or + # else an exception will be raised. + # + # Configuration options: + # * :message - A custom error message (default is: "is invalid"). + # * :with - Regular expression that if the attribute matches will + # result in a successful validation. This can be provided as a proc or + # lambda returning regular expression which will be called at runtime. + # * :without - Regular expression that if the attribute does not + # match will result in a successful validation. This can be provided as + # a proc or lambda returning regular expression which will be called at + # runtime. + # * :multiline - Set to true if your regular expression contains + # anchors that match the beginning or end of lines as opposed to the + # beginning or end of the string. These anchors are ^ and $. + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. + # See ActiveModel::Validations#validates for more information + def validates_format_of(*attr_names) + validates_with FormatValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/helper_methods.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/helper_methods.rb new file mode 100644 index 00000000..730173f2 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/helper_methods.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActiveModel + module Validations + module HelperMethods # :nodoc: + private + def _merge_attributes(attr_names) + options = attr_names.extract_options!.symbolize_keys + attr_names.flatten! + options[:attributes] = attr_names + options + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/inclusion.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/inclusion.rb new file mode 100644 index 00000000..3104e7e3 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/inclusion.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require "active_model/validations/clusivity" + +module ActiveModel + module Validations + class InclusionValidator < EachValidator # :nodoc: + include Clusivity + + def validate_each(record, attribute, value) + unless include?(record, value) + record.errors.add(attribute, :inclusion, options.except(:in, :within).merge!(value: value)) + end + end + end + + module HelperMethods + # Validates whether the value of the specified attribute is available in a + # particular enumerable object. + # + # class Person < ActiveRecord::Base + # validates_inclusion_of :gender, in: %w( m f ) + # validates_inclusion_of :age, in: 0..99 + # validates_inclusion_of :format, in: %w( jpg gif png ), message: "extension %{value} is not included in the list" + # validates_inclusion_of :states, in: ->(person) { STATES[person.country] } + # validates_inclusion_of :karma, in: :available_karmas + # end + # + # Configuration options: + # * :in - An enumerable object of available items. This can be + # supplied as a proc, lambda or symbol which returns an enumerable. If the + # enumerable is a numerical, time or datetime range the test is performed + # with Range#cover?, otherwise with include?. When using + # a proc or lambda the instance under validation is passed as an argument. + # * :within - A synonym(or alias) for :in + # * :message - Specifies a custom error message (default is: "is + # not included in the list"). + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. + # See ActiveModel::Validations#validates for more information + def validates_inclusion_of(*attr_names) + validates_with InclusionValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/length.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/length.rb new file mode 100644 index 00000000..d6c80b2c --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/length.rb @@ -0,0 +1,129 @@ +# frozen_string_literal: true + +module ActiveModel + module Validations + class LengthValidator < EachValidator # :nodoc: + MESSAGES = { is: :wrong_length, minimum: :too_short, maximum: :too_long }.freeze + CHECKS = { is: :==, minimum: :>=, maximum: :<= }.freeze + + RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :too_short, :too_long] + + def initialize(options) + if range = (options.delete(:in) || options.delete(:within)) + raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range) + options[:minimum], options[:maximum] = range.min, range.max + end + + if options[:allow_blank] == false && options[:minimum].nil? && options[:is].nil? + options[:minimum] = 1 + end + + super + end + + def check_validity! + keys = CHECKS.keys & options.keys + + if keys.empty? + raise ArgumentError, "Range unspecified. Specify the :in, :within, :maximum, :minimum, or :is option." + end + + keys.each do |key| + value = options[key] + + unless (value.is_a?(Integer) && value >= 0) || value == Float::INFINITY || value.is_a?(Symbol) || value.is_a?(Proc) + raise ArgumentError, ":#{key} must be a nonnegative Integer, Infinity, Symbol, or Proc" + end + end + end + + def validate_each(record, attribute, value) + value_length = value.respond_to?(:length) ? value.length : value.to_s.length + errors_options = options.except(*RESERVED_OPTIONS) + + CHECKS.each do |key, validity_check| + next unless check_value = options[key] + + if !value.nil? || skip_nil_check?(key) + case check_value + when Proc + check_value = check_value.call(record) + when Symbol + check_value = record.send(check_value) + end + next if value_length.send(validity_check, check_value) + end + + errors_options[:count] = check_value + + default_message = options[MESSAGES[key]] + errors_options[:message] ||= default_message if default_message + + record.errors.add(attribute, MESSAGES[key], errors_options) + end + end + + private + def skip_nil_check?(key) + key == :maximum && options[:allow_nil].nil? && options[:allow_blank].nil? + end + end + + module HelperMethods + # Validates that the specified attributes match the length restrictions + # supplied. Only one constraint option can be used at a time apart from + # +:minimum+ and +:maximum+ that can be combined together: + # + # class Person < ActiveRecord::Base + # validates_length_of :first_name, maximum: 30 + # validates_length_of :last_name, maximum: 30, message: "less than 30 if you don't mind" + # validates_length_of :fax, in: 7..32, allow_nil: true + # validates_length_of :phone, in: 7..32, allow_blank: true + # validates_length_of :user_name, within: 6..20, too_long: 'pick a shorter name', too_short: 'pick a longer name' + # validates_length_of :zip_code, minimum: 5, too_short: 'please enter at least 5 characters' + # validates_length_of :smurf_leader, is: 4, message: "papa is spelled with 4 characters... don't play me." + # validates_length_of :words_in_essay, minimum: 100, too_short: 'Your essay must be at least 100 words.' + # + # private + # + # def words_in_essay + # essay.scan(/\w+/) + # end + # end + # + # Constraint options: + # + # * :minimum - The minimum size of the attribute. + # * :maximum - The maximum size of the attribute. Allows +nil+ by + # default if not used with +:minimum+. + # * :is - The exact size of the attribute. + # * :within - A range specifying the minimum and maximum size of + # the attribute. + # * :in - A synonym (or alias) for :within. + # + # Other options: + # + # * :allow_nil - Attribute may be +nil+; skip validation. + # * :allow_blank - Attribute may be blank; skip validation. + # * :too_long - The error message if the attribute goes over the + # maximum (default is: "is too long (maximum is %{count} characters)"). + # * :too_short - The error message if the attribute goes under the + # minimum (default is: "is too short (minimum is %{count} characters)"). + # * :wrong_length - The error message if using the :is + # method and the attribute is the wrong size (default is: "is the wrong + # length (should be %{count} characters)"). + # * :message - The error message to use for a :minimum, + # :maximum, or :is violation. An alias of the appropriate + # too_long/too_short/wrong_length message. + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+ and +:strict+. + # See ActiveModel::Validations#validates for more information + def validates_length_of(*attr_names) + validates_with LengthValidator, _merge_attributes(attr_names) + end + + alias_method :validates_size_of, :validates_length_of + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/numericality.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/numericality.rb new file mode 100644 index 00000000..c217612d --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/numericality.rb @@ -0,0 +1,189 @@ +# frozen_string_literal: true + +require "bigdecimal/util" + +module ActiveModel + module Validations + class NumericalityValidator < EachValidator # :nodoc: + CHECKS = { greater_than: :>, greater_than_or_equal_to: :>=, + equal_to: :==, less_than: :<, less_than_or_equal_to: :<=, + odd: :odd?, even: :even?, other_than: :!= }.freeze + + RESERVED_OPTIONS = CHECKS.keys + [:only_integer] + + INTEGER_REGEX = /\A[+-]?\d+\z/ + + def check_validity! + keys = CHECKS.keys - [:odd, :even] + options.slice(*keys).each do |option, value| + unless value.is_a?(Numeric) || value.is_a?(Proc) || value.is_a?(Symbol) + raise ArgumentError, ":#{option} must be a number, a symbol or a proc" + end + end + end + + def validate_each(record, attr_name, value) + came_from_user = :"#{attr_name}_came_from_user?" + + if record.respond_to?(came_from_user) + if record.public_send(came_from_user) + raw_value = record.read_attribute_before_type_cast(attr_name) + elsif record.respond_to?(:read_attribute) + raw_value = record.read_attribute(attr_name) + end + else + before_type_cast = :"#{attr_name}_before_type_cast" + if record.respond_to?(before_type_cast) + raw_value = record.public_send(before_type_cast) + end + end + raw_value ||= value + + if record_attribute_changed_in_place?(record, attr_name) + raw_value = value + end + + unless is_number?(raw_value) + record.errors.add(attr_name, :not_a_number, filtered_options(raw_value)) + return + end + + if allow_only_integer?(record) && !is_integer?(raw_value) + record.errors.add(attr_name, :not_an_integer, filtered_options(raw_value)) + return + end + + value = parse_as_number(raw_value) + + options.slice(*CHECKS.keys).each do |option, option_value| + case option + when :odd, :even + unless value.to_i.send(CHECKS[option]) + record.errors.add(attr_name, option, filtered_options(value)) + end + else + case option_value + when Proc + option_value = option_value.call(record) + when Symbol + option_value = record.send(option_value) + end + + option_value = parse_as_number(option_value) + + unless value.send(CHECKS[option], option_value) + record.errors.add(attr_name, option, filtered_options(value).merge!(count: option_value)) + end + end + end + end + + private + + def is_number?(raw_value) + !parse_as_number(raw_value).nil? + rescue ArgumentError, TypeError + false + end + + def parse_as_number(raw_value) + if raw_value.is_a?(Float) + raw_value.to_d + elsif raw_value.is_a?(Numeric) + raw_value + elsif is_integer?(raw_value) + raw_value.to_i + elsif !is_hexadecimal_literal?(raw_value) + Kernel.Float(raw_value).to_d + end + end + + def is_integer?(raw_value) + INTEGER_REGEX === raw_value.to_s + end + + def is_hexadecimal_literal?(raw_value) + /\A0[xX]/ === raw_value.to_s + end + + def filtered_options(value) + filtered = options.except(*RESERVED_OPTIONS) + filtered[:value] = value + filtered + end + + def allow_only_integer?(record) + case options[:only_integer] + when Symbol + record.send(options[:only_integer]) + when Proc + options[:only_integer].call(record) + else + options[:only_integer] + end + end + + def record_attribute_changed_in_place?(record, attr_name) + record.respond_to?(:attribute_changed_in_place?) && + record.attribute_changed_in_place?(attr_name.to_s) + end + end + + module HelperMethods + # Validates whether the value of the specified attribute is numeric by + # trying to convert it to a float with Kernel.Float (if only_integer + # is +false+) or applying it to the regular expression /\A[\+\-]?\d+\z/ + # (if only_integer is set to +true+). + # + # class Person < ActiveRecord::Base + # validates_numericality_of :value, on: :create + # end + # + # Configuration options: + # * :message - A custom error message (default is: "is not a number"). + # * :only_integer - Specifies whether the value has to be an + # integer, e.g. an integral value (default is +false+). + # * :allow_nil - Skip validation if attribute is +nil+ (default is + # +false+). Notice that for Integer and Float columns empty strings are + # converted to +nil+. + # * :greater_than - Specifies the value must be greater than the + # supplied value. + # * :greater_than_or_equal_to - Specifies the value must be + # greater than or equal the supplied value. + # * :equal_to - Specifies the value must be equal to the supplied + # value. + # * :less_than - Specifies the value must be less than the + # supplied value. + # * :less_than_or_equal_to - Specifies the value must be less + # than or equal the supplied value. + # * :other_than - Specifies the value must be other than the + # supplied value. + # * :odd - Specifies the value must be an odd number. + # * :even - Specifies the value must be an even number. + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ . + # See ActiveModel::Validations#validates for more information + # + # The following checks can also be supplied with a proc or a symbol which + # corresponds to a method: + # + # * :greater_than + # * :greater_than_or_equal_to + # * :equal_to + # * :less_than + # * :less_than_or_equal_to + # * :only_integer + # + # For example: + # + # class Person < ActiveRecord::Base + # validates_numericality_of :width, less_than: ->(person) { person.height } + # validates_numericality_of :width, greater_than: :minimum_weight + # end + def validates_numericality_of(*attr_names) + validates_with NumericalityValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/presence.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/presence.rb new file mode 100644 index 00000000..8787a75a --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/presence.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module ActiveModel + module Validations + class PresenceValidator < EachValidator # :nodoc: + def validate_each(record, attr_name, value) + record.errors.add(attr_name, :blank, options) if value.blank? + end + end + + module HelperMethods + # Validates that the specified attributes are not blank (as defined by + # Object#blank?). Happens by default on save. + # + # class Person < ActiveRecord::Base + # validates_presence_of :first_name + # end + # + # The first_name attribute must be in the object and it cannot be blank. + # + # If you want to validate the presence of a boolean field (where the real + # values are +true+ and +false+), you will want to use + # validates_inclusion_of :field_name, in: [true, false]. + # + # This is due to the way Object#blank? handles boolean values: + # false.blank? # => true. + # + # Configuration options: + # * :message - A custom error message (default is: "can't be blank"). + # + # There is also a list of default options supported by every validator: + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. + # See ActiveModel::Validations#validates for more information + def validates_presence_of(*attr_names) + validates_with PresenceValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/validates.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/validates.rb new file mode 100644 index 00000000..e28e7e92 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/validates.rb @@ -0,0 +1,174 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/slice" + +module ActiveModel + module Validations + module ClassMethods + # This method is a shortcut to all default validators and any custom + # validator classes ending in 'Validator'. Note that Rails default + # validators can be overridden inside specific classes by creating + # custom validator classes in their place such as PresenceValidator. + # + # Examples of using the default rails validators: + # + # validates :terms, acceptance: true + # validates :password, confirmation: true + # validates :username, exclusion: { in: %w(admin superuser) } + # validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create } + # validates :age, inclusion: { in: 0..9 } + # validates :first_name, length: { maximum: 30 } + # validates :age, numericality: true + # validates :username, presence: true + # + # The power of the +validates+ method comes when using custom validators + # and default validators in one call for a given attribute. + # + # class EmailValidator < ActiveModel::EachValidator + # def validate_each(record, attribute, value) + # record.errors.add attribute, (options[:message] || "is not an email") unless + # value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i + # end + # end + # + # class Person + # include ActiveModel::Validations + # attr_accessor :name, :email + # + # validates :name, presence: true, length: { maximum: 100 } + # validates :email, presence: true, email: true + # end + # + # Validator classes may also exist within the class being validated + # allowing custom modules of validators to be included as needed. + # + # class Film + # include ActiveModel::Validations + # + # class TitleValidator < ActiveModel::EachValidator + # def validate_each(record, attribute, value) + # record.errors.add attribute, "must start with 'the'" unless value =~ /\Athe/i + # end + # end + # + # validates :name, title: true + # end + # + # Additionally validator classes may be in another namespace and still + # used within any class. + # + # validates :name, :'film/title' => true + # + # The validators hash can also handle regular expressions, ranges, arrays + # and strings in shortcut form. + # + # validates :email, format: /@/ + # validates :gender, inclusion: %w(male female) + # validates :password, length: 6..20 + # + # When using shortcut form, ranges and arrays are passed to your + # validator's initializer as options[:in] while other types + # including regular expressions and strings are passed as options[:with]. + # + # There is also a list of options that could be used along with validators: + # + # * :on - Specifies the contexts where this validation is active. + # Runs in all validation contexts by default +nil+. You can pass a symbol + # or an array of symbols. (e.g. on: :create or + # on: :custom_validation_context or + # on: [:create, :custom_validation_context]) + # * :if - Specifies a method, proc or string to call to determine + # if the validation should occur (e.g. if: :allow_validation, + # or if: Proc.new { |user| user.signup_step > 2 }). The method, + # proc or string should return or evaluate to a +true+ or +false+ value. + # * :unless - Specifies a method, proc or string to call to determine + # if the validation should not occur (e.g. unless: :skip_validation, + # or unless: Proc.new { |user| user.signup_step <= 2 }). The + # method, proc or string should return or evaluate to a +true+ or + # +false+ value. + # * :allow_nil - Skip validation if the attribute is +nil+. + # * :allow_blank - Skip validation if the attribute is blank. + # * :strict - If the :strict option is set to true + # will raise ActiveModel::StrictValidationFailed instead of adding the error. + # :strict option can also be set to any other exception. + # + # Example: + # + # validates :password, presence: true, confirmation: true, if: :password_required? + # validates :token, length: 24, strict: TokenLengthException + # + # + # Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+, +:strict+ + # and +:message+ can be given to one specific validator, as a hash: + # + # validates :password, presence: { if: :password_required?, message: 'is forgotten.' }, confirmation: true + def validates(*attributes) + defaults = attributes.extract_options!.dup + validations = defaults.slice!(*_validates_default_keys) + + raise ArgumentError, "You need to supply at least one attribute" if attributes.empty? + raise ArgumentError, "You need to supply at least one validation" if validations.empty? + + defaults[:attributes] = attributes + + validations.each do |key, options| + next unless options + key = "#{key.to_s.camelize}Validator" + + begin + validator = key.include?("::".freeze) ? key.constantize : const_get(key) + rescue NameError + raise ArgumentError, "Unknown validator: '#{key}'" + end + + validates_with(validator, defaults.merge(_parse_validates_options(options))) + end + end + + # This method is used to define validations that cannot be corrected by end + # users and are considered exceptional. So each validator defined with bang + # or :strict option set to true will always raise + # ActiveModel::StrictValidationFailed instead of adding error + # when validation fails. See validates for more information about + # the validation itself. + # + # class Person + # include ActiveModel::Validations + # + # attr_accessor :name + # validates! :name, presence: true + # end + # + # person = Person.new + # person.name = '' + # person.valid? + # # => ActiveModel::StrictValidationFailed: Name can't be blank + def validates!(*attributes) + options = attributes.extract_options! + options[:strict] = true + validates(*(attributes << options)) + end + + private + + # When creating custom validators, it might be useful to be able to specify + # additional default keys. This can be done by overwriting this method. + def _validates_default_keys + [:if, :unless, :on, :allow_blank, :allow_nil, :strict] + end + + def _parse_validates_options(options) + case options + when TrueClass + {} + when Hash + options + when Range, Array + { in: options } + else + { with: options } + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/with.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/with.rb new file mode 100644 index 00000000..d777ac83 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validations/with.rb @@ -0,0 +1,147 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array/extract_options" + +module ActiveModel + module Validations + class WithValidator < EachValidator # :nodoc: + def validate_each(record, attr, val) + method_name = options[:with] + + if record.method(method_name).arity == 0 + record.send method_name + else + record.send method_name, attr + end + end + end + + module ClassMethods + # Passes the record off to the class or classes specified and allows them + # to add errors based on more complex conditions. + # + # class Person + # include ActiveModel::Validations + # validates_with MyValidator + # end + # + # class MyValidator < ActiveModel::Validator + # def validate(record) + # if some_complex_logic + # record.errors.add :base, 'This record is invalid' + # end + # end + # + # private + # def some_complex_logic + # # ... + # end + # end + # + # You may also pass it multiple classes, like so: + # + # class Person + # include ActiveModel::Validations + # validates_with MyValidator, MyOtherValidator, on: :create + # end + # + # Configuration options: + # * :on - Specifies the contexts where this validation is active. + # Runs in all validation contexts by default +nil+. You can pass a symbol + # or an array of symbols. (e.g. on: :create or + # on: :custom_validation_context or + # on: [:create, :custom_validation_context]) + # * :if - Specifies a method, proc or string to call to determine + # if the validation should occur (e.g. if: :allow_validation, + # or if: Proc.new { |user| user.signup_step > 2 }). + # The method, proc or string should return or evaluate to a +true+ or + # +false+ value. + # * :unless - Specifies a method, proc or string to call to + # determine if the validation should not occur + # (e.g. unless: :skip_validation, or + # unless: Proc.new { |user| user.signup_step <= 2 }). + # The method, proc or string should return or evaluate to a +true+ or + # +false+ value. + # * :strict - Specifies whether validation should be strict. + # See ActiveModel::Validations#validates! for more information. + # + # If you pass any additional configuration options, they will be passed + # to the class and available as +options+: + # + # class Person + # include ActiveModel::Validations + # validates_with MyValidator, my_custom_key: 'my custom value' + # end + # + # class MyValidator < ActiveModel::Validator + # def validate(record) + # options[:my_custom_key] # => "my custom value" + # end + # end + def validates_with(*args, &block) + options = args.extract_options! + options[:class] = self + + args.each do |klass| + validator = klass.new(options, &block) + + if validator.respond_to?(:attributes) && !validator.attributes.empty? + validator.attributes.each do |attribute| + _validators[attribute.to_sym] << validator + end + else + _validators[nil] << validator + end + + validate(validator, options) + end + end + end + + # Passes the record off to the class or classes specified and allows them + # to add errors based on more complex conditions. + # + # class Person + # include ActiveModel::Validations + # + # validate :instance_validations + # + # def instance_validations + # validates_with MyValidator + # end + # end + # + # Please consult the class method documentation for more information on + # creating your own validator. + # + # You may also pass it multiple classes, like so: + # + # class Person + # include ActiveModel::Validations + # + # validate :instance_validations, on: :create + # + # def instance_validations + # validates_with MyValidator, MyOtherValidator + # end + # end + # + # Standard configuration options (:on, :if and + # :unless), which are available on the class version of + # +validates_with+, should instead be placed on the +validates+ method + # as these are applied and tested in the callback. + # + # If you pass any additional configuration options, they will be passed + # to the class and available as +options+, please refer to the + # class version of this method for more information. + def validates_with(*args, &block) + options = args.extract_options! + options[:class] = self.class + + args.each do |klass| + validator = klass.new(options, &block) + validator.validate(self) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validator.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validator.rb new file mode 100644 index 00000000..e17c3ca7 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/validator.rb @@ -0,0 +1,183 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/anonymous" + +module ActiveModel + # == Active \Model \Validator + # + # A simple base class that can be used along with + # ActiveModel::Validations::ClassMethods.validates_with + # + # class Person + # include ActiveModel::Validations + # validates_with MyValidator + # end + # + # class MyValidator < ActiveModel::Validator + # def validate(record) + # if some_complex_logic + # record.errors.add(:base, "This record is invalid") + # end + # end + # + # private + # def some_complex_logic + # # ... + # end + # end + # + # Any class that inherits from ActiveModel::Validator must implement a method + # called +validate+ which accepts a +record+. + # + # class Person + # include ActiveModel::Validations + # validates_with MyValidator + # end + # + # class MyValidator < ActiveModel::Validator + # def validate(record) + # record # => The person instance being validated + # options # => Any non-standard options passed to validates_with + # end + # end + # + # To cause a validation error, you must add to the +record+'s errors directly + # from within the validators message. + # + # class MyValidator < ActiveModel::Validator + # def validate(record) + # record.errors.add :base, "This is some custom error message" + # record.errors.add :first_name, "This is some complex validation" + # # etc... + # end + # end + # + # To add behavior to the initialize method, use the following signature: + # + # class MyValidator < ActiveModel::Validator + # def initialize(options) + # super + # @my_custom_field = options[:field_name] || :first_name + # end + # end + # + # Note that the validator is initialized only once for the whole application + # life cycle, and not on each validation run. + # + # The easiest way to add custom validators for validating individual attributes + # is with the convenient ActiveModel::EachValidator. + # + # class TitleValidator < ActiveModel::EachValidator + # def validate_each(record, attribute, value) + # record.errors.add attribute, 'must be Mr., Mrs., or Dr.' unless %w(Mr. Mrs. Dr.).include?(value) + # end + # end + # + # This can now be used in combination with the +validates+ method + # (see ActiveModel::Validations::ClassMethods.validates for more on this). + # + # class Person + # include ActiveModel::Validations + # attr_accessor :title + # + # validates :title, presence: true, title: true + # end + # + # It can be useful to access the class that is using that validator when there are prerequisites such + # as an +attr_accessor+ being present. This class is accessible via options[:class] in the constructor. + # To setup your validator override the constructor. + # + # class MyValidator < ActiveModel::Validator + # def initialize(options={}) + # super + # options[:class].send :attr_accessor, :custom_attribute + # end + # end + class Validator + attr_reader :options + + # Returns the kind of the validator. + # + # PresenceValidator.kind # => :presence + # AcceptanceValidator.kind # => :acceptance + def self.kind + @kind ||= name.split("::").last.underscore.chomp("_validator").to_sym unless anonymous? + end + + # Accepts options that will be made available through the +options+ reader. + def initialize(options = {}) + @options = options.except(:class).freeze + end + + # Returns the kind for this validator. + # + # PresenceValidator.new(attributes: [:username]).kind # => :presence + # AcceptanceValidator.new(attributes: [:terms]).kind # => :acceptance + def kind + self.class.kind + end + + # Override this method in subclasses with validation logic, adding errors + # to the records +errors+ array where necessary. + def validate(record) + raise NotImplementedError, "Subclasses must implement a validate(record) method." + end + end + + # +EachValidator+ is a validator which iterates through the attributes given + # in the options hash invoking the validate_each method passing in the + # record, attribute and value. + # + # All \Active \Model validations are built on top of this validator. + class EachValidator < Validator #:nodoc: + attr_reader :attributes + + # Returns a new validator instance. All options will be available via the + # +options+ reader, however the :attributes option will be removed + # and instead be made available through the +attributes+ reader. + def initialize(options) + @attributes = Array(options.delete(:attributes)) + raise ArgumentError, ":attributes cannot be blank" if @attributes.empty? + super + check_validity! + end + + # Performs validation on the supplied record. By default this will call + # +validate_each+ to determine validity therefore subclasses should + # override +validate_each+ with validation logic. + def validate(record) + attributes.each do |attribute| + value = record.read_attribute_for_validation(attribute) + next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank]) + validate_each(record, attribute, value) + end + end + + # Override this method in subclasses with the validation logic, adding + # errors to the records +errors+ array where necessary. + def validate_each(record, attribute, value) + raise NotImplementedError, "Subclasses must implement a validate_each(record, attribute, value) method" + end + + # Hook method that gets called by the initializer allowing verification + # that the arguments supplied are valid. You could for example raise an + # +ArgumentError+ when invalid options are supplied. + def check_validity! + end + end + + # +BlockValidator+ is a special +EachValidator+ which receives a block on initialization + # and call this block for each attribute being validated. +validates_each+ uses this validator. + class BlockValidator < EachValidator #:nodoc: + def initialize(options, &block) + @block = block + super + end + + private + + def validate_each(record, attribute, value) + @block.call(record, attribute, value) + end + end +end diff --git a/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/version.rb b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/version.rb new file mode 100644 index 00000000..dd817f56 --- /dev/null +++ b/path/ruby/2.6.0/gems/activemodel-5.2.3/lib/active_model/version.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require_relative "gem_version" + +module ActiveModel + # Returns the version of the currently loaded \Active \Model as a Gem::Version + def self.version + gem_version + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/CHANGELOG.md b/path/ruby/2.6.0/gems/activerecord-5.2.3/CHANGELOG.md new file mode 100644 index 00000000..528733b4 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/CHANGELOG.md @@ -0,0 +1,937 @@ +## Rails 5.2.3 (March 27, 2019) ## + +* Fix different `count` calculation when using `size` with manual `select` with DISTINCT. + + Fixes #35214. + + *Juani Villarejo* + +* Fix prepared statements caching to be enabled even when query caching is enabled. + + *Ryuta Kamizono* + +* Don't allow `where` with invalid value matches to nil values. + + Fixes #33624. + + *Ryuta Kamizono* + +* Restore an ability that class level `update` without giving ids. + + Fixes #34743. + + *Ryuta Kamizono* + +* Fix join table column quoting with SQLite. + + *Gannon McGibbon* + +* Ensure that `delete_all` on collection proxy returns affected count. + + *Ryuta Kamizono* + +* Reset scope after delete on collection association to clear stale offsets of removed records. + + *Gannon McGibbon* + + +## Rails 5.2.2.1 (March 11, 2019) ## + +* No changes. + + +## Rails 5.2.2 (December 04, 2018) ## + +* Do not ignore the scoping with query methods in the scope block. + + *Ryuta Kamizono* + +* Allow aliased attributes to be used in `#update_columns` and `#update`. + + *Gannon McGibbon* + +* Allow spaces in postgres table names. + + Fixes issue where "user post" is misinterpreted as "\"user\".\"post\"" when quoting table names with the postgres + adapter. + + *Gannon McGibbon* + +* Cached columns_hash fields should be excluded from ResultSet#column_types + + PR #34528 addresses the inconsistent behaviour when attribute is defined for an ignored column. The following test + was passing for SQLite and MySQL, but failed for PostgreSQL: + + ```ruby + class DeveloperName < ActiveRecord::Type::String + def deserialize(value) + "Developer: #{value}" + end + end + + class AttributedDeveloper < ActiveRecord::Base + self.table_name = "developers" + + attribute :name, DeveloperName.new + + self.ignored_columns += ["name"] + end + + developer = AttributedDeveloper.create + developer.update_column :name, "name" + + loaded_developer = AttributedDeveloper.where(id: developer.id).select("*").first + puts loaded_developer.name # should be "Developer: name" but it's just "name" + ``` + + *Dmitry Tsepelev* + +* Values of enum are frozen, raising an error when attempting to modify them. + + *Emmanuel Byrd* + +* `update_columns` now correctly raises `ActiveModel::MissingAttributeError` + if the attribute does not exist. + + *Sean Griffin* + +* Do not use prepared statement in queries that have a large number of binds. + + *Ryuta Kamizono* + +* Fix query cache to load before first request. + + *Eileen M. Uchitelle* + +* Fix collection cache key with limit and custom select to avoid ambiguous timestamp column error. + + Fixes #33056. + + *Federico Martinez* + +* Fix duplicated record creation when using nested attributes with `create_with`. + + *Darwin Wu* + +* Fix regression setting children record in parent `before_save` callback. + + *Guo Xiang Tan* + +* Prevent leaking of user's DB credentials on `rails db:create` failure. + + *bogdanvlviv* + +* Clear mutation tracker before continuing the around callbacks. + + *Yuya Tanaka* + +* Prevent deadlocks when waiting for connection from pool. + + *Brent Wheeldon* + +* Avoid extra scoping when using `Relation#update` that was causing this method to change the current scope. + + *Ryuta Kamizono* + +* Fix numericality validator not to be affected by custom getter. + + *Ryuta Kamizono* + +* Fix bulk change table ignores comment option on PostgreSQL. + + *Yoshiyuki Kinjo* + + +## Rails 5.2.1.1 (November 27, 2018) ## + +* No changes. + + +## Rails 5.2.1 (August 07, 2018) ## + +* PostgreSQL: Support new relkind for partitioned tables. + + Fixes #33008. + + *Yannick Schutz* + +* Rollback parent transaction when children fails to update. + + *Guillaume Malette* + +* Fix default value for MySQL time types with specified precision. + + *Nikolay Kondratyev* + +* Fix `touch` option to behave consistently with `Persistence#touch` method. + + *Ryuta Kamizono* + +* Fix `save` in `after_create_commit` won't invoke extra `after_create_commit`. + + Fixes #32831. + + *Ryuta Kamizono* + +* Fix logic on disabling commit callbacks so they are not called unexpectedly when errors occur. + + *Brian Durand* + +* Fix parent record should not get saved with duplicate children records. + + Fixes #32940. + + *Santosh Wadghule* + +* Fix that association's after_touch is not called with counter cache. + + Fixes #31559. + + *Ryuta Kamizono* + +* `becomes` should clear the mutation tracker which is created in `after_initialize`. + + Fixes #32867. + + *Ryuta Kamizono* + +* Allow a belonging to parent object to be created from a new record. + + *Jolyon Pawlyn* + +* Fix that building record with assigning multiple has_one associations + wrongly persists through record. + + Fixes #32511. + + *Sam DeCesare* + +* Fix relation merging when one of the relations is going to skip the + query cache. + + *James Williams* + + +## Rails 5.2.0 (April 09, 2018) ## + +* MySQL: Support mysql2 0.5.x. + + *Aaron Stone* + +* Apply time column precision on assignment. + + PR #20317 changed the behavior of datetime columns so that when they + have a specified precision then on assignment the value is rounded to + that precision. This behavior is now applied to time columns as well. + + Fixes #30301. + + *Andrew White* + +* Normalize time column values for SQLite database. + + For legacy reasons, time columns in SQLite are stored as full datetimes + because until #24542 the quoting for time columns didn't remove the date + component. To ensure that values are consistent we now normalize the + date component to 2001-01-01 on reading and writing. + + *Andrew White* + +* Ensure that the date component is removed when quoting times. + + PR #24542 altered the quoting for time columns so that the date component + was removed however it only removed it when it was 2001-01-01. Now the + date component is removed irrespective of what the date is. + + *Andrew White* + +* Fix `dependent: :destroy` issue for has_one/belongs_to relationship where + the parent class was getting deleted when the child was not. + + Fixes #32022. + + *Fernando Gorodscy* + +* Whitelist `NULLS FIRST` and `NULLS LAST` in order clauses too. + + *Xavier Noria* + +* Fix that after commit callbacks on update does not triggered when optimistic locking is enabled. + + *Ryuta Kamizono* + +* Fix `#columns_for_distinct` of MySQL and PostgreSQL to make + `ActiveRecord::FinderMethods#limited_ids_for` use correct primary key values + even if `ORDER BY` columns include other table's primary key. + + Fixes #28364. + + *Takumi Kagiyama* + +* Make `reflection.klass` raise if `polymorphic?` not to be misused. + + Fixes #31876. + + *Ryuta Kamizono* + +* PostgreSQL: Allow pg-1.0 gem to be used with Active Record. + + *Lars Kanis* + +* Deprecate `expand_hash_conditions_for_aggregates` without replacement. + Using a `Relation` for performing queries is the prefered API. + + *Ryuta Kamizono* + +* Fix not expanded problem when passing an Array object as argument to the where method using `composed_of` column. + + ``` + david_balance = customers(:david).balance + Customer.where(balance: [david_balance]).to_sql + + # Before: WHERE `customers`.`balance` = NULL + # After : WHERE `customers`.`balance` = 50 + ``` + + Fixes #31723. + + *Yutaro Kanagawa* + +* Fix `count(:all)` with eager loading and having an order other than the driving table. + + Fixes #31783. + + *Ryuta Kamizono* + +* Clear the transaction state when an Active Record object is duped. + + Fixes #31670. + + *Yuriy Ustushenko* + +* Support for PostgreSQL foreign tables. + + *fatkodima* + +* Fix relation merger issue with `left_outer_joins`. + + *Mehmet Emin İNAÇ* + +* Don't allow destroyed object mutation after `save` or `save!` is called. + + *Ryuta Kamizono* + +* Take into account association conditions when deleting through records. + + Fixes #18424. + + *Piotr Jakubowski* + +* Fix nested `has_many :through` associations on unpersisted parent instances. + + For example, if you have + + class Post < ActiveRecord::Base + belongs_to :author + has_many :books, through: :author + has_many :subscriptions, through: :books + end + + class Author < ActiveRecord::Base + has_one :post + has_many :books + has_many :subscriptions, through: :books + end + + class Book < ActiveRecord::Base + belongs_to :author + has_many :subscriptions + end + + class Subscription < ActiveRecord::Base + belongs_to :book + end + + Before: + + If `post` is not persisted, then `post.subscriptions` will be empty. + + After: + + If `post` is not persisted, then `post.subscriptions` can be set and used + just like it would if `post` were persisted. + + Fixes #16313. + + *Zoltan Kiss* + +* Fixed inconsistency with `first(n)` when used with `limit()`. + The `first(n)` finder now respects the `limit()`, making it consistent + with `relation.to_a.first(n)`, and also with the behavior of `last(n)`. + + Fixes #23979. + + *Brian Christian* + +* Use `count(:all)` in `HasManyAssociation#count_records` to prevent invalid + SQL queries for association counting. + + *Klas Eskilson* + +* Fix to invoke callbacks when using `update_attribute`. + + *Mike Busch* + +* Fix `count(:all)` to correctly work `distinct` with custom SELECT list. + + *Ryuta Kamizono* + +* Using subselect for `delete_all` with `limit` or `offset`. + + *Ryuta Kamizono* + +* Undefine attribute methods on descendants when resetting column + information. + + *Chris Salzberg* + +* Log database query callers. + + Add `verbose_query_logs` configuration option to display the caller + of database queries in the log to facilitate N+1 query resolution + and other debugging. + + Enabled in development only for new and upgraded applications. Not + recommended for use in the production environment since it relies + on Ruby's `Kernel#caller_locations` which is fairly slow. + + *Olivier Lacan* + +* Fix conflicts `counter_cache` with `touch: true` by optimistic locking. + + ``` + # create_table :posts do |t| + # t.integer :comments_count, default: 0 + # t.integer :lock_version + # t.timestamps + # end + class Post < ApplicationRecord + end + + # create_table :comments do |t| + # t.belongs_to :post + # end + class Comment < ApplicationRecord + belongs_to :post, touch: true, counter_cache: true + end + ``` + + Before: + ``` + post = Post.create! + # => begin transaction + INSERT INTO "posts" ("created_at", "updated_at", "lock_version") + VALUES ("2017-12-11 21:27:11.387397", "2017-12-11 21:27:11.387397", 0) + commit transaction + + comment = Comment.create!(post: post) + # => begin transaction + INSERT INTO "comments" ("post_id") VALUES (1) + + UPDATE "posts" SET "comments_count" = COALESCE("comments_count", 0) + 1, + "lock_version" = COALESCE("lock_version", 0) + 1 WHERE "posts"."id" = 1 + + UPDATE "posts" SET "updated_at" = '2017-12-11 21:27:11.398330', + "lock_version" = 1 WHERE "posts"."id" = 1 AND "posts"."lock_version" = 0 + rollback transaction + # => ActiveRecord::StaleObjectError: Attempted to touch a stale object: Post. + + Comment.take.destroy! + # => begin transaction + DELETE FROM "comments" WHERE "comments"."id" = 1 + + UPDATE "posts" SET "comments_count" = COALESCE("comments_count", 0) - 1, + "lock_version" = COALESCE("lock_version", 0) + 1 WHERE "posts"."id" = 1 + + UPDATE "posts" SET "updated_at" = '2017-12-11 21:42:47.785901', + "lock_version" = 1 WHERE "posts"."id" = 1 AND "posts"."lock_version" = 0 + rollback transaction + # => ActiveRecord::StaleObjectError: Attempted to touch a stale object: Post. + ``` + + After: + ``` + post = Post.create! + # => begin transaction + INSERT INTO "posts" ("created_at", "updated_at", "lock_version") + VALUES ("2017-12-11 21:27:11.387397", "2017-12-11 21:27:11.387397", 0) + commit transaction + + comment = Comment.create!(post: post) + # => begin transaction + INSERT INTO "comments" ("post_id") VALUES (1) + + UPDATE "posts" SET "comments_count" = COALESCE("comments_count", 0) + 1, + "lock_version" = COALESCE("lock_version", 0) + 1, + "updated_at" = '2017-12-11 21:37:09.802642' WHERE "posts"."id" = 1 + commit transaction + + comment.destroy! + # => begin transaction + DELETE FROM "comments" WHERE "comments"."id" = 1 + + UPDATE "posts" SET "comments_count" = COALESCE("comments_count", 0) - 1, + "lock_version" = COALESCE("lock_version", 0) + 1, + "updated_at" = '2017-12-11 21:39:02.685520' WHERE "posts"."id" = 1 + commit transaction + ``` + + Fixes #31199. + + *bogdanvlviv* + +* Add support for PostgreSQL operator classes to `add_index`. + + Example: + + add_index :users, :name, using: :gist, opclass: { name: :gist_trgm_ops } + + *Greg Navis* + +* Don't allow scopes to be defined which conflict with instance methods on `Relation`. + + Fixes #31120. + + *kinnrot* + +* Add new error class `QueryCanceled` which will be raised + when canceling statement due to user request. + + *Ryuta Kamizono* + +* Add `#up_only` to database migrations for code that is only relevant when + migrating up, e.g. populating a new column. + + *Rich Daley* + +* Require raw SQL fragments to be explicitly marked when used in + relation query methods. + + Before: + ``` + Article.order("LENGTH(title)") + ``` + + After: + ``` + Article.order(Arel.sql("LENGTH(title)")) + ``` + + This prevents SQL injection if applications use the [strongly + discouraged] form `Article.order(params[:my_order])`, under the + mistaken belief that only column names will be accepted. + + Raw SQL strings will now cause a deprecation warning, which will + become an UnknownAttributeReference error in Rails 6.0. Applications + can opt in to the future behavior by setting `allow_unsafe_raw_sql` + to `:disabled`. + + Common and judged-safe string values (such as simple column + references) are unaffected: + ``` + Article.order("title DESC") + ``` + + *Ben Toews* + +* `update_all` will now pass its values to `Type#cast` before passing them to + `Type#serialize`. This means that `update_all(foo: 'true')` will properly + persist a boolean. + + *Sean Griffin* + +* Add new error class `StatementTimeout` which will be raised + when statement timeout exceeded. + + *Ryuta Kamizono* + +* Fix `bin/rails db:migrate` with specified `VERSION`. + `bin/rails db:migrate` with empty VERSION behaves as without `VERSION`. + Check a format of `VERSION`: Allow a migration version number + or name of a migration file. Raise error if format of `VERSION` is invalid. + Raise error if target migration doesn't exist. + + *bogdanvlviv* + +* Fixed a bug where column orders for an index weren't written to + `db/schema.rb` when using the sqlite adapter. + + Fixes #30902. + + *Paul Kuruvilla* + +* Remove deprecated method `#sanitize_conditions`. + + *Rafael Mendonça França* + +* Remove deprecated method `#scope_chain`. + + *Rafael Mendonça França* + +* Remove deprecated configuration `.error_on_ignored_order_or_limit`. + + *Rafael Mendonça França* + +* Remove deprecated arguments from `#verify!`. + + *Rafael Mendonça França* + +* Remove deprecated argument `name` from `#indexes`. + + *Rafael Mendonça França* + +* Remove deprecated method `ActiveRecord::Migrator.schema_migrations_table_name`. + + *Rafael Mendonça França* + +* Remove deprecated method `supports_primary_key?`. + + *Rafael Mendonça França* + +* Remove deprecated method `supports_migrations?`. + + *Rafael Mendonça França* + +* Remove deprecated methods `initialize_schema_migrations_table` and `initialize_internal_metadata_table`. + + *Rafael Mendonça França* + +* Raises when calling `lock!` in a dirty record. + + *Rafael Mendonça França* + +* Remove deprecated support to passing a class to `:class_name` on associations. + + *Rafael Mendonça França* + +* Remove deprecated argument `default` from `index_name_exists?`. + + *Rafael Mendonça França* + +* Remove deprecated support to `quoted_id` when typecasting an Active Record object. + + *Rafael Mendonça França* + +* Fix `bin/rails db:setup` and `bin/rails db:test:prepare` create wrong + ar_internal_metadata's data for a test database. + + Before: + ``` + $ RAILS_ENV=test rails dbconsole + > SELECT * FROM ar_internal_metadata; + key|value|created_at|updated_at + environment|development|2017-09-11 23:14:10.815679|2017-09-11 23:14:10.815679 + ``` + + After: + ``` + $ RAILS_ENV=test rails dbconsole + > SELECT * FROM ar_internal_metadata; + key|value|created_at|updated_at + environment|test|2017-09-11 23:14:10.815679|2017-09-11 23:14:10.815679 + ``` + + Fixes #26731. + + *bogdanvlviv* + +* Fix longer sequence name detection for serial columns. + + Fixes #28332. + + *Ryuta Kamizono* + +* MySQL: Don't lose `auto_increment: true` in the `db/schema.rb`. + + Fixes #30894. + + *Ryuta Kamizono* + +* Fix `COUNT(DISTINCT ...)` for `GROUP BY` with `ORDER BY` and `LIMIT`. + + Fixes #30886. + + *Ryuta Kamizono* + +* PostgreSQL `tsrange` now preserves subsecond precision. + + PostgreSQL 9.1+ introduced range types, and Rails added support for using + this datatype in Active Record. However, the serialization of + `PostgreSQL::OID::Range` was incomplete, because it did not properly + cast the bounds that make up the range. This led to subseconds being + dropped in SQL commands: + + Before: + + connection.type_cast(tsrange.serialize(range_value)) + # => "[2010-01-01 13:30:00 UTC,2011-02-02 19:30:00 UTC)" + + Now: + + connection.type_cast(tsrange.serialize(range_value)) + # => "[2010-01-01 13:30:00.670277,2011-02-02 19:30:00.745125)" + + *Thomas Cannon* + +* Passing a `Set` to `Relation#where` now behaves the same as passing an + array. + + *Sean Griffin* + +* Use given algorithm while removing index from database. + + Fixes #24190. + + *Mehmet Emin İNAÇ* + +* Update payload names for `sql.active_record` instrumentation to be + more descriptive. + + Fixes #30586. + + *Jeremy Green* + +* Add new error class `LockWaitTimeout` which will be raised + when lock wait timeout exceeded. + + *Gabriel Courtemanche* + +* Remove deprecated `#migration_keys`. + + *Ryuta Kamizono* + +* Automatically guess the inverse associations for STI. + + *Yuichiro Kaneko* + +* Ensure `sum` honors `distinct` on `has_many :through` associations. + + Fixes #16791. + + *Aaron Wortham* + +* Add `binary` fixture helper method. + + *Atsushi Yoshida* + +* When using `Relation#or`, extract the common conditions and put them before the OR condition. + + *Maxime Handfield Lapointe* + +* `Relation#or` now accepts two relations who have different values for + `references` only, as `references` can be implicitly called by `where`. + + Fixes #29411. + + *Sean Griffin* + +* `ApplicationRecord` is no longer generated when generating models. If you + need to generate it, it can be created with `rails g application_record`. + + *Lisa Ugray* + +* Fix `COUNT(DISTINCT ...)` with `ORDER BY` and `LIMIT` to keep the existing select list. + + *Ryuta Kamizono* + +* When a `has_one` association is destroyed by `dependent: destroy`, + `destroyed_by_association` will now be set to the reflection, matching the + behaviour of `has_many` associations. + + *Lisa Ugray* + +* Fix `unscoped(where: [columns])` removing the wrong bind values. + + When the `where` is called on a relation after a `or`, unscoping the column of that later `where` removed + bind values used by the `or` instead. (possibly other cases too) + + ``` + Post.where(id: 1).or(Post.where(id: 2)).where(foo: 3).unscope(where: :foo).to_sql + # Currently: + # SELECT "posts".* FROM "posts" WHERE ("posts"."id" = 2 OR "posts"."id" = 3) + # With fix: + # SELECT "posts".* FROM "posts" WHERE ("posts"."id" = 1 OR "posts"."id" = 2) + ``` + + *Maxime Handfield Lapointe* + +* Values constructed using multi-parameter assignment will now use the + post-type-cast value for rendering in single-field form inputs. + + *Sean Griffin* + +* `Relation#joins` is no longer affected by the target model's + `current_scope`, with the exception of `unscoped`. + + Fixes #29338. + + *Sean Griffin* + +* Change sqlite3 boolean serialization to use 1 and 0. + + SQLite natively recognizes 1 and 0 as true and false, but does not natively + recognize 't' and 'f' as was previously serialized. + + This change in serialization requires a migration of stored boolean data + for SQLite databases, so it's implemented behind a configuration flag + whose default false value is deprecated. + + *Lisa Ugray* + +* Skip query caching when working with batches of records (`find_each`, `find_in_batches`, + `in_batches`). + + Previously, records would be fetched in batches, but all records would be retained in memory + until the end of the request or job. + + *Eugene Kenny* + +* Prevent errors raised by `sql.active_record` notification subscribers from being converted into + `ActiveRecord::StatementInvalid` exceptions. + + *Dennis Taylor* + +* Fix eager loading/preloading association with scope including joins. + + Fixes #28324. + + *Ryuta Kamizono* + +* Fix transactions to apply state to child transactions. + + Previously, if you had a nested transaction and the outer transaction was rolledback, the record from the + inner transaction would still be marked as persisted. + + This change fixes that by applying the state of the parent transaction to the child transaction when the + parent transaction is rolledback. This will correctly mark records from the inner transaction as not persisted. + + *Eileen M. Uchitelle*, *Aaron Patterson* + +* Deprecate `set_state` method in `TransactionState`. + + Deprecated the `set_state` method in favor of setting the state via specific methods. If you need to mark the + state of the transaction you can now use `rollback!`, `commit!` or `nullify!` instead of + `set_state(:rolledback)`, `set_state(:committed)`, or `set_state(nil)`. + + *Eileen M. Uchitelle*, *Aaron Patterson* + +* Deprecate delegating to `arel` in `Relation`. + + *Ryuta Kamizono* + +* Query cache was unavailable when entering the `ActiveRecord::Base.cache` block + without being connected. + + *Tsukasa Oishi* + +* Previously, when building records using a `has_many :through` association, + if the child records were deleted before the parent was saved, they would + still be persisted. Now, if child records are deleted before the parent is saved + on a `has_many :through` association, the child records will not be persisted. + + *Tobias Kraze* + +* Merging two relations representing nested joins no longer transforms the joins of + the merged relation into LEFT OUTER JOIN. + + Example: + + ``` + Author.joins(:posts).merge(Post.joins(:comments)) + # Before the change: + #=> SELECT ... FROM authors INNER JOIN posts ON ... LEFT OUTER JOIN comments ON... + + # After the change: + #=> SELECT ... FROM authors INNER JOIN posts ON ... INNER JOIN comments ON... + ``` + + *Maxime Handfield Lapointe* + +* `ActiveRecord::Persistence#touch` does not work well when optimistic locking enabled and + `locking_column`, without default value, is null in the database. + + *bogdanvlviv* + +* Fix destroying existing object does not work well when optimistic locking enabled and + `locking_column` is null in the database. + + *bogdanvlviv* + +* Use bulk INSERT to insert fixtures for better performance. + + *Kir Shatrov* + +* Prevent creation of bind param if casted value is nil. + + *Ryuta Kamizono* + +* Deprecate passing arguments and block at the same time to `count` and `sum` in `ActiveRecord::Calculations`. + + *Ryuta Kamizono* + +* Loading model schema from database is now thread-safe. + + Fixes #28589. + + *Vikrant Chaudhary*, *David Abdemoulaie* + +* Add `ActiveRecord::Base#cache_version` to support recyclable cache keys via the new versioned entries + in `ActiveSupport::Cache`. This also means that `ActiveRecord::Base#cache_key` will now return a stable key + that does not include a timestamp any more. + + NOTE: This feature is turned off by default, and `#cache_key` will still return cache keys with timestamps + until you set `ActiveRecord::Base.cache_versioning = true`. That's the setting for all new apps on Rails 5.2+ + + *DHH* + +* Respect `SchemaDumper.ignore_tables` in rake tasks for databases structure dump. + + *Rusty Geldmacher*, *Guillermo Iguaran* + +* Add type caster to `RuntimeReflection#alias_name`. + + Fixes #28959. + + *Jon Moss* + +* Deprecate `supports_statement_cache?`. + + *Ryuta Kamizono* + +* Raise error `UnknownMigrationVersionError` on the movement of migrations + when the current migration does not exist. + + *bogdanvlviv* + +* Fix `bin/rails db:forward` first migration. + + *bogdanvlviv* + +* Support Descending Indexes for MySQL. + + MySQL 8.0.1 and higher supports descending indexes: `DESC` in an index definition is no longer ignored. + See https://dev.mysql.com/doc/refman/8.0/en/descending-indexes.html. + + *Ryuta Kamizono* + +* Fix inconsistency with changed attributes when overriding Active Record attribute reader. + + *bogdanvlviv* + +* When calling the dynamic fixture accessor method with no arguments, it now returns all fixtures of this type. + Previously this method always returned an empty array. + + *Kevin McPhillips* + + +Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/activerecord/CHANGELOG.md) for previous changes. diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/MIT-LICENSE b/path/ruby/2.6.0/gems/activerecord-5.2.3/MIT-LICENSE new file mode 100644 index 00000000..cce00cbc --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2004-2018 David Heinemeier Hansson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/README.rdoc b/path/ruby/2.6.0/gems/activerecord-5.2.3/README.rdoc new file mode 100644 index 00000000..307e0f74 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/README.rdoc @@ -0,0 +1,217 @@ += Active Record -- Object-relational mapping in Rails + +Active Record connects classes to relational database tables to establish an +almost zero-configuration persistence layer for applications. The library +provides a base class that, when subclassed, sets up a mapping between the new +class and an existing table in the database. In the context of an application, +these classes are commonly referred to as *models*. Models can also be +connected to other models; this is done by defining *associations*. + +Active Record relies heavily on naming in that it uses class and association +names to establish mappings between respective database tables and foreign key +columns. Although these mappings can be defined explicitly, it's recommended +to follow naming conventions, especially when getting started with the +library. + +A short rundown of some of the major features: + +* Automated mapping between classes and tables, attributes and columns. + + class Product < ActiveRecord::Base + end + + {Learn more}[link:classes/ActiveRecord/Base.html] + +The Product class is automatically mapped to the table named "products", +which might look like this: + + CREATE TABLE products ( + id bigint NOT NULL auto_increment, + name varchar(255), + PRIMARY KEY (id) + ); + +This would also define the following accessors: Product#name and +Product#name=(new_name). + + +* Associations between objects defined by simple class methods. + + class Firm < ActiveRecord::Base + has_many :clients + has_one :account + belongs_to :conglomerate + end + + {Learn more}[link:classes/ActiveRecord/Associations/ClassMethods.html] + + +* Aggregations of value objects. + + class Account < ActiveRecord::Base + composed_of :balance, class_name: 'Money', + mapping: %w(balance amount) + composed_of :address, + mapping: [%w(address_street street), %w(address_city city)] + end + + {Learn more}[link:classes/ActiveRecord/Aggregations/ClassMethods.html] + + +* Validation rules that can differ for new or existing objects. + + class Account < ActiveRecord::Base + validates :subdomain, :name, :email_address, :password, presence: true + validates :subdomain, uniqueness: true + validates :terms_of_service, acceptance: true, on: :create + validates :password, :email_address, confirmation: true, on: :create + end + + {Learn more}[link:classes/ActiveRecord/Validations.html] + + +* Callbacks available for the entire life cycle (instantiation, saving, destroying, validating, etc.). + + class Person < ActiveRecord::Base + before_destroy :invalidate_payment_plan + # the `invalidate_payment_plan` method gets called just before Person#destroy + end + + {Learn more}[link:classes/ActiveRecord/Callbacks.html] + + +* Inheritance hierarchies. + + class Company < ActiveRecord::Base; end + class Firm < Company; end + class Client < Company; end + class PriorityClient < Client; end + + {Learn more}[link:classes/ActiveRecord/Base.html] + + +* Transactions. + + # Database transaction + Account.transaction do + david.withdrawal(100) + mary.deposit(100) + end + + {Learn more}[link:classes/ActiveRecord/Transactions/ClassMethods.html] + + +* Reflections on columns, associations, and aggregations. + + reflection = Firm.reflect_on_association(:clients) + reflection.klass # => Client (class) + Firm.columns # Returns an array of column descriptors for the firms table + + {Learn more}[link:classes/ActiveRecord/Reflection/ClassMethods.html] + + +* Database abstraction through simple adapters. + + # connect to SQLite3 + ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: 'dbfile.sqlite3') + + # connect to MySQL with authentication + ActiveRecord::Base.establish_connection( + adapter: 'mysql2', + host: 'localhost', + username: 'me', + password: 'secret', + database: 'activerecord' + ) + + {Learn more}[link:classes/ActiveRecord/Base.html] and read about the built-in support for + MySQL[link:classes/ActiveRecord/ConnectionAdapters/Mysql2Adapter.html], + PostgreSQL[link:classes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter.html], and + SQLite3[link:classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html]. + + +* Logging support for Log4r[https://github.com/colbygk/log4r] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc]. + + ActiveRecord::Base.logger = ActiveSupport::Logger.new(STDOUT) + ActiveRecord::Base.logger = Log4r::Logger.new('Application Log') + + +* Database agnostic schema management with Migrations. + + class AddSystemSettings < ActiveRecord::Migration[5.0] + def up + create_table :system_settings do |t| + t.string :name + t.string :label + t.text :value + t.string :type + t.integer :position + end + + SystemSetting.create name: 'notice', label: 'Use notice?', value: 1 + end + + def down + drop_table :system_settings + end + end + + {Learn more}[link:classes/ActiveRecord/Migration.html] + + +== Philosophy + +Active Record is an implementation of the object-relational mapping (ORM) +pattern[https://www.martinfowler.com/eaaCatalog/activeRecord.html] by the same +name described by Martin Fowler: + + "An object that wraps a row in a database table or view, + encapsulates the database access, and adds domain logic on that data." + +Active Record attempts to provide a coherent wrapper as a solution for the inconvenience that is +object-relational mapping. The prime directive for this mapping has been to minimize +the amount of code needed to build a real-world domain model. This is made possible +by relying on a number of conventions that make it easy for Active Record to infer +complex relations and structures from a minimal amount of explicit direction. + +Convention over Configuration: +* No XML files! +* Lots of reflection and run-time extension +* Magic is not inherently a bad word + +Admit the Database: +* Lets you drop down to SQL for odd cases and performance +* Doesn't attempt to duplicate or replace data definitions + + +== Download and installation + +The latest version of Active Record can be installed with RubyGems: + + $ gem install activerecord + +Source code can be downloaded as part of the Rails project on GitHub: + +* https://github.com/rails/rails/tree/5-2-stable/activerecord + + +== License + +Active Record is released under the MIT license: + +* https://opensource.org/licenses/MIT + + +== Support + +API documentation is at: + +* http://api.rubyonrails.org + +Bug reports for the Ruby on Rails project can be filed here: + +* https://github.com/rails/rails/issues + +Feature requests should be discussed on the rails-core mailing list here: + +* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/examples/performance.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/examples/performance.rb new file mode 100644 index 00000000..1a2c78f3 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/examples/performance.rb @@ -0,0 +1,185 @@ +# frozen_string_literal: true + +require "active_record" +require "benchmark/ips" + +TIME = (ENV["BENCHMARK_TIME"] || 20).to_i +RECORDS = (ENV["BENCHMARK_RECORDS"] || TIME * 1000).to_i + +conn = { adapter: "sqlite3", database: ":memory:" } + +ActiveRecord::Base.establish_connection(conn) + +class User < ActiveRecord::Base + connection.create_table :users, force: true do |t| + t.string :name, :email + t.timestamps + end + + has_many :exhibits +end + +class Exhibit < ActiveRecord::Base + connection.create_table :exhibits, force: true do |t| + t.belongs_to :user + t.string :name + t.text :notes + t.timestamps + end + + belongs_to :user + + def look; attributes end + def feel; look; user.name end + + def self.with_name + where("name IS NOT NULL") + end + + def self.with_notes + where("notes IS NOT NULL") + end + + def self.look(exhibits) exhibits.each(&:look) end + def self.feel(exhibits) exhibits.each(&:feel) end +end + +def progress_bar(int); print "." if (int % 100).zero? ; end + +puts "Generating data..." + +module ActiveRecord + class Faker + LOREM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse non aliquet diam. Curabitur vel urna metus, quis malesuada elit. + Integer consequat tincidunt felis. Etiam non erat dolor. Vivamus imperdiet nibh sit amet diam eleifend id posuere diam malesuada. Mauris at accumsan sem. + Donec id lorem neque. Fusce erat lorem, ornare eu congue vitae, malesuada quis neque. Maecenas vel urna a velit pretium fermentum. Donec tortor enim, + tempor venenatis egestas a, tempor sed ipsum. Ut arcu justo, faucibus non imperdiet ac, interdum at diam. Pellentesque ipsum enim, venenatis ut iaculis vitae, + varius vitae sem. Sed rutrum quam ac elit euismod bibendum. Donec ultricies ultricies magna, at lacinia libero mollis aliquam. Sed ac arcu in tortor elementum + tincidunt vel interdum sem. Curabitur eget erat arcu. Praesent eget eros leo. Nam magna enim, sollicitudin vehicula scelerisque in, vulputate ut libero. + Praesent varius tincidunt commodo".split + + def self.name + LOREM.grep(/^\w*$/).sort_by { rand }.first(2).join " " + end + + def self.email + LOREM.grep(/^\w*$/).sort_by { rand }.first(2).join("@") + ".com" + end + end +end + +# pre-compute the insert statements and fake data compilation, +# so the benchmarks below show the actual runtime for the execute +# method, minus the setup steps + +# Using the same paragraph for all exhibits because it is very slow +# to generate unique paragraphs for all exhibits. +notes = ActiveRecord::Faker::LOREM.join " " +today = Date.today + +puts "Inserting #{RECORDS} users and exhibits..." +RECORDS.times do |record| + user = User.create( + created_at: today, + name: ActiveRecord::Faker.name, + email: ActiveRecord::Faker.email + ) + + Exhibit.create( + created_at: today, + name: ActiveRecord::Faker.name, + user: user, + notes: notes + ) + progress_bar(record) +end +puts "Done!\n" + +Benchmark.ips(TIME) do |x| + ar_obj = Exhibit.find(1) + attrs = { name: "sam" } + attrs_first = { name: "sam" } + attrs_second = { name: "tom" } + exhibit = { + name: ActiveRecord::Faker.name, + notes: notes, + created_at: Date.today + } + + x.report("Model#id") do + ar_obj.id + end + + x.report "Model.new (instantiation)" do + Exhibit.new + end + + x.report "Model.new (setting attributes)" do + Exhibit.new(attrs) + end + + x.report "Model.first" do + Exhibit.first.look + end + + x.report "Model.take" do + Exhibit.take + end + + x.report("Model.all limit(100)") do + Exhibit.look Exhibit.limit(100) + end + + x.report("Model.all take(100)") do + Exhibit.look Exhibit.take(100) + end + + x.report "Model.all limit(100) with relationship" do + Exhibit.feel Exhibit.limit(100).includes(:user) + end + + x.report "Model.all limit(10,000)" do + Exhibit.look Exhibit.limit(10000) + end + + x.report "Model.named_scope" do + Exhibit.limit(10).with_name.with_notes + end + + x.report "Model.create" do + Exhibit.create(exhibit) + end + + x.report "Resource#attributes=" do + e = Exhibit.new(attrs_first) + e.attributes = attrs_second + end + + x.report "Resource#update" do + Exhibit.first.update(name: "bob") + end + + x.report "Resource#destroy" do + Exhibit.first.destroy + end + + x.report "Model.transaction" do + Exhibit.transaction { Exhibit.new } + end + + x.report "Model.find(id)" do + User.find(1) + end + + x.report "Model.find_by_sql" do + Exhibit.find_by_sql("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}").first + end + + x.report "Model.log" do + Exhibit.connection.send(:log, "hello", "world") {} + end + + x.report "AR.execute(query)" do + ActiveRecord::Base.connection.execute("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}") + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/examples/simple.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/examples/simple.rb new file mode 100644 index 00000000..280b786d --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/examples/simple.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require "active_record" + +class Person < ActiveRecord::Base + establish_connection adapter: "sqlite3", database: "foobar.db" + connection.create_table table_name, force: true do |t| + t.string :name + end +end + +bob = Person.create!(name: "bob") +puts Person.all.inspect +bob.destroy +puts Person.all.inspect diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record.rb new file mode 100644 index 00000000..b4377ad6 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record.rb @@ -0,0 +1,188 @@ +# frozen_string_literal: true + +#-- +# Copyright (c) 2004-2018 David Heinemeier Hansson +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ + +require "active_support" +require "active_support/rails" +require "active_model" +require "arel" +require "yaml" + +require "active_record/version" +require "active_model/attribute_set" + +module ActiveRecord + extend ActiveSupport::Autoload + + autoload :Base + autoload :Callbacks + autoload :Core + autoload :ConnectionHandling + autoload :CounterCache + autoload :DynamicMatchers + autoload :Enum + autoload :InternalMetadata + autoload :Explain + autoload :Inheritance + autoload :Integration + autoload :Migration + autoload :Migrator, "active_record/migration" + autoload :ModelSchema + autoload :NestedAttributes + autoload :NoTouching + autoload :TouchLater + autoload :Persistence + autoload :QueryCache + autoload :Querying + autoload :CollectionCacheKey + autoload :ReadonlyAttributes + autoload :RecordInvalid, "active_record/validations" + autoload :Reflection + autoload :RuntimeRegistry + autoload :Sanitization + autoload :Schema + autoload :SchemaDumper + autoload :SchemaMigration + autoload :Scoping + autoload :Serialization + autoload :StatementCache + autoload :Store + autoload :Suppressor + autoload :Timestamp + autoload :Transactions + autoload :Translation + autoload :Validations + autoload :SecureToken + + eager_autoload do + autoload :ActiveRecordError, "active_record/errors" + autoload :ConnectionNotEstablished, "active_record/errors" + autoload :ConnectionAdapters, "active_record/connection_adapters/abstract_adapter" + + autoload :Aggregations + autoload :Associations + autoload :AttributeAssignment + autoload :AttributeMethods + autoload :AutosaveAssociation + + autoload :LegacyYamlAdapter + + autoload :Relation + autoload :AssociationRelation + autoload :NullRelation + + autoload_under "relation" do + autoload :QueryMethods + autoload :FinderMethods + autoload :Calculations + autoload :PredicateBuilder + autoload :SpawnMethods + autoload :Batches + autoload :Delegation + end + + autoload :Result + autoload :TableMetadata + autoload :Type + end + + module Coders + autoload :YAMLColumn, "active_record/coders/yaml_column" + autoload :JSON, "active_record/coders/json" + end + + module AttributeMethods + extend ActiveSupport::Autoload + + eager_autoload do + autoload :BeforeTypeCast + autoload :Dirty + autoload :PrimaryKey + autoload :Query + autoload :Read + autoload :TimeZoneConversion + autoload :Write + autoload :Serialization + end + end + + module Locking + extend ActiveSupport::Autoload + + eager_autoload do + autoload :Optimistic + autoload :Pessimistic + end + end + + module ConnectionAdapters + extend ActiveSupport::Autoload + + eager_autoload do + autoload :AbstractAdapter + end + end + + module Scoping + extend ActiveSupport::Autoload + + eager_autoload do + autoload :Named + autoload :Default + end + end + + module Tasks + extend ActiveSupport::Autoload + + autoload :DatabaseTasks + autoload :SQLiteDatabaseTasks, "active_record/tasks/sqlite_database_tasks" + autoload :MySQLDatabaseTasks, "active_record/tasks/mysql_database_tasks" + autoload :PostgreSQLDatabaseTasks, + "active_record/tasks/postgresql_database_tasks" + end + + autoload :TestFixtures, "active_record/fixtures" + + def self.eager_load! + super + ActiveRecord::Locking.eager_load! + ActiveRecord::Scoping.eager_load! + ActiveRecord::Associations.eager_load! + ActiveRecord::AttributeMethods.eager_load! + ActiveRecord::ConnectionAdapters.eager_load! + end +end + +ActiveSupport.on_load(:active_record) do + Arel::Table.engine = self +end + +ActiveSupport.on_load(:i18n) do + I18n.load_path << File.expand_path("active_record/locale/en.yml", __dir__) +end + +YAML.load_tags["!ruby/object:ActiveRecord::AttributeSet"] = "ActiveModel::AttributeSet" +YAML.load_tags["!ruby/object:ActiveRecord::Attribute::FromDatabase"] = "ActiveModel::Attribute::FromDatabase" +YAML.load_tags["!ruby/object:ActiveRecord::LazyAttributeHash"] = "ActiveModel::LazyAttributeHash" diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/aggregations.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/aggregations.rb new file mode 100644 index 00000000..27a641f0 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/aggregations.rb @@ -0,0 +1,283 @@ +# frozen_string_literal: true + +module ActiveRecord + # See ActiveRecord::Aggregations::ClassMethods for documentation + module Aggregations + extend ActiveSupport::Concern + + def initialize_dup(*) # :nodoc: + @aggregation_cache = {} + super + end + + def reload(*) # :nodoc: + clear_aggregation_cache + super + end + + private + + def clear_aggregation_cache + @aggregation_cache.clear if persisted? + end + + def init_internals + @aggregation_cache = {} + super + end + + # Active Record implements aggregation through a macro-like class method called #composed_of + # for representing attributes as value objects. It expresses relationships like "Account [is] + # composed of Money [among other things]" or "Person [is] composed of [an] address". Each call + # to the macro adds a description of how the value objects are created from the attributes of + # the entity object (when the entity is initialized either as a new object or from finding an + # existing object) and how it can be turned back into attributes (when the entity is saved to + # the database). + # + # class Customer < ActiveRecord::Base + # composed_of :balance, class_name: "Money", mapping: %w(balance amount) + # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ] + # end + # + # The customer class now has the following methods to manipulate the value objects: + # * Customer#balance, Customer#balance=(money) + # * Customer#address, Customer#address=(address) + # + # These methods will operate with value objects like the ones described below: + # + # class Money + # include Comparable + # attr_reader :amount, :currency + # EXCHANGE_RATES = { "USD_TO_DKK" => 6 } + # + # def initialize(amount, currency = "USD") + # @amount, @currency = amount, currency + # end + # + # def exchange_to(other_currency) + # exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor + # Money.new(exchanged_amount, other_currency) + # end + # + # def ==(other_money) + # amount == other_money.amount && currency == other_money.currency + # end + # + # def <=>(other_money) + # if currency == other_money.currency + # amount <=> other_money.amount + # else + # amount <=> other_money.exchange_to(currency).amount + # end + # end + # end + # + # class Address + # attr_reader :street, :city + # def initialize(street, city) + # @street, @city = street, city + # end + # + # def close_to?(other_address) + # city == other_address.city + # end + # + # def ==(other_address) + # city == other_address.city && street == other_address.street + # end + # end + # + # Now it's possible to access attributes from the database through the value objects instead. If + # you choose to name the composition the same as the attribute's name, it will be the only way to + # access that attribute. That's the case with our +balance+ attribute. You interact with the value + # objects just like you would with any other attribute: + # + # customer.balance = Money.new(20) # sets the Money value object and the attribute + # customer.balance # => Money value object + # customer.balance.exchange_to("DKK") # => Money.new(120, "DKK") + # customer.balance > Money.new(10) # => true + # customer.balance == Money.new(20) # => true + # customer.balance < Money.new(5) # => false + # + # Value objects can also be composed of multiple attributes, such as the case of Address. The order + # of the mappings will determine the order of the parameters. + # + # customer.address_street = "Hyancintvej" + # customer.address_city = "Copenhagen" + # customer.address # => Address.new("Hyancintvej", "Copenhagen") + # + # customer.address = Address.new("May Street", "Chicago") + # customer.address_street # => "May Street" + # customer.address_city # => "Chicago" + # + # == Writing value objects + # + # Value objects are immutable and interchangeable objects that represent a given value, such as + # a Money object representing $5. Two Money objects both representing $5 should be equal (through + # methods such as == and <=> from Comparable if ranking makes sense). This is + # unlike entity objects where equality is determined by identity. An entity class such as Customer can + # easily have two different objects that both have an address on Hyancintvej. Entity identity is + # determined by object or relational unique identifiers (such as primary keys). Normal + # ActiveRecord::Base classes are entity objects. + # + # It's also important to treat the value objects as immutable. Don't allow the Money object to have + # its amount changed after creation. Create a new Money object with the new value instead. The + # Money#exchange_to method is an example of this. It returns a new value object instead of changing + # its own values. Active Record won't persist value objects that have been changed through means + # other than the writer method. + # + # The immutable requirement is enforced by Active Record by freezing any object assigned as a value + # object. Attempting to change it afterwards will result in a +RuntimeError+. + # + # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not + # keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable + # + # == Custom constructors and converters + # + # By default value objects are initialized by calling the new constructor of the value + # class passing each of the mapped attributes, in the order specified by the :mapping + # option, as arguments. If the value class doesn't support this convention then #composed_of allows + # a custom constructor to be specified. + # + # When a new value is assigned to the value object, the default assumption is that the new value + # is an instance of the value class. Specifying a custom converter allows the new value to be automatically + # converted to an instance of value class if necessary. + # + # For example, the +NetworkResource+ model has +network_address+ and +cidr_range+ attributes that should be + # aggregated using the +NetAddr::CIDR+ value class (http://www.rubydoc.info/gems/netaddr/1.5.0/NetAddr/CIDR). + # The constructor for the value class is called +create+ and it expects a CIDR address string as a parameter. + # New values can be assigned to the value object using either another +NetAddr::CIDR+ object, a string + # or an array. The :constructor and :converter options can be used to meet + # these requirements: + # + # class NetworkResource < ActiveRecord::Base + # composed_of :cidr, + # class_name: 'NetAddr::CIDR', + # mapping: [ %w(network_address network), %w(cidr_range bits) ], + # allow_nil: true, + # constructor: Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") }, + # converter: Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) } + # end + # + # # This calls the :constructor + # network_resource = NetworkResource.new(network_address: '192.168.0.1', cidr_range: 24) + # + # # These assignments will both use the :converter + # network_resource.cidr = [ '192.168.2.1', 8 ] + # network_resource.cidr = '192.168.0.1/24' + # + # # This assignment won't use the :converter as the value is already an instance of the value class + # network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8') + # + # # Saving and then reloading will use the :constructor on reload + # network_resource.save + # network_resource.reload + # + # == Finding records by a value object + # + # Once a #composed_of relationship is specified for a model, records can be loaded from the database + # by specifying an instance of the value object in the conditions hash. The following example + # finds all customers with +address_street+ equal to "May Street" and +address_city+ equal to "Chicago": + # + # Customer.where(address: Address.new("May Street", "Chicago")) + # + module ClassMethods + # Adds reader and writer methods for manipulating a value object: + # composed_of :address adds address and address=(new_address) methods. + # + # Options are: + # * :class_name - Specifies the class name of the association. Use it only if that name + # can't be inferred from the part id. So composed_of :address will by default be linked + # to the Address class, but if the real class name is +CompanyAddress+, you'll have to specify it + # with this option. + # * :mapping - Specifies the mapping of entity attributes to attributes of the value + # object. Each mapping is represented as an array where the first item is the name of the + # entity attribute and the second item is the name of the attribute in the value object. The + # order in which mappings are defined determines the order in which attributes are sent to the + # value class constructor. + # * :allow_nil - Specifies that the value object will not be instantiated when all mapped + # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all + # mapped attributes. + # This defaults to +false+. + # * :constructor - A symbol specifying the name of the constructor method or a Proc that + # is called to initialize the value object. The constructor is passed all of the mapped attributes, + # in the order that they are defined in the :mapping option, as arguments and uses them + # to instantiate a :class_name object. + # The default is :new. + # * :converter - A symbol specifying the name of a class method of :class_name + # or a Proc that is called when a new value is assigned to the value object. The converter is + # passed the single value that is used in the assignment and is only called if the new value is + # not an instance of :class_name. If :allow_nil is set to true, the converter + # can return +nil+ to skip the assignment. + # + # Option examples: + # composed_of :temperature, mapping: %w(reading celsius) + # composed_of :balance, class_name: "Money", mapping: %w(balance amount) + # composed_of :address, mapping: [ %w(address_street street), %w(address_city city) ] + # composed_of :gps_location + # composed_of :gps_location, allow_nil: true + # composed_of :ip_address, + # class_name: 'IPAddr', + # mapping: %w(ip to_i), + # constructor: Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) }, + # converter: Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) } + # + def composed_of(part_id, options = {}) + options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter) + + name = part_id.id2name + class_name = options[:class_name] || name.camelize + mapping = options[:mapping] || [ name, name ] + mapping = [ mapping ] unless mapping.first.is_a?(Array) + allow_nil = options[:allow_nil] || false + constructor = options[:constructor] || :new + converter = options[:converter] + + reader_method(name, class_name, mapping, allow_nil, constructor) + writer_method(name, class_name, mapping, allow_nil, converter) + + reflection = ActiveRecord::Reflection.create(:composed_of, part_id, nil, options, self) + Reflection.add_aggregate_reflection self, part_id, reflection + end + + private + def reader_method(name, class_name, mapping, allow_nil, constructor) + define_method(name) do + if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? { |key, _| !_read_attribute(key).nil? }) + attrs = mapping.collect { |key, _| _read_attribute(key) } + object = constructor.respond_to?(:call) ? + constructor.call(*attrs) : + class_name.constantize.send(constructor, *attrs) + @aggregation_cache[name] = object + end + @aggregation_cache[name] + end + end + + def writer_method(name, class_name, mapping, allow_nil, converter) + define_method("#{name}=") do |part| + klass = class_name.constantize + + unless part.is_a?(klass) || converter.nil? || part.nil? + part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part) + end + + hash_from_multiparameter_assignment = part.is_a?(Hash) && + part.each_key.all? { |k| k.is_a?(Integer) } + if hash_from_multiparameter_assignment + raise ArgumentError unless part.size == part.each_key.max + part = klass.new(*part.sort.map(&:last)) + end + + if part.nil? && allow_nil + mapping.each { |key, _| self[key] = nil } + @aggregation_cache[name] = nil + else + mapping.each { |key, value| self[key] = part.send(value) } + @aggregation_cache[name] = part.freeze + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/association_relation.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/association_relation.rb new file mode 100644 index 00000000..4c538ef2 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/association_relation.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module ActiveRecord + class AssociationRelation < Relation + def initialize(klass, association) + super(klass) + @association = association + end + + def proxy_association + @association + end + + def ==(other) + other == records + end + + def build(*args, &block) + scoping { @association.build(*args, &block) } + end + alias new build + + def create(*args, &block) + scoping { @association.create(*args, &block) } + end + + def create!(*args, &block) + scoping { @association.create!(*args, &block) } + end + + private + + def exec_queries + super do |record| + @association.set_inverse_instance_from_queries(record) + yield record if block_given? + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations.rb new file mode 100644 index 00000000..d5b5baa9 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations.rb @@ -0,0 +1,1860 @@ +# frozen_string_literal: true + +require "active_support/core_ext/enumerable" +require "active_support/core_ext/string/conversions" +require "active_support/core_ext/module/remove_method" +require "active_record/errors" + +module ActiveRecord + class AssociationNotFoundError < ConfigurationError #:nodoc: + def initialize(record = nil, association_name = nil) + if record && association_name + super("Association named '#{association_name}' was not found on #{record.class.name}; perhaps you misspelled it?") + else + super("Association was not found.") + end + end + end + + class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc: + def initialize(reflection = nil, associated_class = nil) + if reflection + super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{associated_class.nil? ? reflection.class_name : associated_class.name})") + else + super("Could not find the inverse association.") + end + end + end + + class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc: + def initialize(owner_class_name = nil, reflection = nil) + if owner_class_name && reflection + super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class_name}") + else + super("Could not find the association.") + end + end + end + + class HasManyThroughAssociationPolymorphicSourceError < ActiveRecordError #:nodoc: + def initialize(owner_class_name = nil, reflection = nil, source_reflection = nil) + if owner_class_name && reflection && source_reflection + super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}' without 'source_type'. Try adding 'source_type: \"#{reflection.name.to_s.classify}\"' to 'has_many :through' definition.") + else + super("Cannot have a has_many :through association.") + end + end + end + + class HasManyThroughAssociationPolymorphicThroughError < ActiveRecordError #:nodoc: + def initialize(owner_class_name = nil, reflection = nil) + if owner_class_name && reflection + super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.") + else + super("Cannot have a has_many :through association.") + end + end + end + + class HasManyThroughAssociationPointlessSourceTypeError < ActiveRecordError #:nodoc: + def initialize(owner_class_name = nil, reflection = nil, source_reflection = nil) + if owner_class_name && reflection && source_reflection + super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' with a :source_type option if the '#{reflection.through_reflection.class_name}##{source_reflection.name}' is not polymorphic. Try removing :source_type on your association.") + else + super("Cannot have a has_many :through association.") + end + end + end + + class HasOneThroughCantAssociateThroughCollection < ActiveRecordError #:nodoc: + def initialize(owner_class_name = nil, reflection = nil, through_reflection = nil) + if owner_class_name && reflection && through_reflection + super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' where the :through association '#{owner_class_name}##{through_reflection.name}' is a collection. Specify a has_one or belongs_to association in the :through option instead.") + else + super("Cannot have a has_one :through association.") + end + end + end + + class HasOneAssociationPolymorphicThroughError < ActiveRecordError #:nodoc: + def initialize(owner_class_name = nil, reflection = nil) + if owner_class_name && reflection + super("Cannot have a has_one :through association '#{owner_class_name}##{reflection.name}' which goes through the polymorphic association '#{owner_class_name}##{reflection.through_reflection.name}'.") + else + super("Cannot have a has_one :through association.") + end + end + end + + class HasManyThroughSourceAssociationNotFoundError < ActiveRecordError #:nodoc: + def initialize(reflection = nil) + if reflection + through_reflection = reflection.through_reflection + source_reflection_names = reflection.source_reflection_names + source_associations = reflection.through_reflection.klass._reflections.keys + super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence(two_words_connector: ' or ', last_word_connector: ', or ', locale: :en)} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => '. Is it one of #{source_associations.to_sentence(two_words_connector: ' or ', last_word_connector: ', or ', locale: :en)}?") + else + super("Could not find the source association(s).") + end + end + end + + class HasManyThroughOrderError < ActiveRecordError #:nodoc: + def initialize(owner_class_name = nil, reflection = nil, through_reflection = nil) + if owner_class_name && reflection && through_reflection + super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' which goes through '#{owner_class_name}##{through_reflection.name}' before the through association is defined.") + else + super("Cannot have a has_many :through association before the through association is defined.") + end + end + end + + class ThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc: + def initialize(owner = nil, reflection = nil) + if owner && reflection + super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.") + else + super("Cannot modify association.") + end + end + end + + class AmbiguousSourceReflectionForThroughAssociation < ActiveRecordError # :nodoc: + def initialize(klass, macro, association_name, options, possible_sources) + example_options = options.dup + example_options[:source] = possible_sources.first + + super("Ambiguous source reflection for through association. Please " \ + "specify a :source directive on your declaration like:\n" \ + "\n" \ + " class #{klass} < ActiveRecord::Base\n" \ + " #{macro} :#{association_name}, #{example_options}\n" \ + " end" + ) + end + end + + class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ThroughCantAssociateThroughHasOneOrManyReflection #:nodoc: + end + + class HasOneThroughCantAssociateThroughHasOneOrManyReflection < ThroughCantAssociateThroughHasOneOrManyReflection #:nodoc: + end + + class ThroughNestedAssociationsAreReadonly < ActiveRecordError #:nodoc: + def initialize(owner = nil, reflection = nil) + if owner && reflection + super("Cannot modify association '#{owner.class.name}##{reflection.name}' because it goes through more than one other association.") + else + super("Through nested associations are read-only.") + end + end + end + + class HasManyThroughNestedAssociationsAreReadonly < ThroughNestedAssociationsAreReadonly #:nodoc: + end + + class HasOneThroughNestedAssociationsAreReadonly < ThroughNestedAssociationsAreReadonly #:nodoc: + end + + # This error is raised when trying to eager load a polymorphic association using a JOIN. + # Eager loading polymorphic associations is only possible with + # {ActiveRecord::Relation#preload}[rdoc-ref:QueryMethods#preload]. + class EagerLoadPolymorphicError < ActiveRecordError + def initialize(reflection = nil) + if reflection + super("Cannot eagerly load the polymorphic association #{reflection.name.inspect}") + else + super("Eager load polymorphic error.") + end + end + end + + # This error is raised when trying to destroy a parent instance in N:1 or 1:1 associations + # (has_many, has_one) when there is at least 1 child associated instance. + # ex: if @project.tasks.size > 0, DeleteRestrictionError will be raised when trying to destroy @project + class DeleteRestrictionError < ActiveRecordError #:nodoc: + def initialize(name = nil) + if name + super("Cannot delete record because of dependent #{name}") + else + super("Delete restriction error.") + end + end + end + + # See ActiveRecord::Associations::ClassMethods for documentation. + module Associations # :nodoc: + extend ActiveSupport::Autoload + extend ActiveSupport::Concern + + # These classes will be loaded when associations are created. + # So there is no need to eager load them. + autoload :Association + autoload :SingularAssociation + autoload :CollectionAssociation + autoload :ForeignAssociation + autoload :CollectionProxy + autoload :ThroughAssociation + + module Builder #:nodoc: + autoload :Association, "active_record/associations/builder/association" + autoload :SingularAssociation, "active_record/associations/builder/singular_association" + autoload :CollectionAssociation, "active_record/associations/builder/collection_association" + + autoload :BelongsTo, "active_record/associations/builder/belongs_to" + autoload :HasOne, "active_record/associations/builder/has_one" + autoload :HasMany, "active_record/associations/builder/has_many" + autoload :HasAndBelongsToMany, "active_record/associations/builder/has_and_belongs_to_many" + end + + eager_autoload do + autoload :BelongsToAssociation + autoload :BelongsToPolymorphicAssociation + autoload :HasManyAssociation + autoload :HasManyThroughAssociation + autoload :HasOneAssociation + autoload :HasOneThroughAssociation + + autoload :Preloader + autoload :JoinDependency + autoload :AssociationScope + autoload :AliasTracker + end + + def self.eager_load! + super + Preloader.eager_load! + end + + # Returns the association instance for the given name, instantiating it if it doesn't already exist + def association(name) #:nodoc: + association = association_instance_get(name) + + if association.nil? + unless reflection = self.class._reflect_on_association(name) + raise AssociationNotFoundError.new(self, name) + end + association = reflection.association_class.new(self, reflection) + association_instance_set(name, association) + end + + association + end + + def association_cached?(name) # :nodoc: + @association_cache.key?(name) + end + + def initialize_dup(*) # :nodoc: + @association_cache = {} + super + end + + def reload(*) # :nodoc: + clear_association_cache + super + end + + private + # Clears out the association cache. + def clear_association_cache + @association_cache.clear if persisted? + end + + def init_internals + @association_cache = {} + super + end + + # Returns the specified association instance if it exists, +nil+ otherwise. + def association_instance_get(name) + @association_cache[name] + end + + # Set the specified association instance. + def association_instance_set(name, association) + @association_cache[name] = association + end + + # \Associations are a set of macro-like class methods for tying objects together through + # foreign keys. They express relationships like "Project has one Project Manager" + # or "Project belongs to a Portfolio". Each macro adds a number of methods to the + # class which are specialized according to the collection or association symbol and the + # options hash. It works much the same way as Ruby's own attr* + # methods. + # + # class Project < ActiveRecord::Base + # belongs_to :portfolio + # has_one :project_manager + # has_many :milestones + # has_and_belongs_to_many :categories + # end + # + # The project class now has the following methods (and more) to ease the traversal and + # manipulation of its relationships: + # * Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil? + # * Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?, + # * Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone), + # Project#milestones.delete(milestone), Project#milestones.destroy(milestone), Project#milestones.find(milestone_id), + # Project#milestones.build, Project#milestones.create + # * Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1), + # Project#categories.delete(category1), Project#categories.destroy(category1) + # + # === A word of warning + # + # Don't create associations that have the same name as {instance methods}[rdoc-ref:ActiveRecord::Core] of + # ActiveRecord::Base. Since the association adds a method with that name to + # its model, using an association with the same name as one provided by ActiveRecord::Base will override the method inherited through ActiveRecord::Base and will break things. + # For instance, +attributes+ and +connection+ would be bad choices for association names, because those names already exist in the list of ActiveRecord::Base instance methods. + # + # == Auto-generated methods + # See also Instance Public methods below for more details. + # + # === Singular associations (one-to-one) + # | | belongs_to | + # generated methods | belongs_to | :polymorphic | has_one + # ----------------------------------+------------+--------------+--------- + # other | X | X | X + # other=(other) | X | X | X + # build_other(attributes={}) | X | | X + # create_other(attributes={}) | X | | X + # create_other!(attributes={}) | X | | X + # reload_other | X | X | X + # + # === Collection associations (one-to-many / many-to-many) + # | | | has_many + # generated methods | habtm | has_many | :through + # ----------------------------------+-------+----------+---------- + # others | X | X | X + # others=(other,other,...) | X | X | X + # other_ids | X | X | X + # other_ids=(id,id,...) | X | X | X + # others<< | X | X | X + # others.push | X | X | X + # others.concat | X | X | X + # others.build(attributes={}) | X | X | X + # others.create(attributes={}) | X | X | X + # others.create!(attributes={}) | X | X | X + # others.size | X | X | X + # others.length | X | X | X + # others.count | X | X | X + # others.sum(*args) | X | X | X + # others.empty? | X | X | X + # others.clear | X | X | X + # others.delete(other,other,...) | X | X | X + # others.delete_all | X | X | X + # others.destroy(other,other,...) | X | X | X + # others.destroy_all | X | X | X + # others.find(*args) | X | X | X + # others.exists? | X | X | X + # others.distinct | X | X | X + # others.reset | X | X | X + # others.reload | X | X | X + # + # === Overriding generated methods + # + # Association methods are generated in a module included into the model + # class, making overrides easy. The original generated method can thus be + # called with +super+: + # + # class Car < ActiveRecord::Base + # belongs_to :owner + # belongs_to :old_owner + # + # def owner=(new_owner) + # self.old_owner = self.owner + # super + # end + # end + # + # The association methods module is included immediately after the + # generated attributes methods module, meaning an association will + # override the methods for an attribute with the same name. + # + # == Cardinality and associations + # + # Active Record associations can be used to describe one-to-one, one-to-many and many-to-many + # relationships between models. Each model uses an association to describe its role in + # the relation. The #belongs_to association is always used in the model that has + # the foreign key. + # + # === One-to-one + # + # Use #has_one in the base, and #belongs_to in the associated model. + # + # class Employee < ActiveRecord::Base + # has_one :office + # end + # class Office < ActiveRecord::Base + # belongs_to :employee # foreign key - employee_id + # end + # + # === One-to-many + # + # Use #has_many in the base, and #belongs_to in the associated model. + # + # class Manager < ActiveRecord::Base + # has_many :employees + # end + # class Employee < ActiveRecord::Base + # belongs_to :manager # foreign key - manager_id + # end + # + # === Many-to-many + # + # There are two ways to build a many-to-many relationship. + # + # The first way uses a #has_many association with the :through option and a join model, so + # there are two stages of associations. + # + # class Assignment < ActiveRecord::Base + # belongs_to :programmer # foreign key - programmer_id + # belongs_to :project # foreign key - project_id + # end + # class Programmer < ActiveRecord::Base + # has_many :assignments + # has_many :projects, through: :assignments + # end + # class Project < ActiveRecord::Base + # has_many :assignments + # has_many :programmers, through: :assignments + # end + # + # For the second way, use #has_and_belongs_to_many in both models. This requires a join table + # that has no corresponding model or primary key. + # + # class Programmer < ActiveRecord::Base + # has_and_belongs_to_many :projects # foreign keys in the join table + # end + # class Project < ActiveRecord::Base + # has_and_belongs_to_many :programmers # foreign keys in the join table + # end + # + # Choosing which way to build a many-to-many relationship is not always simple. + # If you need to work with the relationship model as its own entity, + # use #has_many :through. Use #has_and_belongs_to_many when working with legacy schemas or when + # you never work directly with the relationship itself. + # + # == Is it a #belongs_to or #has_one association? + # + # Both express a 1-1 relationship. The difference is mostly where to place the foreign + # key, which goes on the table for the class declaring the #belongs_to relationship. + # + # class User < ActiveRecord::Base + # # I reference an account. + # belongs_to :account + # end + # + # class Account < ActiveRecord::Base + # # One user references me. + # has_one :user + # end + # + # The tables for these classes could look something like: + # + # CREATE TABLE users ( + # id bigint NOT NULL auto_increment, + # account_id bigint default NULL, + # name varchar default NULL, + # PRIMARY KEY (id) + # ) + # + # CREATE TABLE accounts ( + # id bigint NOT NULL auto_increment, + # name varchar default NULL, + # PRIMARY KEY (id) + # ) + # + # == Unsaved objects and associations + # + # You can manipulate objects and associations before they are saved to the database, but + # there is some special behavior you should be aware of, mostly involving the saving of + # associated objects. + # + # You can set the :autosave option on a #has_one, #belongs_to, + # #has_many, or #has_and_belongs_to_many association. Setting it + # to +true+ will _always_ save the members, whereas setting it to +false+ will + # _never_ save the members. More details about :autosave option is available at + # AutosaveAssociation. + # + # === One-to-one associations + # + # * Assigning an object to a #has_one association automatically saves that object and + # the object being replaced (if there is one), in order to update their foreign + # keys - except if the parent object is unsaved (new_record? == true). + # * If either of these saves fail (due to one of the objects being invalid), an + # ActiveRecord::RecordNotSaved exception is raised and the assignment is + # cancelled. + # * If you wish to assign an object to a #has_one association without saving it, + # use the #build_association method (documented below). The object being + # replaced will still be saved to update its foreign key. + # * Assigning an object to a #belongs_to association does not save the object, since + # the foreign key field belongs on the parent. It does not save the parent either. + # + # === Collections + # + # * Adding an object to a collection (#has_many or #has_and_belongs_to_many) automatically + # saves that object, except if the parent object (the owner of the collection) is not yet + # stored in the database. + # * If saving any of the objects being added to a collection (via push or similar) + # fails, then push returns +false+. + # * If saving fails while replacing the collection (via association=), an + # ActiveRecord::RecordNotSaved exception is raised and the assignment is + # cancelled. + # * You can add an object to a collection without automatically saving it by using the + # collection.build method (documented below). + # * All unsaved (new_record? == true) members of the collection are automatically + # saved when the parent is saved. + # + # == Customizing the query + # + # \Associations are built from Relation objects, and you can use the Relation syntax + # to customize them. For example, to add a condition: + # + # class Blog < ActiveRecord::Base + # has_many :published_posts, -> { where(published: true) }, class_name: 'Post' + # end + # + # Inside the -> { ... } block you can use all of the usual Relation methods. + # + # === Accessing the owner object + # + # Sometimes it is useful to have access to the owner object when building the query. The owner + # is passed as a parameter to the block. For example, the following association would find all + # events that occur on the user's birthday: + # + # class User < ActiveRecord::Base + # has_many :birthday_events, ->(user) { where(starts_on: user.birthday) }, class_name: 'Event' + # end + # + # Note: Joining, eager loading and preloading of these associations is not possible. + # These operations happen before instance creation and the scope will be called with a +nil+ argument. + # + # == Association callbacks + # + # Similar to the normal callbacks that hook into the life cycle of an Active Record object, + # you can also define callbacks that get triggered when you add an object to or remove an + # object from an association collection. + # + # class Project + # has_and_belongs_to_many :developers, after_add: :evaluate_velocity + # + # def evaluate_velocity(developer) + # ... + # end + # end + # + # It's possible to stack callbacks by passing them as an array. Example: + # + # class Project + # has_and_belongs_to_many :developers, + # after_add: [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}] + # end + # + # Possible callbacks are: +before_add+, +after_add+, +before_remove+ and +after_remove+. + # + # If any of the +before_add+ callbacks throw an exception, the object will not be + # added to the collection. + # + # Similarly, if any of the +before_remove+ callbacks throw an exception, the object + # will not be removed from the collection. + # + # == Association extensions + # + # The proxy objects that control the access to associations can be extended through anonymous + # modules. This is especially beneficial for adding new finders, creators, and other + # factory-type methods that are only used as part of this association. + # + # class Account < ActiveRecord::Base + # has_many :people do + # def find_or_create_by_name(name) + # first_name, last_name = name.split(" ", 2) + # find_or_create_by(first_name: first_name, last_name: last_name) + # end + # end + # end + # + # person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson") + # person.first_name # => "David" + # person.last_name # => "Heinemeier Hansson" + # + # If you need to share the same extensions between many associations, you can use a named + # extension module. + # + # module FindOrCreateByNameExtension + # def find_or_create_by_name(name) + # first_name, last_name = name.split(" ", 2) + # find_or_create_by(first_name: first_name, last_name: last_name) + # end + # end + # + # class Account < ActiveRecord::Base + # has_many :people, -> { extending FindOrCreateByNameExtension } + # end + # + # class Company < ActiveRecord::Base + # has_many :people, -> { extending FindOrCreateByNameExtension } + # end + # + # Some extensions can only be made to work with knowledge of the association's internals. + # Extensions can access relevant state using the following methods (where +items+ is the + # name of the association): + # + # * record.association(:items).owner - Returns the object the association is part of. + # * record.association(:items).reflection - Returns the reflection object that describes the association. + # * record.association(:items).target - Returns the associated object for #belongs_to and #has_one, or + # the collection of associated objects for #has_many and #has_and_belongs_to_many. + # + # However, inside the actual extension code, you will not have access to the record as + # above. In this case, you can access proxy_association. For example, + # record.association(:items) and record.items.proxy_association will return + # the same object, allowing you to make calls like proxy_association.owner inside + # association extensions. + # + # == Association Join Models + # + # Has Many associations can be configured with the :through option to use an + # explicit join model to retrieve the data. This operates similarly to a + # #has_and_belongs_to_many association. The advantage is that you're able to add validations, + # callbacks, and extra attributes on the join model. Consider the following schema: + # + # class Author < ActiveRecord::Base + # has_many :authorships + # has_many :books, through: :authorships + # end + # + # class Authorship < ActiveRecord::Base + # belongs_to :author + # belongs_to :book + # end + # + # @author = Author.first + # @author.authorships.collect { |a| a.book } # selects all books that the author's authorships belong to + # @author.books # selects all books by using the Authorship join model + # + # You can also go through a #has_many association on the join model: + # + # class Firm < ActiveRecord::Base + # has_many :clients + # has_many :invoices, through: :clients + # end + # + # class Client < ActiveRecord::Base + # belongs_to :firm + # has_many :invoices + # end + # + # class Invoice < ActiveRecord::Base + # belongs_to :client + # end + # + # @firm = Firm.first + # @firm.clients.flat_map { |c| c.invoices } # select all invoices for all clients of the firm + # @firm.invoices # selects all invoices by going through the Client join model + # + # Similarly you can go through a #has_one association on the join model: + # + # class Group < ActiveRecord::Base + # has_many :users + # has_many :avatars, through: :users + # end + # + # class User < ActiveRecord::Base + # belongs_to :group + # has_one :avatar + # end + # + # class Avatar < ActiveRecord::Base + # belongs_to :user + # end + # + # @group = Group.first + # @group.users.collect { |u| u.avatar }.compact # select all avatars for all users in the group + # @group.avatars # selects all avatars by going through the User join model. + # + # An important caveat with going through #has_one or #has_many associations on the + # join model is that these associations are *read-only*. For example, the following + # would not work following the previous example: + # + # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around + # @group.avatars.delete(@group.avatars.last) # so would this + # + # == Setting Inverses + # + # If you are using a #belongs_to on the join model, it is a good idea to set the + # :inverse_of option on the #belongs_to, which will mean that the following example + # works correctly (where tags is a #has_many :through association): + # + # @post = Post.first + # @tag = @post.tags.build name: "ruby" + # @tag.save + # + # The last line ought to save the through record (a Tagging). This will only work if the + # :inverse_of is set: + # + # class Tagging < ActiveRecord::Base + # belongs_to :post + # belongs_to :tag, inverse_of: :taggings + # end + # + # If you do not set the :inverse_of record, the association will + # do its best to match itself up with the correct inverse. Automatic + # inverse detection only works on #has_many, #has_one, and + # #belongs_to associations. + # + # Extra options on the associations, as defined in the + # AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS constant, will + # also prevent the association's inverse from being found automatically. + # + # The automatic guessing of the inverse association uses a heuristic based + # on the name of the class, so it may not work for all associations, + # especially the ones with non-standard names. + # + # You can turn off the automatic detection of inverse associations by setting + # the :inverse_of option to false like so: + # + # class Tagging < ActiveRecord::Base + # belongs_to :tag, inverse_of: false + # end + # + # == Nested \Associations + # + # You can actually specify *any* association with the :through option, including an + # association which has a :through option itself. For example: + # + # class Author < ActiveRecord::Base + # has_many :posts + # has_many :comments, through: :posts + # has_many :commenters, through: :comments + # end + # + # class Post < ActiveRecord::Base + # has_many :comments + # end + # + # class Comment < ActiveRecord::Base + # belongs_to :commenter + # end + # + # @author = Author.first + # @author.commenters # => People who commented on posts written by the author + # + # An equivalent way of setting up this association this would be: + # + # class Author < ActiveRecord::Base + # has_many :posts + # has_many :commenters, through: :posts + # end + # + # class Post < ActiveRecord::Base + # has_many :comments + # has_many :commenters, through: :comments + # end + # + # class Comment < ActiveRecord::Base + # belongs_to :commenter + # end + # + # When using a nested association, you will not be able to modify the association because there + # is not enough information to know what modification to make. For example, if you tried to + # add a Commenter in the example above, there would be no way to tell how to set up the + # intermediate Post and Comment objects. + # + # == Polymorphic \Associations + # + # Polymorphic associations on models are not restricted on what types of models they + # can be associated with. Rather, they specify an interface that a #has_many association + # must adhere to. + # + # class Asset < ActiveRecord::Base + # belongs_to :attachable, polymorphic: true + # end + # + # class Post < ActiveRecord::Base + # has_many :assets, as: :attachable # The :as option specifies the polymorphic interface to use. + # end + # + # @asset.attachable = @post + # + # This works by using a type column in addition to a foreign key to specify the associated + # record. In the Asset example, you'd need an +attachable_id+ integer column and an + # +attachable_type+ string column. + # + # Using polymorphic associations in combination with single table inheritance (STI) is + # a little tricky. In order for the associations to work as expected, ensure that you + # store the base model for the STI models in the type column of the polymorphic + # association. To continue with the asset example above, suppose there are guest posts + # and member posts that use the posts table for STI. In this case, there must be a +type+ + # column in the posts table. + # + # Note: The attachable_type= method is being called when assigning an +attachable+. + # The +class_name+ of the +attachable+ is passed as a String. + # + # class Asset < ActiveRecord::Base + # belongs_to :attachable, polymorphic: true + # + # def attachable_type=(class_name) + # super(class_name.constantize.base_class.to_s) + # end + # end + # + # class Post < ActiveRecord::Base + # # because we store "Post" in attachable_type now dependent: :destroy will work + # has_many :assets, as: :attachable, dependent: :destroy + # end + # + # class GuestPost < Post + # end + # + # class MemberPost < Post + # end + # + # == Caching + # + # All of the methods are built on a simple caching principle that will keep the result + # of the last query around unless specifically instructed not to. The cache is even + # shared across methods to make it even cheaper to use the macro-added methods without + # worrying too much about performance at the first go. + # + # project.milestones # fetches milestones from the database + # project.milestones.size # uses the milestone cache + # project.milestones.empty? # uses the milestone cache + # project.milestones.reload.size # fetches milestones from the database + # project.milestones # uses the milestone cache + # + # == Eager loading of associations + # + # Eager loading is a way to find objects of a certain class and a number of named associations. + # It is one of the easiest ways to prevent the dreaded N+1 problem in which fetching 100 + # posts that each need to display their author triggers 101 database queries. Through the + # use of eager loading, the number of queries will be reduced from 101 to 2. + # + # class Post < ActiveRecord::Base + # belongs_to :author + # has_many :comments + # end + # + # Consider the following loop using the class above: + # + # Post.all.each do |post| + # puts "Post: " + post.title + # puts "Written by: " + post.author.name + # puts "Last comment on: " + post.comments.first.created_on + # end + # + # To iterate over these one hundred posts, we'll generate 201 database queries. Let's + # first just optimize it for retrieving the author: + # + # Post.includes(:author).each do |post| + # + # This references the name of the #belongs_to association that also used the :author + # symbol. After loading the posts, +find+ will collect the +author_id+ from each one and load + # all of the referenced authors with one query. Doing so will cut down the number of queries + # from 201 to 102. + # + # We can improve upon the situation further by referencing both associations in the finder with: + # + # Post.includes(:author, :comments).each do |post| + # + # This will load all comments with a single query. This reduces the total number of queries + # to 3. In general, the number of queries will be 1 plus the number of associations + # named (except if some of the associations are polymorphic #belongs_to - see below). + # + # To include a deep hierarchy of associations, use a hash: + # + # Post.includes(:author, { comments: { author: :gravatar } }).each do |post| + # + # The above code will load all the comments and all of their associated + # authors and gravatars. You can mix and match any combination of symbols, + # arrays, and hashes to retrieve the associations you want to load. + # + # All of this power shouldn't fool you into thinking that you can pull out huge amounts + # of data with no performance penalty just because you've reduced the number of queries. + # The database still needs to send all the data to Active Record and it still needs to + # be processed. So it's no catch-all for performance problems, but it's a great way to + # cut down on the number of queries in a situation as the one described above. + # + # Since only one table is loaded at a time, conditions or orders cannot reference tables + # other than the main one. If this is the case, Active Record falls back to the previously + # used LEFT OUTER JOIN based strategy. For example: + # + # Post.includes([:author, :comments]).where(['comments.approved = ?', true]) + # + # This will result in a single SQL query with joins along the lines of: + # LEFT OUTER JOIN comments ON comments.post_id = posts.id and + # LEFT OUTER JOIN authors ON authors.id = posts.author_id. Note that using conditions + # like this can have unintended consequences. + # In the above example, posts with no approved comments are not returned at all because + # the conditions apply to the SQL statement as a whole and not just to the association. + # + # You must disambiguate column references for this fallback to happen, for example + # order: "author.name DESC" will work but order: "name DESC" will not. + # + # If you want to load all posts (including posts with no approved comments), then write + # your own LEFT OUTER JOIN query using ON: + # + # Post.joins("LEFT OUTER JOIN comments ON comments.post_id = posts.id AND comments.approved = '1'") + # + # In this case, it is usually more natural to include an association which has conditions defined on it: + # + # class Post < ActiveRecord::Base + # has_many :approved_comments, -> { where(approved: true) }, class_name: 'Comment' + # end + # + # Post.includes(:approved_comments) + # + # This will load posts and eager load the +approved_comments+ association, which contains + # only those comments that have been approved. + # + # If you eager load an association with a specified :limit option, it will be ignored, + # returning all the associated objects: + # + # class Picture < ActiveRecord::Base + # has_many :most_recent_comments, -> { order('id DESC').limit(10) }, class_name: 'Comment' + # end + # + # Picture.includes(:most_recent_comments).first.most_recent_comments # => returns all associated comments. + # + # Eager loading is supported with polymorphic associations. + # + # class Address < ActiveRecord::Base + # belongs_to :addressable, polymorphic: true + # end + # + # A call that tries to eager load the addressable model + # + # Address.includes(:addressable) + # + # This will execute one query to load the addresses and load the addressables with one + # query per addressable type. + # For example, if all the addressables are either of class Person or Company, then a total + # of 3 queries will be executed. The list of addressable types to load is determined on + # the back of the addresses loaded. This is not supported if Active Record has to fallback + # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError. + # The reason is that the parent model's type is a column value so its corresponding table + # name cannot be put in the +FROM+/+JOIN+ clauses of that query. + # + # == Table Aliasing + # + # Active Record uses table aliasing in the case that a table is referenced multiple times + # in a join. If a table is referenced only once, the standard table name is used. The + # second time, the table is aliased as #{reflection_name}_#{parent_table_name}. + # Indexes are appended for any more successive uses of the table name. + # + # Post.joins(:comments) + # # => SELECT ... FROM posts INNER JOIN comments ON ... + # Post.joins(:special_comments) # STI + # # => SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment' + # Post.joins(:comments, :special_comments) # special_comments is the reflection name, posts is the parent table name + # # => SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts + # + # Acts as tree example: + # + # TreeMixin.joins(:children) + # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... + # TreeMixin.joins(children: :parent) + # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... + # INNER JOIN parents_mixins ... + # TreeMixin.joins(children: {parent: :children}) + # # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ... + # INNER JOIN parents_mixins ... + # INNER JOIN mixins childrens_mixins_2 + # + # Has and Belongs to Many join tables use the same idea, but add a _join suffix: + # + # Post.joins(:categories) + # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... + # Post.joins(categories: :posts) + # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... + # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories + # Post.joins(categories: {posts: :categories}) + # # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ... + # INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories + # INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2 + # + # If you wish to specify your own custom joins using ActiveRecord::QueryMethods#joins method, those table + # names will take precedence over the eager associations: + # + # Post.joins(:comments).joins("inner join comments ...") + # # => SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ... + # Post.joins(:comments, :special_comments).joins("inner join comments ...") + # # => SELECT ... FROM posts INNER JOIN comments comments_posts ON ... + # INNER JOIN comments special_comments_posts ... + # INNER JOIN comments ... + # + # Table aliases are automatically truncated according to the maximum length of table identifiers + # according to the specific database. + # + # == Modules + # + # By default, associations will look for objects within the current module scope. Consider: + # + # module MyApplication + # module Business + # class Firm < ActiveRecord::Base + # has_many :clients + # end + # + # class Client < ActiveRecord::Base; end + # end + # end + # + # When Firm#clients is called, it will in turn call + # MyApplication::Business::Client.find_all_by_firm_id(firm.id). + # If you want to associate with a class in another module scope, this can be done by + # specifying the complete class name. + # + # module MyApplication + # module Business + # class Firm < ActiveRecord::Base; end + # end + # + # module Billing + # class Account < ActiveRecord::Base + # belongs_to :firm, class_name: "MyApplication::Business::Firm" + # end + # end + # end + # + # == Bi-directional associations + # + # When you specify an association, there is usually an association on the associated model + # that specifies the same relationship in reverse. For example, with the following models: + # + # class Dungeon < ActiveRecord::Base + # has_many :traps + # has_one :evil_wizard + # end + # + # class Trap < ActiveRecord::Base + # belongs_to :dungeon + # end + # + # class EvilWizard < ActiveRecord::Base + # belongs_to :dungeon + # end + # + # The +traps+ association on +Dungeon+ and the +dungeon+ association on +Trap+ are + # the inverse of each other, and the inverse of the +dungeon+ association on +EvilWizard+ + # is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default, + # Active Record can guess the inverse of the association based on the name + # of the class. The result is the following: + # + # d = Dungeon.first + # t = d.traps.first + # d.object_id == t.dungeon.object_id # => true + # + # The +Dungeon+ instances +d+ and t.dungeon in the above example refer to + # the same in-memory instance since the association matches the name of the class. + # The result would be the same if we added +:inverse_of+ to our model definitions: + # + # class Dungeon < ActiveRecord::Base + # has_many :traps, inverse_of: :dungeon + # has_one :evil_wizard, inverse_of: :dungeon + # end + # + # class Trap < ActiveRecord::Base + # belongs_to :dungeon, inverse_of: :traps + # end + # + # class EvilWizard < ActiveRecord::Base + # belongs_to :dungeon, inverse_of: :evil_wizard + # end + # + # For more information, see the documentation for the +:inverse_of+ option. + # + # == Deleting from associations + # + # === Dependent associations + # + # #has_many, #has_one, and #belongs_to associations support the :dependent option. + # This allows you to specify that associated records should be deleted when the owner is + # deleted. + # + # For example: + # + # class Author + # has_many :posts, dependent: :destroy + # end + # Author.find(1).destroy # => Will destroy all of the author's posts, too + # + # The :dependent option can have different values which specify how the deletion + # is done. For more information, see the documentation for this option on the different + # specific association types. When no option is given, the behavior is to do nothing + # with the associated records when destroying a record. + # + # Note that :dependent is implemented using Rails' callback + # system, which works by processing callbacks in order. Therefore, other + # callbacks declared either before or after the :dependent option + # can affect what it does. + # + # Note that :dependent option is ignored for #has_one :through associations. + # + # === Delete or destroy? + # + # #has_many and #has_and_belongs_to_many associations have the methods destroy, + # delete, destroy_all and delete_all. + # + # For #has_and_belongs_to_many, delete and destroy are the same: they + # cause the records in the join table to be removed. + # + # For #has_many, destroy and destroy_all will always call the destroy method of the + # record(s) being removed so that callbacks are run. However delete and delete_all will either + # do the deletion according to the strategy specified by the :dependent option, or + # if no :dependent option is given, then it will follow the default strategy. + # The default strategy is to do nothing (leave the foreign keys with the parent ids set), except for + # #has_many :through, where the default strategy is delete_all (delete + # the join records, without running their callbacks). + # + # There is also a clear method which is the same as delete_all, except that + # it returns the association rather than the records which have been deleted. + # + # === What gets deleted? + # + # There is a potential pitfall here: #has_and_belongs_to_many and #has_many :through + # associations have records in join tables, as well as the associated records. So when we + # call one of these deletion methods, what exactly should be deleted? + # + # The answer is that it is assumed that deletion on an association is about removing the + # link between the owner and the associated object(s), rather than necessarily the + # associated objects themselves. So with #has_and_belongs_to_many and #has_many + # :through, the join records will be deleted, but the associated records won't. + # + # This makes sense if you think about it: if you were to call post.tags.delete(Tag.find_by(name: 'food')) + # you would want the 'food' tag to be unlinked from the post, rather than for the tag itself + # to be removed from the database. + # + # However, there are examples where this strategy doesn't make sense. For example, suppose + # a person has many projects, and each project has many tasks. If we deleted one of a person's + # tasks, we would probably not want the project to be deleted. In this scenario, the delete method + # won't actually work: it can only be used if the association on the join model is a + # #belongs_to. In other situations you are expected to perform operations directly on + # either the associated records or the :through association. + # + # With a regular #has_many there is no distinction between the "associated records" + # and the "link", so there is only one choice for what gets deleted. + # + # With #has_and_belongs_to_many and #has_many :through, if you want to delete the + # associated records themselves, you can always do something along the lines of + # person.tasks.each(&:destroy). + # + # == Type safety with ActiveRecord::AssociationTypeMismatch + # + # If you attempt to assign an object to an association that doesn't match the inferred + # or specified :class_name, you'll get an ActiveRecord::AssociationTypeMismatch. + # + # == Options + # + # All of the association macros can be specialized through options. This makes cases + # more complex than the simple and guessable ones possible. + module ClassMethods + # Specifies a one-to-many association. The following methods for retrieval and query of + # collections of associated objects will be added: + # + # +collection+ is a placeholder for the symbol passed as the +name+ argument, so + # has_many :clients would add among others clients.empty?. + # + # [collection] + # Returns a Relation of all the associated objects. + # An empty Relation is returned if none are found. + # [collection<<(object, ...)] + # Adds one or more objects to the collection by setting their foreign keys to the collection's primary key. + # Note that this operation instantly fires update SQL without waiting for the save or update call on the + # parent object, unless the parent object is a new record. + # This will also run validations and callbacks of associated object(s). + # [collection.delete(object, ...)] + # Removes one or more objects from the collection by setting their foreign keys to +NULL+. + # Objects will be in addition destroyed if they're associated with dependent: :destroy, + # and deleted if they're associated with dependent: :delete_all. + # + # If the :through option is used, then the join records are deleted (rather than + # nullified) by default, but you can specify dependent: :destroy or + # dependent: :nullify to override this. + # [collection.destroy(object, ...)] + # Removes one or more objects from the collection by running destroy on + # each record, regardless of any dependent option, ensuring callbacks are run. + # + # If the :through option is used, then the join records are destroyed + # instead, not the objects themselves. + # [collection=objects] + # Replaces the collections content by deleting and adding objects as appropriate. If the :through + # option is true callbacks in the join models are triggered except destroy callbacks, since deletion is + # direct by default. You can specify dependent: :destroy or + # dependent: :nullify to override this. + # [collection_singular_ids] + # Returns an array of the associated objects' ids + # [collection_singular_ids=ids] + # Replace the collection with the objects identified by the primary keys in +ids+. This + # method loads the models and calls collection=. See above. + # [collection.clear] + # Removes every object from the collection. This destroys the associated objects if they + # are associated with dependent: :destroy, deletes them directly from the + # database if dependent: :delete_all, otherwise sets their foreign keys to +NULL+. + # If the :through option is true no destroy callbacks are invoked on the join models. + # Join models are directly deleted. + # [collection.empty?] + # Returns +true+ if there are no associated objects. + # [collection.size] + # Returns the number of associated objects. + # [collection.find(...)] + # Finds an associated object according to the same rules as ActiveRecord::FinderMethods#find. + # [collection.exists?(...)] + # Checks whether an associated object with the given conditions exists. + # Uses the same rules as ActiveRecord::FinderMethods#exists?. + # [collection.build(attributes = {}, ...)] + # Returns one or more new objects of the collection type that have been instantiated + # with +attributes+ and linked to this object through a foreign key, but have not yet + # been saved. + # [collection.create(attributes = {})] + # Returns a new object of the collection type that has been instantiated + # with +attributes+, linked to this object through a foreign key, and that has already + # been saved (if it passed the validation). *Note*: This only works if the base model + # already exists in the DB, not if it is a new (unsaved) record! + # [collection.create!(attributes = {})] + # Does the same as collection.create, but raises ActiveRecord::RecordInvalid + # if the record is invalid. + # [collection.reload] + # Returns a Relation of all of the associated objects, forcing a database read. + # An empty Relation is returned if none are found. + # + # === Example + # + # A Firm class declares has_many :clients, which will add: + # * Firm#clients (similar to Client.where(firm_id: id)) + # * Firm#clients<< + # * Firm#clients.delete + # * Firm#clients.destroy + # * Firm#clients= + # * Firm#client_ids + # * Firm#client_ids= + # * Firm#clients.clear + # * Firm#clients.empty? (similar to firm.clients.size == 0) + # * Firm#clients.size (similar to Client.count "firm_id = #{id}") + # * Firm#clients.find (similar to Client.where(firm_id: id).find(id)) + # * Firm#clients.exists?(name: 'ACME') (similar to Client.exists?(name: 'ACME', firm_id: firm.id)) + # * Firm#clients.build (similar to Client.new(firm_id: id)) + # * Firm#clients.create (similar to c = Client.new(firm_id: id); c.save; c) + # * Firm#clients.create! (similar to c = Client.new(firm_id: id); c.save!) + # * Firm#clients.reload + # The declaration can also include an +options+ hash to specialize the behavior of the association. + # + # === Scopes + # + # You can pass a second argument +scope+ as a callable (i.e. proc or + # lambda) to retrieve a specific set of records or customize the generated + # query when you access the associated collection. + # + # Scope examples: + # has_many :comments, -> { where(author_id: 1) } + # has_many :employees, -> { joins(:address) } + # has_many :posts, ->(blog) { where("max_post_length > ?", blog.max_post_length) } + # + # === Extensions + # + # The +extension+ argument allows you to pass a block into a has_many + # association. This is useful for adding new finders, creators and other + # factory-type methods to be used as part of the association. + # + # Extension examples: + # has_many :employees do + # def find_or_create_by_name(name) + # first_name, last_name = name.split(" ", 2) + # find_or_create_by(first_name: first_name, last_name: last_name) + # end + # end + # + # === Options + # [:class_name] + # Specify the class name of the association. Use it only if that name can't be inferred + # from the association name. So has_many :products will by default be linked + # to the +Product+ class, but if the real class name is +SpecialProduct+, you'll have to + # specify it with this option. + # [:foreign_key] + # Specify the foreign key used for the association. By default this is guessed to be the name + # of this class in lower-case and "_id" suffixed. So a Person class that makes a #has_many + # association will use "person_id" as the default :foreign_key. + # + # If you are going to modify the association (rather than just read from it), then it is + # a good idea to set the :inverse_of option. + # [:foreign_type] + # Specify the column used to store the associated object's type, if this is a polymorphic + # association. By default this is guessed to be the name of the polymorphic association + # specified on "as" option with a "_type" suffix. So a class that defines a + # has_many :tags, as: :taggable association will use "taggable_type" as the + # default :foreign_type. + # [:primary_key] + # Specify the name of the column to use as the primary key for the association. By default this is +id+. + # [:dependent] + # Controls what happens to the associated objects when + # their owner is destroyed. Note that these are implemented as + # callbacks, and Rails executes callbacks in order. Therefore, other + # similar callbacks may affect the :dependent behavior, and the + # :dependent behavior may affect other callbacks. + # + # * :destroy causes all the associated objects to also be destroyed. + # * :delete_all causes all the associated objects to be deleted directly from the database (so callbacks will not be executed). + # * :nullify causes the foreign keys to be set to +NULL+. Callbacks are not executed. + # * :restrict_with_exception causes an exception to be raised if there are any associated records. + # * :restrict_with_error causes an error to be added to the owner if there are any associated objects. + # + # If using with the :through option, the association on the join model must be + # a #belongs_to, and the records which get deleted are the join records, rather than + # the associated records. + # + # If using dependent: :destroy on a scoped association, only the scoped objects are destroyed. + # For example, if a Post model defines + # has_many :comments, -> { where published: true }, dependent: :destroy and destroy is + # called on a post, only published comments are destroyed. This means that any unpublished comments in the + # database would still contain a foreign key pointing to the now deleted post. + # [:counter_cache] + # This option can be used to configure a custom named :counter_cache. You only need this option, + # when you customized the name of your :counter_cache on the #belongs_to association. + # [:as] + # Specifies a polymorphic interface (See #belongs_to). + # [:through] + # Specifies an association through which to perform the query. This can be any other type + # of association, including other :through associations. Options for :class_name, + # :primary_key and :foreign_key are ignored, as the association uses the + # source reflection. + # + # If the association on the join model is a #belongs_to, the collection can be modified + # and the records on the :through model will be automatically created and removed + # as appropriate. Otherwise, the collection is read-only, so you should manipulate the + # :through association directly. + # + # If you are going to modify the association (rather than just read from it), then it is + # a good idea to set the :inverse_of option on the source association on the + # join model. This allows associated records to be built which will automatically create + # the appropriate join model records when they are saved. (See the 'Association Join Models' + # section above.) + # [:source] + # Specifies the source association name used by #has_many :through queries. + # Only use it if the name cannot be inferred from the association. + # has_many :subscribers, through: :subscriptions will look for either :subscribers or + # :subscriber on Subscription, unless a :source is given. + # [:source_type] + # Specifies type of the source association used by #has_many :through queries where the source + # association is a polymorphic #belongs_to. + # [:validate] + # When set to +true+, validates new objects added to association when saving the parent object. +true+ by default. + # If you want to ensure associated objects are revalidated on every update, use +validates_associated+. + # [:autosave] + # If true, always save the associated objects or destroy them if marked for destruction, + # when saving the parent object. If false, never save or destroy the associated objects. + # By default, only save associated objects that are new records. This option is implemented as a + # +before_save+ callback. Because callbacks are run in the order they are defined, associated objects + # may need to be explicitly saved in any user-defined +before_save+ callbacks. + # + # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets + # :autosave to true. + # [:inverse_of] + # Specifies the name of the #belongs_to association on the associated object + # that is the inverse of this #has_many association. + # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail. + # [:extend] + # Specifies a module or array of modules that will be extended into the association object returned. + # Useful for defining methods on associations, especially when they should be shared between multiple + # association objects. + # + # Option examples: + # has_many :comments, -> { order("posted_on") } + # has_many :comments, -> { includes(:author) } + # has_many :people, -> { where(deleted: false).order("name") }, class_name: "Person" + # has_many :tracks, -> { order("position") }, dependent: :destroy + # has_many :comments, dependent: :nullify + # has_many :tags, as: :taggable + # has_many :reports, -> { readonly } + # has_many :subscribers, through: :subscriptions, source: :user + def has_many(name, scope = nil, **options, &extension) + reflection = Builder::HasMany.build(self, name, scope, options, &extension) + Reflection.add_reflection self, name, reflection + end + + # Specifies a one-to-one association with another class. This method should only be used + # if the other class contains the foreign key. If the current class contains the foreign key, + # then you should use #belongs_to instead. See also ActiveRecord::Associations::ClassMethods's overview + # on when to use #has_one and when to use #belongs_to. + # + # The following methods for retrieval and query of a single associated object will be added: + # + # +association+ is a placeholder for the symbol passed as the +name+ argument, so + # has_one :manager would add among others manager.nil?. + # + # [association] + # Returns the associated object. +nil+ is returned if none is found. + # [association=(associate)] + # Assigns the associate object, extracts the primary key, sets it as the foreign key, + # and saves the associate object. To avoid database inconsistencies, permanently deletes an existing + # associated object when assigning a new one, even if the new one isn't saved to database. + # [build_association(attributes = {})] + # Returns a new object of the associated type that has been instantiated + # with +attributes+ and linked to this object through a foreign key, but has not + # yet been saved. + # [create_association(attributes = {})] + # Returns a new object of the associated type that has been instantiated + # with +attributes+, linked to this object through a foreign key, and that + # has already been saved (if it passed the validation). + # [create_association!(attributes = {})] + # Does the same as create_association, but raises ActiveRecord::RecordInvalid + # if the record is invalid. + # [reload_association] + # Returns the associated object, forcing a database read. + # + # === Example + # + # An Account class declares has_one :beneficiary, which will add: + # * Account#beneficiary (similar to Beneficiary.where(account_id: id).first) + # * Account#beneficiary=(beneficiary) (similar to beneficiary.account_id = account.id; beneficiary.save) + # * Account#build_beneficiary (similar to Beneficiary.new(account_id: id)) + # * Account#create_beneficiary (similar to b = Beneficiary.new(account_id: id); b.save; b) + # * Account#create_beneficiary! (similar to b = Beneficiary.new(account_id: id); b.save!; b) + # * Account#reload_beneficiary + # + # === Scopes + # + # You can pass a second argument +scope+ as a callable (i.e. proc or + # lambda) to retrieve a specific record or customize the generated query + # when you access the associated object. + # + # Scope examples: + # has_one :author, -> { where(comment_id: 1) } + # has_one :employer, -> { joins(:company) } + # has_one :latest_post, ->(blog) { where("created_at > ?", blog.enabled_at) } + # + # === Options + # + # The declaration can also include an +options+ hash to specialize the behavior of the association. + # + # Options are: + # [:class_name] + # Specify the class name of the association. Use it only if that name can't be inferred + # from the association name. So has_one :manager will by default be linked to the Manager class, but + # if the real class name is Person, you'll have to specify it with this option. + # [:dependent] + # Controls what happens to the associated object when + # its owner is destroyed: + # + # * :destroy causes the associated object to also be destroyed + # * :delete causes the associated object to be deleted directly from the database (so callbacks will not execute) + # * :nullify causes the foreign key to be set to +NULL+. Callbacks are not executed. + # * :restrict_with_exception causes an exception to be raised if there is an associated record + # * :restrict_with_error causes an error to be added to the owner if there is an associated object + # + # Note that :dependent option is ignored when using :through option. + # [:foreign_key] + # Specify the foreign key used for the association. By default this is guessed to be the name + # of this class in lower-case and "_id" suffixed. So a Person class that makes a #has_one association + # will use "person_id" as the default :foreign_key. + # + # If you are going to modify the association (rather than just read from it), then it is + # a good idea to set the :inverse_of option. + # [:foreign_type] + # Specify the column used to store the associated object's type, if this is a polymorphic + # association. By default this is guessed to be the name of the polymorphic association + # specified on "as" option with a "_type" suffix. So a class that defines a + # has_one :tag, as: :taggable association will use "taggable_type" as the + # default :foreign_type. + # [:primary_key] + # Specify the method that returns the primary key used for the association. By default this is +id+. + # [:as] + # Specifies a polymorphic interface (See #belongs_to). + # [:through] + # Specifies a Join Model through which to perform the query. Options for :class_name, + # :primary_key, and :foreign_key are ignored, as the association uses the + # source reflection. You can only use a :through query through a #has_one + # or #belongs_to association on the join model. + # + # If you are going to modify the association (rather than just read from it), then it is + # a good idea to set the :inverse_of option. + # [:source] + # Specifies the source association name used by #has_one :through queries. + # Only use it if the name cannot be inferred from the association. + # has_one :favorite, through: :favorites will look for a + # :favorite on Favorite, unless a :source is given. + # [:source_type] + # Specifies type of the source association used by #has_one :through queries where the source + # association is a polymorphic #belongs_to. + # [:validate] + # When set to +true+, validates new objects added to association when saving the parent object. +false+ by default. + # If you want to ensure associated objects are revalidated on every update, use +validates_associated+. + # [:autosave] + # If true, always save the associated object or destroy it if marked for destruction, + # when saving the parent object. If false, never save or destroy the associated object. + # By default, only save the associated object if it's a new record. + # + # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets + # :autosave to true. + # [:inverse_of] + # Specifies the name of the #belongs_to association on the associated object + # that is the inverse of this #has_one association. + # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail. + # [:required] + # When set to +true+, the association will also have its presence validated. + # This will validate the association itself, not the id. You can use + # +:inverse_of+ to avoid an extra query during validation. + # + # Option examples: + # has_one :credit_card, dependent: :destroy # destroys the associated credit card + # has_one :credit_card, dependent: :nullify # updates the associated records foreign + # # key value to NULL rather than destroying it + # has_one :last_comment, -> { order('posted_on') }, class_name: "Comment" + # has_one :project_manager, -> { where(role: 'project_manager') }, class_name: "Person" + # has_one :attachment, as: :attachable + # has_one :boss, -> { readonly } + # has_one :club, through: :membership + # has_one :primary_address, -> { where(primary: true) }, through: :addressables, source: :addressable + # has_one :credit_card, required: true + def has_one(name, scope = nil, **options) + reflection = Builder::HasOne.build(self, name, scope, options) + Reflection.add_reflection self, name, reflection + end + + # Specifies a one-to-one association with another class. This method should only be used + # if this class contains the foreign key. If the other class contains the foreign key, + # then you should use #has_one instead. See also ActiveRecord::Associations::ClassMethods's overview + # on when to use #has_one and when to use #belongs_to. + # + # Methods will be added for retrieval and query for a single associated object, for which + # this object holds an id: + # + # +association+ is a placeholder for the symbol passed as the +name+ argument, so + # belongs_to :author would add among others author.nil?. + # + # [association] + # Returns the associated object. +nil+ is returned if none is found. + # [association=(associate)] + # Assigns the associate object, extracts the primary key, and sets it as the foreign key. + # [build_association(attributes = {})] + # Returns a new object of the associated type that has been instantiated + # with +attributes+ and linked to this object through a foreign key, but has not yet been saved. + # [create_association(attributes = {})] + # Returns a new object of the associated type that has been instantiated + # with +attributes+, linked to this object through a foreign key, and that + # has already been saved (if it passed the validation). + # [create_association!(attributes = {})] + # Does the same as create_association, but raises ActiveRecord::RecordInvalid + # if the record is invalid. + # [reload_association] + # Returns the associated object, forcing a database read. + # + # === Example + # + # A Post class declares belongs_to :author, which will add: + # * Post#author (similar to Author.find(author_id)) + # * Post#author=(author) (similar to post.author_id = author.id) + # * Post#build_author (similar to post.author = Author.new) + # * Post#create_author (similar to post.author = Author.new; post.author.save; post.author) + # * Post#create_author! (similar to post.author = Author.new; post.author.save!; post.author) + # * Post#reload_author + # The declaration can also include an +options+ hash to specialize the behavior of the association. + # + # === Scopes + # + # You can pass a second argument +scope+ as a callable (i.e. proc or + # lambda) to retrieve a specific record or customize the generated query + # when you access the associated object. + # + # Scope examples: + # belongs_to :firm, -> { where(id: 2) } + # belongs_to :user, -> { joins(:friends) } + # belongs_to :level, ->(game) { where("game_level > ?", game.current_level) } + # + # === Options + # + # [:class_name] + # Specify the class name of the association. Use it only if that name can't be inferred + # from the association name. So belongs_to :author will by default be linked to the Author class, but + # if the real class name is Person, you'll have to specify it with this option. + # [:foreign_key] + # Specify the foreign key used for the association. By default this is guessed to be the name + # of the association with an "_id" suffix. So a class that defines a belongs_to :person + # association will use "person_id" as the default :foreign_key. Similarly, + # belongs_to :favorite_person, class_name: "Person" will use a foreign key + # of "favorite_person_id". + # + # If you are going to modify the association (rather than just read from it), then it is + # a good idea to set the :inverse_of option. + # [:foreign_type] + # Specify the column used to store the associated object's type, if this is a polymorphic + # association. By default this is guessed to be the name of the association with a "_type" + # suffix. So a class that defines a belongs_to :taggable, polymorphic: true + # association will use "taggable_type" as the default :foreign_type. + # [:primary_key] + # Specify the method that returns the primary key of associated object used for the association. + # By default this is id. + # [:dependent] + # If set to :destroy, the associated object is destroyed when this object is. If set to + # :delete, the associated object is deleted *without* calling its destroy method. + # This option should not be specified when #belongs_to is used in conjunction with + # a #has_many relationship on another class because of the potential to leave + # orphaned records behind. + # [:counter_cache] + # Caches the number of belonging objects on the associate class through the use of CounterCache::ClassMethods#increment_counter + # and CounterCache::ClassMethods#decrement_counter. The counter cache is incremented when an object of this + # class is created and decremented when it's destroyed. This requires that a column + # named #{table_name}_count (such as +comments_count+ for a belonging Comment class) + # is used on the associate class (such as a Post class) - that is the migration for + # #{table_name}_count is created on the associate class (such that Post.comments_count will + # return the count cached, see note below). You can also specify a custom counter + # cache column by providing a column name instead of a +true+/+false+ value to this + # option (e.g., counter_cache: :my_custom_counter.) + # Note: Specifying a counter cache will add it to that model's list of readonly attributes + # using +attr_readonly+. + # [:polymorphic] + # Specify this association is a polymorphic association by passing +true+. + # Note: If you've enabled the counter cache, then you may want to add the counter cache attribute + # to the +attr_readonly+ list in the associated classes (e.g. class Post; attr_readonly :comments_count; end). + # [:validate] + # When set to +true+, validates new objects added to association when saving the parent object. +false+ by default. + # If you want to ensure associated objects are revalidated on every update, use +validates_associated+. + # [:autosave] + # If true, always save the associated object or destroy it if marked for destruction, when + # saving the parent object. + # If false, never save or destroy the associated object. + # By default, only save the associated object if it's a new record. + # + # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for + # sets :autosave to true. + # [:touch] + # If true, the associated object will be touched (the updated_at/on attributes set to current time) + # when this record is either saved or destroyed. If you specify a symbol, that attribute + # will be updated with the current time in addition to the updated_at/on attribute. + # Please note that with touching no validation is performed and only the +after_touch+, + # +after_commit+ and +after_rollback+ callbacks are executed. + # [:inverse_of] + # Specifies the name of the #has_one or #has_many association on the associated + # object that is the inverse of this #belongs_to association. + # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail. + # [:optional] + # When set to +true+, the association will not have its presence validated. + # [:required] + # When set to +true+, the association will also have its presence validated. + # This will validate the association itself, not the id. You can use + # +:inverse_of+ to avoid an extra query during validation. + # NOTE: required is set to true by default and is deprecated. If + # you don't want to have association presence validated, use optional: true. + # [:default] + # Provide a callable (i.e. proc or lambda) to specify that the association should + # be initialized with a particular record before validation. + # + # Option examples: + # belongs_to :firm, foreign_key: "client_of" + # belongs_to :person, primary_key: "name", foreign_key: "person_name" + # belongs_to :author, class_name: "Person", foreign_key: "author_id" + # belongs_to :valid_coupon, ->(o) { where "discounts > ?", o.payments_count }, + # class_name: "Coupon", foreign_key: "coupon_id" + # belongs_to :attachable, polymorphic: true + # belongs_to :project, -> { readonly } + # belongs_to :post, counter_cache: true + # belongs_to :comment, touch: true + # belongs_to :company, touch: :employees_last_updated_at + # belongs_to :user, optional: true + # belongs_to :account, default: -> { company.account } + def belongs_to(name, scope = nil, **options) + reflection = Builder::BelongsTo.build(self, name, scope, options) + Reflection.add_reflection self, name, reflection + end + + # Specifies a many-to-many relationship with another class. This associates two classes via an + # intermediate join table. Unless the join table is explicitly specified as an option, it is + # guessed using the lexical order of the class names. So a join between Developer and Project + # will give the default join table name of "developers_projects" because "D" precedes "P" alphabetically. + # Note that this precedence is calculated using the < operator for String. This + # means that if the strings are of different lengths, and the strings are equal when compared + # up to the shortest length, then the longer string is considered of higher + # lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers" + # to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes", + # but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the + # custom :join_table option if you need to. + # If your tables share a common prefix, it will only appear once at the beginning. For example, + # the tables "catalog_categories" and "catalog_products" generate a join table name of "catalog_categories_products". + # + # The join table should not have a primary key or a model associated with it. You must manually generate the + # join table with a migration such as this: + # + # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[5.0] + # def change + # create_join_table :developers, :projects + # end + # end + # + # It's also a good idea to add indexes to each of those columns to speed up the joins process. + # However, in MySQL it is advised to add a compound index for both of the columns as MySQL only + # uses one index per table during the lookup. + # + # Adds the following methods for retrieval and query: + # + # +collection+ is a placeholder for the symbol passed as the +name+ argument, so + # has_and_belongs_to_many :categories would add among others categories.empty?. + # + # [collection] + # Returns a Relation of all the associated objects. + # An empty Relation is returned if none are found. + # [collection<<(object, ...)] + # Adds one or more objects to the collection by creating associations in the join table + # (collection.push and collection.concat are aliases to this method). + # Note that this operation instantly fires update SQL without waiting for the save or update call on the + # parent object, unless the parent object is a new record. + # [collection.delete(object, ...)] + # Removes one or more objects from the collection by removing their associations from the join table. + # This does not destroy the objects. + # [collection.destroy(object, ...)] + # Removes one or more objects from the collection by running destroy on each association in the join table, overriding any dependent option. + # This does not destroy the objects. + # [collection=objects] + # Replaces the collection's content by deleting and adding objects as appropriate. + # [collection_singular_ids] + # Returns an array of the associated objects' ids. + # [collection_singular_ids=ids] + # Replace the collection by the objects identified by the primary keys in +ids+. + # [collection.clear] + # Removes every object from the collection. This does not destroy the objects. + # [collection.empty?] + # Returns +true+ if there are no associated objects. + # [collection.size] + # Returns the number of associated objects. + # [collection.find(id)] + # Finds an associated object responding to the +id+ and that + # meets the condition that it has to be associated with this object. + # Uses the same rules as ActiveRecord::FinderMethods#find. + # [collection.exists?(...)] + # Checks whether an associated object with the given conditions exists. + # Uses the same rules as ActiveRecord::FinderMethods#exists?. + # [collection.build(attributes = {})] + # Returns a new object of the collection type that has been instantiated + # with +attributes+ and linked to this object through the join table, but has not yet been saved. + # [collection.create(attributes = {})] + # Returns a new object of the collection type that has been instantiated + # with +attributes+, linked to this object through the join table, and that has already been + # saved (if it passed the validation). + # [collection.reload] + # Returns a Relation of all of the associated objects, forcing a database read. + # An empty Relation is returned if none are found. + # + # === Example + # + # A Developer class declares has_and_belongs_to_many :projects, which will add: + # * Developer#projects + # * Developer#projects<< + # * Developer#projects.delete + # * Developer#projects.destroy + # * Developer#projects= + # * Developer#project_ids + # * Developer#project_ids= + # * Developer#projects.clear + # * Developer#projects.empty? + # * Developer#projects.size + # * Developer#projects.find(id) + # * Developer#projects.exists?(...) + # * Developer#projects.build (similar to Project.new(developer_id: id)) + # * Developer#projects.create (similar to c = Project.new(developer_id: id); c.save; c) + # * Developer#projects.reload + # The declaration may include an +options+ hash to specialize the behavior of the association. + # + # === Scopes + # + # You can pass a second argument +scope+ as a callable (i.e. proc or + # lambda) to retrieve a specific set of records or customize the generated + # query when you access the associated collection. + # + # Scope examples: + # has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) } + # has_and_belongs_to_many :categories, ->(post) { + # where("default_category = ?", post.default_category) + # + # === Extensions + # + # The +extension+ argument allows you to pass a block into a + # has_and_belongs_to_many association. This is useful for adding new + # finders, creators and other factory-type methods to be used as part of + # the association. + # + # Extension examples: + # has_and_belongs_to_many :contractors do + # def find_or_create_by_name(name) + # first_name, last_name = name.split(" ", 2) + # find_or_create_by(first_name: first_name, last_name: last_name) + # end + # end + # + # === Options + # + # [:class_name] + # Specify the class name of the association. Use it only if that name can't be inferred + # from the association name. So has_and_belongs_to_many :projects will by default be linked to the + # Project class, but if the real class name is SuperProject, you'll have to specify it with this option. + # [:join_table] + # Specify the name of the join table if the default based on lexical order isn't what you want. + # WARNING: If you're overwriting the table name of either class, the +table_name+ method + # MUST be declared underneath any #has_and_belongs_to_many declaration in order to work. + # [:foreign_key] + # Specify the foreign key used for the association. By default this is guessed to be the name + # of this class in lower-case and "_id" suffixed. So a Person class that makes + # a #has_and_belongs_to_many association to Project will use "person_id" as the + # default :foreign_key. + # + # If you are going to modify the association (rather than just read from it), then it is + # a good idea to set the :inverse_of option. + # [:association_foreign_key] + # Specify the foreign key used for the association on the receiving side of the association. + # By default this is guessed to be the name of the associated class in lower-case and "_id" suffixed. + # So if a Person class makes a #has_and_belongs_to_many association to Project, + # the association will use "project_id" as the default :association_foreign_key. + # [:validate] + # When set to +true+, validates new objects added to association when saving the parent object. +true+ by default. + # If you want to ensure associated objects are revalidated on every update, use +validates_associated+. + # [:autosave] + # If true, always save the associated objects or destroy them if marked for destruction, when + # saving the parent object. + # If false, never save or destroy the associated objects. + # By default, only save associated objects that are new records. + # + # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets + # :autosave to true. + # + # Option examples: + # has_and_belongs_to_many :projects + # has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) } + # has_and_belongs_to_many :nations, class_name: "Country" + # has_and_belongs_to_many :categories, join_table: "prods_cats" + # has_and_belongs_to_many :categories, -> { readonly } + def has_and_belongs_to_many(name, scope = nil, **options, &extension) + habtm_reflection = ActiveRecord::Reflection::HasAndBelongsToManyReflection.new(name, scope, options, self) + + builder = Builder::HasAndBelongsToMany.new name, self, options + + join_model = builder.through_model + + const_set join_model.name, join_model + private_constant join_model.name + + middle_reflection = builder.middle_reflection join_model + + Builder::HasMany.define_callbacks self, middle_reflection + Reflection.add_reflection self, middle_reflection.name, middle_reflection + middle_reflection.parent_reflection = habtm_reflection + + include Module.new { + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def destroy_associations + association(:#{middle_reflection.name}).delete_all(:delete_all) + association(:#{name}).reset + super + end + RUBY + } + + hm_options = {} + hm_options[:through] = middle_reflection.name + hm_options[:source] = join_model.right_reflection.name + + [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend].each do |k| + hm_options[k] = options[k] if options.key? k + end + + has_many name, scope, hm_options, &extension + _reflections[name.to_s].parent_reflection = habtm_reflection + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/alias_tracker.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/alias_tracker.rb new file mode 100644 index 00000000..272eede8 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/alias_tracker.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/conversions" + +module ActiveRecord + module Associations + # Keeps track of table aliases for ActiveRecord::Associations::JoinDependency + class AliasTracker # :nodoc: + def self.create(connection, initial_table, joins) + if joins.empty? + aliases = Hash.new(0) + else + aliases = Hash.new { |h, k| + h[k] = initial_count_for(connection, k, joins) + } + end + aliases[initial_table] = 1 + new(connection, aliases) + end + + def self.initial_count_for(connection, name, table_joins) + quoted_name = nil + + counts = table_joins.map do |join| + if join.is_a?(Arel::Nodes::StringJoin) + # quoted_name should be case ignored as some database adapters (Oracle) return quoted name in uppercase + quoted_name ||= connection.quote_table_name(name) + + # Table names + table aliases + join.left.scan( + /JOIN(?:\s+\w+)?\s+(?:\S+\s+)?(?:#{quoted_name}|#{name})\sON/i + ).size + elsif join.is_a?(Arel::Nodes::Join) + join.left.name == name ? 1 : 0 + elsif join.is_a?(Hash) + join[name] + else + raise ArgumentError, "joins list should be initialized by list of Arel::Nodes::Join" + end + end + + counts.sum + end + + # table_joins is an array of arel joins which might conflict with the aliases we assign here + def initialize(connection, aliases) + @aliases = aliases + @connection = connection + end + + def aliased_table_for(table_name, aliased_name, type_caster) + if aliases[table_name].zero? + # If it's zero, we can have our table_name + aliases[table_name] = 1 + Arel::Table.new(table_name, type_caster: type_caster) + else + # Otherwise, we need to use an alias + aliased_name = @connection.table_alias_for(aliased_name) + + # Update the count + aliases[aliased_name] += 1 + + table_alias = if aliases[aliased_name] > 1 + "#{truncate(aliased_name)}_#{aliases[aliased_name]}" + else + aliased_name + end + Arel::Table.new(table_name, type_caster: type_caster).alias(table_alias) + end + end + + attr_reader :aliases + + private + + def truncate(name) + name.slice(0, @connection.table_alias_length - 2) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/association.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/association.rb new file mode 100644 index 00000000..e4433356 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/association.rb @@ -0,0 +1,299 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array/wrap" + +module ActiveRecord + module Associations + # = Active Record Associations + # + # This is the root class of all associations ('+ Foo' signifies an included module Foo): + # + # Association + # SingularAssociation + # HasOneAssociation + ForeignAssociation + # HasOneThroughAssociation + ThroughAssociation + # BelongsToAssociation + # BelongsToPolymorphicAssociation + # CollectionAssociation + # HasManyAssociation + ForeignAssociation + # HasManyThroughAssociation + ThroughAssociation + class Association #:nodoc: + attr_reader :owner, :target, :reflection + + delegate :options, to: :reflection + + def initialize(owner, reflection) + reflection.check_validity! + + @owner, @reflection = owner, reflection + + reset + reset_scope + end + + # Resets the \loaded flag to +false+ and sets the \target to +nil+. + def reset + @loaded = false + @target = nil + @stale_state = nil + @inversed = false + end + + # Reloads the \target and returns +self+ on success. + def reload + reset + reset_scope + load_target + self unless target.nil? + end + + # Has the \target been already \loaded? + def loaded? + @loaded + end + + # Asserts the \target has been loaded setting the \loaded flag to +true+. + def loaded! + @loaded = true + @stale_state = stale_state + @inversed = false + end + + # The target is stale if the target no longer points to the record(s) that the + # relevant foreign_key(s) refers to. If stale, the association accessor method + # on the owner will reload the target. It's up to subclasses to implement the + # stale_state method if relevant. + # + # Note that if the target has not been loaded, it is not considered stale. + def stale_target? + !@inversed && loaded? && @stale_state != stale_state + end + + # Sets the target of this association to \target, and the \loaded flag to +true+. + def target=(target) + @target = target + loaded! + end + + def scope + target_scope.merge!(association_scope) + end + + # The scope for this association. + # + # Note that the association_scope is merged into the target_scope only when the + # scope method is called. This is because at that point the call may be surrounded + # by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which + # actually gets built. + def association_scope + if klass + @association_scope ||= AssociationScope.scope(self) + end + end + + def reset_scope + @association_scope = nil + end + + # Set the inverse association, if possible + def set_inverse_instance(record) + if inverse = inverse_association_for(record) + inverse.inversed_from(owner) + end + record + end + + def set_inverse_instance_from_queries(record) + if inverse = inverse_association_for(record) + inverse.inversed_from_queries(owner) + end + record + end + + # Remove the inverse association, if possible + def remove_inverse_instance(record) + if inverse = inverse_association_for(record) + inverse.inversed_from(nil) + end + end + + def inversed_from(record) + self.target = record + @inversed = !!record + end + alias :inversed_from_queries :inversed_from + + # Returns the class of the target. belongs_to polymorphic overrides this to look at the + # polymorphic_type field on the owner. + def klass + reflection.klass + end + + # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the + # through association's scope) + def target_scope + AssociationRelation.create(klass, self).merge!(klass.all) + end + + def extensions + extensions = klass.default_extensions | reflection.extensions + + if reflection.scope + extensions |= reflection.scope_for(klass.unscoped, owner).extensions + end + + extensions + end + + # Loads the \target if needed and returns it. + # + # This method is abstract in the sense that it relies on +find_target+, + # which is expected to be provided by descendants. + # + # If the \target is already \loaded it is just returned. Thus, you can call + # +load_target+ unconditionally to get the \target. + # + # ActiveRecord::RecordNotFound is rescued within the method, and it is + # not reraised. The proxy is \reset and +nil+ is the return value. + def load_target + @target = find_target if (@stale_state && stale_target?) || find_target? + + loaded! unless loaded? + target + rescue ActiveRecord::RecordNotFound + reset + end + + # We can't dump @reflection and @through_reflection since it contains the scope proc + def marshal_dump + ivars = (instance_variables - [:@reflection, :@through_reflection]).map { |name| [name, instance_variable_get(name)] } + [@reflection.name, ivars] + end + + def marshal_load(data) + reflection_name, ivars = data + ivars.each { |name, val| instance_variable_set(name, val) } + @reflection = @owner.class._reflect_on_association(reflection_name) + end + + def initialize_attributes(record, except_from_scope_attributes = nil) #:nodoc: + except_from_scope_attributes ||= {} + skip_assign = [reflection.foreign_key, reflection.type].compact + assigned_keys = record.changed_attribute_names_to_save + assigned_keys += except_from_scope_attributes.keys.map(&:to_s) + attributes = scope_for_create.except!(*(assigned_keys - skip_assign)) + record.send(:_assign_attributes, attributes) if attributes.any? + set_inverse_instance(record) + end + + def create(attributes = {}, &block) + _create_record(attributes, &block) + end + + def create!(attributes = {}, &block) + _create_record(attributes, true, &block) + end + + private + def scope_for_create + scope.scope_for_create + end + + def find_target? + !loaded? && (!owner.new_record? || foreign_key_present?) && klass + end + + def creation_attributes + attributes = {} + + if (reflection.has_one? || reflection.collection?) && !options[:through] + attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key] + + if reflection.type + attributes[reflection.type] = owner.class.polymorphic_name + end + end + + attributes + end + + # Sets the owner attributes on the given record + def set_owner_attributes(record) + creation_attributes.each { |key, value| record[key] = value } + end + + # Returns true if there is a foreign key present on the owner which + # references the target. This is used to determine whether we can load + # the target if the owner is currently a new record (and therefore + # without a key). If the owner is a new record then foreign_key must + # be present in order to load target. + # + # Currently implemented by belongs_to (vanilla and polymorphic) and + # has_one/has_many :through associations which go through a belongs_to. + def foreign_key_present? + false + end + + # Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of + # the kind of the class of the associated objects. Meant to be used as + # a sanity check when you are about to assign an associated record. + def raise_on_type_mismatch!(record) + unless record.is_a?(reflection.klass) + fresh_class = reflection.class_name.safe_constantize + unless fresh_class && record.is_a?(fresh_class) + message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, "\ + "got #{record.inspect} which is an instance of #{record.class}(##{record.class.object_id})" + raise ActiveRecord::AssociationTypeMismatch, message + end + end + end + + def inverse_association_for(record) + if invertible_for?(record) + record.association(inverse_reflection_for(record).name) + end + end + + # Can be redefined by subclasses, notably polymorphic belongs_to + # The record parameter is necessary to support polymorphic inverses as we must check for + # the association in the specific class of the record. + def inverse_reflection_for(record) + reflection.inverse_of + end + + # Returns true if inverse association on the given record needs to be set. + # This method is redefined by subclasses. + def invertible_for?(record) + foreign_key_for?(record) && inverse_reflection_for(record) + end + + # Returns true if record contains the foreign_key + def foreign_key_for?(record) + record.has_attribute?(reflection.foreign_key) + end + + # This should be implemented to return the values of the relevant key(s) on the owner, + # so that when stale_state is different from the value stored on the last find_target, + # the target is stale. + # + # This is only relevant to certain associations, which is why it returns +nil+ by default. + def stale_state + end + + def build_record(attributes) + reflection.build_association(attributes) do |record| + initialize_attributes(record, attributes) + yield(record) if block_given? + end + end + + # Returns true if statement cache should be skipped on the association reader. + def skip_statement_cache?(scope) + reflection.has_scope? || + scope.eager_loading? || + klass.scope_attributes? || + reflection.source_reflection.active_record.default_scopes.any? + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/association_scope.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/association_scope.rb new file mode 100644 index 00000000..156fb5e8 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/association_scope.rb @@ -0,0 +1,168 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + class AssociationScope #:nodoc: + def self.scope(association) + INSTANCE.scope(association) + end + + def self.create(&block) + block ||= lambda { |val| val } + new(block) + end + + def initialize(value_transformation) + @value_transformation = value_transformation + end + + INSTANCE = create + + def scope(association) + klass = association.klass + reflection = association.reflection + scope = klass.unscoped + owner = association.owner + chain = get_chain(reflection, association, scope.alias_tracker) + + scope.extending! reflection.extensions + add_constraints(scope, owner, chain) + end + + def self.get_bind_values(owner, chain) + binds = [] + last_reflection = chain.last + + binds << last_reflection.join_id_for(owner) + if last_reflection.type + binds << owner.class.polymorphic_name + end + + chain.each_cons(2).each do |reflection, next_reflection| + if reflection.type + binds << next_reflection.klass.polymorphic_name + end + end + binds + end + + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected + + attr_reader :value_transformation + + private + def join(table, constraint) + table.create_join(table, table.create_on(constraint)) + end + + def last_chain_scope(scope, reflection, owner) + join_keys = reflection.join_keys + key = join_keys.key + foreign_key = join_keys.foreign_key + + table = reflection.aliased_table + value = transform_value(owner[foreign_key]) + scope = apply_scope(scope, table, key, value) + + if reflection.type + polymorphic_type = transform_value(owner.class.polymorphic_name) + scope = apply_scope(scope, table, reflection.type, polymorphic_type) + end + + scope + end + + def transform_value(value) + value_transformation.call(value) + end + + def next_chain_scope(scope, reflection, next_reflection) + join_keys = reflection.join_keys + key = join_keys.key + foreign_key = join_keys.foreign_key + + table = reflection.aliased_table + foreign_table = next_reflection.aliased_table + constraint = table[key].eq(foreign_table[foreign_key]) + + if reflection.type + value = transform_value(next_reflection.klass.polymorphic_name) + scope = apply_scope(scope, table, reflection.type, value) + end + + scope.joins!(join(foreign_table, constraint)) + end + + class ReflectionProxy < SimpleDelegator # :nodoc: + attr_reader :aliased_table + + def initialize(reflection, aliased_table) + super(reflection) + @aliased_table = aliased_table + end + + def all_includes; nil; end + end + + def get_chain(reflection, association, tracker) + name = reflection.name + chain = [Reflection::RuntimeReflection.new(reflection, association)] + reflection.chain.drop(1).each do |refl| + aliased_table = tracker.aliased_table_for( + refl.table_name, + refl.alias_candidate(name), + refl.klass.type_caster + ) + chain << ReflectionProxy.new(refl, aliased_table) + end + chain + end + + def add_constraints(scope, owner, chain) + scope = last_chain_scope(scope, chain.last, owner) + + chain.each_cons(2) do |reflection, next_reflection| + scope = next_chain_scope(scope, reflection, next_reflection) + end + + chain_head = chain.first + chain.reverse_each do |reflection| + # Exclude the scope of the association itself, because that + # was already merged in the #scope method. + reflection.constraints.each do |scope_chain_item| + item = eval_scope(reflection, scope_chain_item, owner) + + if scope_chain_item == chain_head.scope + scope.merge! item.except(:where, :includes, :unscope, :order) + end + + reflection.all_includes do + scope.includes! item.includes_values + end + + scope.unscope!(*item.unscope_values) + scope.where_clause += item.where_clause + scope.order_values = item.order_values | scope.order_values + end + end + + scope + end + + def apply_scope(scope, table, key, value) + if scope.table == table + scope.where!(key => value) + else + scope.where!(table.name => { key => value }) + end + end + + def eval_scope(reflection, scope, owner) + relation = reflection.build_scope(reflection.aliased_table) + relation.instance_exec(owner, &scope) || relation + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/belongs_to_association.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/belongs_to_association.rb new file mode 100644 index 00000000..e1d02fb0 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/belongs_to_association.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + # = Active Record Belongs To Association + class BelongsToAssociation < SingularAssociation #:nodoc: + def handle_dependency + return unless load_target + + case options[:dependent] + when :destroy + target.destroy + raise ActiveRecord::Rollback unless target.destroyed? + else + target.send(options[:dependent]) + end + end + + def replace(record) + if record + raise_on_type_mismatch!(record) + update_counters_on_replace(record) + set_inverse_instance(record) + @updated = true + else + decrement_counters + end + + replace_keys(record) + + self.target = record + end + + def inversed_from(record) + replace_keys(record) + super + end + + def default(&block) + writer(owner.instance_exec(&block)) if reader.nil? + end + + def reset + super + @updated = false + end + + def updated? + @updated + end + + def decrement_counters # :nodoc: + update_counters(-1) + end + + def increment_counters # :nodoc: + update_counters(1) + end + + def target_changed? + owner.saved_change_to_attribute?(reflection.foreign_key) + end + + private + + def update_counters(by) + if require_counter_update? && foreign_key_present? + if target && !stale_target? + target.increment!(reflection.counter_cache_column, by, touch: reflection.options[:touch]) + else + klass.update_counters(target_id, reflection.counter_cache_column => by, touch: reflection.options[:touch]) + end + end + end + + def find_target? + !loaded? && foreign_key_present? && klass + end + + def require_counter_update? + reflection.counter_cache_column && owner.persisted? + end + + def update_counters_on_replace(record) + if require_counter_update? && different_target?(record) + owner.instance_variable_set :@_after_replace_counter_called, true + record.increment!(reflection.counter_cache_column, touch: reflection.options[:touch]) + decrement_counters + end + end + + # Checks whether record is different to the current target, without loading it + def different_target?(record) + record._read_attribute(primary_key(record)) != owner._read_attribute(reflection.foreign_key) + end + + def replace_keys(record) + owner[reflection.foreign_key] = record ? record._read_attribute(primary_key(record)) : nil + end + + def primary_key(record) + reflection.association_primary_key(record.class) + end + + def foreign_key_present? + owner._read_attribute(reflection.foreign_key) + end + + # NOTE - for now, we're only supporting inverse setting from belongs_to back onto + # has_one associations. + def invertible_for?(record) + inverse = inverse_reflection_for(record) + inverse && inverse.has_one? + end + + def target_id + if options[:primary_key] + owner.send(reflection.name).try(:id) + else + owner._read_attribute(reflection.foreign_key) + end + end + + def stale_state + result = owner._read_attribute(reflection.foreign_key) { |n| owner.send(:missing_attribute, n, caller) } + result && result.to_s + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/belongs_to_polymorphic_association.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/belongs_to_polymorphic_association.rb new file mode 100644 index 00000000..3fd2fb5f --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/belongs_to_polymorphic_association.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + # = Active Record Belongs To Polymorphic Association + class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc: + def klass + type = owner[reflection.foreign_type] + type.presence && type.constantize + end + + def target_changed? + super || owner.saved_change_to_attribute?(reflection.foreign_type) + end + + private + def replace_keys(record) + super + owner[reflection.foreign_type] = record ? record.class.polymorphic_name : nil + end + + def different_target?(record) + super || record.class != klass + end + + def inverse_reflection_for(record) + reflection.polymorphic_inverse_of(record.class) + end + + def raise_on_type_mismatch!(record) + # A polymorphic association cannot have a type mismatch, by definition + end + + def stale_state + foreign_key = super + foreign_key && [foreign_key.to_s, owner[reflection.foreign_type].to_s] + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/builder/association.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/builder/association.rb new file mode 100644 index 00000000..7c69cd65 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/builder/association.rb @@ -0,0 +1,140 @@ +# frozen_string_literal: true + +# This is the parent Association class which defines the variables +# used by all associations. +# +# The hierarchy is defined as follows: +# Association +# - SingularAssociation +# - BelongsToAssociation +# - HasOneAssociation +# - CollectionAssociation +# - HasManyAssociation + +module ActiveRecord::Associations::Builder # :nodoc: + class Association #:nodoc: + class << self + attr_accessor :extensions + end + self.extensions = [] + + VALID_OPTIONS = [:class_name, :anonymous_class, :foreign_key, :validate] # :nodoc: + + def self.build(model, name, scope, options, &block) + if model.dangerous_attribute_method?(name) + raise ArgumentError, "You tried to define an association named #{name} on the model #{model.name}, but " \ + "this will conflict with a method #{name} already defined by Active Record. " \ + "Please choose a different association name." + end + + extension = define_extensions model, name, &block + reflection = create_reflection model, name, scope, options, extension + define_accessors model, reflection + define_callbacks model, reflection + define_validations model, reflection + reflection + end + + def self.create_reflection(model, name, scope, options, extension = nil) + raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol) + + validate_options(options) + + scope = build_scope(scope, extension) + + ActiveRecord::Reflection.create(macro, name, scope, options, model) + end + + def self.build_scope(scope, extension) + new_scope = scope + + if scope && scope.arity == 0 + new_scope = proc { instance_exec(&scope) } + end + + if extension + new_scope = wrap_scope new_scope, extension + end + + new_scope + end + + def self.wrap_scope(scope, extension) + scope + end + + def self.macro + raise NotImplementedError + end + + def self.valid_options(options) + VALID_OPTIONS + Association.extensions.flat_map(&:valid_options) + end + + def self.validate_options(options) + options.assert_valid_keys(valid_options(options)) + end + + def self.define_extensions(model, name) + end + + def self.define_callbacks(model, reflection) + if dependent = reflection.options[:dependent] + check_dependent_options(dependent) + add_destroy_callbacks(model, reflection) + end + + Association.extensions.each do |extension| + extension.build model, reflection + end + end + + # Defines the setter and getter methods for the association + # class Post < ActiveRecord::Base + # has_many :comments + # end + # + # Post.first.comments and Post.first.comments= methods are defined by this method... + def self.define_accessors(model, reflection) + mixin = model.generated_association_methods + name = reflection.name + define_readers(mixin, name) + define_writers(mixin, name) + end + + def self.define_readers(mixin, name) + mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{name} + association(:#{name}).reader + end + CODE + end + + def self.define_writers(mixin, name) + mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{name}=(value) + association(:#{name}).writer(value) + end + CODE + end + + def self.define_validations(model, reflection) + # noop + end + + def self.valid_dependent_options + raise NotImplementedError + end + + def self.check_dependent_options(dependent) + unless valid_dependent_options.include? dependent + raise ArgumentError, "The :dependent option must be one of #{valid_dependent_options}, but is :#{dependent}" + end + end + + def self.add_destroy_callbacks(model, reflection) + name = reflection.name + model.before_destroy lambda { |o| o.association(name).handle_dependency } + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/builder/belongs_to.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/builder/belongs_to.rb new file mode 100644 index 00000000..4b6cb760 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/builder/belongs_to.rb @@ -0,0 +1,163 @@ +# frozen_string_literal: true + +module ActiveRecord::Associations::Builder # :nodoc: + class BelongsTo < SingularAssociation #:nodoc: + def self.macro + :belongs_to + end + + def self.valid_options(options) + super + [:polymorphic, :touch, :counter_cache, :optional, :default] + end + + def self.valid_dependent_options + [:destroy, :delete] + end + + def self.define_callbacks(model, reflection) + super + add_counter_cache_callbacks(model, reflection) if reflection.options[:counter_cache] + add_touch_callbacks(model, reflection) if reflection.options[:touch] + add_default_callbacks(model, reflection) if reflection.options[:default] + end + + def self.define_accessors(mixin, reflection) + super + add_counter_cache_methods mixin + end + + def self.add_counter_cache_methods(mixin) + return if mixin.method_defined? :belongs_to_counter_cache_after_update + + mixin.class_eval do + def belongs_to_counter_cache_after_update(reflection) + foreign_key = reflection.foreign_key + cache_column = reflection.counter_cache_column + + if (@_after_replace_counter_called ||= false) + @_after_replace_counter_called = false + elsif association(reflection.name).target_changed? + if reflection.polymorphic? + model = attribute_in_database(reflection.foreign_type).try(:constantize) + model_was = attribute_before_last_save(reflection.foreign_type).try(:constantize) + else + model = reflection.klass + model_was = reflection.klass + end + + foreign_key_was = attribute_before_last_save foreign_key + foreign_key = attribute_in_database foreign_key + + if foreign_key && model.respond_to?(:increment_counter) + foreign_key = counter_cache_target(reflection, model, foreign_key) + model.increment_counter(cache_column, foreign_key) + end + + if foreign_key_was && model_was.respond_to?(:decrement_counter) + foreign_key_was = counter_cache_target(reflection, model_was, foreign_key_was) + model_was.decrement_counter(cache_column, foreign_key_was) + end + end + end + + private + def counter_cache_target(reflection, model, foreign_key) + primary_key = reflection.association_primary_key(model) + model.unscoped.where!(primary_key => foreign_key) + end + end + end + + def self.add_counter_cache_callbacks(model, reflection) + cache_column = reflection.counter_cache_column + + model.after_update lambda { |record| + record.belongs_to_counter_cache_after_update(reflection) + } + + klass = reflection.class_name.safe_constantize + klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly) + end + + def self.touch_record(o, changes, foreign_key, name, touch, touch_method) # :nodoc: + old_foreign_id = changes[foreign_key] && changes[foreign_key].first + + if old_foreign_id + association = o.association(name) + reflection = association.reflection + if reflection.polymorphic? + foreign_type = reflection.foreign_type + klass = changes[foreign_type] && changes[foreign_type].first || o.public_send(foreign_type) + klass = klass.constantize + else + klass = association.klass + end + primary_key = reflection.association_primary_key(klass) + old_record = klass.find_by(primary_key => old_foreign_id) + + if old_record + if touch != true + old_record.send(touch_method, touch) + else + old_record.send(touch_method) + end + end + end + + record = o.send name + if record && record.persisted? + if touch != true + record.send(touch_method, touch) + else + record.send(touch_method) + end + end + end + + def self.add_touch_callbacks(model, reflection) + foreign_key = reflection.foreign_key + n = reflection.name + touch = reflection.options[:touch] + + callback = lambda { |changes_method| lambda { |record| + BelongsTo.touch_record(record, record.send(changes_method), foreign_key, n, touch, belongs_to_touch_method) + }} + + unless reflection.counter_cache_column + model.after_create callback.(:saved_changes), if: :saved_changes? + model.after_destroy callback.(:changes_to_save) + end + + model.after_update callback.(:saved_changes), if: :saved_changes? + model.after_touch callback.(:changes_to_save) + end + + def self.add_default_callbacks(model, reflection) + model.before_validation lambda { |o| + o.association(reflection.name).default(&reflection.options[:default]) + } + end + + def self.add_destroy_callbacks(model, reflection) + model.after_destroy lambda { |o| o.association(reflection.name).handle_dependency } + end + + def self.define_validations(model, reflection) + if reflection.options.key?(:required) + reflection.options[:optional] = !reflection.options.delete(:required) + end + + if reflection.options[:optional].nil? + required = model.belongs_to_required_by_default + else + required = !reflection.options[:optional] + end + + super + + if required + model.validates_presence_of reflection.name, message: :required + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/builder/collection_association.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/builder/collection_association.rb new file mode 100644 index 00000000..35a72c38 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/builder/collection_association.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require "active_record/associations" + +module ActiveRecord::Associations::Builder # :nodoc: + class CollectionAssociation < Association #:nodoc: + CALLBACKS = [:before_add, :after_add, :before_remove, :after_remove] + + def self.valid_options(options) + super + [:table_name, :before_add, + :after_add, :before_remove, :after_remove, :extend] + end + + def self.define_callbacks(model, reflection) + super + name = reflection.name + options = reflection.options + CALLBACKS.each { |callback_name| + define_callback(model, callback_name, name, options) + } + end + + def self.define_extensions(model, name) + if block_given? + extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension" + extension = Module.new(&Proc.new) + model.parent.const_set(extension_module_name, extension) + end + end + + def self.define_callback(model, callback_name, name, options) + full_callback_name = "#{callback_name}_for_#{name}" + + # TODO : why do i need method_defined? I think its because of the inheritance chain + model.class_attribute full_callback_name unless model.method_defined?(full_callback_name) + callbacks = Array(options[callback_name.to_sym]).map do |callback| + case callback + when Symbol + ->(method, owner, record) { owner.send(callback, record) } + when Proc + ->(method, owner, record) { callback.call(owner, record) } + else + ->(method, owner, record) { callback.send(method, owner, record) } + end + end + model.send "#{full_callback_name}=", callbacks + end + + # Defines the setter and getter methods for the collection_singular_ids. + def self.define_readers(mixin, name) + super + + mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{name.to_s.singularize}_ids + association(:#{name}).ids_reader + end + CODE + end + + def self.define_writers(mixin, name) + super + + mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{name.to_s.singularize}_ids=(ids) + association(:#{name}).ids_writer(ids) + end + CODE + end + + def self.wrap_scope(scope, mod) + if scope + if scope.arity > 0 + proc { |owner| instance_exec(owner, &scope).extending(mod) } + else + proc { instance_exec(&scope).extending(mod) } + end + else + proc { extending(mod) } + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/builder/has_and_belongs_to_many.rb new file mode 100644 index 00000000..1981da11 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/builder/has_and_belongs_to_many.rb @@ -0,0 +1,135 @@ +# frozen_string_literal: true + +module ActiveRecord::Associations::Builder # :nodoc: + class HasAndBelongsToMany # :nodoc: + class JoinTableResolver # :nodoc: + KnownTable = Struct.new :join_table + + class KnownClass # :nodoc: + def initialize(lhs_class, rhs_class_name) + @lhs_class = lhs_class + @rhs_class_name = rhs_class_name + @join_table = nil + end + + def join_table + @join_table ||= [@lhs_class.table_name, klass.table_name].sort.join("\0").gsub(/^(.*[._])(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_") + end + + private + + def klass + @lhs_class.send(:compute_type, @rhs_class_name) + end + end + + def self.build(lhs_class, name, options) + if options[:join_table] + KnownTable.new options[:join_table].to_s + else + class_name = options.fetch(:class_name) { + name.to_s.camelize.singularize + } + KnownClass.new lhs_class, class_name.to_s + end + end + end + + attr_reader :lhs_model, :association_name, :options + + def initialize(association_name, lhs_model, options) + @association_name = association_name + @lhs_model = lhs_model + @options = options + end + + def through_model + habtm = JoinTableResolver.build lhs_model, association_name, options + + join_model = Class.new(ActiveRecord::Base) { + class << self + attr_accessor :left_model + attr_accessor :name + attr_accessor :table_name_resolver + attr_accessor :left_reflection + attr_accessor :right_reflection + end + + def self.table_name + table_name_resolver.join_table + end + + def self.compute_type(class_name) + left_model.compute_type class_name + end + + def self.add_left_association(name, options) + belongs_to name, required: false, **options + self.left_reflection = _reflect_on_association(name) + end + + def self.add_right_association(name, options) + rhs_name = name.to_s.singularize.to_sym + belongs_to rhs_name, required: false, **options + self.right_reflection = _reflect_on_association(rhs_name) + end + + def self.retrieve_connection + left_model.retrieve_connection + end + + private + + def self.suppress_composite_primary_key(pk) + pk unless pk.is_a?(Array) + end + } + + join_model.name = "HABTM_#{association_name.to_s.camelize}" + join_model.table_name_resolver = habtm + join_model.left_model = lhs_model + + join_model.add_left_association :left_side, anonymous_class: lhs_model + join_model.add_right_association association_name, belongs_to_options(options) + join_model + end + + def middle_reflection(join_model) + middle_name = [lhs_model.name.downcase.pluralize, + association_name].join("_".freeze).gsub("::".freeze, "_".freeze).to_sym + middle_options = middle_options join_model + + HasMany.create_reflection(lhs_model, + middle_name, + nil, + middle_options) + end + + private + + def middle_options(join_model) + middle_options = {} + middle_options[:class_name] = "#{lhs_model.name}::#{join_model.name}" + middle_options[:source] = join_model.left_reflection.name + if options.key? :foreign_key + middle_options[:foreign_key] = options[:foreign_key] + end + middle_options + end + + def belongs_to_options(options) + rhs_options = {} + + if options.key? :class_name + rhs_options[:foreign_key] = options[:class_name].to_s.foreign_key + rhs_options[:class_name] = options[:class_name] + end + + if options.key? :association_foreign_key + rhs_options[:foreign_key] = options[:association_foreign_key] + end + + rhs_options + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/builder/has_many.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/builder/has_many.rb new file mode 100644 index 00000000..5b9617bc --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/builder/has_many.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActiveRecord::Associations::Builder # :nodoc: + class HasMany < CollectionAssociation #:nodoc: + def self.macro + :has_many + end + + def self.valid_options(options) + super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table, :foreign_type, :index_errors] + end + + def self.valid_dependent_options + [:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception] + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/builder/has_one.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/builder/has_one.rb new file mode 100644 index 00000000..bfb37d6e --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/builder/has_one.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module ActiveRecord::Associations::Builder # :nodoc: + class HasOne < SingularAssociation #:nodoc: + def self.macro + :has_one + end + + def self.valid_options(options) + valid = super + [:as] + valid += [:through, :source, :source_type] if options[:through] + valid + end + + def self.valid_dependent_options + [:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception] + end + + def self.add_destroy_callbacks(model, reflection) + super unless reflection.options[:through] + end + + def self.define_validations(model, reflection) + super + if reflection.options[:required] + model.validates_presence_of reflection.name, message: :required + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/builder/singular_association.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/builder/singular_association.rb new file mode 100644 index 00000000..0a02ef4c --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/builder/singular_association.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +# This class is inherited by the has_one and belongs_to association classes + +module ActiveRecord::Associations::Builder # :nodoc: + class SingularAssociation < Association #:nodoc: + def self.valid_options(options) + super + [:foreign_type, :dependent, :primary_key, :inverse_of, :required] + end + + def self.define_accessors(model, reflection) + super + mixin = model.generated_association_methods + name = reflection.name + + define_constructors(mixin, name) if reflection.constructable? + + mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 + def reload_#{name} + association(:#{name}).force_reload_reader + end + CODE + end + + # Defines the (build|create)_association methods for belongs_to or has_one association + def self.define_constructors(mixin, name) + mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1 + def build_#{name}(*args, &block) + association(:#{name}).build(*args, &block) + end + + def create_#{name}(*args, &block) + association(:#{name}).create(*args, &block) + end + + def create_#{name}!(*args, &block) + association(:#{name}).create!(*args, &block) + end + CODE + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/collection_association.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/collection_association.rb new file mode 100644 index 00000000..a39cc57e --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/collection_association.rb @@ -0,0 +1,513 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + # = Active Record Association Collection + # + # CollectionAssociation is an abstract class that provides common stuff to + # ease the implementation of association proxies that represent + # collections. See the class hierarchy in Association. + # + # CollectionAssociation: + # HasManyAssociation => has_many + # HasManyThroughAssociation + ThroughAssociation => has_many :through + # + # The CollectionAssociation class provides common methods to the collections + # defined by +has_and_belongs_to_many+, +has_many+ or +has_many+ with + # the +:through association+ option. + # + # You need to be careful with assumptions regarding the target: The proxy + # does not fetch records from the database until it needs them, but new + # ones created with +build+ are added to the target. So, the target may be + # non-empty and still lack children waiting to be read from the database. + # If you look directly to the database you cannot assume that's the entire + # collection because new records may have been added to the target, etc. + # + # If you need to work on all current children, new and existing records, + # +load_target+ and the +loaded+ flag are your friends. + class CollectionAssociation < Association #:nodoc: + # Implements the reader method, e.g. foo.items for Foo.has_many :items + def reader + if stale_target? + reload + end + + @proxy ||= CollectionProxy.create(klass, self) + @proxy.reset_scope + end + + # Implements the writer method, e.g. foo.items= for Foo.has_many :items + def writer(records) + replace(records) + end + + # Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items + def ids_reader + if loaded? + target.pluck(reflection.association_primary_key) + elsif !target.empty? + load_target.pluck(reflection.association_primary_key) + else + @association_ids ||= scope.pluck(reflection.association_primary_key) + end + end + + # Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items + def ids_writer(ids) + primary_key = reflection.association_primary_key + pk_type = klass.type_for_attribute(primary_key) + ids = Array(ids).reject(&:blank?) + ids.map! { |i| pk_type.cast(i) } + + records = klass.where(primary_key => ids).index_by do |r| + r.public_send(primary_key) + end.values_at(*ids).compact + + if records.size != ids.size + found_ids = records.map { |record| record.public_send(primary_key) } + not_found_ids = ids - found_ids + klass.all.raise_record_not_found_exception!(ids, records.size, ids.size, primary_key, not_found_ids) + else + replace(records) + end + end + + def reset + super + @target = [] + @association_ids = nil + end + + def find(*args) + if options[:inverse_of] && loaded? + args_flatten = args.flatten + model = scope.klass + + if args_flatten.blank? + error_message = "Couldn't find #{model.name} without an ID" + raise RecordNotFound.new(error_message, model.name, model.primary_key, args) + end + + result = find_by_scan(*args) + + result_size = Array(result).size + if !result || result_size != args_flatten.size + scope.raise_record_not_found_exception!(args_flatten, result_size, args_flatten.size) + else + result + end + else + scope.find(*args) + end + end + + def build(attributes = {}, &block) + if attributes.is_a?(Array) + attributes.collect { |attr| build(attr, &block) } + else + add_to_target(build_record(attributes, &block)) + end + end + + # Add +records+ to this association. Since +<<+ flattens its argument list + # and inserts each record, +push+ and +concat+ behave identically. + def concat(*records) + records = records.flatten + if owner.new_record? + load_target + concat_records(records) + else + transaction { concat_records(records) } + end + end + + # Starts a transaction in the association class's database connection. + # + # class Author < ActiveRecord::Base + # has_many :books + # end + # + # Author.first.books.transaction do + # # same effect as calling Book.transaction + # end + def transaction(*args) + reflection.klass.transaction(*args) do + yield + end + end + + # Removes all records from the association without calling callbacks + # on the associated records. It honors the +:dependent+ option. However + # if the +:dependent+ value is +:destroy+ then in that case the +:delete_all+ + # deletion strategy for the association is applied. + # + # You can force a particular deletion strategy by passing a parameter. + # + # Example: + # + # @author.books.delete_all(:nullify) + # @author.books.delete_all(:delete_all) + # + # See delete for more info. + def delete_all(dependent = nil) + if dependent && ![:nullify, :delete_all].include?(dependent) + raise ArgumentError, "Valid values are :nullify or :delete_all" + end + + dependent = if dependent + dependent + elsif options[:dependent] == :destroy + :delete_all + else + options[:dependent] + end + + delete_or_nullify_all_records(dependent).tap do + reset + loaded! + end + end + + # Destroy all the records from this association. + # + # See destroy for more info. + def destroy_all + destroy(load_target).tap do + reset + loaded! + end + end + + # Removes +records+ from this association calling +before_remove+ and + # +after_remove+ callbacks. + # + # This method is abstract in the sense that +delete_records+ has to be + # provided by descendants. Note this method does not imply the records + # are actually removed from the database, that depends precisely on + # +delete_records+. They are in any case removed from the collection. + def delete(*records) + delete_or_destroy(records, options[:dependent]) + end + + # Deletes the +records+ and removes them from this association calling + # +before_remove+ , +after_remove+ , +before_destroy+ and +after_destroy+ callbacks. + # + # Note that this method removes records from the database ignoring the + # +:dependent+ option. + def destroy(*records) + delete_or_destroy(records, :destroy) + end + + # Returns the size of the collection by executing a SELECT COUNT(*) + # query if the collection hasn't been loaded, and calling + # collection.size if it has. + # + # If the collection has been already loaded +size+ and +length+ are + # equivalent. If not and you are going to need the records anyway + # +length+ will take one less query. Otherwise +size+ is more efficient. + # + # This method is abstract in the sense that it relies on + # +count_records+, which is a method descendants have to provide. + def size + if !find_target? || loaded? + target.size + elsif !association_scope.group_values.empty? + load_target.size + elsif !association_scope.distinct_value && target.is_a?(Array) + unsaved_records = target.select(&:new_record?) + unsaved_records.size + count_records + else + count_records + end + end + + # Returns true if the collection is empty. + # + # If the collection has been loaded + # it is equivalent to collection.size.zero?. If the + # collection has not been loaded, it is equivalent to + # collection.exists?. If the collection has not already been + # loaded and you are going to fetch the records anyway it is better to + # check collection.length.zero?. + def empty? + if loaded? + size.zero? + else + @target.blank? && !scope.exists? + end + end + + # Replace this collection with +other_array+. This will perform a diff + # and delete/add only records that have changed. + def replace(other_array) + other_array.each { |val| raise_on_type_mismatch!(val) } + original_target = load_target.dup + + if owner.new_record? + replace_records(other_array, original_target) + else + replace_common_records_in_memory(other_array, original_target) + if other_array != original_target + transaction { replace_records(other_array, original_target) } + else + other_array + end + end + end + + def include?(record) + if record.is_a?(reflection.klass) + if record.new_record? + include_in_memory?(record) + else + loaded? ? target.include?(record) : scope.exists?(record.id) + end + else + false + end + end + + def load_target + if find_target? + @target = merge_target_lists(find_target, target) + end + + loaded! + target + end + + def add_to_target(record, skip_callbacks = false, &block) + if association_scope.distinct_value + index = @target.index(record) + end + replace_on_target(record, index, skip_callbacks, &block) + end + + def scope + scope = super + scope.none! if null_scope? + scope + end + + def null_scope? + owner.new_record? && !foreign_key_present? + end + + def find_from_target? + loaded? || + owner.new_record? || + target.any? { |record| record.new_record? || record.changed? } + end + + private + + def find_target + scope = self.scope + return scope.to_a if skip_statement_cache?(scope) + + conn = klass.connection + sc = reflection.association_scope_cache(conn, owner) do |params| + as = AssociationScope.create { params.bind } + target_scope.merge!(as.scope(self)) + end + + binds = AssociationScope.get_bind_values(owner, reflection.chain) + sc.execute(binds, conn) do |record| + set_inverse_instance(record) + end + end + + # We have some records loaded from the database (persisted) and some that are + # in-memory (memory). The same record may be represented in the persisted array + # and in the memory array. + # + # So the task of this method is to merge them according to the following rules: + # + # * The final array must not have duplicates + # * The order of the persisted array is to be preserved + # * Any changes made to attributes on objects in the memory array are to be preserved + # * Otherwise, attributes should have the value found in the database + def merge_target_lists(persisted, memory) + return persisted if memory.empty? + return memory if persisted.empty? + + persisted.map! do |record| + if mem_record = memory.delete(record) + + ((record.attribute_names & mem_record.attribute_names) - mem_record.changed_attribute_names_to_save).each do |name| + mem_record[name] = record[name] + end + + mem_record + else + record + end + end + + persisted + memory + end + + def _create_record(attributes, raise = false, &block) + unless owner.persisted? + raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved" + end + + if attributes.is_a?(Array) + attributes.collect { |attr| _create_record(attr, raise, &block) } + else + record = build_record(attributes, &block) + transaction do + result = nil + add_to_target(record) do + result = insert_record(record, true, raise) { + @_was_loaded = loaded? + } + end + raise ActiveRecord::Rollback unless result + end + record + end + end + + # Do the relevant stuff to insert the given record into the association collection. + def insert_record(record, validate = true, raise = false, &block) + if raise + record.save!(validate: validate, &block) + else + record.save(validate: validate, &block) + end + end + + def delete_or_destroy(records, method) + return if records.empty? + records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) } + records = records.flatten + records.each { |record| raise_on_type_mismatch!(record) } + existing_records = records.reject(&:new_record?) + + if existing_records.empty? + remove_records(existing_records, records, method) + else + transaction { remove_records(existing_records, records, method) } + end + end + + def remove_records(existing_records, records, method) + records.each { |record| callback(:before_remove, record) } + + delete_records(existing_records, method) if existing_records.any? + records.each { |record| target.delete(record) } + @association_ids = nil + + records.each { |record| callback(:after_remove, record) } + end + + # Delete the given records from the association, + # using one of the methods +:destroy+, +:delete_all+ + # or +:nullify+ (or +nil+, in which case a default is used). + def delete_records(records, method) + raise NotImplementedError + end + + def replace_records(new_target, original_target) + delete(difference(target, new_target)) + + unless concat(difference(new_target, target)) + @target = original_target + raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \ + "new records could not be saved." + end + + target + end + + def replace_common_records_in_memory(new_target, original_target) + common_records = intersection(new_target, original_target) + common_records.each do |record| + skip_callbacks = true + replace_on_target(record, @target.index(record), skip_callbacks) + end + end + + def concat_records(records, raise = false) + result = true + + records.each do |record| + raise_on_type_mismatch!(record) + add_to_target(record) do + unless owner.new_record? + result &&= insert_record(record, true, raise) { + @_was_loaded = loaded? + } + end + end + end + + raise ActiveRecord::Rollback unless result + + records + end + + def replace_on_target(record, index, skip_callbacks) + callback(:before_add, record) unless skip_callbacks + + set_inverse_instance(record) + + @_was_loaded = true + + yield(record) if block_given? + + if index + target[index] = record + elsif @_was_loaded || !loaded? + @association_ids = nil + target << record + end + + callback(:after_add, record) unless skip_callbacks + + record + ensure + @_was_loaded = nil + end + + def callback(method, record) + callbacks_for(method).each do |callback| + callback.call(method, owner, record) + end + end + + def callbacks_for(callback_name) + full_callback_name = "#{callback_name}_for_#{reflection.name}" + owner.class.send(full_callback_name) + end + + def include_in_memory?(record) + if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection) + assoc = owner.association(reflection.through_reflection.name) + assoc.reader.any? { |source| + target_reflection = source.send(reflection.source_reflection.name) + target_reflection.respond_to?(:include?) ? target_reflection.include?(record) : target_reflection == record + } || target.include?(record) + else + target.include?(record) + end + end + + # If the :inverse_of option has been + # specified, then #find scans the entire collection. + def find_by_scan(*args) + expects_array = args.first.kind_of?(Array) + ids = args.flatten.compact.map(&:to_s).uniq + + if ids.size == 1 + id = ids.first + record = load_target.detect { |r| id == r.id.to_s } + expects_array ? [ record ] : record + else + load_target.select { |r| ids.include?(r.id.to_s) } + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/collection_proxy.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/collection_proxy.rb new file mode 100644 index 00000000..727d52ed --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/collection_proxy.rb @@ -0,0 +1,1131 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + # Association proxies in Active Record are middlemen between the object that + # holds the association, known as the @owner, and the actual associated + # object, known as the @target. The kind of association any proxy is + # about is available in @reflection. That's an instance of the class + # ActiveRecord::Reflection::AssociationReflection. + # + # For example, given + # + # class Blog < ActiveRecord::Base + # has_many :posts + # end + # + # blog = Blog.first + # + # the association proxy in blog.posts has the object in +blog+ as + # @owner, the collection of its posts as @target, and + # the @reflection object represents a :has_many macro. + # + # This class delegates unknown methods to @target via + # method_missing. + # + # The @target object is not \loaded until needed. For example, + # + # blog.posts.count + # + # is computed directly through SQL and does not trigger by itself the + # instantiation of the actual post records. + class CollectionProxy < Relation + def initialize(klass, association) #:nodoc: + @association = association + super klass + + extensions = association.extensions + extend(*extensions) if extensions.any? + end + + def target + @association.target + end + + def load_target + @association.load_target + end + + # Returns +true+ if the association has been loaded, otherwise +false+. + # + # person.pets.loaded? # => false + # person.pets + # person.pets.loaded? # => true + def loaded? + @association.loaded? + end + + ## + # :method: select + # + # :call-seq: + # select(*fields, &block) + # + # Works in two ways. + # + # *First:* Specify a subset of fields to be selected from the result set. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.select(:name) + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.select(:id, :name) + # # => [ + # # #, + # # #, + # # # + # # ] + # + # Be careful because this also means you're initializing a model + # object with only the fields that you've selected. If you attempt + # to access a field except +id+ that is not in the initialized record you'll + # receive: + # + # person.pets.select(:name).first.person_id + # # => ActiveModel::MissingAttributeError: missing attribute: person_id + # + # *Second:* You can pass a block so it can be used just like Array#select. + # This builds an array of objects from the database for the scope, + # converting them into an array and iterating through them using + # Array#select. + # + # person.pets.select { |pet| pet.name =~ /oo/ } + # # => [ + # # #, + # # # + # # ] + + # Finds an object in the collection responding to the +id+. Uses the same + # rules as ActiveRecord::Base.find. Returns ActiveRecord::RecordNotFound + # error if the object cannot be found. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.find(1) # => # + # person.pets.find(4) # => ActiveRecord::RecordNotFound: Couldn't find Pet with 'id'=4 + # + # person.pets.find(2) { |pet| pet.name.downcase! } + # # => # + # + # person.pets.find(2, 3) + # # => [ + # # #, + # # # + # # ] + def find(*args) + return super if block_given? + @association.find(*args) + end + + ## + # :method: first + # + # :call-seq: + # first(limit = nil) + # + # Returns the first record, or the first +n+ records, from the collection. + # If the collection is empty, the first form returns +nil+, and the second + # form returns an empty array. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.first # => # + # + # person.pets.first(2) + # # => [ + # # #, + # # # + # # ] + # + # another_person_without.pets # => [] + # another_person_without.pets.first # => nil + # another_person_without.pets.first(3) # => [] + + ## + # :method: second + # + # :call-seq: + # second() + # + # Same as #first except returns only the second record. + + ## + # :method: third + # + # :call-seq: + # third() + # + # Same as #first except returns only the third record. + + ## + # :method: fourth + # + # :call-seq: + # fourth() + # + # Same as #first except returns only the fourth record. + + ## + # :method: fifth + # + # :call-seq: + # fifth() + # + # Same as #first except returns only the fifth record. + + ## + # :method: forty_two + # + # :call-seq: + # forty_two() + # + # Same as #first except returns only the forty second record. + # Also known as accessing "the reddit". + + ## + # :method: third_to_last + # + # :call-seq: + # third_to_last() + # + # Same as #first except returns only the third-to-last record. + + ## + # :method: second_to_last + # + # :call-seq: + # second_to_last() + # + # Same as #first except returns only the second-to-last record. + + # Returns the last record, or the last +n+ records, from the collection. + # If the collection is empty, the first form returns +nil+, and the second + # form returns an empty array. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.last # => # + # + # person.pets.last(2) + # # => [ + # # #, + # # # + # # ] + # + # another_person_without.pets # => [] + # another_person_without.pets.last # => nil + # another_person_without.pets.last(3) # => [] + def last(limit = nil) + load_target if find_from_target? + super + end + + # Gives a record (or N records if a parameter is supplied) from the collection + # using the same rules as ActiveRecord::Base.take. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.take # => # + # + # person.pets.take(2) + # # => [ + # # #, + # # # + # # ] + # + # another_person_without.pets # => [] + # another_person_without.pets.take # => nil + # another_person_without.pets.take(2) # => [] + def take(limit = nil) + load_target if find_from_target? + super + end + + # Returns a new object of the collection type that has been instantiated + # with +attributes+ and linked to this object, but have not yet been saved. + # You can pass an array of attributes hashes, this will return an array + # with the new objects. + # + # class Person + # has_many :pets + # end + # + # person.pets.build + # # => # + # + # person.pets.build(name: 'Fancy-Fancy') + # # => # + # + # person.pets.build([{name: 'Spook'}, {name: 'Choo-Choo'}, {name: 'Brain'}]) + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.size # => 5 # size of the collection + # person.pets.count # => 0 # count from database + def build(attributes = {}, &block) + @association.build(attributes, &block) + end + alias_method :new, :build + + # Returns a new object of the collection type that has been instantiated with + # attributes, linked to this object and that has already been saved (if it + # passes the validations). + # + # class Person + # has_many :pets + # end + # + # person.pets.create(name: 'Fancy-Fancy') + # # => # + # + # person.pets.create([{name: 'Spook'}, {name: 'Choo-Choo'}]) + # # => [ + # # #, + # # # + # # ] + # + # person.pets.size # => 3 + # person.pets.count # => 3 + # + # person.pets.find(1, 2, 3) + # # => [ + # # #, + # # #, + # # # + # # ] + def create(attributes = {}, &block) + @association.create(attributes, &block) + end + + # Like #create, except that if the record is invalid, raises an exception. + # + # class Person + # has_many :pets + # end + # + # class Pet + # validates :name, presence: true + # end + # + # person.pets.create!(name: nil) + # # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank + def create!(attributes = {}, &block) + @association.create!(attributes, &block) + end + + # Replaces this collection with +other_array+. This will perform a diff + # and delete/add only records that have changed. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets + # # => [#] + # + # other_pets = [Pet.new(name: 'Puff', group: 'celebrities'] + # + # person.pets.replace(other_pets) + # + # person.pets + # # => [#] + # + # If the supplied array has an incorrect association type, it raises + # an ActiveRecord::AssociationTypeMismatch error: + # + # person.pets.replace(["doo", "ggie", "gaga"]) + # # => ActiveRecord::AssociationTypeMismatch: Pet expected, got String + def replace(other_array) + @association.replace(other_array) + end + + # Deletes all the records from the collection according to the strategy + # specified by the +:dependent+ option. If no +:dependent+ option is given, + # then it will follow the default strategy. + # + # For has_many :through associations, the default deletion strategy is + # +:delete_all+. + # + # For +has_many+ associations, the default deletion strategy is +:nullify+. + # This sets the foreign keys to +NULL+. + # + # class Person < ActiveRecord::Base + # has_many :pets # dependent: :nullify option by default + # end + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.delete_all + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.size # => 0 + # person.pets # => [] + # + # Pet.find(1, 2, 3) + # # => [ + # # #, + # # #, + # # # + # # ] + # + # Both +has_many+ and has_many :through dependencies default to the + # +:delete_all+ strategy if the +:dependent+ option is set to +:destroy+. + # Records are not instantiated and callbacks will not be fired. + # + # class Person < ActiveRecord::Base + # has_many :pets, dependent: :destroy + # end + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.delete_all + # + # Pet.find(1, 2, 3) + # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3) + # + # If it is set to :delete_all, all the objects are deleted + # *without* calling their +destroy+ method. + # + # class Person < ActiveRecord::Base + # has_many :pets, dependent: :delete_all + # end + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.delete_all + # + # Pet.find(1, 2, 3) + # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3) + def delete_all(dependent = nil) + @association.delete_all(dependent).tap { reset_scope } + end + + # Deletes the records of the collection directly from the database + # ignoring the +:dependent+ option. Records are instantiated and it + # invokes +before_remove+, +after_remove+ , +before_destroy+ and + # +after_destroy+ callbacks. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.destroy_all + # + # person.pets.size # => 0 + # person.pets # => [] + # + # Pet.find(1) # => Couldn't find Pet with id=1 + def destroy_all + @association.destroy_all.tap { reset_scope } + end + + # Deletes the +records+ supplied from the collection according to the strategy + # specified by the +:dependent+ option. If no +:dependent+ option is given, + # then it will follow the default strategy. Returns an array with the + # deleted records. + # + # For has_many :through associations, the default deletion strategy is + # +:delete_all+. + # + # For +has_many+ associations, the default deletion strategy is +:nullify+. + # This sets the foreign keys to +NULL+. + # + # class Person < ActiveRecord::Base + # has_many :pets # dependent: :nullify option by default + # end + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.delete(Pet.find(1)) + # # => [#] + # + # person.pets.size # => 2 + # person.pets + # # => [ + # # #, + # # # + # # ] + # + # Pet.find(1) + # # => # + # + # If it is set to :destroy all the +records+ are removed by calling + # their +destroy+ method. See +destroy+ for more information. + # + # class Person < ActiveRecord::Base + # has_many :pets, dependent: :destroy + # end + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.delete(Pet.find(1), Pet.find(3)) + # # => [ + # # #, + # # # + # # ] + # + # person.pets.size # => 1 + # person.pets + # # => [#] + # + # Pet.find(1, 3) + # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 3) + # + # If it is set to :delete_all, all the +records+ are deleted + # *without* calling their +destroy+ method. + # + # class Person < ActiveRecord::Base + # has_many :pets, dependent: :delete_all + # end + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.delete(Pet.find(1)) + # # => [#] + # + # person.pets.size # => 2 + # person.pets + # # => [ + # # #, + # # # + # # ] + # + # Pet.find(1) + # # => ActiveRecord::RecordNotFound: Couldn't find Pet with 'id'=1 + # + # You can pass +Integer+ or +String+ values, it finds the records + # responding to the +id+ and executes delete on them. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.delete("1") + # # => [#] + # + # person.pets.delete(2, 3) + # # => [ + # # #, + # # # + # # ] + def delete(*records) + @association.delete(*records).tap { reset_scope } + end + + # Destroys the +records+ supplied and removes them from the collection. + # This method will _always_ remove record from the database ignoring + # the +:dependent+ option. Returns an array with the removed records. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.destroy(Pet.find(1)) + # # => [#] + # + # person.pets.size # => 2 + # person.pets + # # => [ + # # #, + # # # + # # ] + # + # person.pets.destroy(Pet.find(2), Pet.find(3)) + # # => [ + # # #, + # # # + # # ] + # + # person.pets.size # => 0 + # person.pets # => [] + # + # Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3) + # + # You can pass +Integer+ or +String+ values, it finds the records + # responding to the +id+ and then deletes them from the database. + # + # person.pets.size # => 3 + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.destroy("4") + # # => # + # + # person.pets.size # => 2 + # person.pets + # # => [ + # # #, + # # # + # # ] + # + # person.pets.destroy(5, 6) + # # => [ + # # #, + # # # + # # ] + # + # person.pets.size # => 0 + # person.pets # => [] + # + # Pet.find(4, 5, 6) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (4, 5, 6) + def destroy(*records) + @association.destroy(*records).tap { reset_scope } + end + + ## + # :method: distinct + # + # :call-seq: + # distinct(value = true) + # + # Specifies whether the records should be unique or not. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets.select(:name) + # # => [ + # # #, + # # # + # # ] + # + # person.pets.select(:name).distinct + # # => [#] + # + # person.pets.select(:name).distinct.distinct(false) + # # => [ + # # #, + # # # + # # ] + + #-- + def calculate(operation, column_name) + null_scope? ? scope.calculate(operation, column_name) : super + end + + def pluck(*column_names) + null_scope? ? scope.pluck(*column_names) : super + end + + ## + # :method: count + # + # :call-seq: + # count(column_name = nil, &block) + # + # Count all records. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # # This will perform the count using SQL. + # person.pets.count # => 3 + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # Passing a block will select all of a person's pets in SQL and then + # perform the count using Ruby. + # + # person.pets.count { |pet| pet.name.include?('-') } # => 2 + + # Returns the size of the collection. If the collection hasn't been loaded, + # it executes a SELECT COUNT(*) query. Else it calls collection.size. + # + # If the collection has been already loaded +size+ and +length+ are + # equivalent. If not and you are going to need the records anyway + # +length+ will take one less query. Otherwise +size+ is more efficient. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets.size # => 3 + # # executes something like SELECT COUNT(*) FROM "pets" WHERE "pets"."person_id" = 1 + # + # person.pets # This will execute a SELECT * FROM query + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.size # => 3 + # # Because the collection is already loaded, this will behave like + # # collection.size and no SQL count query is executed. + def size + @association.size + end + + ## + # :method: length + # + # :call-seq: + # length() + # + # Returns the size of the collection calling +size+ on the target. + # If the collection has been already loaded, +length+ and +size+ are + # equivalent. If not and you are going to need the records anyway this + # method will take one less query. Otherwise +size+ is more efficient. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets.length # => 3 + # # executes something like SELECT "pets".* FROM "pets" WHERE "pets"."person_id" = 1 + # + # # Because the collection is loaded, you can + # # call the collection with no additional queries: + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + + # Returns +true+ if the collection is empty. If the collection has been + # loaded it is equivalent + # to collection.size.zero?. If the collection has not been loaded, + # it is equivalent to !collection.exists?. If the collection has + # not already been loaded and you are going to fetch the records anyway it + # is better to check collection.length.zero?. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets.count # => 1 + # person.pets.empty? # => false + # + # person.pets.delete_all + # + # person.pets.count # => 0 + # person.pets.empty? # => true + def empty? + @association.empty? + end + + ## + # :method: any? + # + # :call-seq: + # any?() + # + # Returns +true+ if the collection is not empty. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets.count # => 0 + # person.pets.any? # => false + # + # person.pets << Pet.new(name: 'Snoop') + # person.pets.count # => 1 + # person.pets.any? # => true + # + # You can also pass a +block+ to define criteria. The behavior + # is the same, it returns true if the collection based on the + # criteria is not empty. + # + # person.pets + # # => [#] + # + # person.pets.any? do |pet| + # pet.group == 'cats' + # end + # # => false + # + # person.pets.any? do |pet| + # pet.group == 'dogs' + # end + # # => true + + ## + # :method: many? + # + # :call-seq: + # many?() + # + # Returns true if the collection has more than one record. + # Equivalent to collection.size > 1. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets.count # => 1 + # person.pets.many? # => false + # + # person.pets << Pet.new(name: 'Snoopy') + # person.pets.count # => 2 + # person.pets.many? # => true + # + # You can also pass a +block+ to define criteria. The + # behavior is the same, it returns true if the collection + # based on the criteria has more than one record. + # + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # person.pets.many? do |pet| + # pet.group == 'dogs' + # end + # # => false + # + # person.pets.many? do |pet| + # pet.group == 'cats' + # end + # # => true + + # Returns +true+ if the given +record+ is present in the collection. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets # => [#] + # + # person.pets.include?(Pet.find(20)) # => true + # person.pets.include?(Pet.find(21)) # => false + def include?(record) + !!@association.include?(record) + end + + def proxy_association + @association + end + + # Returns a Relation object for the records in this association + def scope + @scope ||= @association.scope + end + + # Equivalent to Array#==. Returns +true+ if the two arrays + # contain the same number of elements and if each element is equal + # to the corresponding element in the +other+ array, otherwise returns + # +false+. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets + # # => [ + # # #, + # # # + # # ] + # + # other = person.pets.to_ary + # + # person.pets == other + # # => true + # + # other = [Pet.new(id: 1), Pet.new(id: 2)] + # + # person.pets == other + # # => false + def ==(other) + load_target == other + end + + ## + # :method: to_ary + # + # :call-seq: + # to_ary() + # + # Returns a new array of objects from the collection. If the collection + # hasn't been loaded, it fetches the records from the database. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + # + # other_pets = person.pets.to_ary + # # => [ + # # #, + # # #, + # # # + # # ] + # + # other_pets.replace([Pet.new(name: 'BooGoo')]) + # + # other_pets + # # => [#] + # + # person.pets + # # This is not affected by replace + # # => [ + # # #, + # # #, + # # # + # # ] + + def records # :nodoc: + load_target + end + + # Adds one or more +records+ to the collection by setting their foreign keys + # to the association's primary key. Since +<<+ flattens its argument list and + # inserts each record, +push+ and +concat+ behave identically. Returns +self+ + # so several appends may be chained together. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets.size # => 0 + # person.pets << Pet.new(name: 'Fancy-Fancy') + # person.pets << [Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo')] + # person.pets.size # => 3 + # + # person.id # => 1 + # person.pets + # # => [ + # # #, + # # #, + # # # + # # ] + def <<(*records) + proxy_association.concat(records) && self + end + alias_method :push, :<< + alias_method :append, :<< + alias_method :concat, :<< + + def prepend(*args) + raise NoMethodError, "prepend on association is not defined. Please use <<, push or append" + end + + # Equivalent to +delete_all+. The difference is that returns +self+, instead + # of an array with the deleted objects, so methods can be chained. See + # +delete_all+ for more information. + # Note that because +delete_all+ removes records by directly + # running an SQL query into the database, the +updated_at+ column of + # the object is not changed. + def clear + delete_all + self + end + + # Reloads the collection from the database. Returns +self+. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets # fetches pets from the database + # # => [#] + # + # person.pets # uses the pets cache + # # => [#] + # + # person.pets.reload # fetches pets from the database + # # => [#] + def reload + proxy_association.reload + reset_scope + end + + # Unloads the association. Returns +self+. + # + # class Person < ActiveRecord::Base + # has_many :pets + # end + # + # person.pets # fetches pets from the database + # # => [#] + # + # person.pets # uses the pets cache + # # => [#] + # + # person.pets.reset # clears the pets cache + # + # person.pets # fetches pets from the database + # # => [#] + def reset + proxy_association.reset + proxy_association.reset_scope + reset_scope + end + + def reset_scope # :nodoc: + @offsets = {} + @scope = nil + self + end + + delegate_methods = [ + QueryMethods, + SpawnMethods, + ].flat_map { |klass| + klass.public_instance_methods(false) + } - self.public_instance_methods(false) - [:select] + [:scoping] + + delegate(*delegate_methods, to: :scope) + + private + + def find_nth_with_limit(index, limit) + load_target if find_from_target? + super + end + + def find_nth_from_last(index) + load_target if find_from_target? + super + end + + def null_scope? + @association.null_scope? + end + + def find_from_target? + @association.find_from_target? + end + + def exec_queries + load_target + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/foreign_association.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/foreign_association.rb new file mode 100644 index 00000000..40010cde --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/foreign_association.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module ActiveRecord::Associations + module ForeignAssociation # :nodoc: + def foreign_key_present? + if reflection.klass.primary_key + owner.attribute_present?(reflection.active_record_primary_key) + else + false + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/has_many_association.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/has_many_association.rb new file mode 100644 index 00000000..f6fdbcde --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/has_many_association.rb @@ -0,0 +1,144 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + # = Active Record Has Many Association + # This is the proxy that handles a has many association. + # + # If the association has a :through option further specialization + # is provided by its child HasManyThroughAssociation. + class HasManyAssociation < CollectionAssociation #:nodoc: + include ForeignAssociation + + def handle_dependency + case options[:dependent] + when :restrict_with_exception + raise ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty? + + when :restrict_with_error + unless empty? + record = owner.class.human_attribute_name(reflection.name).downcase + owner.errors.add(:base, :'restrict_dependent_destroy.has_many', record: record) + throw(:abort) + end + + when :destroy + # No point in executing the counter update since we're going to destroy the parent anyway + load_target.each { |t| t.destroyed_by_association = reflection } + destroy_all + else + delete_all + end + end + + def insert_record(record, validate = true, raise = false) + set_owner_attributes(record) + super + end + + def empty? + if reflection.has_cached_counter? + size.zero? + else + super + end + end + + private + + # Returns the number of records in this collection. + # + # If the association has a counter cache it gets that value. Otherwise + # it will attempt to do a count via SQL, bounded to :limit if + # there's one. Some configuration options like :group make it impossible + # to do an SQL count, in those cases the array count will be used. + # + # That does not depend on whether the collection has already been loaded + # or not. The +size+ method is the one that takes the loaded flag into + # account and delegates to +count_records+ if needed. + # + # If the collection is empty the target is set to an empty array and + # the loaded flag is set to true as well. + def count_records + count = if reflection.has_cached_counter? + owner._read_attribute(reflection.counter_cache_column).to_i + else + scope.count(:all) + end + + # If there's nothing in the database and @target has no new records + # we are certain the current target is an empty array. This is a + # documented side-effect of the method that may avoid an extra SELECT. + (@target ||= []) && loaded! if count == 0 + + [association_scope.limit_value, count].compact.min + end + + def update_counter(difference, reflection = reflection()) + if reflection.has_cached_counter? + owner.increment!(reflection.counter_cache_column, difference) + end + end + + def update_counter_in_memory(difference, reflection = reflection()) + if reflection.counter_must_be_updated_by_has_many? + counter = reflection.counter_cache_column + owner.increment(counter, difference) + owner.send(:clear_attribute_change, counter) # eww + end + end + + def delete_count(method, scope) + if method == :delete_all + scope.delete_all + else + scope.update_all(reflection.foreign_key => nil) + end + end + + def delete_or_nullify_all_records(method) + count = delete_count(method, scope) + update_counter(-count) + count + end + + # Deletes the records according to the :dependent option. + def delete_records(records, method) + if method == :destroy + records.each(&:destroy!) + update_counter(-records.length) unless reflection.inverse_updates_counter_cache? + else + scope = self.scope.where(reflection.klass.primary_key => records) + update_counter(-delete_count(method, scope)) + end + end + + def concat_records(records, *) + update_counter_if_success(super, records.length) + end + + def _create_record(attributes, *) + if attributes.is_a?(Array) + super + else + update_counter_if_success(super, 1) + end + end + + def update_counter_if_success(saved_successfully, difference) + if saved_successfully + update_counter_in_memory(difference) + end + saved_successfully + end + + def difference(a, b) + a - b + end + + def intersection(a, b) + a & b + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/has_many_through_association.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/has_many_through_association.rb new file mode 100644 index 00000000..8ce67840 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/has_many_through_association.rb @@ -0,0 +1,227 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + # = Active Record Has Many Through Association + class HasManyThroughAssociation < HasManyAssociation #:nodoc: + include ThroughAssociation + + def initialize(owner, reflection) + super + @through_records = {} + end + + def concat(*records) + unless owner.new_record? + records.flatten.each do |record| + raise_on_type_mismatch!(record) + end + end + + super + end + + def concat_records(records) + ensure_not_nested + + records = super(records, true) + + if owner.new_record? && records + records.flatten.each do |record| + build_through_record(record) + end + end + + records + end + + def insert_record(record, validate = true, raise = false) + ensure_not_nested + + if record.new_record? || record.has_changes_to_save? + return unless super + end + + save_through_record(record) + + record + end + + private + # The through record (built with build_record) is temporarily cached + # so that it may be reused if insert_record is subsequently called. + # + # However, after insert_record has been called, the cache is cleared in + # order to allow multiple instances of the same record in an association. + def build_through_record(record) + @through_records[record.object_id] ||= begin + ensure_mutable + + through_record = through_association.build(*options_for_through_record) + through_record.send("#{source_reflection.name}=", record) + + if options[:source_type] + through_record.send("#{source_reflection.foreign_type}=", options[:source_type]) + end + + through_record + end + end + + def options_for_through_record + [through_scope_attributes] + end + + def through_scope_attributes + scope.where_values_hash(through_association.reflection.name.to_s). + except!(through_association.reflection.foreign_key, + through_association.reflection.klass.inheritance_column) + end + + def save_through_record(record) + association = build_through_record(record) + if association.changed? + association.save! + end + ensure + @through_records.delete(record.object_id) + end + + def build_record(attributes) + ensure_not_nested + + record = super + + inverse = source_reflection.inverse_of + if inverse + if inverse.collection? + record.send(inverse.name) << build_through_record(record) + elsif inverse.has_one? + record.send("#{inverse.name}=", build_through_record(record)) + end + end + + record + end + + def remove_records(existing_records, records, method) + super + delete_through_records(records) + end + + def target_reflection_has_associated_record? + !(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?) + end + + def update_through_counter?(method) + case method + when :destroy + !through_reflection.inverse_updates_counter_cache? + when :nullify + false + else + true + end + end + + def delete_or_nullify_all_records(method) + delete_records(load_target, method) + end + + def delete_records(records, method) + ensure_not_nested + + scope = through_association.scope + scope.where! construct_join_attributes(*records) + scope = scope.where(through_scope_attributes) + + case method + when :destroy + if scope.klass.primary_key + count = scope.destroy_all.count(&:destroyed?) + else + scope.each(&:_run_destroy_callbacks) + count = scope.delete_all + end + when :nullify + count = scope.update_all(source_reflection.foreign_key => nil) + else + count = scope.delete_all + end + + delete_through_records(records) + + if source_reflection.options[:counter_cache] && method != :destroy + counter = source_reflection.counter_cache_column + klass.decrement_counter counter, records.map(&:id) + end + + if through_reflection.collection? && update_through_counter?(method) + update_counter(-count, through_reflection) + else + update_counter(-count) + end + + count + end + + def difference(a, b) + distribution = distribution(b) + + a.reject { |record| mark_occurrence(distribution, record) } + end + + def intersection(a, b) + distribution = distribution(b) + + a.select { |record| mark_occurrence(distribution, record) } + end + + def mark_occurrence(distribution, record) + distribution[record] > 0 && distribution[record] -= 1 + end + + def distribution(array) + array.each_with_object(Hash.new(0)) do |record, distribution| + distribution[record] += 1 + end + end + + def through_records_for(record) + attributes = construct_join_attributes(record) + candidates = Array.wrap(through_association.target) + candidates.find_all do |c| + attributes.all? do |key, value| + c.public_send(key) == value + end + end + end + + def delete_through_records(records) + records.each do |record| + through_records = through_records_for(record) + + if through_reflection.collection? + through_records.each { |r| through_association.target.delete(r) } + else + if through_records.include?(through_association.target) + through_association.target = nil + end + end + + @through_records.delete(record.object_id) + end + end + + def find_target + return [] unless target_reflection_has_associated_record? + super + end + + # NOTE - not sure that we can actually cope with inverses here + def invertible_for?(record) + false + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/has_one_association.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/has_one_association.rb new file mode 100644 index 00000000..2d48f9e4 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/has_one_association.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + # = Active Record Has One Association + class HasOneAssociation < SingularAssociation #:nodoc: + include ForeignAssociation + + def handle_dependency + case options[:dependent] + when :restrict_with_exception + raise ActiveRecord::DeleteRestrictionError.new(reflection.name) if load_target + + when :restrict_with_error + if load_target + record = owner.class.human_attribute_name(reflection.name).downcase + owner.errors.add(:base, :'restrict_dependent_destroy.has_one', record: record) + throw(:abort) + end + + else + delete + end + end + + def replace(record, save = true) + raise_on_type_mismatch!(record) if record + load_target + + return target unless target || record + + assigning_another_record = target != record + if assigning_another_record || record.has_changes_to_save? + save &&= owner.persisted? + + transaction_if(save) do + remove_target!(options[:dependent]) if target && !target.destroyed? && assigning_another_record + + if record + set_owner_attributes(record) + set_inverse_instance(record) + + if save && !record.save + nullify_owner_attributes(record) + set_owner_attributes(target) if target + raise RecordNotSaved, "Failed to save the new associated #{reflection.name}." + end + end + end + end + + self.target = record + end + + def delete(method = options[:dependent]) + if load_target + case method + when :delete + target.delete + when :destroy + target.destroyed_by_association = reflection + target.destroy + throw(:abort) unless target.destroyed? + when :nullify + target.update_columns(reflection.foreign_key => nil) if target.persisted? + end + end + end + + private + + # The reason that the save param for replace is false, if for create (not just build), + # is because the setting of the foreign keys is actually handled by the scoping when + # the record is instantiated, and so they are set straight away and do not need to be + # updated within replace. + def set_new_record(record) + replace(record, false) + end + + def remove_target!(method) + case method + when :delete + target.delete + when :destroy + target.destroyed_by_association = reflection + target.destroy + else + nullify_owner_attributes(target) + remove_inverse_instance(target) + + if target.persisted? && owner.persisted? && !target.save + set_owner_attributes(target) + raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " \ + "The record failed to save after its foreign key was set to nil." + end + end + end + + def nullify_owner_attributes(record) + record[reflection.foreign_key] = nil + end + + def transaction_if(value) + if value + reflection.klass.transaction { yield } + else + yield + end + end + + def _create_record(attributes, raise_error = false, &block) + unless owner.persisted? + raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved" + end + + super + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/has_one_through_association.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/has_one_through_association.rb new file mode 100644 index 00000000..019bf072 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/has_one_through_association.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + # = Active Record Has One Through Association + class HasOneThroughAssociation < HasOneAssociation #:nodoc: + include ThroughAssociation + + def replace(record, save = true) + create_through_record(record, save) + self.target = record + end + + private + def create_through_record(record, save) + ensure_not_nested + + through_proxy = through_association + through_record = through_proxy.load_target + + if through_record && !record + through_record.destroy + elsif record + attributes = construct_join_attributes(record) + + if through_record && through_record.destroyed? + through_record = through_proxy.tap(&:reload).target + end + + if through_record + if through_record.new_record? + through_record.assign_attributes(attributes) + else + through_record.update(attributes) + end + elsif owner.new_record? || !save + through_proxy.build(attributes) + else + through_proxy.create(attributes) + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/join_dependency.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/join_dependency.rb new file mode 100644 index 00000000..ebf43bf2 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/join_dependency.rb @@ -0,0 +1,262 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + class JoinDependency # :nodoc: + autoload :JoinBase, "active_record/associations/join_dependency/join_base" + autoload :JoinAssociation, "active_record/associations/join_dependency/join_association" + + class Aliases # :nodoc: + def initialize(tables) + @tables = tables + @alias_cache = tables.each_with_object({}) { |table, h| + h[table.node] = table.columns.each_with_object({}) { |column, i| + i[column.name] = column.alias + } + } + @name_and_alias_cache = tables.each_with_object({}) { |table, h| + h[table.node] = table.columns.map { |column| + [column.name, column.alias] + } + } + end + + def columns + @tables.flat_map(&:column_aliases) + end + + # An array of [column_name, alias] pairs for the table + def column_aliases(node) + @name_and_alias_cache[node] + end + + def column_alias(node, column) + @alias_cache[node][column] + end + + Table = Struct.new(:node, :columns) do # :nodoc: + def column_aliases + t = node.table + columns.map { |column| t[column.name].as Arel.sql column.alias } + end + end + Column = Struct.new(:name, :alias) + end + + def self.make_tree(associations) + hash = {} + walk_tree associations, hash + hash + end + + def self.walk_tree(associations, hash) + case associations + when Symbol, String + hash[associations.to_sym] ||= {} + when Array + associations.each do |assoc| + walk_tree assoc, hash + end + when Hash + associations.each do |k, v| + cache = hash[k] ||= {} + walk_tree v, cache + end + else + raise ConfigurationError, associations.inspect + end + end + + def initialize(base, table, associations) + tree = self.class.make_tree associations + @join_root = JoinBase.new(base, table, build(tree, base)) + end + + def reflections + join_root.drop(1).map!(&:reflection) + end + + def join_constraints(joins_to_add, join_type, alias_tracker) + @alias_tracker = alias_tracker + + construct_tables!(join_root) + joins = make_join_constraints(join_root, join_type) + + joins.concat joins_to_add.flat_map { |oj| + construct_tables!(oj.join_root) + if join_root.match? oj.join_root + walk join_root, oj.join_root + else + make_join_constraints(oj.join_root, join_type) + end + } + end + + def instantiate(result_set, &block) + primary_key = aliases.column_alias(join_root, join_root.primary_key) + + seen = Hash.new { |i, object_id| + i[object_id] = Hash.new { |j, child_class| + j[child_class] = {} + } + } + + model_cache = Hash.new { |h, klass| h[klass] = {} } + parents = model_cache[join_root] + column_aliases = aliases.column_aliases join_root + + message_bus = ActiveSupport::Notifications.instrumenter + + payload = { + record_count: result_set.length, + class_name: join_root.base_klass.name + } + + message_bus.instrument("instantiation.active_record", payload) do + result_set.each { |row_hash| + parent_key = primary_key ? row_hash[primary_key] : row_hash + parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, &block) + construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases) + } + end + + parents.values + end + + def apply_column_aliases(relation) + relation._select!(-> { aliases.columns }) + end + + protected + attr_reader :alias_tracker, :join_root + + private + def aliases + @aliases ||= Aliases.new join_root.each_with_index.map { |join_part, i| + columns = join_part.column_names.each_with_index.map { |column_name, j| + Aliases::Column.new column_name, "t#{i}_r#{j}" + } + Aliases::Table.new(join_part, columns) + } + end + + def construct_tables!(join_root) + join_root.each_children do |parent, child| + child.tables = table_aliases_for(parent, child) + end + end + + def make_join_constraints(join_root, join_type) + join_root.children.flat_map do |child| + make_constraints(join_root, child, join_type) + end + end + + def make_constraints(parent, child, join_type = Arel::Nodes::OuterJoin) + foreign_table = parent.table + foreign_klass = parent.base_klass + joins = child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker) + joins.concat child.children.flat_map { |c| make_constraints(child, c, join_type) } + end + + def table_aliases_for(parent, node) + node.reflection.chain.map { |reflection| + alias_tracker.aliased_table_for( + reflection.table_name, + table_alias_for(reflection, parent, reflection != node.reflection), + reflection.klass.type_caster + ) + } + end + + def table_alias_for(reflection, parent, join) + name = "#{reflection.plural_name}_#{parent.table_name}" + join ? "#{name}_join" : name + end + + def walk(left, right) + intersection, missing = right.children.map { |node1| + [left.children.find { |node2| node1.match? node2 }, node1] + }.partition(&:first) + + joins = intersection.flat_map { |l, r| r.table = l.table; walk(l, r) } + joins.concat missing.flat_map { |_, n| make_constraints(left, n) } + end + + def find_reflection(klass, name) + klass._reflect_on_association(name) || + raise(ConfigurationError, "Can't join '#{klass.name}' to association named '#{name}'; perhaps you misspelled it?") + end + + def build(associations, base_klass) + associations.map do |name, right| + reflection = find_reflection base_klass, name + reflection.check_validity! + reflection.check_eager_loadable! + + if reflection.polymorphic? + raise EagerLoadPolymorphicError.new(reflection) + end + + JoinAssociation.new(reflection, build(right, reflection.klass)) + end + end + + def construct(ar_parent, parent, row, rs, seen, model_cache, aliases) + return if ar_parent.nil? + + parent.children.each do |node| + if node.reflection.collection? + other = ar_parent.association(node.reflection.name) + other.loaded! + elsif ar_parent.association_cached?(node.reflection.name) + model = ar_parent.association(node.reflection.name).target + construct(model, node, row, rs, seen, model_cache, aliases) + next + end + + key = aliases.column_alias(node, node.primary_key) + id = row[key] + if id.nil? + nil_association = ar_parent.association(node.reflection.name) + nil_association.loaded! + next + end + + model = seen[ar_parent.object_id][node][id] + + if model + construct(model, node, row, rs, seen, model_cache, aliases) + else + model = construct_model(ar_parent, node, row, model_cache, id, aliases) + + if node.reflection.scope && + node.reflection.scope_for(node.base_klass.unscoped).readonly_value + model.readonly! + end + + seen[ar_parent.object_id][node][id] = model + construct(model, node, row, rs, seen, model_cache, aliases) + end + end + end + + def construct_model(record, node, row, model_cache, id, aliases) + other = record.association(node.reflection.name) + + model = model_cache[node][id] ||= + node.instantiate(row, aliases.column_aliases(node)) do |m| + other.set_inverse_instance(m) + end + + if node.reflection.collection? + other.target.push(model) + else + other.target = model + end + + model + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/join_dependency/join_association.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/join_dependency/join_association.rb new file mode 100644 index 00000000..6e5e950e --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/join_dependency/join_association.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require "active_record/associations/join_dependency/join_part" + +module ActiveRecord + module Associations + class JoinDependency # :nodoc: + class JoinAssociation < JoinPart # :nodoc: + attr_reader :reflection, :tables + attr_accessor :table + + def initialize(reflection, children) + super(reflection.klass, children) + + @reflection = reflection + @tables = nil + end + + def match?(other) + return true if self == other + super && reflection == other.reflection + end + + def join_constraints(foreign_table, foreign_klass, join_type, alias_tracker) + joins = [] + + # The chain starts with the target table, but we want to end with it here (makes + # more sense in this context), so we reverse + reflection.chain.reverse_each.with_index(1) do |reflection, i| + table = tables[-i] + klass = reflection.klass + + constraint = reflection.build_join_constraint(table, foreign_table) + + joins << table.create_join(table, table.create_on(constraint), join_type) + + join_scope = reflection.join_scope(table, foreign_klass) + arel = join_scope.arel(alias_tracker.aliases) + + if arel.constraints.any? + joins.concat arel.join_sources + right = joins.last.right + right.expr = right.expr.and(arel.constraints) + end + + # The current table in this iteration becomes the foreign table in the next + foreign_table, foreign_klass = table, klass + end + + joins + end + + def tables=(tables) + @tables = tables + @table = tables.first + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/join_dependency/join_base.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/join_dependency/join_base.rb new file mode 100644 index 00000000..988b4e8f --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/join_dependency/join_base.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require "active_record/associations/join_dependency/join_part" + +module ActiveRecord + module Associations + class JoinDependency # :nodoc: + class JoinBase < JoinPart # :nodoc: + attr_reader :table + + def initialize(base_klass, table, children) + super(base_klass, children) + @table = table + end + + def match?(other) + return true if self == other + super && base_klass == other.base_klass + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/join_dependency/join_part.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/join_dependency/join_part.rb new file mode 100644 index 00000000..3cabb219 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/join_dependency/join_part.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + class JoinDependency # :nodoc: + # A JoinPart represents a part of a JoinDependency. It is inherited + # by JoinBase and JoinAssociation. A JoinBase represents the Active Record which + # everything else is being joined onto. A JoinAssociation represents an association which + # is joining to the base. A JoinAssociation may result in more than one actual join + # operations (for example a has_and_belongs_to_many JoinAssociation would result in + # two; one for the join table and one for the target table). + class JoinPart # :nodoc: + include Enumerable + + # The Active Record class which this join part is associated 'about'; for a JoinBase + # this is the actual base model, for a JoinAssociation this is the target model of the + # association. + attr_reader :base_klass, :children + + delegate :table_name, :column_names, :primary_key, to: :base_klass + + def initialize(base_klass, children) + @base_klass = base_klass + @children = children + end + + def match?(other) + self.class == other.class + end + + def each(&block) + yield self + children.each { |child| child.each(&block) } + end + + def each_children(&block) + children.each do |child| + yield self, child + child.each_children(&block) + end + end + + # An Arel::Table for the active_record + def table + raise NotImplementedError + end + + def extract_record(row, column_names_with_alias) + # This code is performance critical as it is called per row. + # see: https://github.com/rails/rails/pull/12185 + hash = {} + + index = 0 + length = column_names_with_alias.length + + while index < length + column_name, alias_name = column_names_with_alias[index] + hash[column_name] = row[alias_name] + index += 1 + end + + hash + end + + def instantiate(row, aliases, &block) + base_klass.instantiate(extract_record(row, aliases), &block) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/preloader.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/preloader.rb new file mode 100644 index 00000000..59320431 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/preloader.rb @@ -0,0 +1,193 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + # Implements the details of eager loading of Active Record associations. + # + # Suppose that you have the following two Active Record models: + # + # class Author < ActiveRecord::Base + # # columns: name, age + # has_many :books + # end + # + # class Book < ActiveRecord::Base + # # columns: title, sales, author_id + # end + # + # When you load an author with all associated books Active Record will make + # multiple queries like this: + # + # Author.includes(:books).where(name: ['bell hooks', 'Homer']).to_a + # + # => SELECT `authors`.* FROM `authors` WHERE `name` IN ('bell hooks', 'Homer') + # => SELECT `books`.* FROM `books` WHERE `author_id` IN (2, 5) + # + # Active Record saves the ids of the records from the first query to use in + # the second. Depending on the number of associations involved there can be + # arbitrarily many SQL queries made. + # + # However, if there is a WHERE clause that spans across tables Active + # Record will fall back to a slightly more resource-intensive single query: + # + # Author.includes(:books).where(books: {title: 'Illiad'}).to_a + # => SELECT `authors`.`id` AS t0_r0, `authors`.`name` AS t0_r1, `authors`.`age` AS t0_r2, + # `books`.`id` AS t1_r0, `books`.`title` AS t1_r1, `books`.`sales` AS t1_r2 + # FROM `authors` + # LEFT OUTER JOIN `books` ON `authors`.`id` = `books`.`author_id` + # WHERE `books`.`title` = 'Illiad' + # + # This could result in many rows that contain redundant data and it performs poorly at scale + # and is therefore only used when necessary. + # + class Preloader #:nodoc: + extend ActiveSupport::Autoload + + eager_autoload do + autoload :Association, "active_record/associations/preloader/association" + autoload :ThroughAssociation, "active_record/associations/preloader/through_association" + end + + # Eager loads the named associations for the given Active Record record(s). + # + # In this description, 'association name' shall refer to the name passed + # to an association creation method. For example, a model that specifies + # belongs_to :author, has_many :buyers has association + # names +:author+ and +:buyers+. + # + # == Parameters + # +records+ is an array of ActiveRecord::Base. This array needs not be flat, + # i.e. +records+ itself may also contain arrays of records. In any case, + # +preload_associations+ will preload the all associations records by + # flattening +records+. + # + # +associations+ specifies one or more associations that you want to + # preload. It may be: + # - a Symbol or a String which specifies a single association name. For + # example, specifying +:books+ allows this method to preload all books + # for an Author. + # - an Array which specifies multiple association names. This array + # is processed recursively. For example, specifying [:avatar, :books] + # allows this method to preload an author's avatar as well as all of his + # books. + # - a Hash which specifies multiple association names, as well as + # association names for the to-be-preloaded association objects. For + # example, specifying { author: :avatar } will preload a + # book's author, as well as that author's avatar. + # + # +:associations+ has the same format as the +:include+ option for + # ActiveRecord::Base.find. So +associations+ could look like this: + # + # :books + # [ :books, :author ] + # { author: :avatar } + # [ :books, { author: :avatar } ] + def preload(records, associations, preload_scope = nil) + records = Array.wrap(records).compact + + if records.empty? + [] + else + records.uniq! + Array.wrap(associations).flat_map { |association| + preloaders_on association, records, preload_scope + } + end + end + + private + + # Loads all the given data into +records+ for the +association+. + def preloaders_on(association, records, scope) + case association + when Hash + preloaders_for_hash(association, records, scope) + when Symbol + preloaders_for_one(association, records, scope) + when String + preloaders_for_one(association.to_sym, records, scope) + else + raise ArgumentError, "#{association.inspect} was not recognized for preload" + end + end + + def preloaders_for_hash(association, records, scope) + association.flat_map { |parent, child| + loaders = preloaders_for_one parent, records, scope + + recs = loaders.flat_map(&:preloaded_records).uniq + loaders.concat Array.wrap(child).flat_map { |assoc| + preloaders_on assoc, recs, scope + } + loaders + } + end + + # Loads all the given data into +records+ for a singular +association+. + # + # Functions by instantiating a preloader class such as Preloader::HasManyThrough and + # call the +run+ method for each passed in class in the +records+ argument. + # + # Not all records have the same class, so group then preload group on the reflection + # itself so that if various subclass share the same association then we do not split + # them unnecessarily + # + # Additionally, polymorphic belongs_to associations can have multiple associated + # classes, depending on the polymorphic_type field. So we group by the classes as + # well. + def preloaders_for_one(association, records, scope) + grouped_records(association, records).flat_map do |reflection, klasses| + klasses.map do |rhs_klass, rs| + loader = preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope) + loader.run self + loader + end + end + end + + def grouped_records(association, records) + h = {} + records.each do |record| + next unless record + assoc = record.association(association) + next unless assoc.klass + klasses = h[assoc.reflection] ||= {} + (klasses[assoc.klass] ||= []) << record + end + h + end + + class AlreadyLoaded # :nodoc: + def initialize(klass, owners, reflection, preload_scope) + @owners = owners + @reflection = reflection + end + + def run(preloader); end + + def preloaded_records + owners.flat_map { |owner| owner.association(reflection.name).target } + end + + protected + attr_reader :owners, :reflection + end + + # Returns a class containing the logic needed to load preload the data + # and attach it to a relation. The class returned implements a `run` method + # that accepts a preloader. + def preloader_for(reflection, owners) + if owners.first.association(reflection.name).loaded? + return AlreadyLoaded + end + reflection.check_preloadable! + + if reflection.options[:through] + ThroughAssociation + else + Association + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/preloader/association.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/preloader/association.rb new file mode 100644 index 00000000..7a6c402f --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/preloader/association.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + class Preloader + class Association #:nodoc: + attr_reader :preloaded_records + + def initialize(klass, owners, reflection, preload_scope) + @klass = klass + @owners = owners + @reflection = reflection + @preload_scope = preload_scope + @model = owners.first && owners.first.class + @preloaded_records = [] + end + + def run(preloader) + records = load_records do |record| + owner = owners_by_key[convert_key(record[association_key_name])] + association = owner.association(reflection.name) + association.set_inverse_instance(record) + end + + owners.each do |owner| + associate_records_to_owner(owner, records[convert_key(owner[owner_key_name])] || []) + end + end + + protected + attr_reader :owners, :reflection, :preload_scope, :model, :klass + + private + # The name of the key on the associated records + def association_key_name + reflection.join_primary_key(klass) + end + + # The name of the key on the model which declares the association + def owner_key_name + reflection.join_foreign_key + end + + def associate_records_to_owner(owner, records) + association = owner.association(reflection.name) + association.loaded! + if reflection.collection? + association.target.concat(records) + else + association.target = records.first unless records.empty? + end + end + + def owner_keys + @owner_keys ||= owners_by_key.keys + end + + def owners_by_key + unless defined?(@owners_by_key) + @owners_by_key = owners.each_with_object({}) do |owner, h| + key = convert_key(owner[owner_key_name]) + h[key] = owner if key + end + end + @owners_by_key + end + + def key_conversion_required? + unless defined?(@key_conversion_required) + @key_conversion_required = (association_key_type != owner_key_type) + end + + @key_conversion_required + end + + def convert_key(key) + if key_conversion_required? + key.to_s + else + key + end + end + + def association_key_type + @klass.type_for_attribute(association_key_name).type + end + + def owner_key_type + @model.type_for_attribute(owner_key_name).type + end + + def load_records(&block) + return {} if owner_keys.empty? + # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000) + # Make several smaller queries if necessary or make one query if the adapter supports it + slices = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size) + @preloaded_records = slices.flat_map do |slice| + records_for(slice, &block) + end + @preloaded_records.group_by do |record| + convert_key(record[association_key_name]) + end + end + + def records_for(ids, &block) + scope.where(association_key_name => ids).load(&block) + end + + def scope + @scope ||= build_scope + end + + def reflection_scope + @reflection_scope ||= reflection.scope ? reflection.scope_for(klass.unscoped) : klass.unscoped + end + + def build_scope + scope = klass.scope_for_association + + if reflection.type + scope.where!(reflection.type => model.polymorphic_name) + end + + scope.merge!(reflection_scope) if reflection.scope + scope.merge!(preload_scope) if preload_scope + scope + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/preloader/through_association.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/preloader/through_association.rb new file mode 100644 index 00000000..a6b7ab80 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/preloader/through_association.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + class Preloader + class ThroughAssociation < Association # :nodoc: + def run(preloader) + already_loaded = owners.first.association(through_reflection.name).loaded? + through_scope = through_scope() + reflection_scope = target_reflection_scope + through_preloaders = preloader.preload(owners, through_reflection.name, through_scope) + middle_records = through_preloaders.flat_map(&:preloaded_records) + preloaders = preloader.preload(middle_records, source_reflection.name, reflection_scope) + @preloaded_records = preloaders.flat_map(&:preloaded_records) + + owners.each do |owner| + through_records = Array(owner.association(through_reflection.name).target) + if already_loaded + if source_type = reflection.options[:source_type] + through_records = through_records.select do |record| + record[reflection.foreign_type] == source_type + end + end + else + owner.association(through_reflection.name).reset if through_scope + end + result = through_records.flat_map do |record| + association = record.association(source_reflection.name) + target = association.target + association.reset if preload_scope + target + end + result.compact! + if reflection_scope + result.sort_by! { |rhs| preload_index[rhs] } if reflection_scope.order_values.any? + result.uniq! if reflection_scope.distinct_value + end + associate_records_to_owner(owner, result) + end + end + + private + def through_reflection + reflection.through_reflection + end + + def source_reflection + reflection.source_reflection + end + + def preload_index + @preload_index ||= @preloaded_records.each_with_object({}).with_index do |(id, result), index| + result[id] = index + end + end + + def through_scope + scope = through_reflection.klass.unscoped + options = reflection.options + + if options[:source_type] + scope.where! reflection.foreign_type => options[:source_type] + elsif !reflection_scope.where_clause.empty? + scope.where_clause = reflection_scope.where_clause + values = reflection_scope.values + + if includes = values[:includes] + scope.includes!(source_reflection.name => includes) + else + scope.includes!(source_reflection.name) + end + + if values[:references] && !values[:references].empty? + scope.references!(values[:references]) + else + scope.references!(source_reflection.table_name) + end + + if joins = values[:joins] + scope.joins!(source_reflection.name => joins) + end + + if left_outer_joins = values[:left_outer_joins] + scope.left_outer_joins!(source_reflection.name => left_outer_joins) + end + + if scope.eager_loading? && order_values = values[:order] + scope = scope.order(order_values) + end + end + + scope unless scope.empty_scope? + end + + def target_reflection_scope + if preload_scope + reflection_scope.merge(preload_scope) + elsif reflection.scope + reflection_scope + else + nil + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/singular_association.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/singular_association.rb new file mode 100644 index 00000000..cfab16a7 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/singular_association.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + class SingularAssociation < Association #:nodoc: + # Implements the reader method, e.g. foo.bar for Foo.has_one :bar + def reader + if !loaded? || stale_target? + reload + end + + target + end + + # Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar + def writer(record) + replace(record) + end + + def build(attributes = {}, &block) + record = build_record(attributes, &block) + set_new_record(record) + record + end + + # Implements the reload reader method, e.g. foo.reload_bar for + # Foo.has_one :bar + def force_reload_reader + klass.uncached { reload } + target + end + + private + def scope_for_create + super.except!(klass.primary_key) + end + + def find_target + scope = self.scope + return scope.take if skip_statement_cache?(scope) + + conn = klass.connection + sc = reflection.association_scope_cache(conn, owner) do |params| + as = AssociationScope.create { params.bind } + target_scope.merge!(as.scope(self)).limit(1) + end + + binds = AssociationScope.get_bind_values(owner, reflection.chain) + sc.execute(binds, conn) do |record| + set_inverse_instance record + end.first + rescue ::RangeError + nil + end + + def replace(record) + raise NotImplementedError, "Subclasses must implement a replace(record) method" + end + + def set_new_record(record) + replace(record) + end + + def _create_record(attributes, raise_error = false, &block) + record = build_record(attributes, &block) + saved = record.save + set_new_record(record) + raise RecordInvalid.new(record) if !saved && raise_error + record + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/through_association.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/through_association.rb new file mode 100644 index 00000000..15e6565e --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/associations/through_association.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true + +module ActiveRecord + module Associations + # = Active Record Through Association + module ThroughAssociation #:nodoc: + delegate :source_reflection, to: :reflection + + private + def through_reflection + @through_reflection ||= begin + refl = reflection.through_reflection + + while refl.through_reflection? + refl = refl.through_reflection + end + + refl + end + end + + def through_association + @through_association ||= owner.association(through_reflection.name) + end + + # We merge in these scopes for two reasons: + # + # 1. To get the default_scope conditions for any of the other reflections in the chain + # 2. To get the type conditions for any STI models in the chain + def target_scope + scope = super + reflection.chain.drop(1).each do |reflection| + relation = reflection.klass.scope_for_association + scope.merge!( + relation.except(:select, :create_with, :includes, :preload, :joins, :eager_load) + ) + end + scope + end + + # Construct attributes for :through pointing to owner and associate. This is used by the + # methods which create and delete records on the association. + # + # We only support indirectly modifying through associations which have a belongs_to source. + # This is the "has_many :tags, through: :taggings" situation, where the join model + # typically has a belongs_to on both side. In other words, associations which could also + # be represented as has_and_belongs_to_many associations. + # + # We do not support creating/deleting records on the association where the source has + # some other type, because this opens up a whole can of worms, and in basically any + # situation it is more natural for the user to just create or modify their join records + # directly as required. + def construct_join_attributes(*records) + ensure_mutable + + association_primary_key = source_reflection.association_primary_key(reflection.klass) + + if association_primary_key == reflection.klass.primary_key && !options[:source_type] + join_attributes = { source_reflection.name => records } + else + join_attributes = { + source_reflection.foreign_key => records.map(&association_primary_key.to_sym) + } + end + + if options[:source_type] + join_attributes[source_reflection.foreign_type] = [ options[:source_type] ] + end + + if records.count == 1 + join_attributes.transform_values!(&:first) + else + join_attributes + end + end + + # Note: this does not capture all cases, for example it would be crazy to try to + # properly support stale-checking for nested associations. + def stale_state + if through_reflection.belongs_to? + owner[through_reflection.foreign_key] && owner[through_reflection.foreign_key].to_s + end + end + + def foreign_key_present? + through_reflection.belongs_to? && !owner[through_reflection.foreign_key].nil? + end + + def ensure_mutable + unless source_reflection.belongs_to? + if reflection.has_one? + raise HasOneThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection) + else + raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection) + end + end + end + + def ensure_not_nested + if reflection.nested? + if reflection.has_one? + raise HasOneThroughNestedAssociationsAreReadonly.new(owner, reflection) + else + raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection) + end + end + end + + def build_record(attributes) + inverse = source_reflection.inverse_of + target = through_association.target + + if inverse && target && !target.is_a?(Array) + attributes[inverse.foreign_key] = target.id + end + + super + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_assignment.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_assignment.rb new file mode 100644 index 00000000..d6ac7db8 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_assignment.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +require "active_model/forbidden_attributes_protection" + +module ActiveRecord + module AttributeAssignment + extend ActiveSupport::Concern + include ActiveModel::AttributeAssignment + + private + + def _assign_attributes(attributes) + multi_parameter_attributes = {} + nested_parameter_attributes = {} + + attributes.each do |k, v| + if k.include?("(") + multi_parameter_attributes[k] = attributes.delete(k) + elsif v.is_a?(Hash) + nested_parameter_attributes[k] = attributes.delete(k) + end + end + super(attributes) + + assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty? + assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty? + end + + # Assign any deferred nested attributes after the base attributes have been set. + def assign_nested_parameter_attributes(pairs) + pairs.each { |k, v| _assign_attribute(k, v) } + end + + # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done + # by calling new on the column type or aggregation type (through composed_of) object with these parameters. + # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate + # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the + # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Integer and + # f for Float. If all the values for a given attribute are empty, the attribute will be set to +nil+. + def assign_multiparameter_attributes(pairs) + execute_callstack_for_multiparameter_attributes( + extract_callstack_for_multiparameter_attributes(pairs) + ) + end + + def execute_callstack_for_multiparameter_attributes(callstack) + errors = [] + callstack.each do |name, values_with_empty_parameters| + begin + if values_with_empty_parameters.each_value.all?(&:nil?) + values = nil + else + values = values_with_empty_parameters + end + send("#{name}=", values) + rescue => ex + errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name) + end + end + unless errors.empty? + error_descriptions = errors.map(&:message).join(",") + raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]" + end + end + + def extract_callstack_for_multiparameter_attributes(pairs) + attributes = {} + + pairs.each do |(multiparameter_name, value)| + attribute_name = multiparameter_name.split("(").first + attributes[attribute_name] ||= {} + + parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value) + attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value + end + + attributes + end + + def type_cast_attribute_value(multiparameter_name, value) + multiparameter_name =~ /\([0-9]*([if])\)/ ? value.send("to_" + $1) : value + end + + def find_parameter_position(multiparameter_name) + multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_decorators.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_decorators.rb new file mode 100644 index 00000000..98b7805c --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_decorators.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +module ActiveRecord + module AttributeDecorators # :nodoc: + extend ActiveSupport::Concern + + included do + class_attribute :attribute_type_decorations, instance_accessor: false, default: TypeDecorator.new # :internal: + end + + module ClassMethods # :nodoc: + # This method is an internal API used to create class macros such as + # +serialize+, and features like time zone aware attributes. + # + # Used to wrap the type of an attribute in a new type. + # When the schema for a model is loaded, attributes with the same name as + # +column_name+ will have their type yielded to the given block. The + # return value of that block will be used instead. + # + # Subsequent calls where +column_name+ and +decorator_name+ are the same + # will override the previous decorator, not decorate twice. This can be + # used to create idempotent class macros like +serialize+ + def decorate_attribute_type(column_name, decorator_name, &block) + matcher = ->(name, _) { name == column_name.to_s } + key = "_#{column_name}_#{decorator_name}" + decorate_matching_attribute_types(matcher, key, &block) + end + + # This method is an internal API used to create higher level features like + # time zone aware attributes. + # + # When the schema for a model is loaded, +matcher+ will be called for each + # attribute with its name and type. If the matcher returns a truthy value, + # the type will then be yielded to the given block, and the return value + # of that block will replace the type. + # + # Subsequent calls to this method with the same value for +decorator_name+ + # will replace the previous decorator, not decorate twice. This can be + # used to ensure that class macros are idempotent. + def decorate_matching_attribute_types(matcher, decorator_name, &block) + reload_schema_from_cache + decorator_name = decorator_name.to_s + + # Create new hashes so we don't modify parent classes + self.attribute_type_decorations = attribute_type_decorations.merge(decorator_name => [matcher, block]) + end + + private + + def load_schema! + super + attribute_types.each do |name, type| + decorated_type = attribute_type_decorations.apply(name, type) + define_attribute(name, decorated_type) + end + end + end + + class TypeDecorator # :nodoc: + delegate :clear, to: :@decorations + + def initialize(decorations = {}) + @decorations = decorations + end + + def merge(*args) + TypeDecorator.new(@decorations.merge(*args)) + end + + def apply(name, type) + decorations = decorators_for(name, type) + decorations.inject(type) do |new_type, block| + block.call(new_type) + end + end + + private + + def decorators_for(name, type) + matching(name, type).map(&:last) + end + + def matching(name, type) + @decorations.values.select do |(matcher, _)| + matcher.call(name, type) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_methods.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_methods.rb new file mode 100644 index 00000000..d87f79f0 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_methods.rb @@ -0,0 +1,492 @@ +# frozen_string_literal: true + +require "mutex_m" + +module ActiveRecord + # = Active Record Attribute Methods + module AttributeMethods + extend ActiveSupport::Concern + include ActiveModel::AttributeMethods + + included do + initialize_generated_modules + include Read + include Write + include BeforeTypeCast + include Query + include PrimaryKey + include TimeZoneConversion + include Dirty + include Serialization + + delegate :column_for_attribute, to: :class + end + + AttrNames = Module.new { + def self.set_name_cache(name, value) + const_name = "ATTR_#{name}" + unless const_defined? const_name + const_set const_name, value.dup.freeze + end + end + } + + BLACKLISTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass) + + class GeneratedAttributeMethods < Module #:nodoc: + include Mutex_m + end + + module ClassMethods + def inherited(child_class) #:nodoc: + child_class.initialize_generated_modules + super + end + + def initialize_generated_modules # :nodoc: + @generated_attribute_methods = GeneratedAttributeMethods.new + @attribute_methods_generated = false + include @generated_attribute_methods + + super + end + + # Generates all the attribute related methods for columns in the database + # accessors, mutators and query methods. + def define_attribute_methods # :nodoc: + return false if @attribute_methods_generated + # Use a mutex; we don't want two threads simultaneously trying to define + # attribute methods. + generated_attribute_methods.synchronize do + return false if @attribute_methods_generated + superclass.define_attribute_methods unless self == base_class + super(attribute_names) + @attribute_methods_generated = true + end + end + + def undefine_attribute_methods # :nodoc: + generated_attribute_methods.synchronize do + super if defined?(@attribute_methods_generated) && @attribute_methods_generated + @attribute_methods_generated = false + end + end + + # Raises an ActiveRecord::DangerousAttributeError exception when an + # \Active \Record method is defined in the model, otherwise +false+. + # + # class Person < ActiveRecord::Base + # def save + # 'already defined by Active Record' + # end + # end + # + # Person.instance_method_already_implemented?(:save) + # # => ActiveRecord::DangerousAttributeError: save is defined by Active Record. Check to make sure that you don't have an attribute or method with the same name. + # + # Person.instance_method_already_implemented?(:name) + # # => false + def instance_method_already_implemented?(method_name) + if dangerous_attribute_method?(method_name) + raise DangerousAttributeError, "#{method_name} is defined by Active Record. Check to make sure that you don't have an attribute or method with the same name." + end + + if superclass == Base + super + else + # If ThisClass < ... < SomeSuperClass < ... < Base and SomeSuperClass + # defines its own attribute method, then we don't want to overwrite that. + defined = method_defined_within?(method_name, superclass, Base) && + ! superclass.instance_method(method_name).owner.is_a?(GeneratedAttributeMethods) + defined || super + end + end + + # A method name is 'dangerous' if it is already (re)defined by Active Record, but + # not by any ancestors. (So 'puts' is not dangerous but 'save' is.) + def dangerous_attribute_method?(name) # :nodoc: + method_defined_within?(name, Base) + end + + def method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc: + if klass.method_defined?(name) || klass.private_method_defined?(name) + if superklass.method_defined?(name) || superklass.private_method_defined?(name) + klass.instance_method(name).owner != superklass.instance_method(name).owner + else + true + end + else + false + end + end + + # A class method is 'dangerous' if it is already (re)defined by Active Record, but + # not by any ancestors. (So 'puts' is not dangerous but 'new' is.) + def dangerous_class_method?(method_name) + BLACKLISTED_CLASS_METHODS.include?(method_name.to_s) || class_method_defined_within?(method_name, Base) + end + + def class_method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc: + if klass.respond_to?(name, true) + if superklass.respond_to?(name, true) + klass.method(name).owner != superklass.method(name).owner + else + true + end + else + false + end + end + + # Returns +true+ if +attribute+ is an attribute method and table exists, + # +false+ otherwise. + # + # class Person < ActiveRecord::Base + # end + # + # Person.attribute_method?('name') # => true + # Person.attribute_method?(:age=) # => true + # Person.attribute_method?(:nothing) # => false + def attribute_method?(attribute) + super || (table_exists? && column_names.include?(attribute.to_s.sub(/=$/, ""))) + end + + # Returns an array of column names as strings if it's not an abstract class and + # table exists. Otherwise it returns an empty array. + # + # class Person < ActiveRecord::Base + # end + # + # Person.attribute_names + # # => ["id", "created_at", "updated_at", "name", "age"] + def attribute_names + @attribute_names ||= if !abstract_class? && table_exists? + attribute_types.keys + else + [] + end + end + + # Regexp whitelist. Matches the following: + # "#{table_name}.#{column_name}" + # "#{column_name}" + COLUMN_NAME_WHITELIST = /\A(?:\w+\.)?\w+\z/i + + # Regexp whitelist. Matches the following: + # "#{table_name}.#{column_name}" + # "#{table_name}.#{column_name} #{direction}" + # "#{table_name}.#{column_name} #{direction} NULLS FIRST" + # "#{table_name}.#{column_name} NULLS LAST" + # "#{column_name}" + # "#{column_name} #{direction}" + # "#{column_name} #{direction} NULLS FIRST" + # "#{column_name} NULLS LAST" + COLUMN_NAME_ORDER_WHITELIST = / + \A + (?:\w+\.)? + \w+ + (?:\s+asc|\s+desc)? + (?:\s+nulls\s+(?:first|last))? + \z + /ix + + def enforce_raw_sql_whitelist(args, whitelist: COLUMN_NAME_WHITELIST) # :nodoc: + unexpected = args.reject do |arg| + arg.kind_of?(Arel::Node) || + arg.is_a?(Arel::Nodes::SqlLiteral) || + arg.is_a?(Arel::Attributes::Attribute) || + arg.to_s.split(/\s*,\s*/).all? { |part| whitelist.match?(part) } + end + + return if unexpected.none? + + if allow_unsafe_raw_sql == :deprecated + ActiveSupport::Deprecation.warn( + "Dangerous query method (method whose arguments are used as raw " \ + "SQL) called with non-attribute argument(s): " \ + "#{unexpected.map(&:inspect).join(", ")}. Non-attribute " \ + "arguments will be disallowed in Rails 6.0. This method should " \ + "not be called with user-provided values, such as request " \ + "parameters or model attributes. Known-safe values can be passed " \ + "by wrapping them in Arel.sql()." + ) + else + raise(ActiveRecord::UnknownAttributeReference, + "Query method called with non-attribute argument(s): " + + unexpected.map(&:inspect).join(", ") + ) + end + end + + # Returns true if the given attribute exists, otherwise false. + # + # class Person < ActiveRecord::Base + # end + # + # Person.has_attribute?('name') # => true + # Person.has_attribute?(:age) # => true + # Person.has_attribute?(:nothing) # => false + def has_attribute?(attr_name) + attribute_types.key?(attr_name.to_s) + end + + # Returns the column object for the named attribute. + # Returns a +ActiveRecord::ConnectionAdapters::NullColumn+ if the + # named attribute does not exist. + # + # class Person < ActiveRecord::Base + # end + # + # person = Person.new + # person.column_for_attribute(:name) # the result depends on the ConnectionAdapter + # # => # + # + # person.column_for_attribute(:nothing) + # # => #, ...> + def column_for_attribute(name) + name = name.to_s + columns_hash.fetch(name) do + ConnectionAdapters::NullColumn.new(name) + end + end + end + + # A Person object with a name attribute can ask person.respond_to?(:name), + # person.respond_to?(:name=), and person.respond_to?(:name?) + # which will all return +true+. It also defines the attribute methods if they have + # not been generated. + # + # class Person < ActiveRecord::Base + # end + # + # person = Person.new + # person.respond_to?(:name) # => true + # person.respond_to?(:name=) # => true + # person.respond_to?(:name?) # => true + # person.respond_to?('age') # => true + # person.respond_to?('age=') # => true + # person.respond_to?('age?') # => true + # person.respond_to?(:nothing) # => false + def respond_to?(name, include_private = false) + return false unless super + + case name + when :to_partial_path + name = "to_partial_path".freeze + when :to_model + name = "to_model".freeze + else + name = name.to_s + end + + # If the result is true then check for the select case. + # For queries selecting a subset of columns, return false for unselected columns. + # We check defined?(@attributes) not to issue warnings if called on objects that + # have been allocated but not yet initialized. + if defined?(@attributes) && self.class.column_names.include?(name) + return has_attribute?(name) + end + + true + end + + # Returns +true+ if the given attribute is in the attributes hash, otherwise +false+. + # + # class Person < ActiveRecord::Base + # end + # + # person = Person.new + # person.has_attribute?(:name) # => true + # person.has_attribute?('age') # => true + # person.has_attribute?(:nothing) # => false + def has_attribute?(attr_name) + @attributes.key?(attr_name.to_s) + end + + # Returns an array of names for the attributes available on this object. + # + # class Person < ActiveRecord::Base + # end + # + # person = Person.new + # person.attribute_names + # # => ["id", "created_at", "updated_at", "name", "age"] + def attribute_names + @attributes.keys + end + + # Returns a hash of all the attributes with their names as keys and the values of the attributes as values. + # + # class Person < ActiveRecord::Base + # end + # + # person = Person.create(name: 'Francesco', age: 22) + # person.attributes + # # => {"id"=>3, "created_at"=>Sun, 21 Oct 2012 04:53:04, "updated_at"=>Sun, 21 Oct 2012 04:53:04, "name"=>"Francesco", "age"=>22} + def attributes + @attributes.to_hash + end + + # Returns an #inspect-like string for the value of the + # attribute +attr_name+. String attributes are truncated up to 50 + # characters, Date and Time attributes are returned in the + # :db format. Other attributes return the value of + # #inspect without modification. + # + # person = Person.create!(name: 'David Heinemeier Hansson ' * 3) + # + # person.attribute_for_inspect(:name) + # # => "\"David Heinemeier Hansson David Heinemeier Hansson ...\"" + # + # person.attribute_for_inspect(:created_at) + # # => "\"2012-10-22 00:15:07\"" + # + # person.attribute_for_inspect(:tag_ids) + # # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]" + def attribute_for_inspect(attr_name) + value = read_attribute(attr_name) + + if value.is_a?(String) && value.length > 50 + "#{value[0, 50]}...".inspect + elsif value.is_a?(Date) || value.is_a?(Time) + %("#{value.to_s(:db)}") + else + value.inspect + end + end + + # Returns +true+ if the specified +attribute+ has been set by the user or by a + # database load and is neither +nil+ nor empty? (the latter only applies + # to objects that respond to empty?, most notably Strings). Otherwise, +false+. + # Note that it always returns +true+ with boolean attributes. + # + # class Task < ActiveRecord::Base + # end + # + # task = Task.new(title: '', is_done: false) + # task.attribute_present?(:title) # => false + # task.attribute_present?(:is_done) # => true + # task.title = 'Buy milk' + # task.is_done = true + # task.attribute_present?(:title) # => true + # task.attribute_present?(:is_done) # => true + def attribute_present?(attribute) + value = _read_attribute(attribute) + !value.nil? && !(value.respond_to?(:empty?) && value.empty?) + end + + # Returns the value of the attribute identified by attr_name after it has been typecast (for example, + # "2004-12-12" in a date column is cast to a date object, like Date.new(2004, 12, 12)). It raises + # ActiveModel::MissingAttributeError if the identified attribute is missing. + # + # Note: +:id+ is always present. + # + # class Person < ActiveRecord::Base + # belongs_to :organization + # end + # + # person = Person.new(name: 'Francesco', age: '22') + # person[:name] # => "Francesco" + # person[:age] # => 22 + # + # person = Person.select('id').first + # person[:name] # => ActiveModel::MissingAttributeError: missing attribute: name + # person[:organization_id] # => ActiveModel::MissingAttributeError: missing attribute: organization_id + def [](attr_name) + read_attribute(attr_name) { |n| missing_attribute(n, caller) } + end + + # Updates the attribute identified by attr_name with the specified +value+. + # (Alias for the protected #write_attribute method). + # + # class Person < ActiveRecord::Base + # end + # + # person = Person.new + # person[:age] = '22' + # person[:age] # => 22 + # person[:age].class # => Integer + def []=(attr_name, value) + write_attribute(attr_name, value) + end + + # Returns the name of all database fields which have been read from this + # model. This can be useful in development mode to determine which fields + # need to be selected. For performance critical pages, selecting only the + # required fields can be an easy performance win (assuming you aren't using + # all of the fields on the model). + # + # For example: + # + # class PostsController < ActionController::Base + # after_action :print_accessed_fields, only: :index + # + # def index + # @posts = Post.all + # end + # + # private + # + # def print_accessed_fields + # p @posts.first.accessed_fields + # end + # end + # + # Which allows you to quickly change your code to: + # + # class PostsController < ActionController::Base + # def index + # @posts = Post.select(:id, :title, :author_id, :updated_at) + # end + # end + def accessed_fields + @attributes.accessed + end + + protected + + def attribute_method?(attr_name) # :nodoc: + # We check defined? because Syck calls respond_to? before actually calling initialize. + defined?(@attributes) && @attributes.key?(attr_name) + end + + private + + def attributes_with_values_for_create(attribute_names) + attributes_with_values(attributes_for_create(attribute_names)) + end + + def attributes_with_values_for_update(attribute_names) + attributes_with_values(attributes_for_update(attribute_names)) + end + + def attributes_with_values(attribute_names) + attribute_names.each_with_object({}) do |name, attrs| + attrs[name] = _read_attribute(name) + end + end + + # Filters the primary keys and readonly attributes from the attribute names. + def attributes_for_update(attribute_names) + attribute_names.reject do |name| + readonly_attribute?(name) + end + end + + # Filters out the primary keys, from the attribute names, when the primary + # key is to be generated (e.g. the id attribute has no value). + def attributes_for_create(attribute_names) + attribute_names.reject do |name| + pk_attribute?(name) && id.nil? + end + end + + def readonly_attribute?(name) + self.class.readonly_attributes.include?(name) + end + + def pk_attribute?(name) + name == self.class.primary_key + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_methods/before_type_cast.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_methods/before_type_cast.rb new file mode 100644 index 00000000..5941f51a --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_methods/before_type_cast.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +module ActiveRecord + module AttributeMethods + # = Active Record Attribute Methods Before Type Cast + # + # ActiveRecord::AttributeMethods::BeforeTypeCast provides a way to + # read the value of the attributes before typecasting and deserialization. + # + # class Task < ActiveRecord::Base + # end + # + # task = Task.new(id: '1', completed_on: '2012-10-21') + # task.id # => 1 + # task.completed_on # => Sun, 21 Oct 2012 + # + # task.attributes_before_type_cast + # # => {"id"=>"1", "completed_on"=>"2012-10-21", ... } + # task.read_attribute_before_type_cast('id') # => "1" + # task.read_attribute_before_type_cast('completed_on') # => "2012-10-21" + # + # In addition to #read_attribute_before_type_cast and #attributes_before_type_cast, + # it declares a method for all attributes with the *_before_type_cast + # suffix. + # + # task.id_before_type_cast # => "1" + # task.completed_on_before_type_cast # => "2012-10-21" + module BeforeTypeCast + extend ActiveSupport::Concern + + included do + attribute_method_suffix "_before_type_cast" + attribute_method_suffix "_came_from_user?" + end + + # Returns the value of the attribute identified by +attr_name+ before + # typecasting and deserialization. + # + # class Task < ActiveRecord::Base + # end + # + # task = Task.new(id: '1', completed_on: '2012-10-21') + # task.read_attribute('id') # => 1 + # task.read_attribute_before_type_cast('id') # => '1' + # task.read_attribute('completed_on') # => Sun, 21 Oct 2012 + # task.read_attribute_before_type_cast('completed_on') # => "2012-10-21" + # task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21" + def read_attribute_before_type_cast(attr_name) + @attributes[attr_name.to_s].value_before_type_cast + end + + # Returns a hash of attributes before typecasting and deserialization. + # + # class Task < ActiveRecord::Base + # end + # + # task = Task.new(title: nil, is_done: true, completed_on: '2012-10-21') + # task.attributes + # # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>Sun, 21 Oct 2012, "created_at"=>nil, "updated_at"=>nil} + # task.attributes_before_type_cast + # # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil} + def attributes_before_type_cast + @attributes.values_before_type_cast + end + + private + + # Handle *_before_type_cast for method_missing. + def attribute_before_type_cast(attribute_name) + read_attribute_before_type_cast(attribute_name) + end + + def attribute_came_from_user?(attribute_name) + @attributes[attribute_name].came_from_user? + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_methods/dirty.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_methods/dirty.rb new file mode 100644 index 00000000..282322fa --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_methods/dirty.rb @@ -0,0 +1,150 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attribute_accessors" + +module ActiveRecord + module AttributeMethods + module Dirty + extend ActiveSupport::Concern + + include ActiveModel::Dirty + + included do + if self < ::ActiveRecord::Timestamp + raise "You cannot include Dirty after Timestamp" + end + + class_attribute :partial_writes, instance_writer: false, default: true + + # Attribute methods for "changed in last call to save?" + attribute_method_affix(prefix: "saved_change_to_", suffix: "?") + attribute_method_prefix("saved_change_to_") + attribute_method_suffix("_before_last_save") + + # Attribute methods for "will change if I call save?" + attribute_method_affix(prefix: "will_save_change_to_", suffix: "?") + attribute_method_suffix("_change_to_be_saved", "_in_database") + end + + # reload the record and clears changed attributes. + def reload(*) + super.tap do + @previously_changed = ActiveSupport::HashWithIndifferentAccess.new + @mutations_before_last_save = nil + @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new + @mutations_from_database = nil + end + end + + # Did this attribute change when we last saved? This method can be invoked + # as +saved_change_to_name?+ instead of saved_change_to_attribute?("name"). + # Behaves similarly to +attribute_changed?+. This method is useful in + # after callbacks to determine if the call to save changed a certain + # attribute. + # + # ==== Options + # + # +from+ When passed, this method will return false unless the original + # value is equal to the given option + # + # +to+ When passed, this method will return false unless the value was + # changed to the given value + def saved_change_to_attribute?(attr_name, **options) + mutations_before_last_save.changed?(attr_name, **options) + end + + # Returns the change to an attribute during the last save. If the + # attribute was changed, the result will be an array containing the + # original value and the saved value. + # + # Behaves similarly to +attribute_change+. This method is useful in after + # callbacks, to see the change in an attribute that just occurred + # + # This method can be invoked as +saved_change_to_name+ in instead of + # saved_change_to_attribute("name") + def saved_change_to_attribute(attr_name) + mutations_before_last_save.change_to_attribute(attr_name) + end + + # Returns the original value of an attribute before the last save. + # Behaves similarly to +attribute_was+. This method is useful in after + # callbacks to get the original value of an attribute before the save that + # just occurred + def attribute_before_last_save(attr_name) + mutations_before_last_save.original_value(attr_name) + end + + # Did the last call to +save+ have any changes to change? + def saved_changes? + mutations_before_last_save.any_changes? + end + + # Returns a hash containing all the changes that were just saved. + def saved_changes + mutations_before_last_save.changes + end + + # Alias for +attribute_changed?+ + def will_save_change_to_attribute?(attr_name, **options) + mutations_from_database.changed?(attr_name, **options) + end + + # Alias for +attribute_change+ + def attribute_change_to_be_saved(attr_name) + mutations_from_database.change_to_attribute(attr_name) + end + + # Alias for +attribute_was+ + def attribute_in_database(attr_name) + mutations_from_database.original_value(attr_name) + end + + # Alias for +changed?+ + def has_changes_to_save? + mutations_from_database.any_changes? + end + + # Alias for +changes+ + def changes_to_save + mutations_from_database.changes + end + + # Alias for +changed+ + def changed_attribute_names_to_save + mutations_from_database.changed_attribute_names + end + + # Alias for +changed_attributes+ + def attributes_in_database + mutations_from_database.changed_values + end + + private + def write_attribute_without_type_cast(attr_name, value) + name = attr_name.to_s + if self.class.attribute_alias?(name) + name = self.class.attribute_alias(name) + end + result = super(name, value) + clear_attribute_change(name) + result + end + + def _update_record(*) + affected_rows = partial_writes? ? super(keys_for_partial_write) : super + changes_applied + affected_rows + end + + def _create_record(*) + id = partial_writes? ? super(keys_for_partial_write) : super + changes_applied + id + end + + def keys_for_partial_write + changed_attribute_names_to_save & self.class.column_names + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_methods/primary_key.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_methods/primary_key.rb new file mode 100644 index 00000000..d8fc046e --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_methods/primary_key.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +require "set" + +module ActiveRecord + module AttributeMethods + module PrimaryKey + extend ActiveSupport::Concern + + # Returns this record's primary key value wrapped in an array if one is + # available. + def to_key + key = id + [key] if key + end + + # Returns the primary key value. + def id + sync_with_transaction_state + primary_key = self.class.primary_key + _read_attribute(primary_key) if primary_key + end + + # Sets the primary key value. + def id=(value) + sync_with_transaction_state + primary_key = self.class.primary_key + _write_attribute(primary_key, value) if primary_key + end + + # Queries the primary key value. + def id? + sync_with_transaction_state + query_attribute(self.class.primary_key) + end + + # Returns the primary key value before type cast. + def id_before_type_cast + sync_with_transaction_state + read_attribute_before_type_cast(self.class.primary_key) + end + + # Returns the primary key previous value. + def id_was + sync_with_transaction_state + attribute_was(self.class.primary_key) + end + + def id_in_database + sync_with_transaction_state + attribute_in_database(self.class.primary_key) + end + + private + + def attribute_method?(attr_name) + attr_name == "id" || super + end + + module ClassMethods + ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was id_in_database).to_set + + def instance_method_already_implemented?(method_name) + super || primary_key && ID_ATTRIBUTE_METHODS.include?(method_name) + end + + def dangerous_attribute_method?(method_name) + super && !ID_ATTRIBUTE_METHODS.include?(method_name) + end + + # Defines the primary key field -- can be overridden in subclasses. + # Overwriting will negate any effect of the +primary_key_prefix_type+ + # setting, though. + def primary_key + @primary_key = reset_primary_key unless defined? @primary_key + @primary_key + end + + # Returns a quoted version of the primary key name, used to construct + # SQL statements. + def quoted_primary_key + @quoted_primary_key ||= connection.quote_column_name(primary_key) + end + + def reset_primary_key #:nodoc: + if self == base_class + self.primary_key = get_primary_key(base_class.name) + else + self.primary_key = base_class.primary_key + end + end + + def get_primary_key(base_name) #:nodoc: + if base_name && primary_key_prefix_type == :table_name + base_name.foreign_key(false) + elsif base_name && primary_key_prefix_type == :table_name_with_underscore + base_name.foreign_key + else + if ActiveRecord::Base != self && table_exists? + pk = connection.schema_cache.primary_keys(table_name) + suppress_composite_primary_key(pk) + else + "id" + end + end + end + + # Sets the name of the primary key column. + # + # class Project < ActiveRecord::Base + # self.primary_key = 'sysid' + # end + # + # You can also define the #primary_key method yourself: + # + # class Project < ActiveRecord::Base + # def self.primary_key + # 'foo_' + super + # end + # end + # + # Project.primary_key # => "foo_id" + def primary_key=(value) + @primary_key = value && value.to_s + @quoted_primary_key = nil + @attributes_builder = nil + end + + private + + def suppress_composite_primary_key(pk) + return pk unless pk.is_a?(Array) + + warn <<-WARNING.strip_heredoc + WARNING: Active Record does not support composite primary key. + + #{table_name} has composite primary key. Composite primary key is ignored. + WARNING + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_methods/query.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_methods/query.rb new file mode 100644 index 00000000..6757e9b6 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_methods/query.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module ActiveRecord + module AttributeMethods + module Query + extend ActiveSupport::Concern + + included do + attribute_method_suffix "?" + end + + def query_attribute(attr_name) + value = self[attr_name] + + case value + when true then true + when false, nil then false + else + column = self.class.columns_hash[attr_name] + if column.nil? + if Numeric === value || value !~ /[^0-9]/ + !value.to_i.zero? + else + return false if ActiveModel::Type::Boolean::FALSE_VALUES.include?(value) + !value.blank? + end + elsif value.respond_to?(:zero?) + !value.zero? + else + !value.blank? + end + end + end + + private + # Handle *? for method_missing. + def attribute?(attribute_name) + query_attribute(attribute_name) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_methods/read.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_methods/read.rb new file mode 100644 index 00000000..8b73cb54 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_methods/read.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +module ActiveRecord + module AttributeMethods + module Read + extend ActiveSupport::Concern + + module ClassMethods # :nodoc: + private + + # We want to generate the methods via module_eval rather than + # define_method, because define_method is slower on dispatch. + # Evaluating many similar methods may use more memory as the instruction + # sequences are duplicated and cached (in MRI). define_method may + # be slower on dispatch, but if you're careful about the closure + # created, then define_method will consume much less memory. + # + # But sometimes the database might return columns with + # characters that are not allowed in normal method names (like + # 'my_column(omg)'. So to work around this we first define with + # the __temp__ identifier, and then use alias method to rename + # it to what we want. + # + # We are also defining a constant to hold the frozen string of + # the attribute name. Using a constant means that we do not have + # to allocate an object on each call to the attribute method. + # Making it frozen means that it doesn't get duped when used to + # key the @attributes in read_attribute. + def define_method_attribute(name) + safe_name = name.unpack("h*".freeze).first + temp_method = "__temp__#{safe_name}" + + ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name + sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key + + generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 + def #{temp_method} + #{sync_with_transaction_state} + name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} + _read_attribute(name) { |n| missing_attribute(n, caller) } + end + STR + + generated_attribute_methods.module_eval do + alias_method name, temp_method + undef_method temp_method + end + end + end + + # Returns the value of the attribute identified by attr_name after + # it has been typecast (for example, "2004-12-12" in a date column is cast + # to a date object, like Date.new(2004, 12, 12)). + def read_attribute(attr_name, &block) + name = if self.class.attribute_alias?(attr_name) + self.class.attribute_alias(attr_name).to_s + else + attr_name.to_s + end + + primary_key = self.class.primary_key + name = primary_key if name == "id".freeze && primary_key + sync_with_transaction_state if name == primary_key + _read_attribute(name, &block) + end + + # This method exists to avoid the expensive primary_key check internally, without + # breaking compatibility with the read_attribute API + if defined?(JRUBY_VERSION) + # This form is significantly faster on JRuby, and this is one of our biggest hotspots. + # https://github.com/jruby/jruby/pull/2562 + def _read_attribute(attr_name, &block) # :nodoc: + @attributes.fetch_value(attr_name.to_s, &block) + end + else + def _read_attribute(attr_name) # :nodoc: + @attributes.fetch_value(attr_name.to_s) { |n| yield n if block_given? } + end + end + + alias :attribute :_read_attribute + private :attribute + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_methods/serialization.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_methods/serialization.rb new file mode 100644 index 00000000..ebc2baed --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_methods/serialization.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +module ActiveRecord + module AttributeMethods + module Serialization + extend ActiveSupport::Concern + + class ColumnNotSerializableError < StandardError + def initialize(name, type) + super <<-EOS.strip_heredoc + Column `#{name}` of type #{type.class} does not support `serialize` feature. + Usually it means that you are trying to use `serialize` + on a column that already implements serialization natively. + EOS + end + end + + module ClassMethods + # If you have an attribute that needs to be saved to the database as an + # object, and retrieved as the same object, then specify the name of that + # attribute using this method and it will be handled automatically. The + # serialization is done through YAML. If +class_name+ is specified, the + # serialized object must be of that class on assignment and retrieval. + # Otherwise SerializationTypeMismatch will be raised. + # + # Empty objects as {}, in the case of +Hash+, or [], in the case of + # +Array+, will always be persisted as null. + # + # Keep in mind that database adapters handle certain serialization tasks + # for you. For instance: +json+ and +jsonb+ types in PostgreSQL will be + # converted between JSON object/array syntax and Ruby +Hash+ or +Array+ + # objects transparently. There is no need to use #serialize in this + # case. + # + # For more complex cases, such as conversion to or from your application + # domain objects, consider using the ActiveRecord::Attributes API. + # + # ==== Parameters + # + # * +attr_name+ - The field name that should be serialized. + # * +class_name_or_coder+ - Optional, a coder object, which responds to +.load+ and +.dump+ + # or a class name that the object type should be equal to. + # + # ==== Example + # + # # Serialize a preferences attribute. + # class User < ActiveRecord::Base + # serialize :preferences + # end + # + # # Serialize preferences using JSON as coder. + # class User < ActiveRecord::Base + # serialize :preferences, JSON + # end + # + # # Serialize preferences as Hash using YAML coder. + # class User < ActiveRecord::Base + # serialize :preferences, Hash + # end + def serialize(attr_name, class_name_or_coder = Object) + # When ::JSON is used, force it to go through the Active Support JSON encoder + # to ensure special objects (e.g. Active Record models) are dumped correctly + # using the #as_json hook. + coder = if class_name_or_coder == ::JSON + Coders::JSON + elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) } + class_name_or_coder + else + Coders::YAMLColumn.new(attr_name, class_name_or_coder) + end + + decorate_attribute_type(attr_name, :serialize) do |type| + if type_incompatible_with_serialize?(type, class_name_or_coder) + raise ColumnNotSerializableError.new(attr_name, type) + end + + Type::Serialized.new(type, coder) + end + end + + private + + def type_incompatible_with_serialize?(type, class_name) + type.is_a?(ActiveRecord::Type::Json) && class_name == ::JSON || + type.respond_to?(:type_cast_array, true) && class_name == ::Array + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_methods/time_zone_conversion.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_methods/time_zone_conversion.rb new file mode 100644 index 00000000..d2b7817b --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +module ActiveRecord + module AttributeMethods + module TimeZoneConversion + class TimeZoneConverter < DelegateClass(Type::Value) # :nodoc: + def deserialize(value) + convert_time_to_time_zone(super) + end + + def cast(value) + return if value.nil? + + if value.is_a?(Hash) + set_time_zone_without_conversion(super) + elsif value.respond_to?(:in_time_zone) + begin + super(user_input_in_time_zone(value)) || super + rescue ArgumentError + nil + end + else + map_avoiding_infinite_recursion(super) { |v| cast(v) } + end + end + + private + + def convert_time_to_time_zone(value) + return if value.nil? + + if value.acts_like?(:time) + value.in_time_zone + elsif value.is_a?(::Float) + value + else + map_avoiding_infinite_recursion(value) { |v| convert_time_to_time_zone(v) } + end + end + + def set_time_zone_without_conversion(value) + ::Time.zone.local_to_utc(value).try(:in_time_zone) if value + end + + def map_avoiding_infinite_recursion(value) + map(value) do |v| + if value.equal?(v) + nil + else + yield(v) + end + end + end + end + + extend ActiveSupport::Concern + + included do + mattr_accessor :time_zone_aware_attributes, instance_writer: false, default: false + + class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false, default: [] + class_attribute :time_zone_aware_types, instance_writer: false, default: [ :datetime, :time ] + end + + module ClassMethods # :nodoc: + private + + def inherited(subclass) + super + # We need to apply this decorator here, rather than on module inclusion. The closure + # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the + # sub class being decorated. As such, changes to `time_zone_aware_attributes`, or + # `skip_time_zone_conversion_for_attributes` would not be picked up. + subclass.class_eval do + matcher = ->(name, type) { create_time_zone_conversion_attribute?(name, type) } + decorate_matching_attribute_types(matcher, :_time_zone_conversion) do |type| + TimeZoneConverter.new(type) + end + end + end + + def create_time_zone_conversion_attribute?(name, cast_type) + enabled_for_column = time_zone_aware_attributes && + !skip_time_zone_conversion_for_attributes.include?(name.to_sym) + + enabled_for_column && time_zone_aware_types.include?(cast_type.type) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_methods/write.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_methods/write.rb new file mode 100644 index 00000000..bb0ec6a8 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attribute_methods/write.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +module ActiveRecord + module AttributeMethods + module Write + extend ActiveSupport::Concern + + included do + attribute_method_suffix "=" + end + + module ClassMethods # :nodoc: + private + + def define_method_attribute=(name) + safe_name = name.unpack("h*".freeze).first + ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name + sync_with_transaction_state = "sync_with_transaction_state" if name == primary_key + + generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 + def __temp__#{safe_name}=(value) + name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} + #{sync_with_transaction_state} + _write_attribute(name, value) + end + alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= + undef_method :__temp__#{safe_name}= + STR + end + end + + # Updates the attribute identified by attr_name with the + # specified +value+. Empty strings for Integer and Float columns are + # turned into +nil+. + def write_attribute(attr_name, value) + name = if self.class.attribute_alias?(attr_name) + self.class.attribute_alias(attr_name).to_s + else + attr_name.to_s + end + + primary_key = self.class.primary_key + name = primary_key if name == "id".freeze && primary_key + sync_with_transaction_state if name == primary_key + _write_attribute(name, value) + end + + # This method exists to avoid the expensive primary_key check internally, without + # breaking compatibility with the write_attribute API + def _write_attribute(attr_name, value) # :nodoc: + @attributes.write_from_user(attr_name.to_s, value) + value + end + + private + def write_attribute_without_type_cast(attr_name, value) + name = attr_name.to_s + @attributes.write_cast_value(name, value) + value + end + + # Handle *= for method_missing. + def attribute=(attribute_name, value) + _write_attribute(attribute_name, value) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attributes.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attributes.rb new file mode 100644 index 00000000..35150889 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/attributes.rb @@ -0,0 +1,266 @@ +# frozen_string_literal: true + +require "active_model/attribute/user_provided_default" + +module ActiveRecord + # See ActiveRecord::Attributes::ClassMethods for documentation + module Attributes + extend ActiveSupport::Concern + + included do + class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false, default: {} # :internal: + end + + module ClassMethods + # Defines an attribute with a type on this model. It will override the + # type of existing attributes if needed. This allows control over how + # values are converted to and from SQL when assigned to a model. It also + # changes the behavior of values passed to + # {ActiveRecord::Base.where}[rdoc-ref:QueryMethods#where]. This will let you use + # your domain objects across much of Active Record, without having to + # rely on implementation details or monkey patching. + # + # +name+ The name of the methods to define attribute methods for, and the + # column which this will persist to. + # + # +cast_type+ A symbol such as +:string+ or +:integer+, or a type object + # to be used for this attribute. See the examples below for more + # information about providing custom type objects. + # + # ==== Options + # + # The following options are accepted: + # + # +default+ The default value to use when no value is provided. If this option + # is not passed, the previous default value (if any) will be used. + # Otherwise, the default will be +nil+. + # + # +array+ (PostgreSQL only) specifies that the type should be an array (see the + # examples below). + # + # +range+ (PostgreSQL only) specifies that the type should be a range (see the + # examples below). + # + # ==== Examples + # + # The type detected by Active Record can be overridden. + # + # # db/schema.rb + # create_table :store_listings, force: true do |t| + # t.decimal :price_in_cents + # end + # + # # app/models/store_listing.rb + # class StoreListing < ActiveRecord::Base + # end + # + # store_listing = StoreListing.new(price_in_cents: '10.1') + # + # # before + # store_listing.price_in_cents # => BigDecimal(10.1) + # + # class StoreListing < ActiveRecord::Base + # attribute :price_in_cents, :integer + # end + # + # # after + # store_listing.price_in_cents # => 10 + # + # A default can also be provided. + # + # # db/schema.rb + # create_table :store_listings, force: true do |t| + # t.string :my_string, default: "original default" + # end + # + # StoreListing.new.my_string # => "original default" + # + # # app/models/store_listing.rb + # class StoreListing < ActiveRecord::Base + # attribute :my_string, :string, default: "new default" + # end + # + # StoreListing.new.my_string # => "new default" + # + # class Product < ActiveRecord::Base + # attribute :my_default_proc, :datetime, default: -> { Time.now } + # end + # + # Product.new.my_default_proc # => 2015-05-30 11:04:48 -0600 + # sleep 1 + # Product.new.my_default_proc # => 2015-05-30 11:04:49 -0600 + # + # \Attributes do not need to be backed by a database column. + # + # # app/models/my_model.rb + # class MyModel < ActiveRecord::Base + # attribute :my_string, :string + # attribute :my_int_array, :integer, array: true + # attribute :my_float_range, :float, range: true + # end + # + # model = MyModel.new( + # my_string: "string", + # my_int_array: ["1", "2", "3"], + # my_float_range: "[1,3.5]", + # ) + # model.attributes + # # => + # { + # my_string: "string", + # my_int_array: [1, 2, 3], + # my_float_range: 1.0..3.5 + # } + # + # ==== Creating Custom Types + # + # Users may also define their own custom types, as long as they respond + # to the methods defined on the value type. The method +deserialize+ or + # +cast+ will be called on your type object, with raw input from the + # database or from your controllers. See ActiveModel::Type::Value for the + # expected API. It is recommended that your type objects inherit from an + # existing type, or from ActiveRecord::Type::Value + # + # class MoneyType < ActiveRecord::Type::Integer + # def cast(value) + # if !value.kind_of?(Numeric) && value.include?('$') + # price_in_dollars = value.gsub(/\$/, '').to_f + # super(price_in_dollars * 100) + # else + # super + # end + # end + # end + # + # # config/initializers/types.rb + # ActiveRecord::Type.register(:money, MoneyType) + # + # # app/models/store_listing.rb + # class StoreListing < ActiveRecord::Base + # attribute :price_in_cents, :money + # end + # + # store_listing = StoreListing.new(price_in_cents: '$10.00') + # store_listing.price_in_cents # => 1000 + # + # For more details on creating custom types, see the documentation for + # ActiveModel::Type::Value. For more details on registering your types + # to be referenced by a symbol, see ActiveRecord::Type.register. You can + # also pass a type object directly, in place of a symbol. + # + # ==== \Querying + # + # When {ActiveRecord::Base.where}[rdoc-ref:QueryMethods#where] is called, it will + # use the type defined by the model class to convert the value to SQL, + # calling +serialize+ on your type object. For example: + # + # class Money < Struct.new(:amount, :currency) + # end + # + # class MoneyType < Type::Value + # def initialize(currency_converter:) + # @currency_converter = currency_converter + # end + # + # # value will be the result of +deserialize+ or + # # +cast+. Assumed to be an instance of +Money+ in + # # this case. + # def serialize(value) + # value_in_bitcoins = @currency_converter.convert_to_bitcoins(value) + # value_in_bitcoins.amount + # end + # end + # + # # config/initializers/types.rb + # ActiveRecord::Type.register(:money, MoneyType) + # + # # app/models/product.rb + # class Product < ActiveRecord::Base + # currency_converter = ConversionRatesFromTheInternet.new + # attribute :price_in_bitcoins, :money, currency_converter: currency_converter + # end + # + # Product.where(price_in_bitcoins: Money.new(5, "USD")) + # # => SELECT * FROM products WHERE price_in_bitcoins = 0.02230 + # + # Product.where(price_in_bitcoins: Money.new(5, "GBP")) + # # => SELECT * FROM products WHERE price_in_bitcoins = 0.03412 + # + # ==== Dirty Tracking + # + # The type of an attribute is given the opportunity to change how dirty + # tracking is performed. The methods +changed?+ and +changed_in_place?+ + # will be called from ActiveModel::Dirty. See the documentation for those + # methods in ActiveModel::Type::Value for more details. + def attribute(name, cast_type = Type::Value.new, **options) + name = name.to_s + reload_schema_from_cache + + self.attributes_to_define_after_schema_loads = + attributes_to_define_after_schema_loads.merge( + name => [cast_type, options] + ) + end + + # This is the low level API which sits beneath +attribute+. It only + # accepts type objects, and will do its work immediately instead of + # waiting for the schema to load. Automatic schema detection and + # ClassMethods#attribute both call this under the hood. While this method + # is provided so it can be used by plugin authors, application code + # should probably use ClassMethods#attribute. + # + # +name+ The name of the attribute being defined. Expected to be a +String+. + # + # +cast_type+ The type object to use for this attribute. + # + # +default+ The default value to use when no value is provided. If this option + # is not passed, the previous default value (if any) will be used. + # Otherwise, the default will be +nil+. A proc can also be passed, and + # will be called once each time a new value is needed. + # + # +user_provided_default+ Whether the default value should be cast using + # +cast+ or +deserialize+. + def define_attribute( + name, + cast_type, + default: NO_DEFAULT_PROVIDED, + user_provided_default: true + ) + attribute_types[name] = cast_type + define_default_attribute(name, default, cast_type, from_user: user_provided_default) + end + + def load_schema! # :nodoc: + super + attributes_to_define_after_schema_loads.each do |name, (type, options)| + if type.is_a?(Symbol) + type = ActiveRecord::Type.lookup(type, **options.except(:default)) + end + + define_attribute(name, type, **options.slice(:default)) + end + end + + private + + NO_DEFAULT_PROVIDED = Object.new # :nodoc: + private_constant :NO_DEFAULT_PROVIDED + + def define_default_attribute(name, value, type, from_user:) + if value == NO_DEFAULT_PROVIDED + default_attribute = _default_attributes[name].with_type(type) + elsif from_user + default_attribute = ActiveModel::Attribute::UserProvidedDefault.new( + name, + value, + type, + _default_attributes.fetch(name.to_s) { nil }, + ) + else + default_attribute = ActiveModel::Attribute.from_database(name, value, type) + end + _default_attributes[name] = default_attribute + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/autosave_association.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/autosave_association.rb new file mode 100644 index 00000000..783a8366 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/autosave_association.rb @@ -0,0 +1,498 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record Autosave Association + # + # AutosaveAssociation is a module that takes care of automatically saving + # associated records when their parent is saved. In addition to saving, it + # also destroys any associated records that were marked for destruction. + # (See #mark_for_destruction and #marked_for_destruction?). + # + # Saving of the parent, its associations, and the destruction of marked + # associations, all happen inside a transaction. This should never leave the + # database in an inconsistent state. + # + # If validations for any of the associations fail, their error messages will + # be applied to the parent. + # + # Note that it also means that associations marked for destruction won't + # be destroyed directly. They will however still be marked for destruction. + # + # Note that autosave: false is not same as not declaring :autosave. + # When the :autosave option is not present then new association records are + # saved but the updated association records are not saved. + # + # == Validation + # + # Child records are validated unless :validate is +false+. + # + # == Callbacks + # + # Association with autosave option defines several callbacks on your + # model (before_save, after_create, after_update). Please note that + # callbacks are executed in the order they were defined in + # model. You should avoid modifying the association content, before + # autosave callbacks are executed. Placing your callbacks after + # associations is usually a good practice. + # + # === One-to-one Example + # + # class Post < ActiveRecord::Base + # has_one :author, autosave: true + # end + # + # Saving changes to the parent and its associated model can now be performed + # automatically _and_ atomically: + # + # post = Post.find(1) + # post.title # => "The current global position of migrating ducks" + # post.author.name # => "alloy" + # + # post.title = "On the migration of ducks" + # post.author.name = "Eloy Duran" + # + # post.save + # post.reload + # post.title # => "On the migration of ducks" + # post.author.name # => "Eloy Duran" + # + # Destroying an associated model, as part of the parent's save action, is as + # simple as marking it for destruction: + # + # post.author.mark_for_destruction + # post.author.marked_for_destruction? # => true + # + # Note that the model is _not_ yet removed from the database: + # + # id = post.author.id + # Author.find_by(id: id).nil? # => false + # + # post.save + # post.reload.author # => nil + # + # Now it _is_ removed from the database: + # + # Author.find_by(id: id).nil? # => true + # + # === One-to-many Example + # + # When :autosave is not declared new children are saved when their parent is saved: + # + # class Post < ActiveRecord::Base + # has_many :comments # :autosave option is not declared + # end + # + # post = Post.new(title: 'ruby rocks') + # post.comments.build(body: 'hello world') + # post.save # => saves both post and comment + # + # post = Post.create(title: 'ruby rocks') + # post.comments.build(body: 'hello world') + # post.save # => saves both post and comment + # + # post = Post.create(title: 'ruby rocks') + # post.comments.create(body: 'hello world') + # post.save # => saves both post and comment + # + # When :autosave is true all children are saved, no matter whether they + # are new records or not: + # + # class Post < ActiveRecord::Base + # has_many :comments, autosave: true + # end + # + # post = Post.create(title: 'ruby rocks') + # post.comments.create(body: 'hello world') + # post.comments[0].body = 'hi everyone' + # post.comments.build(body: "good morning.") + # post.title += "!" + # post.save # => saves both post and comments. + # + # Destroying one of the associated models as part of the parent's save action + # is as simple as marking it for destruction: + # + # post.comments # => [#, # + # post.comments[1].mark_for_destruction + # post.comments[1].marked_for_destruction? # => true + # post.comments.length # => 2 + # + # Note that the model is _not_ yet removed from the database: + # + # id = post.comments.last.id + # Comment.find_by(id: id).nil? # => false + # + # post.save + # post.reload.comments.length # => 1 + # + # Now it _is_ removed from the database: + # + # Comment.find_by(id: id).nil? # => true + module AutosaveAssociation + extend ActiveSupport::Concern + + module AssociationBuilderExtension #:nodoc: + def self.build(model, reflection) + model.send(:add_autosave_association_callbacks, reflection) + end + + def self.valid_options + [ :autosave ] + end + end + + included do + Associations::Builder::Association.extensions << AssociationBuilderExtension + mattr_accessor :index_nested_attribute_errors, instance_writer: false, default: false + end + + module ClassMethods # :nodoc: + private + + def define_non_cyclic_method(name, &block) + return if method_defined?(name) + define_method(name) do |*args| + result = true; @_already_called ||= {} + # Loop prevention for validation of associations + unless @_already_called[name] + begin + @_already_called[name] = true + result = instance_eval(&block) + ensure + @_already_called[name] = false + end + end + + result + end + end + + # Adds validation and save callbacks for the association as specified by + # the +reflection+. + # + # For performance reasons, we don't check whether to validate at runtime. + # However the validation and callback methods are lazy and those methods + # get created when they are invoked for the very first time. However, + # this can change, for instance, when using nested attributes, which is + # called _after_ the association has been defined. Since we don't want + # the callbacks to get defined multiple times, there are guards that + # check if the save or validation methods have already been defined + # before actually defining them. + def add_autosave_association_callbacks(reflection) + save_method = :"autosave_associated_records_for_#{reflection.name}" + + if reflection.collection? + before_save :before_save_collection_association + after_save :after_save_collection_association + + define_non_cyclic_method(save_method) { save_collection_association(reflection) } + # Doesn't use after_save as that would save associations added in after_create/after_update twice + after_create save_method + after_update save_method + elsif reflection.has_one? + define_method(save_method) { save_has_one_association(reflection) } unless method_defined?(save_method) + # Configures two callbacks instead of a single after_save so that + # the model may rely on their execution order relative to its + # own callbacks. + # + # For example, given that after_creates run before after_saves, if + # we configured instead an after_save there would be no way to fire + # a custom after_create callback after the child association gets + # created. + after_create save_method + after_update save_method + else + define_non_cyclic_method(save_method) { throw(:abort) if save_belongs_to_association(reflection) == false } + before_save save_method + end + + define_autosave_validation_callbacks(reflection) + end + + def define_autosave_validation_callbacks(reflection) + validation_method = :"validate_associated_records_for_#{reflection.name}" + if reflection.validate? && !method_defined?(validation_method) + if reflection.collection? + method = :validate_collection_association + else + method = :validate_single_association + end + + define_non_cyclic_method(validation_method) { send(method, reflection) } + validate validation_method + after_validation :_ensure_no_duplicate_errors + end + end + end + + # Reloads the attributes of the object as usual and clears marked_for_destruction flag. + def reload(options = nil) + @marked_for_destruction = false + @destroyed_by_association = nil + super + end + + # Marks this record to be destroyed as part of the parent's save transaction. + # This does _not_ actually destroy the record instantly, rather child record will be destroyed + # when parent.save is called. + # + # Only useful if the :autosave option on the parent is enabled for this associated model. + def mark_for_destruction + @marked_for_destruction = true + end + + # Returns whether or not this record will be destroyed as part of the parent's save transaction. + # + # Only useful if the :autosave option on the parent is enabled for this associated model. + def marked_for_destruction? + @marked_for_destruction + end + + # Records the association that is being destroyed and destroying this + # record in the process. + def destroyed_by_association=(reflection) + @destroyed_by_association = reflection + end + + # Returns the association for the parent being destroyed. + # + # Used to avoid updating the counter cache unnecessarily. + def destroyed_by_association + @destroyed_by_association + end + + # Returns whether or not this record has been changed in any way (including whether + # any of its nested autosave associations are likewise changed) + def changed_for_autosave? + new_record? || has_changes_to_save? || marked_for_destruction? || nested_records_changed_for_autosave? + end + + private + + # Returns the record for an association collection that should be validated + # or saved. If +autosave+ is +false+ only new records will be returned, + # unless the parent is/was a new record itself. + def associated_records_to_validate_or_save(association, new_record, autosave) + if new_record + association && association.target + elsif autosave + association.target.find_all(&:changed_for_autosave?) + else + association.target.find_all(&:new_record?) + end + end + + # go through nested autosave associations that are loaded in memory (without loading + # any new ones), and return true if is changed for autosave + def nested_records_changed_for_autosave? + @_nested_records_changed_for_autosave_already_called ||= false + return false if @_nested_records_changed_for_autosave_already_called + begin + @_nested_records_changed_for_autosave_already_called = true + self.class._reflections.values.any? do |reflection| + if reflection.options[:autosave] + association = association_instance_get(reflection.name) + association && Array.wrap(association.target).any?(&:changed_for_autosave?) + end + end + ensure + @_nested_records_changed_for_autosave_already_called = false + end + end + + # Validate the association if :validate or :autosave is + # turned on for the association. + def validate_single_association(reflection) + association = association_instance_get(reflection.name) + record = association && association.reader + association_valid?(reflection, record) if record + end + + # Validate the associated records if :validate or + # :autosave is turned on for the association specified by + # +reflection+. + def validate_collection_association(reflection) + if association = association_instance_get(reflection.name) + if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave]) + records.each_with_index { |record, index| association_valid?(reflection, record, index) } + end + end + end + + # Returns whether or not the association is valid and applies any errors to + # the parent, self, if it wasn't. Skips any :autosave + # enabled records if they're marked_for_destruction? or destroyed. + def association_valid?(reflection, record, index = nil) + return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?) + + context = validation_context unless [:create, :update].include?(validation_context) + + unless valid = record.valid?(context) + if reflection.options[:autosave] + indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors) + + record.errors.each do |attribute, message| + attribute = normalize_reflection_attribute(indexed_attribute, reflection, index, attribute) + errors[attribute] << message + errors[attribute].uniq! + end + + record.errors.details.each_key do |attribute| + reflection_attribute = + normalize_reflection_attribute(indexed_attribute, reflection, index, attribute).to_sym + + record.errors.details[attribute].each do |error| + errors.details[reflection_attribute] << error + errors.details[reflection_attribute].uniq! + end + end + else + errors.add(reflection.name) + end + end + valid + end + + def normalize_reflection_attribute(indexed_attribute, reflection, index, attribute) + if indexed_attribute + "#{reflection.name}[#{index}].#{attribute}" + else + "#{reflection.name}.#{attribute}" + end + end + + # Is used as a before_save callback to check while saving a collection + # association whether or not the parent was a new record before saving. + def before_save_collection_association + @new_record_before_save = new_record? + end + + def after_save_collection_association + @new_record_before_save = false + end + + # Saves any new associated records, or all loaded autosave associations if + # :autosave is enabled on the association. + # + # In addition, it destroys all children that were marked for destruction + # with #mark_for_destruction. + # + # This all happens inside a transaction, _if_ the Transactions module is included into + # ActiveRecord::Base after the AutosaveAssociation module, which it does by default. + def save_collection_association(reflection) + if association = association_instance_get(reflection.name) + autosave = reflection.options[:autosave] + + # reconstruct the scope now that we know the owner's id + association.reset_scope + + if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave) + if autosave + records_to_destroy = records.select(&:marked_for_destruction?) + records_to_destroy.each { |record| association.destroy(record) } + records -= records_to_destroy + end + + records.each do |record| + next if record.destroyed? + + saved = true + + if autosave != false && (@new_record_before_save || record.new_record?) + if autosave + saved = association.insert_record(record, false) + elsif !reflection.nested? + association_saved = association.insert_record(record) + + if reflection.validate? + errors.add(reflection.name) unless association_saved + saved = association_saved + end + end + elsif autosave + saved = record.save(validate: false) + end + + raise ActiveRecord::Rollback unless saved + end + end + end + end + + # Saves the associated record if it's new or :autosave is enabled + # on the association. + # + # In addition, it will destroy the association if it was marked for + # destruction with #mark_for_destruction. + # + # This all happens inside a transaction, _if_ the Transactions module is included into + # ActiveRecord::Base after the AutosaveAssociation module, which it does by default. + def save_has_one_association(reflection) + association = association_instance_get(reflection.name) + record = association && association.load_target + + if record && !record.destroyed? + autosave = reflection.options[:autosave] + + if autosave && record.marked_for_destruction? + record.destroy + elsif autosave != false + key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id + + if (autosave && record.changed_for_autosave?) || new_record? || record_changed?(reflection, record, key) + unless reflection.through_reflection + record[reflection.foreign_key] = key + if inverse_reflection = reflection.inverse_of + record.association(inverse_reflection.name).loaded! + end + end + + saved = record.save(validate: !autosave) + raise ActiveRecord::Rollback if !saved && autosave + saved + end + end + end + end + + # If the record is new or it has changed, returns true. + def record_changed?(reflection, record, key) + record.new_record? || + (record.has_attribute?(reflection.foreign_key) && record[reflection.foreign_key] != key) || + record.will_save_change_to_attribute?(reflection.foreign_key) + end + + # Saves the associated record if it's new or :autosave is enabled. + # + # In addition, it will destroy the association if it was marked for destruction. + def save_belongs_to_association(reflection) + association = association_instance_get(reflection.name) + return unless association && association.loaded? && !association.stale_target? + + record = association.load_target + if record && !record.destroyed? + autosave = reflection.options[:autosave] + + if autosave && record.marked_for_destruction? + self[reflection.foreign_key] = nil + record.destroy + elsif autosave != false + saved = record.save(validate: !autosave) if record.new_record? || (autosave && record.changed_for_autosave?) + + if association.updated? + association_id = record.send(reflection.options[:primary_key] || :id) + self[reflection.foreign_key] = association_id + association.loaded! + end + + saved if autosave + end + end + end + + def _ensure_no_duplicate_errors + errors.messages.each_key do |attribute| + errors[attribute].uniq! + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/base.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/base.rb new file mode 100644 index 00000000..b7ad944c --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/base.rb @@ -0,0 +1,329 @@ +# frozen_string_literal: true + +require "yaml" +require "active_support/benchmarkable" +require "active_support/dependencies" +require "active_support/descendants_tracker" +require "active_support/time" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/hash/deep_merge" +require "active_support/core_ext/hash/slice" +require "active_support/core_ext/hash/transform_values" +require "active_support/core_ext/string/behavior" +require "active_support/core_ext/kernel/singleton_class" +require "active_support/core_ext/module/introspection" +require "active_support/core_ext/object/duplicable" +require "active_support/core_ext/class/subclasses" +require "active_record/attribute_decorators" +require "active_record/define_callbacks" +require "active_record/errors" +require "active_record/log_subscriber" +require "active_record/explain_subscriber" +require "active_record/relation/delegation" +require "active_record/attributes" +require "active_record/type_caster" + +module ActiveRecord #:nodoc: + # = Active Record + # + # Active Record objects don't specify their attributes directly, but rather infer them from + # the table definition with which they're linked. Adding, removing, and changing attributes + # and their type is done directly in the database. Any change is instantly reflected in the + # Active Record objects. The mapping that binds a given Active Record class to a certain + # database table will happen automatically in most common cases, but can be overwritten for the uncommon ones. + # + # See the mapping rules in table_name and the full example in link:files/activerecord/README_rdoc.html for more insight. + # + # == Creation + # + # Active Records accept constructor parameters either in a hash or as a block. The hash + # method is especially useful when you're receiving the data from somewhere else, like an + # HTTP request. It works like this: + # + # user = User.new(name: "David", occupation: "Code Artist") + # user.name # => "David" + # + # You can also use block initialization: + # + # user = User.new do |u| + # u.name = "David" + # u.occupation = "Code Artist" + # end + # + # And of course you can just create a bare object and specify the attributes after the fact: + # + # user = User.new + # user.name = "David" + # user.occupation = "Code Artist" + # + # == Conditions + # + # Conditions can either be specified as a string, array, or hash representing the WHERE-part of an SQL statement. + # The array form is to be used when the condition input is tainted and requires sanitization. The string form can + # be used for statements that don't involve tainted data. The hash form works much like the array form, except + # only equality and range is possible. Examples: + # + # class User < ActiveRecord::Base + # def self.authenticate_unsafely(user_name, password) + # where("user_name = '#{user_name}' AND password = '#{password}'").first + # end + # + # def self.authenticate_safely(user_name, password) + # where("user_name = ? AND password = ?", user_name, password).first + # end + # + # def self.authenticate_safely_simply(user_name, password) + # where(user_name: user_name, password: password).first + # end + # end + # + # The authenticate_unsafely method inserts the parameters directly into the query + # and is thus susceptible to SQL-injection attacks if the user_name and +password+ + # parameters come directly from an HTTP request. The authenticate_safely and + # authenticate_safely_simply both will sanitize the user_name and +password+ + # before inserting them in the query, which will ensure that an attacker can't escape the + # query and fake the login (or worse). + # + # When using multiple parameters in the conditions, it can easily become hard to read exactly + # what the fourth or fifth question mark is supposed to represent. In those cases, you can + # resort to named bind variables instead. That's done by replacing the question marks with + # symbols and supplying a hash with values for the matching symbol keys: + # + # Company.where( + # "id = :id AND name = :name AND division = :division AND created_at > :accounting_date", + # { id: 3, name: "37signals", division: "First", accounting_date: '2005-01-01' } + # ).first + # + # Similarly, a simple hash without a statement will generate conditions based on equality with the SQL AND + # operator. For instance: + # + # Student.where(first_name: "Harvey", status: 1) + # Student.where(params[:student]) + # + # A range may be used in the hash to use the SQL BETWEEN operator: + # + # Student.where(grade: 9..12) + # + # An array may be used in the hash to use the SQL IN operator: + # + # Student.where(grade: [9,11,12]) + # + # When joining tables, nested hashes or keys written in the form 'table_name.column_name' + # can be used to qualify the table name of a particular condition. For instance: + # + # Student.joins(:schools).where(schools: { category: 'public' }) + # Student.joins(:schools).where('schools.category' => 'public' ) + # + # == Overwriting default accessors + # + # All column values are automatically available through basic accessors on the Active Record + # object, but sometimes you want to specialize this behavior. This can be done by overwriting + # the default accessors (using the same name as the attribute) and calling + # +super+ to actually change things. + # + # class Song < ActiveRecord::Base + # # Uses an integer of seconds to hold the length of the song + # + # def length=(minutes) + # super(minutes.to_i * 60) + # end + # + # def length + # super / 60 + # end + # end + # + # == Attribute query methods + # + # In addition to the basic accessors, query methods are also automatically available on the Active Record object. + # Query methods allow you to test whether an attribute value is present. + # Additionally, when dealing with numeric values, a query method will return false if the value is zero. + # + # For example, an Active Record User with the name attribute has a name? method that you can call + # to determine whether the user has a name: + # + # user = User.new(name: "David") + # user.name? # => true + # + # anonymous = User.new(name: "") + # anonymous.name? # => false + # + # == Accessing attributes before they have been typecasted + # + # Sometimes you want to be able to read the raw attribute data without having the column-determined + # typecast run its course first. That can be done by using the _before_type_cast + # accessors that all attributes have. For example, if your Account model has a balance attribute, + # you can call account.balance_before_type_cast or account.id_before_type_cast. + # + # This is especially useful in validation situations where the user might supply a string for an + # integer field and you want to display the original string back in an error message. Accessing the + # attribute normally would typecast the string to 0, which isn't what you want. + # + # == Dynamic attribute-based finders + # + # Dynamic attribute-based finders are a mildly deprecated way of getting (and/or creating) objects + # by simple queries without turning to SQL. They work by appending the name of an attribute + # to find_by_ like Person.find_by_user_name. + # Instead of writing Person.find_by(user_name: user_name), you can use + # Person.find_by_user_name(user_name). + # + # It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an + # ActiveRecord::RecordNotFound error if they do not return any records, + # like Person.find_by_last_name!. + # + # It's also possible to use multiple attributes in the same find_by_ by separating them with + # "_and_". + # + # Person.find_by(user_name: user_name, password: password) + # Person.find_by_user_name_and_password(user_name, password) # with dynamic finder + # + # It's even possible to call these dynamic finder methods on relations and named scopes. + # + # Payment.order("created_on").find_by_amount(50) + # + # == Saving arrays, hashes, and other non-mappable objects in text columns + # + # Active Record can serialize any object in text columns using YAML. To do so, you must + # specify this with a call to the class method + # {serialize}[rdoc-ref:AttributeMethods::Serialization::ClassMethods#serialize]. + # This makes it possible to store arrays, hashes, and other non-mappable objects without doing + # any additional work. + # + # class User < ActiveRecord::Base + # serialize :preferences + # end + # + # user = User.create(preferences: { "background" => "black", "display" => large }) + # User.find(user.id).preferences # => { "background" => "black", "display" => large } + # + # You can also specify a class option as the second parameter that'll raise an exception + # if a serialized object is retrieved as a descendant of a class not in the hierarchy. + # + # class User < ActiveRecord::Base + # serialize :preferences, Hash + # end + # + # user = User.create(preferences: %w( one two three )) + # User.find(user.id).preferences # raises SerializationTypeMismatch + # + # When you specify a class option, the default value for that attribute will be a new + # instance of that class. + # + # class User < ActiveRecord::Base + # serialize :preferences, OpenStruct + # end + # + # user = User.new + # user.preferences.theme_color = "red" + # + # + # == Single table inheritance + # + # Active Record allows inheritance by storing the name of the class in a + # column that is named "type" by default. See ActiveRecord::Inheritance for + # more details. + # + # == Connection to multiple databases in different models + # + # Connections are usually created through + # {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] and retrieved + # by ActiveRecord::Base.connection. All classes inheriting from ActiveRecord::Base will use this + # connection. But you can also set a class-specific connection. For example, if Course is an + # ActiveRecord::Base, but resides in a different database, you can just say Course.establish_connection + # and Course and all of its subclasses will use this connection instead. + # + # This feature is implemented by keeping a connection pool in ActiveRecord::Base that is + # a hash indexed by the class. If a connection is requested, the + # {ActiveRecord::Base.retrieve_connection}[rdoc-ref:ConnectionHandling#retrieve_connection] method + # will go up the class-hierarchy until a connection is found in the connection pool. + # + # == Exceptions + # + # * ActiveRecordError - Generic error class and superclass of all other errors raised by Active Record. + # * AdapterNotSpecified - The configuration hash used in + # {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] + # didn't include an :adapter key. + # * AdapterNotFound - The :adapter key used in + # {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] + # specified a non-existent adapter + # (or a bad spelling of an existing one). + # * AssociationTypeMismatch - The object assigned to the association wasn't of the type + # specified in the association definition. + # * AttributeAssignmentError - An error occurred while doing a mass assignment through the + # {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method. + # You can inspect the +attribute+ property of the exception object to determine which attribute + # triggered the error. + # * ConnectionNotEstablished - No connection has been established. + # Use {ActiveRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] before querying. + # * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the + # {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method. + # The +errors+ property of this exception contains an array of + # AttributeAssignmentError + # objects that should be inspected to determine which attributes triggered the errors. + # * RecordInvalid - raised by {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] and + # {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!] + # when the record is invalid. + # * RecordNotFound - No record responded to the {ActiveRecord::Base.find}[rdoc-ref:FinderMethods#find] method. + # Either the row with the given ID doesn't exist or the row didn't meet the additional restrictions. + # Some {ActiveRecord::Base.find}[rdoc-ref:FinderMethods#find] calls do not raise this exception to signal + # nothing was found, please check its documentation for further details. + # * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter. + # * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message. + # + # *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level). + # So it's possible to assign a logger to the class through Base.logger= which will then be used by all + # instances in the current object space. + class Base + extend ActiveModel::Naming + + extend ActiveSupport::Benchmarkable + extend ActiveSupport::DescendantsTracker + + extend ConnectionHandling + extend QueryCache::ClassMethods + extend Querying + extend Translation + extend DynamicMatchers + extend Explain + extend Enum + extend Delegation::DelegateCache + extend CollectionCacheKey + + include Core + include Persistence + include ReadonlyAttributes + include ModelSchema + include Inheritance + include Scoping + include Sanitization + include AttributeAssignment + include ActiveModel::Conversion + include Integration + include Validations + include CounterCache + include Attributes + include AttributeDecorators + include Locking::Optimistic + include Locking::Pessimistic + include DefineCallbacks + include AttributeMethods + include Callbacks + include Timestamp + include Associations + include ActiveModel::SecurePassword + include AutosaveAssociation + include NestedAttributes + include Aggregations + include Transactions + include TouchLater + include NoTouching + include Reflection + include Serialization + include Store + include SecureToken + include Suppressor + end + + ActiveSupport.run_load_hooks(:active_record, Base) +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/callbacks.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/callbacks.rb new file mode 100644 index 00000000..53d21092 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/callbacks.rb @@ -0,0 +1,353 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record \Callbacks + # + # \Callbacks are hooks into the life cycle of an Active Record object that allow you to trigger logic + # before or after an alteration of the object state. This can be used to make sure that associated and + # dependent objects are deleted when {ActiveRecord::Base#destroy}[rdoc-ref:Persistence#destroy] is called (by overwriting +before_destroy+) or + # to massage attributes before they're validated (by overwriting +before_validation+). + # As an example of the callbacks initiated, consider the {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] call for a new record: + # + # * (-) save + # * (-) valid + # * (1) before_validation + # * (-) validate + # * (2) after_validation + # * (3) before_save + # * (4) before_create + # * (-) create + # * (5) after_create + # * (6) after_save + # * (7) after_commit + # + # Also, an after_rollback callback can be configured to be triggered whenever a rollback is issued. + # Check out ActiveRecord::Transactions for more details about after_commit and + # after_rollback. + # + # Additionally, an after_touch callback is triggered whenever an + # object is touched. + # + # Lastly an after_find and after_initialize callback is triggered for each object that + # is found and instantiated by a finder, with after_initialize being triggered after new objects + # are instantiated as well. + # + # There are nineteen callbacks in total, which give you immense power to react and prepare for each state in the + # Active Record life cycle. The sequence for calling {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] for an existing record is similar, + # except that each _create callback is replaced by the corresponding _update callback. + # + # Examples: + # class CreditCard < ActiveRecord::Base + # # Strip everything but digits, so the user can specify "555 234 34" or + # # "5552-3434" and both will mean "55523434" + # before_validation(on: :create) do + # self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number") + # end + # end + # + # class Subscription < ActiveRecord::Base + # before_create :record_signup + # + # private + # def record_signup + # self.signed_up_on = Date.today + # end + # end + # + # class Firm < ActiveRecord::Base + # # Disables access to the system, for associated clients and people when the firm is destroyed + # before_destroy { |record| Person.where(firm_id: record.id).update_all(access: 'disabled') } + # before_destroy { |record| Client.where(client_of: record.id).update_all(access: 'disabled') } + # end + # + # == Inheritable callback queues + # + # Besides the overwritable callback methods, it's also possible to register callbacks through the + # use of the callback macros. Their main advantage is that the macros add behavior into a callback + # queue that is kept intact down through an inheritance hierarchy. + # + # class Topic < ActiveRecord::Base + # before_destroy :destroy_author + # end + # + # class Reply < Topic + # before_destroy :destroy_readers + # end + # + # Now, when Topic#destroy is run only +destroy_author+ is called. When Reply#destroy is + # run, both +destroy_author+ and +destroy_readers+ are called. Contrast this to the following situation + # where the +before_destroy+ method is overridden: + # + # class Topic < ActiveRecord::Base + # def before_destroy() destroy_author end + # end + # + # class Reply < Topic + # def before_destroy() destroy_readers end + # end + # + # In that case, Reply#destroy would only run +destroy_readers+ and _not_ +destroy_author+. + # So, use the callback macros when you want to ensure that a certain callback is called for the entire + # hierarchy, and use the regular overwritable methods when you want to leave it up to each descendant + # to decide whether they want to call +super+ and trigger the inherited callbacks. + # + # *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the + # callbacks before specifying the associations. Otherwise, you might trigger the loading of a + # child before the parent has registered the callbacks and they won't be inherited. + # + # == Types of callbacks + # + # There are four types of callbacks accepted by the callback macros: Method references (symbol), callback objects, + # inline methods (using a proc). Method references and callback objects + # are the recommended approaches, inline methods using a proc are sometimes appropriate (such as for + # creating mix-ins). + # + # The method reference callbacks work by specifying a protected or private method available in the object, like this: + # + # class Topic < ActiveRecord::Base + # before_destroy :delete_parents + # + # private + # def delete_parents + # self.class.delete_all "parent_id = #{id}" + # end + # end + # + # The callback objects have methods named after the callback called with the record as the only parameter, such as: + # + # class BankAccount < ActiveRecord::Base + # before_save EncryptionWrapper.new + # after_save EncryptionWrapper.new + # after_initialize EncryptionWrapper.new + # end + # + # class EncryptionWrapper + # def before_save(record) + # record.credit_card_number = encrypt(record.credit_card_number) + # end + # + # def after_save(record) + # record.credit_card_number = decrypt(record.credit_card_number) + # end + # + # alias_method :after_initialize, :after_save + # + # private + # def encrypt(value) + # # Secrecy is committed + # end + # + # def decrypt(value) + # # Secrecy is unveiled + # end + # end + # + # So you specify the object you want messaged on a given callback. When that callback is triggered, the object has + # a method by the name of the callback messaged. You can make these callbacks more flexible by passing in other + # initialization data such as the name of the attribute to work with: + # + # class BankAccount < ActiveRecord::Base + # before_save EncryptionWrapper.new("credit_card_number") + # after_save EncryptionWrapper.new("credit_card_number") + # after_initialize EncryptionWrapper.new("credit_card_number") + # end + # + # class EncryptionWrapper + # def initialize(attribute) + # @attribute = attribute + # end + # + # def before_save(record) + # record.send("#{@attribute}=", encrypt(record.send("#{@attribute}"))) + # end + # + # def after_save(record) + # record.send("#{@attribute}=", decrypt(record.send("#{@attribute}"))) + # end + # + # alias_method :after_initialize, :after_save + # + # private + # def encrypt(value) + # # Secrecy is committed + # end + # + # def decrypt(value) + # # Secrecy is unveiled + # end + # end + # + # == before_validation* returning statements + # + # If the +before_validation+ callback throws +:abort+, the process will be + # aborted and {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] will return +false+. + # If {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] is called it will raise an ActiveRecord::RecordInvalid exception. + # Nothing will be appended to the errors object. + # + # == Canceling callbacks + # + # If a before_* callback throws +:abort+, all the later callbacks and + # the associated action are cancelled. + # Callbacks are generally run in the order they are defined, with the exception of callbacks defined as + # methods on the model, which are called last. + # + # == Ordering callbacks + # + # Sometimes the code needs that the callbacks execute in a specific order. For example, a +before_destroy+ + # callback (+log_children+ in this case) should be executed before the children get destroyed by the + # dependent: :destroy option. + # + # Let's look at the code below: + # + # class Topic < ActiveRecord::Base + # has_many :children, dependent: :destroy + # + # before_destroy :log_children + # + # private + # def log_children + # # Child processing + # end + # end + # + # In this case, the problem is that when the +before_destroy+ callback is executed, the children are not available + # because the {ActiveRecord::Base#destroy}[rdoc-ref:Persistence#destroy] callback gets executed first. + # You can use the +prepend+ option on the +before_destroy+ callback to avoid this. + # + # class Topic < ActiveRecord::Base + # has_many :children, dependent: :destroy + # + # before_destroy :log_children, prepend: true + # + # private + # def log_children + # # Child processing + # end + # end + # + # This way, the +before_destroy+ gets executed before the dependent: :destroy is called, and the data is still available. + # + # Also, there are cases when you want several callbacks of the same type to + # be executed in order. + # + # For example: + # + # class Topic < ActiveRecord::Base + # has_many :children + # + # after_save :log_children + # after_save :do_something_else + # + # private + # + # def log_children + # # Child processing + # end + # + # def do_something_else + # # Something else + # end + # end + # + # In this case the +log_children+ gets executed before +do_something_else+. + # The same applies to all non-transactional callbacks. + # + # In case there are multiple transactional callbacks as seen below, the order + # is reversed. + # + # For example: + # + # class Topic < ActiveRecord::Base + # has_many :children + # + # after_commit :log_children + # after_commit :do_something_else + # + # private + # + # def log_children + # # Child processing + # end + # + # def do_something_else + # # Something else + # end + # end + # + # In this case the +do_something_else+ gets executed before +log_children+. + # + # == \Transactions + # + # The entire callback chain of a {#save}[rdoc-ref:Persistence#save], {#save!}[rdoc-ref:Persistence#save!], + # or {#destroy}[rdoc-ref:Persistence#destroy] call runs within a transaction. That includes after_* hooks. + # If everything goes fine a COMMIT is executed once the chain has been completed. + # + # If a before_* callback cancels the action a ROLLBACK is issued. You + # can also trigger a ROLLBACK raising an exception in any of the callbacks, + # including after_* hooks. Note, however, that in that case the client + # needs to be aware of it because an ordinary {#save}[rdoc-ref:Persistence#save] will raise such exception + # instead of quietly returning +false+. + # + # == Debugging callbacks + # + # The callback chain is accessible via the _*_callbacks method on an object. Active Model \Callbacks support + # :before, :after and :around as values for the kind property. The kind property + # defines what part of the chain the callback runs in. + # + # To find all callbacks in the before_save callback chain: + # + # Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) } + # + # Returns an array of callback objects that form the before_save chain. + # + # To further check if the before_save chain contains a proc defined as rest_when_dead use the filter property of the callback object: + # + # Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }.collect(&:filter).include?(:rest_when_dead) + # + # Returns true or false depending on whether the proc is contained in the before_save callback chain on a Topic model. + # + module Callbacks + extend ActiveSupport::Concern + + CALLBACKS = [ + :after_initialize, :after_find, :after_touch, :before_validation, :after_validation, + :before_save, :around_save, :after_save, :before_create, :around_create, + :after_create, :before_update, :around_update, :after_update, + :before_destroy, :around_destroy, :after_destroy, :after_commit, :after_rollback + ] + + def destroy #:nodoc: + @_destroy_callback_already_called ||= false + return if @_destroy_callback_already_called + @_destroy_callback_already_called = true + _run_destroy_callbacks { super } + rescue RecordNotDestroyed => e + @_association_destroy_exception = e + false + ensure + @_destroy_callback_already_called = false + end + + def touch(*) #:nodoc: + _run_touch_callbacks { super } + end + + def increment!(attribute, by = 1, touch: nil) # :nodoc: + touch ? _run_touch_callbacks { super } : super + end + + private + + def create_or_update(*) + _run_save_callbacks { super } + end + + def _create_record + _run_create_callbacks { super } + end + + def _update_record(*) + _run_update_callbacks { super } + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/coders/json.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/coders/json.rb new file mode 100644 index 00000000..a69b3848 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/coders/json.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActiveRecord + module Coders # :nodoc: + class JSON # :nodoc: + def self.dump(obj) + ActiveSupport::JSON.encode(obj) + end + + def self.load(json) + ActiveSupport::JSON.decode(json) unless json.blank? + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/coders/yaml_column.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/coders/yaml_column.rb new file mode 100644 index 00000000..11559141 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/coders/yaml_column.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "yaml" + +module ActiveRecord + module Coders # :nodoc: + class YAMLColumn # :nodoc: + attr_accessor :object_class + + def initialize(attr_name, object_class = Object) + @attr_name = attr_name + @object_class = object_class + check_arity_of_constructor + end + + def dump(obj) + return if obj.nil? + + assert_valid_value(obj, action: "dump") + YAML.dump obj + end + + def load(yaml) + return object_class.new if object_class != Object && yaml.nil? + return yaml unless yaml.is_a?(String) && /^---/.match?(yaml) + obj = YAML.load(yaml) + + assert_valid_value(obj, action: "load") + obj ||= object_class.new if object_class != Object + + obj + end + + def assert_valid_value(obj, action:) + unless obj.nil? || obj.is_a?(object_class) + raise SerializationTypeMismatch, + "can't #{action} `#{@attr_name}`: was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}" + end + end + + private + + def check_arity_of_constructor + load(nil) + rescue ArgumentError + raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor." + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/collection_cache_key.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/collection_cache_key.rb new file mode 100644 index 00000000..7ef405db --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/collection_cache_key.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module ActiveRecord + module CollectionCacheKey + def collection_cache_key(collection = all, timestamp_column = :updated_at) # :nodoc: + query_signature = ActiveSupport::Digest.hexdigest(collection.to_sql) + key = "#{collection.model_name.cache_key}/query-#{query_signature}" + + if collection.loaded? || collection.distinct_value + size = collection.records.size + if size > 0 + timestamp = collection.max_by(×tamp_column)._read_attribute(timestamp_column) + end + else + if collection.eager_loading? + collection = collection.send(:apply_join_dependency) + end + column_type = type_for_attribute(timestamp_column) + column = connection.column_name_from_arel_node(collection.arel_attribute(timestamp_column)) + select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp" + + if collection.has_limit_or_offset? + query = collection.select("#{column} AS collection_cache_key_timestamp") + subquery_alias = "subquery_for_cache_key" + subquery_column = "#{subquery_alias}.collection_cache_key_timestamp" + subquery = query.arel.as(subquery_alias) + arel = Arel::SelectManager.new(subquery).project(select_values % subquery_column) + else + query = collection.unscope(:order) + query.select_values = [select_values % column] + arel = query.arel + end + + result = connection.select_one(arel, nil) + + if result.blank? + size = 0 + timestamp = nil + else + size = result["size"] + timestamp = column_type.deserialize(result["timestamp"]) + end + + end + + if timestamp + "#{key}-#{size}-#{timestamp.utc.to_s(cache_timestamp_format)}" + else + "#{key}-#{size}" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/connection_pool.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/connection_pool.rb new file mode 100644 index 00000000..d0544735 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -0,0 +1,1068 @@ +# frozen_string_literal: true + +require "thread" +require "concurrent/map" +require "monitor" + +module ActiveRecord + # Raised when a connection could not be obtained within the connection + # acquisition timeout period: because max connections in pool + # are in use. + class ConnectionTimeoutError < ConnectionNotEstablished + end + + # Raised when a pool was unable to get ahold of all its connections + # to perform a "group" action such as + # {ActiveRecord::Base.connection_pool.disconnect!}[rdoc-ref:ConnectionAdapters::ConnectionPool#disconnect!] + # or {ActiveRecord::Base.clear_reloadable_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_reloadable_connections!]. + class ExclusiveConnectionTimeoutError < ConnectionTimeoutError + end + + module ConnectionAdapters + # Connection pool base class for managing Active Record database + # connections. + # + # == Introduction + # + # A connection pool synchronizes thread access to a limited number of + # database connections. The basic idea is that each thread checks out a + # database connection from the pool, uses that connection, and checks the + # connection back in. ConnectionPool is completely thread-safe, and will + # ensure that a connection cannot be used by two threads at the same time, + # as long as ConnectionPool's contract is correctly followed. It will also + # handle cases in which there are more threads than connections: if all + # connections have been checked out, and a thread tries to checkout a + # connection anyway, then ConnectionPool will wait until some other thread + # has checked in a connection. + # + # == Obtaining (checking out) a connection + # + # Connections can be obtained and used from a connection pool in several + # ways: + # + # 1. Simply use {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling.connection] + # as with Active Record 2.1 and + # earlier (pre-connection-pooling). Eventually, when you're done with + # the connection(s) and wish it to be returned to the pool, you call + # {ActiveRecord::Base.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!]. + # This will be the default behavior for Active Record when used in conjunction with + # Action Pack's request handling cycle. + # 2. Manually check out a connection from the pool with + # {ActiveRecord::Base.connection_pool.checkout}[rdoc-ref:#checkout]. You are responsible for + # returning this connection to the pool when finished by calling + # {ActiveRecord::Base.connection_pool.checkin(connection)}[rdoc-ref:#checkin]. + # 3. Use {ActiveRecord::Base.connection_pool.with_connection(&block)}[rdoc-ref:#with_connection], which + # obtains a connection, yields it as the sole argument to the block, + # and returns it to the pool after the block completes. + # + # Connections in the pool are actually AbstractAdapter objects (or objects + # compatible with AbstractAdapter's interface). + # + # == Options + # + # There are several connection-pooling-related options that you can add to + # your database connection configuration: + # + # * +pool+: maximum number of connections the pool may manage (default 5). + # * +idle_timeout+: number of seconds that a connection will be kept + # unused in the pool before it is automatically disconnected (default + # 300 seconds). Set this to zero to keep connections forever. + # * +checkout_timeout+: number of seconds to wait for a connection to + # become available before giving up and raising a timeout error (default + # 5 seconds). + # + #-- + # Synchronization policy: + # * all public methods can be called outside +synchronize+ + # * access to these instance variables needs to be in +synchronize+: + # * @connections + # * @now_connecting + # * private methods that require being called in a +synchronize+ blocks + # are now explicitly documented + class ConnectionPool + # Threadsafe, fair, LIFO queue. Meant to be used by ConnectionPool + # with which it shares a Monitor. + class Queue + def initialize(lock = Monitor.new) + @lock = lock + @cond = @lock.new_cond + @num_waiting = 0 + @queue = [] + end + + # Test if any threads are currently waiting on the queue. + def any_waiting? + synchronize do + @num_waiting > 0 + end + end + + # Returns the number of threads currently waiting on this + # queue. + def num_waiting + synchronize do + @num_waiting + end + end + + # Add +element+ to the queue. Never blocks. + def add(element) + synchronize do + @queue.push element + @cond.signal + end + end + + # If +element+ is in the queue, remove and return it, or +nil+. + def delete(element) + synchronize do + @queue.delete(element) + end + end + + # Remove all elements from the queue. + def clear + synchronize do + @queue.clear + end + end + + # Remove the head of the queue. + # + # If +timeout+ is not given, remove and return the head the + # queue if the number of available elements is strictly + # greater than the number of threads currently waiting (that + # is, don't jump ahead in line). Otherwise, return +nil+. + # + # If +timeout+ is given, block if there is no element + # available, waiting up to +timeout+ seconds for an element to + # become available. + # + # Raises: + # - ActiveRecord::ConnectionTimeoutError if +timeout+ is given and no element + # becomes available within +timeout+ seconds, + def poll(timeout = nil) + synchronize { internal_poll(timeout) } + end + + private + + def internal_poll(timeout) + no_wait_poll || (timeout && wait_poll(timeout)) + end + + def synchronize(&block) + @lock.synchronize(&block) + end + + # Test if the queue currently contains any elements. + def any? + !@queue.empty? + end + + # A thread can remove an element from the queue without + # waiting if and only if the number of currently available + # connections is strictly greater than the number of waiting + # threads. + def can_remove_no_wait? + @queue.size > @num_waiting + end + + # Removes and returns the head of the queue if possible, or +nil+. + def remove + @queue.pop + end + + # Remove and return the head the queue if the number of + # available elements is strictly greater than the number of + # threads currently waiting. Otherwise, return +nil+. + def no_wait_poll + remove if can_remove_no_wait? + end + + # Waits on the queue up to +timeout+ seconds, then removes and + # returns the head of the queue. + def wait_poll(timeout) + @num_waiting += 1 + + t0 = Time.now + elapsed = 0 + loop do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @cond.wait(timeout - elapsed) + end + + return remove if any? + + elapsed = Time.now - t0 + if elapsed >= timeout + msg = "could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use" % + [timeout, elapsed] + raise ConnectionTimeoutError, msg + end + end + ensure + @num_waiting -= 1 + end + end + + # Adds the ability to turn a basic fair FIFO queue into one + # biased to some thread. + module BiasableQueue # :nodoc: + class BiasedConditionVariable # :nodoc: + # semantics of condition variables guarantee that +broadcast+, +broadcast_on_biased+, + # +signal+ and +wait+ methods are only called while holding a lock + def initialize(lock, other_cond, preferred_thread) + @real_cond = lock.new_cond + @other_cond = other_cond + @preferred_thread = preferred_thread + @num_waiting_on_real_cond = 0 + end + + def broadcast + broadcast_on_biased + @other_cond.broadcast + end + + def broadcast_on_biased + @num_waiting_on_real_cond = 0 + @real_cond.broadcast + end + + def signal + if @num_waiting_on_real_cond > 0 + @num_waiting_on_real_cond -= 1 + @real_cond + else + @other_cond + end.signal + end + + def wait(timeout) + if Thread.current == @preferred_thread + @num_waiting_on_real_cond += 1 + @real_cond + else + @other_cond + end.wait(timeout) + end + end + + def with_a_bias_for(thread) + previous_cond = nil + new_cond = nil + synchronize do + previous_cond = @cond + @cond = new_cond = BiasedConditionVariable.new(@lock, @cond, thread) + end + yield + ensure + synchronize do + @cond = previous_cond if previous_cond + new_cond.broadcast_on_biased if new_cond # wake up any remaining sleepers + end + end + end + + # Connections must be leased while holding the main pool mutex. This is + # an internal subclass that also +.leases+ returned connections while + # still in queue's critical section (queue synchronizes with the same + # @lock as the main pool) so that a returned connection is already + # leased and there is no need to re-enter synchronized block. + class ConnectionLeasingQueue < Queue # :nodoc: + include BiasableQueue + + private + def internal_poll(timeout) + conn = super + conn.lease if conn + conn + end + end + + # Every +frequency+ seconds, the reaper will call +reap+ and +flush+ on + # +pool+. A reaper instantiated with a zero frequency will never reap + # the connection pool. + # + # Configure the frequency by setting +reaping_frequency+ in your database + # yaml file (default 60 seconds). + class Reaper + attr_reader :pool, :frequency + + def initialize(pool, frequency) + @pool = pool + @frequency = frequency + end + + def run + return unless frequency && frequency > 0 + Thread.new(frequency, pool) { |t, p| + loop do + sleep t + p.reap + p.flush + end + } + end + end + + include MonitorMixin + include QueryCache::ConnectionPoolConfiguration + + attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache + attr_reader :spec, :connections, :size, :reaper + + # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification + # object which describes database connection information (e.g. adapter, + # host name, username, password, etc), as well as the maximum size for + # this ConnectionPool. + # + # The default ConnectionPool maximum size is 5. + def initialize(spec) + super() + + @spec = spec + + @checkout_timeout = (spec.config[:checkout_timeout] && spec.config[:checkout_timeout].to_f) || 5 + if @idle_timeout = spec.config.fetch(:idle_timeout, 300) + @idle_timeout = @idle_timeout.to_f + @idle_timeout = nil if @idle_timeout <= 0 + end + + # default max pool size to 5 + @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5 + + # This variable tracks the cache of threads mapped to reserved connections, with the + # sole purpose of speeding up the +connection+ method. It is not the authoritative + # registry of which thread owns which connection. Connection ownership is tracked by + # the +connection.owner+ attr on each +connection+ instance. + # The invariant works like this: if there is mapping of thread => conn, + # then that +thread+ does indeed own that +conn+. However, an absence of a such + # mapping does not mean that the +thread+ doesn't own the said connection. In + # that case +conn.owner+ attr should be consulted. + # Access and modification of @thread_cached_conns does not require + # synchronization. + @thread_cached_conns = Concurrent::Map.new(initial_capacity: @size) + + @connections = [] + @automatic_reconnect = true + + # Connection pool allows for concurrent (outside the main +synchronize+ section) + # establishment of new connections. This variable tracks the number of threads + # currently in the process of independently establishing connections to the DB. + @now_connecting = 0 + + @threads_blocking_new_connections = 0 + + @available = ConnectionLeasingQueue.new self + + @lock_thread = false + + # +reaping_frequency+ is configurable mostly for historical reasons, but it could + # also be useful if someone wants a very low +idle_timeout+. + reaping_frequency = spec.config.fetch(:reaping_frequency, 60) + @reaper = Reaper.new(self, reaping_frequency && reaping_frequency.to_f) + @reaper.run + end + + def lock_thread=(lock_thread) + if lock_thread + @lock_thread = Thread.current + else + @lock_thread = nil + end + end + + # Retrieve the connection associated with the current thread, or call + # #checkout to obtain one if necessary. + # + # #connection can be called any number of times; the connection is + # held in a cache keyed by a thread. + def connection + @thread_cached_conns[connection_cache_key(@lock_thread || Thread.current)] ||= checkout + end + + # Returns true if there is an open connection being used for the current thread. + # + # This method only works for connections that have been obtained through + # #connection or #with_connection methods. Connections obtained through + # #checkout will not be detected by #active_connection? + def active_connection? + @thread_cached_conns[connection_cache_key(Thread.current)] + end + + # Signal that the thread is finished with the current connection. + # #release_connection releases the connection-thread association + # and returns the connection to the pool. + # + # This method only works for connections that have been obtained through + # #connection or #with_connection methods, connections obtained through + # #checkout will not be automatically released. + def release_connection(owner_thread = Thread.current) + if conn = @thread_cached_conns.delete(connection_cache_key(owner_thread)) + checkin conn + end + end + + # If a connection obtained through #connection or #with_connection methods + # already exists yield it to the block. If no such connection + # exists checkout a connection, yield it to the block, and checkin the + # connection when finished. + def with_connection + unless conn = @thread_cached_conns[connection_cache_key(Thread.current)] + conn = connection + fresh_connection = true + end + yield conn + ensure + release_connection if fresh_connection + end + + # Returns true if a connection has already been opened. + def connected? + synchronize { @connections.any? } + end + + # Disconnects all connections in the pool, and clears the pool. + # + # Raises: + # - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all + # connections in the pool within a timeout interval (default duration is + # spec.config[:checkout_timeout] * 2 seconds). + def disconnect(raise_on_acquisition_timeout = true) + with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do + synchronize do + @connections.each do |conn| + if conn.in_use? + conn.steal! + checkin conn + end + conn.disconnect! + end + @connections = [] + @available.clear + end + end + end + + # Disconnects all connections in the pool, and clears the pool. + # + # The pool first tries to gain ownership of all connections. If unable to + # do so within a timeout interval (default duration is + # spec.config[:checkout_timeout] * 2 seconds), then the pool is forcefully + # disconnected without any regard for other connection owning threads. + def disconnect! + disconnect(false) + end + + # Discards all connections in the pool (even if they're currently + # leased!), along with the pool itself. Any further interaction with the + # pool (except #spec and #schema_cache) is undefined. + # + # See AbstractAdapter#discard! + def discard! # :nodoc: + synchronize do + return if @connections.nil? # already discarded + @connections.each do |conn| + conn.discard! + end + @connections = @available = @thread_cached_conns = nil + end + end + + # Clears the cache which maps classes and re-connects connections that + # require reloading. + # + # Raises: + # - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all + # connections in the pool within a timeout interval (default duration is + # spec.config[:checkout_timeout] * 2 seconds). + def clear_reloadable_connections(raise_on_acquisition_timeout = true) + with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do + synchronize do + @connections.each do |conn| + if conn.in_use? + conn.steal! + checkin conn + end + conn.disconnect! if conn.requires_reloading? + end + @connections.delete_if(&:requires_reloading?) + @available.clear + end + end + end + + # Clears the cache which maps classes and re-connects connections that + # require reloading. + # + # The pool first tries to gain ownership of all connections. If unable to + # do so within a timeout interval (default duration is + # spec.config[:checkout_timeout] * 2 seconds), then the pool forcefully + # clears the cache and reloads connections without any regard for other + # connection owning threads. + def clear_reloadable_connections! + clear_reloadable_connections(false) + end + + # Check-out a database connection from the pool, indicating that you want + # to use it. You should call #checkin when you no longer need this. + # + # This is done by either returning and leasing existing connection, or by + # creating a new connection and leasing it. + # + # If all connections are leased and the pool is at capacity (meaning the + # number of currently leased connections is greater than or equal to the + # size limit set), an ActiveRecord::ConnectionTimeoutError exception will be raised. + # + # Returns: an AbstractAdapter object. + # + # Raises: + # - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool. + def checkout(checkout_timeout = @checkout_timeout) + checkout_and_verify(acquire_connection(checkout_timeout)) + end + + # Check-in a database connection back into the pool, indicating that you + # no longer need this connection. + # + # +conn+: an AbstractAdapter object, which was obtained by earlier by + # calling #checkout on this pool. + def checkin(conn) + conn.lock.synchronize do + synchronize do + remove_connection_from_thread_cache conn + + conn._run_checkin_callbacks do + conn.expire + end + + @available.add conn + end + end + end + + # Remove a connection from the connection pool. The connection will + # remain open and active but will no longer be managed by this pool. + def remove(conn) + needs_new_connection = false + + synchronize do + remove_connection_from_thread_cache conn + + @connections.delete conn + @available.delete conn + + # @available.any_waiting? => true means that prior to removing this + # conn, the pool was at its max size (@connections.size == @size). + # This would mean that any threads stuck waiting in the queue wouldn't + # know they could checkout_new_connection, so let's do it for them. + # Because condition-wait loop is encapsulated in the Queue class + # (that in turn is oblivious to ConnectionPool implementation), threads + # that are "stuck" there are helpless. They have no way of creating + # new connections and are completely reliant on us feeding available + # connections into the Queue. + needs_new_connection = @available.any_waiting? + end + + # This is intentionally done outside of the synchronized section as we + # would like not to hold the main mutex while checking out new connections. + # Thus there is some chance that needs_new_connection information is now + # stale, we can live with that (bulk_make_new_connections will make + # sure not to exceed the pool's @size limit). + bulk_make_new_connections(1) if needs_new_connection + end + + # Recover lost connections for the pool. A lost connection can occur if + # a programmer forgets to checkin a connection at the end of a thread + # or a thread dies unexpectedly. + def reap + stale_connections = synchronize do + @connections.select do |conn| + conn.in_use? && !conn.owner.alive? + end.each do |conn| + conn.steal! + end + end + + stale_connections.each do |conn| + if conn.active? + conn.reset! + checkin conn + else + remove conn + end + end + end + + # Disconnect all connections that have been idle for at least + # +minimum_idle+ seconds. Connections currently checked out, or that were + # checked in less than +minimum_idle+ seconds ago, are unaffected. + def flush(minimum_idle = @idle_timeout) + return if minimum_idle.nil? + + idle_connections = synchronize do + @connections.select do |conn| + !conn.in_use? && conn.seconds_idle >= minimum_idle + end.each do |conn| + conn.lease + + @available.delete conn + @connections.delete conn + end + end + + idle_connections.each do |conn| + conn.disconnect! + end + end + + # Disconnect all currently idle connections. Connections currently checked + # out are unaffected. + def flush! + reap + flush(-1) + end + + def num_waiting_in_queue # :nodoc: + @available.num_waiting + end + + # Return connection pool's usage statistic + # Example: + # + # ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 } + def stat + synchronize do + { + size: size, + connections: @connections.size, + busy: @connections.count { |c| c.in_use? && c.owner.alive? }, + dead: @connections.count { |c| c.in_use? && !c.owner.alive? }, + idle: @connections.count { |c| !c.in_use? }, + waiting: num_waiting_in_queue, + checkout_timeout: checkout_timeout + } + end + end + + private + #-- + # this is unfortunately not concurrent + def bulk_make_new_connections(num_new_conns_needed) + num_new_conns_needed.times do + # try_to_checkout_new_connection will not exceed pool's @size limit + if new_conn = try_to_checkout_new_connection + # make the new_conn available to the starving threads stuck @available Queue + checkin(new_conn) + end + end + end + + #-- + # From the discussion on GitHub: + # https://github.com/rails/rails/pull/14938#commitcomment-6601951 + # This hook-in method allows for easier monkey-patching fixes needed by + # JRuby users that use Fibers. + def connection_cache_key(thread) + thread + end + + # Take control of all existing connections so a "group" action such as + # reload/disconnect can be performed safely. It is no longer enough to + # wrap it in +synchronize+ because some pool's actions are allowed + # to be performed outside of the main +synchronize+ block. + def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true) + with_new_connections_blocked do + attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout) + yield + end + end + + def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true) + collected_conns = synchronize do + # account for our own connections + @connections.select { |conn| conn.owner == Thread.current } + end + + newly_checked_out = [] + timeout_time = Time.now + (@checkout_timeout * 2) + + @available.with_a_bias_for(Thread.current) do + loop do + synchronize do + return if collected_conns.size == @connections.size && @now_connecting == 0 + remaining_timeout = timeout_time - Time.now + remaining_timeout = 0 if remaining_timeout < 0 + conn = checkout_for_exclusive_access(remaining_timeout) + collected_conns << conn + newly_checked_out << conn + end + end + end + rescue ExclusiveConnectionTimeoutError + # raise_on_acquisition_timeout == false means we are directed to ignore any + # timeouts and are expected to just give up: we've obtained as many connections + # as possible, note that in a case like that we don't return any of the + # +newly_checked_out+ connections. + + if raise_on_acquisition_timeout + release_newly_checked_out = true + raise + end + rescue Exception # if something else went wrong + # this can't be a "naked" rescue, because we have should return conns + # even for non-StandardErrors + release_newly_checked_out = true + raise + ensure + if release_newly_checked_out && newly_checked_out + # releasing only those conns that were checked out in this method, conns + # checked outside this method (before it was called) are not for us to release + newly_checked_out.each { |conn| checkin(conn) } + end + end + + #-- + # Must be called in a synchronize block. + def checkout_for_exclusive_access(checkout_timeout) + checkout(checkout_timeout) + rescue ConnectionTimeoutError + # this block can't be easily moved into attempt_to_checkout_all_existing_connections's + # rescue block, because doing so would put it outside of synchronize section, without + # being in a critical section thread_report might become inaccurate + msg = "could not obtain ownership of all database connections in #{checkout_timeout} seconds".dup + + thread_report = [] + @connections.each do |conn| + unless conn.owner == Thread.current + thread_report << "#{conn} is owned by #{conn.owner}" + end + end + + msg << " (#{thread_report.join(', ')})" if thread_report.any? + + raise ExclusiveConnectionTimeoutError, msg + end + + def with_new_connections_blocked + synchronize do + @threads_blocking_new_connections += 1 + end + + yield + ensure + num_new_conns_required = 0 + + synchronize do + @threads_blocking_new_connections -= 1 + + if @threads_blocking_new_connections.zero? + @available.clear + + num_new_conns_required = num_waiting_in_queue + + @connections.each do |conn| + next if conn.in_use? + + @available.add conn + num_new_conns_required -= 1 + end + end + end + + bulk_make_new_connections(num_new_conns_required) if num_new_conns_required > 0 + end + + # Acquire a connection by one of 1) immediately removing one + # from the queue of available connections, 2) creating a new + # connection if the pool is not at capacity, 3) waiting on the + # queue for a connection to become available. + # + # Raises: + # - ActiveRecord::ConnectionTimeoutError if a connection could not be acquired + # + #-- + # Implementation detail: the connection returned by +acquire_connection+ + # will already be "+connection.lease+ -ed" to the current thread. + def acquire_connection(checkout_timeout) + # NOTE: we rely on @available.poll and +try_to_checkout_new_connection+ to + # +conn.lease+ the returned connection (and to do this in a +synchronized+ + # section). This is not the cleanest implementation, as ideally we would + # synchronize { conn.lease } in this method, but by leaving it to @available.poll + # and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections + # of the said methods and avoid an additional +synchronize+ overhead. + if conn = @available.poll || try_to_checkout_new_connection + conn + else + reap + @available.poll(checkout_timeout) + end + end + + #-- + # if owner_thread param is omitted, this must be called in synchronize block + def remove_connection_from_thread_cache(conn, owner_thread = conn.owner) + @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn) + end + alias_method :release, :remove_connection_from_thread_cache + + def new_connection + Base.send(spec.adapter_method, spec.config).tap do |conn| + conn.schema_cache = schema_cache.dup if schema_cache + end + end + + # If the pool is not at a @size limit, establish new connection. Connecting + # to the DB is done outside main synchronized section. + #-- + # Implementation constraint: a newly established connection returned by this + # method must be in the +.leased+ state. + def try_to_checkout_new_connection + # first in synchronized section check if establishing new conns is allowed + # and increment @now_connecting, to prevent overstepping this pool's @size + # constraint + do_checkout = synchronize do + if @threads_blocking_new_connections.zero? && (@connections.size + @now_connecting) < @size + @now_connecting += 1 + end + end + if do_checkout + begin + # if successfully incremented @now_connecting establish new connection + # outside of synchronized section + conn = checkout_new_connection + ensure + synchronize do + if conn + adopt_connection(conn) + # returned conn needs to be already leased + conn.lease + end + @now_connecting -= 1 + end + end + end + end + + def adopt_connection(conn) + conn.pool = self + @connections << conn + end + + def checkout_new_connection + raise ConnectionNotEstablished unless @automatic_reconnect + new_connection + end + + def checkout_and_verify(c) + c._run_checkout_callbacks do + c.verify! + end + c + rescue + remove c + c.disconnect! + raise + end + end + + # ConnectionHandler is a collection of ConnectionPool objects. It is used + # for keeping separate connection pools that connect to different databases. + # + # For example, suppose that you have 5 models, with the following hierarchy: + # + # class Author < ActiveRecord::Base + # end + # + # class BankAccount < ActiveRecord::Base + # end + # + # class Book < ActiveRecord::Base + # establish_connection :library_db + # end + # + # class ScaryBook < Book + # end + # + # class GoodBook < Book + # end + # + # And a database.yml that looked like this: + # + # development: + # database: my_application + # host: localhost + # + # library_db: + # database: library + # host: some.library.org + # + # Your primary database in the development environment is "my_application" + # but the Book model connects to a separate database called "library_db" + # (this can even be a database on a different machine). + # + # Book, ScaryBook and GoodBook will all use the same connection pool to + # "library_db" while Author, BankAccount, and any other models you create + # will use the default connection pool to "my_application". + # + # The various connection pools are managed by a single instance of + # ConnectionHandler accessible via ActiveRecord::Base.connection_handler. + # All Active Record models use this handler to determine the connection pool that they + # should use. + # + # The ConnectionHandler class is not coupled with the Active models, as it has no knowledge + # about the model. The model needs to pass a specification name to the handler, + # in order to look up the correct connection pool. + class ConnectionHandler + def self.create_owner_to_pool # :nodoc: + Concurrent::Map.new(initial_capacity: 2) do |h, k| + # Discard the parent's connection pools immediately; we have no need + # of them + discard_unowned_pools(h) + + h[k] = Concurrent::Map.new(initial_capacity: 2) + end + end + + def self.unowned_pool_finalizer(pid_map) # :nodoc: + lambda do |_| + discard_unowned_pools(pid_map) + end + end + + def self.discard_unowned_pools(pid_map) # :nodoc: + pid_map.each do |pid, pools| + pools.values.compact.each(&:discard!) unless pid == Process.pid + end + end + + def initialize + # These caches are keyed by spec.name (ConnectionSpecification#name). + @owner_to_pool = ConnectionHandler.create_owner_to_pool + + # Backup finalizer: if the forked child never needed a pool, the above + # early discard has not occurred + ObjectSpace.define_finalizer self, ConnectionHandler.unowned_pool_finalizer(@owner_to_pool) + end + + def connection_pool_list + owner_to_pool.values.compact + end + alias :connection_pools :connection_pool_list + + def establish_connection(config) + resolver = ConnectionSpecification::Resolver.new(Base.configurations) + spec = resolver.spec(config) + + remove_connection(spec.name) + + message_bus = ActiveSupport::Notifications.instrumenter + payload = { + connection_id: object_id + } + if spec + payload[:spec_name] = spec.name + payload[:config] = spec.config + end + + message_bus.instrument("!connection.active_record", payload) do + owner_to_pool[spec.name] = ConnectionAdapters::ConnectionPool.new(spec) + end + + owner_to_pool[spec.name] + end + + # Returns true if there are any active connections among the connection + # pools that the ConnectionHandler is managing. + def active_connections? + connection_pool_list.any?(&:active_connection?) + end + + # Returns any connections in use by the current thread back to the pool, + # and also returns connections to the pool cached by threads that are no + # longer alive. + def clear_active_connections! + connection_pool_list.each(&:release_connection) + end + + # Clears the cache which maps classes. + # + # See ConnectionPool#clear_reloadable_connections! for details. + def clear_reloadable_connections! + connection_pool_list.each(&:clear_reloadable_connections!) + end + + def clear_all_connections! + connection_pool_list.each(&:disconnect!) + end + + # Disconnects all currently idle connections. + # + # See ConnectionPool#flush! for details. + def flush_idle_connections! + connection_pool_list.each(&:flush!) + end + + # Locate the connection of the nearest super class. This can be an + # active or defined connection: if it is the latter, it will be + # opened and set as the active connection for the class it was defined + # for (not necessarily the current class). + def retrieve_connection(spec_name) #:nodoc: + pool = retrieve_connection_pool(spec_name) + raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found." unless pool + pool.connection + end + + # Returns true if a connection that's accessible to this class has + # already been opened. + def connected?(spec_name) + conn = retrieve_connection_pool(spec_name) + conn && conn.connected? + end + + # Remove the connection for this class. This will close the active + # connection and the defined connection (if they exist). The result + # can be used as an argument for #establish_connection, for easily + # re-establishing the connection. + def remove_connection(spec_name) + if pool = owner_to_pool.delete(spec_name) + pool.automatic_reconnect = false + pool.disconnect! + pool.spec.config + end + end + + # Retrieving the connection pool happens a lot, so we cache it in @owner_to_pool. + # This makes retrieving the connection pool O(1) once the process is warm. + # When a connection is established or removed, we invalidate the cache. + def retrieve_connection_pool(spec_name) + owner_to_pool.fetch(spec_name) do + # Check if a connection was previously established in an ancestor process, + # which may have been forked. + if ancestor_pool = pool_from_any_process_for(spec_name) + # A connection was established in an ancestor process that must have + # subsequently forked. We can't reuse the connection, but we can copy + # the specification and establish a new connection with it. + establish_connection(ancestor_pool.spec.to_hash).tap do |pool| + pool.schema_cache = ancestor_pool.schema_cache if ancestor_pool.schema_cache + end + else + owner_to_pool[spec_name] = nil + end + end + end + + private + + def owner_to_pool + @owner_to_pool[Process.pid] + end + + def pool_from_any_process_for(spec_name) + owner_to_pool = @owner_to_pool.values.reverse.find { |v| v[spec_name] } + owner_to_pool && owner_to_pool[spec_name] + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/database_limits.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/database_limits.rb new file mode 100644 index 00000000..3a4200b9 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/database_limits.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters # :nodoc: + module DatabaseLimits + # Returns the maximum length of a table alias. + def table_alias_length + 255 + end + + # Returns the maximum length of a column name. + def column_name_length + 64 + end + + # Returns the maximum length of a table name. + def table_name_length + 64 + end + + # Returns the maximum allowed length for an index name. This + # limit is enforced by \Rails and is less than or equal to + # #index_name_length. The gap between + # #index_name_length is to allow internal \Rails + # operations to use prefixes in temporary operations. + def allowed_index_name_length + index_name_length + end + + # Returns the maximum length of an index name. + def index_name_length + 64 + end + + # Returns the maximum number of columns per table. + def columns_per_table + 1024 + end + + # Returns the maximum number of indexes per table. + def indexes_per_table + 16 + end + + # Returns the maximum number of columns in a multicolumn index. + def columns_per_multicolumn_index + 16 + end + + # Returns the maximum number of elements in an IN (x,y,z) clause. + # +nil+ means no limit. + def in_clause_length + nil + end + + # Returns the maximum length of an SQL query. + def sql_query_length + 1048575 + end + + # Returns maximum number of joins in a single query. + def joins_per_query + 256 + end + + private + def bind_params_length + 65535 + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/database_statements.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/database_statements.rb new file mode 100644 index 00000000..d01f9b26 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -0,0 +1,540 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters # :nodoc: + module DatabaseStatements + def initialize + super + reset_transaction + end + + # Converts an arel AST to SQL + def to_sql(arel_or_sql_string, binds = []) + sql, _ = to_sql_and_binds(arel_or_sql_string, binds) + sql + end + + def to_sql_and_binds(arel_or_sql_string, binds = []) # :nodoc: + if arel_or_sql_string.respond_to?(:ast) + unless binds.empty? + raise "Passing bind parameters with an arel AST is forbidden. " \ + "The values must be stored on the AST directly" + end + + if prepared_statements + sql, binds = visitor.accept(arel_or_sql_string.ast, collector).value + + if binds.length > bind_params_length + unprepared_statement do + sql, binds = to_sql_and_binds(arel_or_sql_string) + visitor.preparable = false + end + end + else + sql = visitor.accept(arel_or_sql_string.ast, collector).value + end + [sql.freeze, binds] + else + visitor.preparable = false if prepared_statements + [arel_or_sql_string.dup.freeze, binds] + end + end + private :to_sql_and_binds + + # This is used in the StatementCache object. It returns an object that + # can be used to query the database repeatedly. + def cacheable_query(klass, arel) # :nodoc: + if prepared_statements + sql, binds = visitor.accept(arel.ast, collector).value + query = klass.query(sql) + else + collector = PartialQueryCollector.new + parts, binds = visitor.accept(arel.ast, collector).value + query = klass.partial_query(parts) + end + [query, binds] + end + + # Returns an ActiveRecord::Result instance. + def select_all(arel, name = nil, binds = [], preparable: nil) + arel = arel_from_relation(arel) + sql, binds = to_sql_and_binds(arel, binds) + + if preparable.nil? + preparable = prepared_statements ? visitor.preparable : false + end + + if prepared_statements && preparable + select_prepared(sql, name, binds) + else + select(sql, name, binds) + end + end + + # Returns a record hash with the column names as keys and column values + # as values. + def select_one(arel, name = nil, binds = []) + select_all(arel, name, binds).first + end + + # Returns a single value from a record + def select_value(arel, name = nil, binds = []) + single_value_from_rows(select_rows(arel, name, binds)) + end + + # Returns an array of the values of the first column in a select: + # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3] + def select_values(arel, name = nil, binds = []) + select_rows(arel, name, binds).map(&:first) + end + + # Returns an array of arrays containing the field values. + # Order is the same as that returned by +columns+. + def select_rows(arel, name = nil, binds = []) + select_all(arel, name, binds).rows + end + + def query_value(sql, name = nil) # :nodoc: + single_value_from_rows(query(sql, name)) + end + + def query_values(sql, name = nil) # :nodoc: + query(sql, name).map(&:first) + end + + def query(sql, name = nil) # :nodoc: + exec_query(sql, name).rows + end + + # Executes the SQL statement in the context of this connection and returns + # the raw result from the connection adapter. + # Note: depending on your database connector, the result returned by this + # method may be manually memory managed. Consider using the exec_query + # wrapper instead. + def execute(sql, name = nil) + raise NotImplementedError + end + + # Executes +sql+ statement in the context of this connection using + # +binds+ as the bind substitutes. +name+ is logged along with + # the executed +sql+ statement. + def exec_query(sql, name = "SQL", binds = [], prepare: false) + raise NotImplementedError + end + + # Executes insert +sql+ statement in the context of this connection using + # +binds+ as the bind substitutes. +name+ is logged along with + # the executed +sql+ statement. + def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil) + sql, binds = sql_for_insert(sql, pk, nil, sequence_name, binds) + exec_query(sql, name, binds) + end + + # Executes delete +sql+ statement in the context of this connection using + # +binds+ as the bind substitutes. +name+ is logged along with + # the executed +sql+ statement. + def exec_delete(sql, name = nil, binds = []) + exec_query(sql, name, binds) + end + + # Executes the truncate statement. + def truncate(table_name, name = nil) + raise NotImplementedError + end + + # Executes update +sql+ statement in the context of this connection using + # +binds+ as the bind substitutes. +name+ is logged along with + # the executed +sql+ statement. + def exec_update(sql, name = nil, binds = []) + exec_query(sql, name, binds) + end + + # Executes an INSERT query and returns the new record's ID + # + # +id_value+ will be returned unless the value is +nil+, in + # which case the database will attempt to calculate the last inserted + # id and return that value. + # + # If the next id was calculated in advance (as in Oracle), it should be + # passed in as +id_value+. + def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = []) + sql, binds = to_sql_and_binds(arel, binds) + value = exec_insert(sql, name, binds, pk, sequence_name) + id_value || last_inserted_id(value) + end + alias create insert + + # Executes the update statement and returns the number of rows affected. + def update(arel, name = nil, binds = []) + sql, binds = to_sql_and_binds(arel, binds) + exec_update(sql, name, binds) + end + + # Executes the delete statement and returns the number of rows affected. + def delete(arel, name = nil, binds = []) + sql, binds = to_sql_and_binds(arel, binds) + exec_delete(sql, name, binds) + end + + # Returns +true+ when the connection adapter supports prepared statement + # caching, otherwise returns +false+ + def supports_statement_cache? # :nodoc: + true + end + deprecate :supports_statement_cache? + + # Runs the given block in a database transaction, and returns the result + # of the block. + # + # == Nested transactions support + # + # Most databases don't support true nested transactions. At the time of + # writing, the only database that supports true nested transactions that + # we're aware of, is MS-SQL. + # + # In order to get around this problem, #transaction will emulate the effect + # of nested transactions, by using savepoints: + # https://dev.mysql.com/doc/refman/5.7/en/savepoint.html + # Savepoints are supported by MySQL and PostgreSQL. SQLite3 version >= '3.6.8' + # supports savepoints. + # + # It is safe to call this method if a database transaction is already open, + # i.e. if #transaction is called within another #transaction block. In case + # of a nested call, #transaction will behave as follows: + # + # - The block will be run without doing anything. All database statements + # that happen within the block are effectively appended to the already + # open database transaction. + # - However, if +:requires_new+ is set, the block will be wrapped in a + # database savepoint acting as a sub-transaction. + # + # === Caveats + # + # MySQL doesn't support DDL transactions. If you perform a DDL operation, + # then any created savepoints will be automatically released. For example, + # if you've created a savepoint, then you execute a CREATE TABLE statement, + # then the savepoint that was created will be automatically released. + # + # This means that, on MySQL, you shouldn't execute DDL operations inside + # a #transaction call that you know might create a savepoint. Otherwise, + # #transaction will raise exceptions when it tries to release the + # already-automatically-released savepoints: + # + # Model.connection.transaction do # BEGIN + # Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1 + # Model.connection.create_table(...) + # # active_record_1 now automatically released + # end # RELEASE SAVEPOINT active_record_1 <--- BOOM! database error! + # end + # + # == Transaction isolation + # + # If your database supports setting the isolation level for a transaction, you can set + # it like so: + # + # Post.transaction(isolation: :serializable) do + # # ... + # end + # + # Valid isolation levels are: + # + # * :read_uncommitted + # * :read_committed + # * :repeatable_read + # * :serializable + # + # You should consult the documentation for your database to understand the + # semantics of these different levels: + # + # * https://www.postgresql.org/docs/current/static/transaction-iso.html + # * https://dev.mysql.com/doc/refman/5.7/en/set-transaction.html + # + # An ActiveRecord::TransactionIsolationError will be raised if: + # + # * The adapter does not support setting the isolation level + # * You are joining an existing open transaction + # * You are creating a nested (savepoint) transaction + # + # The mysql2 and postgresql adapters support setting the transaction + # isolation level. + def transaction(requires_new: nil, isolation: nil, joinable: true) + if !requires_new && current_transaction.joinable? + if isolation + raise ActiveRecord::TransactionIsolationError, "cannot set isolation when joining a transaction" + end + yield + else + transaction_manager.within_new_transaction(isolation: isolation, joinable: joinable) { yield } + end + rescue ActiveRecord::Rollback + # rollbacks are silently swallowed + end + + attr_reader :transaction_manager #:nodoc: + + delegate :within_new_transaction, :open_transactions, :current_transaction, :begin_transaction, :commit_transaction, :rollback_transaction, to: :transaction_manager + + def transaction_open? + current_transaction.open? + end + + def reset_transaction #:nodoc: + @transaction_manager = ConnectionAdapters::TransactionManager.new(self) + end + + # Register a record with the current transaction so that its after_commit and after_rollback callbacks + # can be called. + def add_transaction_record(record) + current_transaction.add_record(record) + end + + def transaction_state + current_transaction.state + end + + # Begins the transaction (and turns off auto-committing). + def begin_db_transaction() end + + def transaction_isolation_levels + { + read_uncommitted: "READ UNCOMMITTED", + read_committed: "READ COMMITTED", + repeatable_read: "REPEATABLE READ", + serializable: "SERIALIZABLE" + } + end + + # Begins the transaction with the isolation level set. Raises an error by + # default; adapters that support setting the isolation level should implement + # this method. + def begin_isolated_db_transaction(isolation) + raise ActiveRecord::TransactionIsolationError, "adapter does not support setting transaction isolation" + end + + # Commits the transaction (and turns on auto-committing). + def commit_db_transaction() end + + # Rolls back the transaction (and turns on auto-committing). Must be + # done if the transaction block raises an exception or returns false. + def rollback_db_transaction + exec_rollback_db_transaction + end + + def exec_rollback_db_transaction() end #:nodoc: + + def rollback_to_savepoint(name = nil) + exec_rollback_to_savepoint(name) + end + + def default_sequence_name(table, column) + nil + end + + # Set the sequence to the max value of the table's column. + def reset_sequence!(table, column, sequence = nil) + # Do nothing by default. Implement for PostgreSQL, Oracle, ... + end + + # Inserts the given fixture into the table. Overridden in adapters that require + # something beyond a simple insert (eg. Oracle). + # Most of adapters should implement `insert_fixtures` that leverages bulk SQL insert. + # We keep this method to provide fallback + # for databases like sqlite that do not support bulk inserts. + def insert_fixture(fixture, table_name) + fixture = fixture.stringify_keys + + columns = schema_cache.columns_hash(table_name) + binds = fixture.map do |name, value| + if column = columns[name] + type = lookup_cast_type_from_column(column) + Relation::QueryAttribute.new(name, value, type) + else + raise Fixture::FixtureError, %(table "#{table_name}" has no column named #{name.inspect}.) + end + end + + table = Arel::Table.new(table_name) + + values = binds.map do |bind| + value = with_yaml_fallback(bind.value_for_database) + [table[bind.name], value] + end + + manager = Arel::InsertManager.new + manager.into(table) + manager.insert(values) + execute manager.to_sql, "Fixture Insert" + end + + # Inserts a set of fixtures into the table. Overridden in adapters that require + # something beyond a simple insert (eg. Oracle). + def insert_fixtures(fixtures, table_name) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + `insert_fixtures` is deprecated and will be removed in the next version of Rails. + Consider using `insert_fixtures_set` for performance improvement. + MSG + return if fixtures.empty? + + execute(build_fixture_sql(fixtures, table_name), "Fixtures Insert") + end + + def insert_fixtures_set(fixture_set, tables_to_delete = []) + fixture_inserts = fixture_set.map do |table_name, fixtures| + next if fixtures.empty? + + build_fixture_sql(fixtures, table_name) + end.compact + + table_deletes = tables_to_delete.map { |table| "DELETE FROM #{quote_table_name table}".dup } + total_sql = Array.wrap(combine_multi_statements(table_deletes + fixture_inserts)) + + disable_referential_integrity do + transaction(requires_new: true) do + total_sql.each do |sql| + execute sql, "Fixtures Load" + yield if block_given? + end + end + end + end + + def empty_insert_statement_value + "DEFAULT VALUES" + end + + # Sanitizes the given LIMIT parameter in order to prevent SQL injection. + # + # The +limit+ may be anything that can evaluate to a string via #to_s. It + # should look like an integer, or an Arel SQL literal. + # + # Returns Integer and Arel::Nodes::SqlLiteral limits as is. + def sanitize_limit(limit) + if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral) + limit + else + Integer(limit) + end + end + + # The default strategy for an UPDATE with joins is to use a subquery. This doesn't work + # on MySQL (even when aliasing the tables), but MySQL allows using JOIN directly in + # an UPDATE statement, so in the MySQL adapters we redefine this to do that. + def join_to_update(update, select, key) # :nodoc: + subselect = subquery_for(key, select) + + update.where key.in(subselect) + end + alias join_to_delete join_to_update + + private + def default_insert_value(column) + Arel.sql("DEFAULT") + end + + def build_fixture_sql(fixtures, table_name) + columns = schema_cache.columns_hash(table_name) + + values = fixtures.map do |fixture| + fixture = fixture.stringify_keys + + unknown_columns = fixture.keys - columns.keys + if unknown_columns.any? + raise Fixture::FixtureError, %(table "#{table_name}" has no columns named #{unknown_columns.map(&:inspect).join(', ')}.) + end + + columns.map do |name, column| + if fixture.key?(name) + type = lookup_cast_type_from_column(column) + bind = Relation::QueryAttribute.new(name, fixture[name], type) + with_yaml_fallback(bind.value_for_database) + else + default_insert_value(column) + end + end + end + + table = Arel::Table.new(table_name) + manager = Arel::InsertManager.new + manager.into(table) + columns.each_key { |column| manager.columns << table[column] } + manager.values = manager.create_values_list(values) + + manager.to_sql + end + + def combine_multi_statements(total_sql) + total_sql.join(";\n") + end + + # Returns a subquery for the given key using the join information. + def subquery_for(key, select) + subselect = select.clone + subselect.projections = [key] + subselect + end + + # Returns an ActiveRecord::Result instance. + def select(sql, name = nil, binds = []) + exec_query(sql, name, binds, prepare: false) + end + + def select_prepared(sql, name = nil, binds = []) + exec_query(sql, name, binds, prepare: true) + end + + def sql_for_insert(sql, pk, id_value, sequence_name, binds) + [sql, binds] + end + + def last_inserted_id(result) + single_value_from_rows(result.rows) + end + + def single_value_from_rows(rows) + row = rows.first + row && row.first + end + + def arel_from_relation(relation) + if relation.is_a?(Relation) + relation.arel + else + relation + end + end + + # Fixture value is quoted by Arel, however scalar values + # are not quotable. In this case we want to convert + # the column value to YAML. + def with_yaml_fallback(value) + if value.is_a?(Hash) || value.is_a?(Array) + YAML.dump(value) + else + value + end + end + + class PartialQueryCollector + def initialize + @parts = [] + @binds = [] + end + + def <<(str) + @parts << str + self + end + + def add_bind(obj) + @binds << obj + @parts << Arel::Nodes::BindParam.new(1) + self + end + + def value + [@parts, @binds] + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/query_cache.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/query_cache.rb new file mode 100644 index 00000000..6dbf4b0a --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -0,0 +1,145 @@ +# frozen_string_literal: true + +require "concurrent/map" + +module ActiveRecord + module ConnectionAdapters # :nodoc: + module QueryCache + class << self + def included(base) #:nodoc: + dirties_query_cache base, :insert, :update, :delete, :rollback_to_savepoint, :rollback_db_transaction + + base.set_callback :checkout, :after, :configure_query_cache! + base.set_callback :checkin, :after, :disable_query_cache! + end + + def dirties_query_cache(base, *method_names) + method_names.each do |method_name| + base.class_eval <<-end_code, __FILE__, __LINE__ + 1 + def #{method_name}(*) + clear_query_cache if @query_cache_enabled + super + end + end_code + end + end + end + + module ConnectionPoolConfiguration + def initialize(*) + super + @query_cache_enabled = Concurrent::Map.new { false } + end + + def enable_query_cache! + @query_cache_enabled[connection_cache_key(Thread.current)] = true + connection.enable_query_cache! if active_connection? + end + + def disable_query_cache! + @query_cache_enabled.delete connection_cache_key(Thread.current) + connection.disable_query_cache! if active_connection? + end + + def query_cache_enabled + @query_cache_enabled[connection_cache_key(Thread.current)] + end + end + + attr_reader :query_cache, :query_cache_enabled + + def initialize(*) + super + @query_cache = Hash.new { |h, sql| h[sql] = {} } + @query_cache_enabled = false + end + + # Enable the query cache within the block. + def cache + old, @query_cache_enabled = @query_cache_enabled, true + yield + ensure + @query_cache_enabled = old + clear_query_cache unless @query_cache_enabled + end + + def enable_query_cache! + @query_cache_enabled = true + end + + def disable_query_cache! + @query_cache_enabled = false + clear_query_cache + end + + # Disable the query cache within the block. + def uncached + old, @query_cache_enabled = @query_cache_enabled, false + yield + ensure + @query_cache_enabled = old + end + + # Clears the query cache. + # + # One reason you may wish to call this method explicitly is between queries + # that ask the database to randomize results. Otherwise the cache would see + # the same SQL query and repeatedly return the same result each time, silently + # undermining the randomness you were expecting. + def clear_query_cache + @lock.synchronize do + @query_cache.clear + end + end + + def select_all(arel, name = nil, binds = [], preparable: nil) + if @query_cache_enabled && !locked?(arel) + arel = arel_from_relation(arel) + sql, binds = to_sql_and_binds(arel, binds) + + if preparable.nil? + preparable = prepared_statements ? visitor.preparable : false + end + + cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable) } + else + super + end + end + + private + + def cache_sql(sql, name, binds) + @lock.synchronize do + result = + if @query_cache[sql].key?(binds) + ActiveSupport::Notifications.instrument( + "sql.active_record", + sql: sql, + binds: binds, + type_casted_binds: -> { type_casted_binds(binds) }, + name: name, + connection_id: object_id, + cached: true, + ) + @query_cache[sql][binds] + else + @query_cache[sql][binds] = yield + end + result.dup + end + end + + # If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such + # queries should not be cached. + def locked?(arel) + arel = arel.arel if arel.is_a?(Relation) + arel.respond_to?(:locked) && arel.locked + end + + def configure_query_cache! + enable_query_cache! if pool.query_cache_enabled + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/quoting.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/quoting.rb new file mode 100644 index 00000000..98b13481 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/quoting.rb @@ -0,0 +1,200 @@ +# frozen_string_literal: true + +require "active_support/core_ext/big_decimal/conversions" +require "active_support/multibyte/chars" + +module ActiveRecord + module ConnectionAdapters # :nodoc: + module Quoting + # Quotes the column value to help prevent + # {SQL injection attacks}[https://en.wikipedia.org/wiki/SQL_injection]. + def quote(value) + value = id_value_for_database(value) if value.is_a?(Base) + + if value.respond_to?(:value_for_database) + value = value.value_for_database + end + + _quote(value) + end + + # Cast a +value+ to a type that the database understands. For example, + # SQLite does not understand dates, so this method will convert a Date + # to a String. + def type_cast(value, column = nil) + value = id_value_for_database(value) if value.is_a?(Base) + + if column + value = type_cast_from_column(column, value) + end + + _type_cast(value) + rescue TypeError + to_type = column ? " to #{column.type}" : "" + raise TypeError, "can't cast #{value.class}#{to_type}" + end + + # If you are having to call this function, you are likely doing something + # wrong. The column does not have sufficient type information if the user + # provided a custom type on the class level either explicitly (via + # Attributes::ClassMethods#attribute) or implicitly (via + # AttributeMethods::Serialization::ClassMethods#serialize, +time_zone_aware_attributes+). + # In almost all cases, the sql type should only be used to change quoting behavior, when the primitive to + # represent the type doesn't sufficiently reflect the differences + # (varchar vs binary) for example. The type used to get this primitive + # should have been provided before reaching the connection adapter. + def type_cast_from_column(column, value) # :nodoc: + if column + type = lookup_cast_type_from_column(column) + type.serialize(value) + else + value + end + end + + # See docs for #type_cast_from_column + def lookup_cast_type_from_column(column) # :nodoc: + lookup_cast_type(column.sql_type) + end + + # Quotes a string, escaping any ' (single quote) and \ (backslash) + # characters. + def quote_string(s) + s.gsub('\\'.freeze, '\&\&'.freeze).gsub("'".freeze, "''".freeze) # ' (for ruby-mode) + end + + # Quotes the column name. Defaults to no quoting. + def quote_column_name(column_name) + column_name.to_s + end + + # Quotes the table name. Defaults to column name quoting. + def quote_table_name(table_name) + quote_column_name(table_name) + end + + # Override to return the quoted table name for assignment. Defaults to + # table quoting. + # + # This works for mysql2 where table.column can be used to + # resolve ambiguity. + # + # We override this in the sqlite3 and postgresql adapters to use only + # the column name (as per syntax requirements). + def quote_table_name_for_assignment(table, attr) + quote_table_name("#{table}.#{attr}") + end + + def quote_default_expression(value, column) # :nodoc: + if value.is_a?(Proc) + value.call + else + value = lookup_cast_type(column.sql_type).serialize(value) + quote(value) + end + end + + def quoted_true + "TRUE".freeze + end + + def unquoted_true + true + end + + def quoted_false + "FALSE".freeze + end + + def unquoted_false + false + end + + # Quote date/time values for use in SQL input. Includes microseconds + # if the value is a Time responding to usec. + def quoted_date(value) + if value.acts_like?(:time) + zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal + + if value.respond_to?(zone_conversion_method) + value = value.send(zone_conversion_method) + end + end + + result = value.to_s(:db) + if value.respond_to?(:usec) && value.usec > 0 + "#{result}.#{sprintf("%06d", value.usec)}" + else + result + end + end + + def quoted_time(value) # :nodoc: + value = value.change(year: 2000, month: 1, day: 1) + quoted_date(value).sub(/\A\d\d\d\d-\d\d-\d\d /, "") + end + + def quoted_binary(value) # :nodoc: + "'#{quote_string(value.to_s)}'" + end + + def type_casted_binds(binds) # :nodoc: + if binds.first.is_a?(Array) + binds.map { |column, value| type_cast(value, column) } + else + binds.map { |attr| type_cast(attr.value_for_database) } + end + end + + private + def lookup_cast_type(sql_type) + type_map.lookup(sql_type) + end + + def id_value_for_database(value) + if primary_key = value.class.primary_key + value.instance_variable_get(:@attributes)[primary_key].value_for_database + end + end + + def types_which_need_no_typecasting + [nil, Numeric, String] + end + + def _quote(value) + case value + when String, ActiveSupport::Multibyte::Chars + "'#{quote_string(value.to_s)}'" + when true then quoted_true + when false then quoted_false + when nil then "NULL" + # BigDecimals need to be put in a non-normalized form and quoted. + when BigDecimal then value.to_s("F") + when Numeric, ActiveSupport::Duration then value.to_s + when Type::Binary::Data then quoted_binary(value) + when Type::Time::Value then "'#{quoted_time(value)}'" + when Date, Time then "'#{quoted_date(value)}'" + when Symbol then "'#{quote_string(value.to_s)}'" + when Class then "'#{value}'" + else raise TypeError, "can't quote #{value.class.name}" + end + end + + def _type_cast(value) + case value + when Symbol, ActiveSupport::Multibyte::Chars, Type::Binary::Data + value.to_s + when true then unquoted_true + when false then unquoted_false + # BigDecimals need to be put in a non-normalized form and quoted. + when BigDecimal then value.to_s("F") + when Type::Time::Value then quoted_time(value) + when Date, Time then quoted_date(value) + when *types_which_need_no_typecasting + value + else raise TypeError + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/savepoints.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/savepoints.rb new file mode 100644 index 00000000..52a796b9 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/savepoints.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module Savepoints + def current_savepoint_name + current_transaction.savepoint_name + end + + def create_savepoint(name = current_savepoint_name) + execute("SAVEPOINT #{name}") + end + + def exec_rollback_to_savepoint(name = current_savepoint_name) + execute("ROLLBACK TO SAVEPOINT #{name}") + end + + def release_savepoint(name = current_savepoint_name) + execute("RELEASE SAVEPOINT #{name}") + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/schema_creation.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/schema_creation.rb new file mode 100644 index 00000000..4a191d33 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/schema_creation.rb @@ -0,0 +1,146 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/strip" + +module ActiveRecord + module ConnectionAdapters + class AbstractAdapter + class SchemaCreation # :nodoc: + def initialize(conn) + @conn = conn + @cache = {} + end + + def accept(o) + m = @cache[o.class] ||= "visit_#{o.class.name.split('::').last}" + send m, o + end + + delegate :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql, + :options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys_in_create?, :foreign_key_options, to: :@conn + private :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql, + :options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys_in_create?, :foreign_key_options + + private + + def visit_AlterTable(o) + sql = "ALTER TABLE #{quote_table_name(o.name)} ".dup + sql << o.adds.map { |col| accept col }.join(" ") + sql << o.foreign_key_adds.map { |fk| visit_AddForeignKey fk }.join(" ") + sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(" ") + end + + def visit_ColumnDefinition(o) + o.sql_type = type_to_sql(o.type, o.options) + column_sql = "#{quote_column_name(o.name)} #{o.sql_type}".dup + add_column_options!(column_sql, column_options(o)) unless o.type == :primary_key + column_sql + end + + def visit_AddColumnDefinition(o) + "ADD #{accept(o.column)}".dup + end + + def visit_TableDefinition(o) + create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(o.name)} ".dup + + statements = o.columns.map { |c| accept c } + statements << accept(o.primary_keys) if o.primary_keys + + if supports_indexes_in_create? + statements.concat(o.indexes.map { |column_name, options| index_in_create(o.name, column_name, options) }) + end + + if supports_foreign_keys_in_create? + statements.concat(o.foreign_keys.map { |to_table, options| foreign_key_in_create(o.name, to_table, options) }) + end + + create_sql << "(#{statements.join(', ')})" if statements.present? + add_table_options!(create_sql, table_options(o)) + create_sql << " AS #{to_sql(o.as)}" if o.as + create_sql + end + + def visit_PrimaryKeyDefinition(o) + "PRIMARY KEY (#{o.name.map { |name| quote_column_name(name) }.join(', ')})" + end + + def visit_ForeignKeyDefinition(o) + sql = <<-SQL.strip_heredoc + CONSTRAINT #{quote_column_name(o.name)} + FOREIGN KEY (#{quote_column_name(o.column)}) + REFERENCES #{quote_table_name(o.to_table)} (#{quote_column_name(o.primary_key)}) + SQL + sql << " #{action_sql('DELETE', o.on_delete)}" if o.on_delete + sql << " #{action_sql('UPDATE', o.on_update)}" if o.on_update + sql + end + + def visit_AddForeignKey(o) + "ADD #{accept(o)}" + end + + def visit_DropForeignKey(name) + "DROP CONSTRAINT #{quote_column_name(name)}" + end + + def table_options(o) + table_options = {} + table_options[:comment] = o.comment + table_options[:options] = o.options + table_options + end + + def add_table_options!(create_sql, options) + if options_sql = options[:options] + create_sql << " #{options_sql}" + end + create_sql + end + + def column_options(o) + o.options.merge(column: o) + end + + def add_column_options!(sql, options) + sql << " DEFAULT #{quote_default_expression(options[:default], options[:column])}" if options_include_default?(options) + # must explicitly check for :null to allow change_column to work on migrations + if options[:null] == false + sql << " NOT NULL" + end + if options[:auto_increment] == true + sql << " AUTO_INCREMENT" + end + if options[:primary_key] == true + sql << " PRIMARY KEY" + end + sql + end + + def to_sql(sql) + sql = sql.to_sql if sql.respond_to?(:to_sql) + sql + end + + def foreign_key_in_create(from_table, to_table, options) + options = foreign_key_options(from_table, to_table, options) + accept ForeignKeyDefinition.new(from_table, to_table, options) + end + + def action_sql(action, dependency) + case dependency + when :nullify then "ON #{action} SET NULL" + when :cascade then "ON #{action} CASCADE" + when :restrict then "ON #{action} RESTRICT" + else + raise ArgumentError, <<-MSG.strip_heredoc + '#{dependency}' is not supported for :on_update or :on_delete. + Supported values are: :nullify, :cascade, :restrict + MSG + end + end + end + end + SchemaCreation = AbstractAdapter::SchemaCreation # :nodoc: + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/schema_definitions.rb new file mode 100644 index 00000000..52cc7cfe --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -0,0 +1,685 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters #:nodoc: + # Abstract representation of an index definition on a table. Instances of + # this type are typically created and returned by methods in database + # adapters. e.g. ActiveRecord::ConnectionAdapters::MySQL::SchemaStatements#indexes + class IndexDefinition # :nodoc: + attr_reader :table, :name, :unique, :columns, :lengths, :orders, :opclasses, :where, :type, :using, :comment + + def initialize( + table, name, + unique = false, + columns = [], + lengths: {}, + orders: {}, + opclasses: {}, + where: nil, + type: nil, + using: nil, + comment: nil + ) + @table = table + @name = name + @unique = unique + @columns = columns + @lengths = concise_options(lengths) + @orders = concise_options(orders) + @opclasses = concise_options(opclasses) + @where = where + @type = type + @using = using + @comment = comment + end + + private + def concise_options(options) + if columns.size == options.size && options.values.uniq.size == 1 + options.values.first + else + options + end + end + end + + # Abstract representation of a column definition. Instances of this type + # are typically created by methods in TableDefinition, and added to the + # +columns+ attribute of said TableDefinition object, in order to be used + # for generating a number of table creation or table changing SQL statements. + ColumnDefinition = Struct.new(:name, :type, :options, :sql_type) do # :nodoc: + def primary_key? + options[:primary_key] + end + + [:limit, :precision, :scale, :default, :null, :collation, :comment].each do |option_name| + module_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{option_name} + options[:#{option_name}] + end + + def #{option_name}=(value) + options[:#{option_name}] = value + end + CODE + end + end + + AddColumnDefinition = Struct.new(:column) # :nodoc: + + ChangeColumnDefinition = Struct.new(:column, :name) #:nodoc: + + PrimaryKeyDefinition = Struct.new(:name) # :nodoc: + + ForeignKeyDefinition = Struct.new(:from_table, :to_table, :options) do #:nodoc: + def name + options[:name] + end + + def column + options[:column] + end + + def primary_key + options[:primary_key] || default_primary_key + end + + def on_delete + options[:on_delete] + end + + def on_update + options[:on_update] + end + + def custom_primary_key? + options[:primary_key] != default_primary_key + end + + def validate? + options.fetch(:validate, true) + end + alias validated? validate? + + def defined_for?(to_table_ord = nil, to_table: nil, **options) + if to_table_ord + self.to_table == to_table_ord.to_s + else + (to_table.nil? || to_table.to_s == self.to_table) && + options.all? { |k, v| self.options[k].to_s == v.to_s } + end + end + + private + def default_primary_key + "id" + end + end + + class ReferenceDefinition # :nodoc: + def initialize( + name, + polymorphic: false, + index: true, + foreign_key: false, + type: :bigint, + **options + ) + @name = name + @polymorphic = polymorphic + @index = index + @foreign_key = foreign_key + @type = type + @options = options + + if polymorphic && foreign_key + raise ArgumentError, "Cannot add a foreign key to a polymorphic relation" + end + end + + def add_to(table) + columns.each do |column_options| + table.column(*column_options) + end + + if index + table.index(column_names, index_options) + end + + if foreign_key + table.foreign_key(foreign_table_name, foreign_key_options) + end + end + + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected + + attr_reader :name, :polymorphic, :index, :foreign_key, :type, :options + + private + + def as_options(value) + value.is_a?(Hash) ? value : {} + end + + def polymorphic_options + as_options(polymorphic).merge(options.slice(:null, :first, :after)) + end + + def index_options + as_options(index) + end + + def foreign_key_options + as_options(foreign_key).merge(column: column_name) + end + + def columns + result = [[column_name, type, options]] + if polymorphic + result.unshift(["#{name}_type", :string, polymorphic_options]) + end + result + end + + def column_name + "#{name}_id" + end + + def column_names + columns.map(&:first) + end + + def foreign_table_name + foreign_key_options.fetch(:to_table) do + Base.pluralize_table_names ? name.to_s.pluralize : name + end + end + end + + module ColumnMethods + # Appends a primary key definition to the table definition. + # Can be called multiple times, but this is probably not a good idea. + def primary_key(name, type = :primary_key, **options) + column(name, type, options.merge(primary_key: true)) + end + + # Appends a column or columns of a specified type. + # + # t.string(:goat) + # t.string(:goat, :sheep) + # + # See TableDefinition#column + [ + :bigint, + :binary, + :boolean, + :date, + :datetime, + :decimal, + :float, + :integer, + :json, + :string, + :text, + :time, + :timestamp, + :virtual, + ].each do |column_type| + module_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{column_type}(*args, **options) + args.each { |name| column(name, :#{column_type}, options) } + end + CODE + end + alias_method :numeric, :decimal + end + + # Represents the schema of an SQL table in an abstract way. This class + # provides methods for manipulating the schema representation. + # + # Inside migration files, the +t+ object in {create_table}[rdoc-ref:SchemaStatements#create_table] + # is actually of this type: + # + # class SomeMigration < ActiveRecord::Migration[5.0] + # def up + # create_table :foo do |t| + # puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition" + # end + # end + # + # def down + # ... + # end + # end + # + class TableDefinition + include ColumnMethods + + attr_accessor :indexes + attr_reader :name, :temporary, :options, :as, :foreign_keys, :comment + + def initialize(name, temporary = false, options = nil, as = nil, comment: nil) + @columns_hash = {} + @indexes = [] + @foreign_keys = [] + @primary_keys = nil + @temporary = temporary + @options = options + @as = as + @name = name + @comment = comment + end + + def primary_keys(name = nil) # :nodoc: + @primary_keys = PrimaryKeyDefinition.new(name) if name + @primary_keys + end + + # Returns an array of ColumnDefinition objects for the columns of the table. + def columns; @columns_hash.values; end + + # Returns a ColumnDefinition for the column with name +name+. + def [](name) + @columns_hash[name.to_s] + end + + # Instantiates a new column for the table. + # See {connection.add_column}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_column] + # for available options. + # + # Additional options are: + # * :index - + # Create an index for the column. Can be either true or an options hash. + # + # This method returns self. + # + # == Examples + # + # # Assuming +td+ is an instance of TableDefinition + # td.column(:granted, :boolean, index: true) + # + # == Short-hand examples + # + # Instead of calling #column directly, you can also work with the short-hand definitions for the default types. + # They use the type as the method name instead of as a parameter and allow for multiple columns to be defined + # in a single statement. + # + # What can be written like this with the regular calls to column: + # + # create_table :products do |t| + # t.column :shop_id, :integer + # t.column :creator_id, :integer + # t.column :item_number, :string + # t.column :name, :string, default: "Untitled" + # t.column :value, :string, default: "Untitled" + # t.column :created_at, :datetime + # t.column :updated_at, :datetime + # end + # add_index :products, :item_number + # + # can also be written as follows using the short-hand: + # + # create_table :products do |t| + # t.integer :shop_id, :creator_id + # t.string :item_number, index: true + # t.string :name, :value, default: "Untitled" + # t.timestamps null: false + # end + # + # There's a short-hand method for each of the type values declared at the top. And then there's + # TableDefinition#timestamps that'll add +created_at+ and +updated_at+ as datetimes. + # + # TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type + # column if the :polymorphic option is supplied. If :polymorphic is a hash of + # options, these will be used when creating the _type column. The :index option + # will also create an index, similar to calling {add_index}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_index]. + # So what can be written like this: + # + # create_table :taggings do |t| + # t.integer :tag_id, :tagger_id, :taggable_id + # t.string :tagger_type + # t.string :taggable_type, default: 'Photo' + # end + # add_index :taggings, :tag_id, name: 'index_taggings_on_tag_id' + # add_index :taggings, [:tagger_id, :tagger_type] + # + # Can also be written as follows using references: + # + # create_table :taggings do |t| + # t.references :tag, index: { name: 'index_taggings_on_tag_id' } + # t.references :tagger, polymorphic: true, index: true + # t.references :taggable, polymorphic: { default: 'Photo' } + # end + def column(name, type, options = {}) + name = name.to_s + type = type.to_sym if type + options = options.dup + + if @columns_hash[name] && @columns_hash[name].primary_key? + raise ArgumentError, "you can't redefine the primary key column '#{name}'. To define a custom primary key, pass { id: false } to create_table." + end + + index_options = options.delete(:index) + index(name, index_options.is_a?(Hash) ? index_options : {}) if index_options + @columns_hash[name] = new_column_definition(name, type, options) + self + end + + # remove the column +name+ from the table. + # remove_column(:account_id) + def remove_column(name) + @columns_hash.delete name.to_s + end + + # Adds index options to the indexes hash, keyed by column name + # This is primarily used to track indexes that need to be created after the table + # + # index(:account_id, name: 'index_projects_on_account_id') + def index(column_name, options = {}) + indexes << [column_name, options] + end + + def foreign_key(table_name, options = {}) # :nodoc: + table_name_prefix = ActiveRecord::Base.table_name_prefix + table_name_suffix = ActiveRecord::Base.table_name_suffix + table_name = "#{table_name_prefix}#{table_name}#{table_name_suffix}" + foreign_keys.push([table_name, options]) + end + + # Appends :datetime columns :created_at and + # :updated_at to the table. See {connection.add_timestamps}[rdoc-ref:SchemaStatements#add_timestamps] + # + # t.timestamps null: false + def timestamps(**options) + options[:null] = false if options[:null].nil? + + column(:created_at, :datetime, options) + column(:updated_at, :datetime, options) + end + + # Adds a reference. + # + # t.references(:user) + # t.belongs_to(:supplier, foreign_key: true) + # + # See {connection.add_reference}[rdoc-ref:SchemaStatements#add_reference] for details of the options you can use. + def references(*args, **options) + args.each do |ref_name| + ReferenceDefinition.new(ref_name, options).add_to(self) + end + end + alias :belongs_to :references + + def new_column_definition(name, type, **options) # :nodoc: + if integer_like_primary_key?(type, options) + type = integer_like_primary_key_type(type, options) + end + type = aliased_types(type.to_s, type) + options[:primary_key] ||= type == :primary_key + options[:null] = false if options[:primary_key] + create_column_definition(name, type, options) + end + + private + def create_column_definition(name, type, options) + ColumnDefinition.new(name, type, options) + end + + def aliased_types(name, fallback) + "timestamp" == name ? :datetime : fallback + end + + def integer_like_primary_key?(type, options) + options[:primary_key] && [:integer, :bigint].include?(type) && !options.key?(:default) + end + + def integer_like_primary_key_type(type, options) + type + end + end + + class AlterTable # :nodoc: + attr_reader :adds + attr_reader :foreign_key_adds + attr_reader :foreign_key_drops + + def initialize(td) + @td = td + @adds = [] + @foreign_key_adds = [] + @foreign_key_drops = [] + end + + def name; @td.name; end + + def add_foreign_key(to_table, options) + @foreign_key_adds << ForeignKeyDefinition.new(name, to_table, options) + end + + def drop_foreign_key(name) + @foreign_key_drops << name + end + + def add_column(name, type, options) + name = name.to_s + type = type.to_sym + @adds << AddColumnDefinition.new(@td.new_column_definition(name, type, options)) + end + end + + # Represents an SQL table in an abstract way for updating a table. + # Also see TableDefinition and {connection.create_table}[rdoc-ref:SchemaStatements#create_table] + # + # Available transformations are: + # + # change_table :table do |t| + # t.primary_key + # t.column + # t.index + # t.rename_index + # t.timestamps + # t.change + # t.change_default + # t.rename + # t.references + # t.belongs_to + # t.string + # t.text + # t.integer + # t.bigint + # t.float + # t.decimal + # t.numeric + # t.datetime + # t.timestamp + # t.time + # t.date + # t.binary + # t.boolean + # t.foreign_key + # t.json + # t.virtual + # t.remove + # t.remove_references + # t.remove_belongs_to + # t.remove_index + # t.remove_timestamps + # end + # + class Table + include ColumnMethods + + attr_reader :name + + def initialize(table_name, base) + @name = table_name + @base = base + end + + # Adds a new column to the named table. + # + # t.column(:name, :string) + # + # See TableDefinition#column for details of the options you can use. + def column(column_name, type, options = {}) + @base.add_column(name, column_name, type, options) + end + + # Checks to see if a column exists. + # + # t.string(:name) unless t.column_exists?(:name, :string) + # + # See {connection.column_exists?}[rdoc-ref:SchemaStatements#column_exists?] + def column_exists?(column_name, type = nil, options = {}) + @base.column_exists?(name, column_name, type, options) + end + + # Adds a new index to the table. +column_name+ can be a single Symbol, or + # an Array of Symbols. + # + # t.index(:name) + # t.index([:branch_id, :party_id], unique: true) + # t.index([:branch_id, :party_id], unique: true, name: 'by_branch_party') + # + # See {connection.add_index}[rdoc-ref:SchemaStatements#add_index] for details of the options you can use. + def index(column_name, options = {}) + @base.add_index(name, column_name, options) + end + + # Checks to see if an index exists. + # + # unless t.index_exists?(:branch_id) + # t.index(:branch_id) + # end + # + # See {connection.index_exists?}[rdoc-ref:SchemaStatements#index_exists?] + def index_exists?(column_name, options = {}) + @base.index_exists?(name, column_name, options) + end + + # Renames the given index on the table. + # + # t.rename_index(:user_id, :account_id) + # + # See {connection.rename_index}[rdoc-ref:SchemaStatements#rename_index] + def rename_index(index_name, new_index_name) + @base.rename_index(name, index_name, new_index_name) + end + + # Adds timestamps (+created_at+ and +updated_at+) columns to the table. + # + # t.timestamps(null: false) + # + # See {connection.add_timestamps}[rdoc-ref:SchemaStatements#add_timestamps] + def timestamps(options = {}) + @base.add_timestamps(name, options) + end + + # Changes the column's definition according to the new options. + # + # t.change(:name, :string, limit: 80) + # t.change(:description, :text) + # + # See TableDefinition#column for details of the options you can use. + def change(column_name, type, options = {}) + @base.change_column(name, column_name, type, options) + end + + # Sets a new default value for a column. + # + # t.change_default(:qualification, 'new') + # t.change_default(:authorized, 1) + # t.change_default(:status, from: nil, to: "draft") + # + # See {connection.change_column_default}[rdoc-ref:SchemaStatements#change_column_default] + def change_default(column_name, default_or_changes) + @base.change_column_default(name, column_name, default_or_changes) + end + + # Removes the column(s) from the table definition. + # + # t.remove(:qualification) + # t.remove(:qualification, :experience) + # + # See {connection.remove_columns}[rdoc-ref:SchemaStatements#remove_columns] + def remove(*column_names) + @base.remove_columns(name, *column_names) + end + + # Removes the given index from the table. + # + # t.remove_index(:branch_id) + # t.remove_index(column: [:branch_id, :party_id]) + # t.remove_index(name: :by_branch_party) + # + # See {connection.remove_index}[rdoc-ref:SchemaStatements#remove_index] + def remove_index(options = {}) + @base.remove_index(name, options) + end + + # Removes the timestamp columns (+created_at+ and +updated_at+) from the table. + # + # t.remove_timestamps + # + # See {connection.remove_timestamps}[rdoc-ref:SchemaStatements#remove_timestamps] + def remove_timestamps(options = {}) + @base.remove_timestamps(name, options) + end + + # Renames a column. + # + # t.rename(:description, :name) + # + # See {connection.rename_column}[rdoc-ref:SchemaStatements#rename_column] + def rename(column_name, new_column_name) + @base.rename_column(name, column_name, new_column_name) + end + + # Adds a reference. + # + # t.references(:user) + # t.belongs_to(:supplier, foreign_key: true) + # + # See {connection.add_reference}[rdoc-ref:SchemaStatements#add_reference] for details of the options you can use. + def references(*args, **options) + args.each do |ref_name| + @base.add_reference(name, ref_name, options) + end + end + alias :belongs_to :references + + # Removes a reference. Optionally removes a +type+ column. + # + # t.remove_references(:user) + # t.remove_belongs_to(:supplier, polymorphic: true) + # + # See {connection.remove_reference}[rdoc-ref:SchemaStatements#remove_reference] + def remove_references(*args, **options) + args.each do |ref_name| + @base.remove_reference(name, ref_name, options) + end + end + alias :remove_belongs_to :remove_references + + # Adds a foreign key. + # + # t.foreign_key(:authors) + # + # See {connection.add_foreign_key}[rdoc-ref:SchemaStatements#add_foreign_key] + def foreign_key(*args) + @base.add_foreign_key(name, *args) + end + + # Checks to see if a foreign key exists. + # + # t.foreign_key(:authors) unless t.foreign_key_exists?(:authors) + # + # See {connection.foreign_key_exists?}[rdoc-ref:SchemaStatements#foreign_key_exists?] + def foreign_key_exists?(*args) + @base.foreign_key_exists?(name, *args) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/schema_dumper.rb new file mode 100644 index 00000000..19266034 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/schema_dumper.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/compact" + +module ActiveRecord + module ConnectionAdapters # :nodoc: + class SchemaDumper < SchemaDumper # :nodoc: + def self.create(connection, options) + new(connection, options) + end + + private + def column_spec(column) + [schema_type_with_virtual(column), prepare_column_options(column)] + end + + def column_spec_for_primary_key(column) + return {} if default_primary_key?(column) + spec = { id: schema_type(column).inspect } + spec.merge!(prepare_column_options(column).except!(:null)) + spec[:default] ||= "nil" if explicit_primary_key_default?(column) + spec + end + + def prepare_column_options(column) + spec = {} + spec[:limit] = schema_limit(column) + spec[:precision] = schema_precision(column) + spec[:scale] = schema_scale(column) + spec[:default] = schema_default(column) + spec[:null] = "false" unless column.null + spec[:collation] = schema_collation(column) + spec[:comment] = column.comment.inspect if column.comment.present? + spec.compact! + spec + end + + def default_primary_key?(column) + schema_type(column) == :bigint + end + + def explicit_primary_key_default?(column) + false + end + + def schema_type_with_virtual(column) + if @connection.supports_virtual_columns? && column.virtual? + :virtual + else + schema_type(column) + end + end + + def schema_type(column) + if column.bigint? + :bigint + else + column.type + end + end + + def schema_limit(column) + limit = column.limit unless column.bigint? + limit.inspect if limit && limit != @connection.native_database_types[column.type][:limit] + end + + def schema_precision(column) + column.precision.inspect if column.precision + end + + def schema_scale(column) + column.scale.inspect if column.scale + end + + def schema_default(column) + return unless column.has_default? + type = @connection.lookup_cast_type_from_column(column) + default = type.deserialize(column.default) + if default.nil? + schema_expression(column) + else + type.type_cast_for_schema(default) + end + end + + def schema_expression(column) + "-> { #{column.default_function.inspect} }" if column.default_function + end + + def schema_collation(column) + column.collation.inspect if column.collation + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/schema_statements.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/schema_statements.rb new file mode 100644 index 00000000..e2758213 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -0,0 +1,1396 @@ +# frozen_string_literal: true + +require "active_record/migration/join_table" +require "active_support/core_ext/string/access" +require "digest/sha2" + +module ActiveRecord + module ConnectionAdapters # :nodoc: + module SchemaStatements + include ActiveRecord::Migration::JoinTable + + # Returns a hash of mappings from the abstract data types to the native + # database types. See TableDefinition#column for details on the recognized + # abstract data types. + def native_database_types + {} + end + + def table_options(table_name) + nil + end + + # Returns the table comment that's stored in database metadata. + def table_comment(table_name) + nil + end + + # Truncates a table alias according to the limits of the current adapter. + def table_alias_for(table_name) + table_name[0...table_alias_length].tr(".", "_") + end + + # Returns the relation names useable to back Active Record models. + # For most adapters this means all #tables and #views. + def data_sources + query_values(data_source_sql, "SCHEMA") + rescue NotImplementedError + tables | views + end + + # Checks to see if the data source +name+ exists on the database. + # + # data_source_exists?(:ebooks) + # + def data_source_exists?(name) + query_values(data_source_sql(name), "SCHEMA").any? if name.present? + rescue NotImplementedError + data_sources.include?(name.to_s) + end + + # Returns an array of table names defined in the database. + def tables + query_values(data_source_sql(type: "BASE TABLE"), "SCHEMA") + end + + # Checks to see if the table +table_name+ exists on the database. + # + # table_exists?(:developers) + # + def table_exists?(table_name) + query_values(data_source_sql(table_name, type: "BASE TABLE"), "SCHEMA").any? if table_name.present? + rescue NotImplementedError + tables.include?(table_name.to_s) + end + + # Returns an array of view names defined in the database. + def views + query_values(data_source_sql(type: "VIEW"), "SCHEMA") + end + + # Checks to see if the view +view_name+ exists on the database. + # + # view_exists?(:ebooks) + # + def view_exists?(view_name) + query_values(data_source_sql(view_name, type: "VIEW"), "SCHEMA").any? if view_name.present? + rescue NotImplementedError + views.include?(view_name.to_s) + end + + # Returns an array of indexes for the given table. + def indexes(table_name) + raise NotImplementedError, "#indexes is not implemented" + end + + # Checks to see if an index exists on a table for a given index definition. + # + # # Check an index exists + # index_exists?(:suppliers, :company_id) + # + # # Check an index on multiple columns exists + # index_exists?(:suppliers, [:company_id, :company_type]) + # + # # Check a unique index exists + # index_exists?(:suppliers, :company_id, unique: true) + # + # # Check an index with a custom name exists + # index_exists?(:suppliers, :company_id, name: "idx_company_id") + # + def index_exists?(table_name, column_name, options = {}) + column_names = Array(column_name).map(&:to_s) + checks = [] + checks << lambda { |i| i.columns == column_names } + checks << lambda { |i| i.unique } if options[:unique] + checks << lambda { |i| i.name == options[:name].to_s } if options[:name] + + indexes(table_name).any? { |i| checks.all? { |check| check[i] } } + end + + # Returns an array of +Column+ objects for the table specified by +table_name+. + def columns(table_name) + table_name = table_name.to_s + column_definitions(table_name).map do |field| + new_column_from_field(table_name, field) + end + end + + # Checks to see if a column exists in a given table. + # + # # Check a column exists + # column_exists?(:suppliers, :name) + # + # # Check a column exists of a particular type + # column_exists?(:suppliers, :name, :string) + # + # # Check a column exists with a specific definition + # column_exists?(:suppliers, :name, :string, limit: 100) + # column_exists?(:suppliers, :name, :string, default: 'default') + # column_exists?(:suppliers, :name, :string, null: false) + # column_exists?(:suppliers, :tax, :decimal, precision: 8, scale: 2) + # + def column_exists?(table_name, column_name, type = nil, options = {}) + column_name = column_name.to_s + checks = [] + checks << lambda { |c| c.name == column_name } + checks << lambda { |c| c.type == type } if type + column_options_keys.each do |attr| + checks << lambda { |c| c.send(attr) == options[attr] } if options.key?(attr) + end + + columns(table_name).any? { |c| checks.all? { |check| check[c] } } + end + + # Returns just a table's primary key + def primary_key(table_name) + pk = primary_keys(table_name) + pk = pk.first unless pk.size > 1 + pk + end + + # Creates a new table with the name +table_name+. +table_name+ may either + # be a String or a Symbol. + # + # There are two ways to work with #create_table. You can use the block + # form or the regular form, like this: + # + # === Block form + # + # # create_table() passes a TableDefinition object to the block. + # # This form will not only create the table, but also columns for the + # # table. + # + # create_table(:suppliers) do |t| + # t.column :name, :string, limit: 60 + # # Other fields here + # end + # + # === Block form, with shorthand + # + # # You can also use the column types as method calls, rather than calling the column method. + # create_table(:suppliers) do |t| + # t.string :name, limit: 60 + # # Other fields here + # end + # + # === Regular form + # + # # Creates a table called 'suppliers' with no columns. + # create_table(:suppliers) + # # Add a column to 'suppliers'. + # add_column(:suppliers, :name, :string, {limit: 60}) + # + # The +options+ hash can include the following keys: + # [:id] + # Whether to automatically add a primary key column. Defaults to true. + # Join tables for {ActiveRecord::Base.has_and_belongs_to_many}[rdoc-ref:Associations::ClassMethods#has_and_belongs_to_many] should set it to false. + # + # A Symbol can be used to specify the type of the generated primary key column. + # [:primary_key] + # The name of the primary key, if one is to be added automatically. + # Defaults to +id+. If :id is false, then this option is ignored. + # + # If an array is passed, a composite primary key will be created. + # + # Note that Active Record models will automatically detect their + # primary key. This can be avoided by using + # {self.primary_key=}[rdoc-ref:AttributeMethods::PrimaryKey::ClassMethods#primary_key=] on the model + # to define the key explicitly. + # + # [:options] + # Any extra options you want appended to the table definition. + # [:temporary] + # Make a temporary table. + # [:force] + # Set to true to drop the table before creating it. + # Set to +:cascade+ to drop dependent objects as well. + # Defaults to false. + # [:as] + # SQL to use to generate the table. When this option is used, the block is + # ignored, as are the :id and :primary_key options. + # + # ====== Add a backend specific option to the generated SQL (MySQL) + # + # create_table(:suppliers, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8') + # + # generates: + # + # CREATE TABLE suppliers ( + # id bigint auto_increment PRIMARY KEY + # ) ENGINE=InnoDB DEFAULT CHARSET=utf8 + # + # ====== Rename the primary key column + # + # create_table(:objects, primary_key: 'guid') do |t| + # t.column :name, :string, limit: 80 + # end + # + # generates: + # + # CREATE TABLE objects ( + # guid bigint auto_increment PRIMARY KEY, + # name varchar(80) + # ) + # + # ====== Change the primary key column type + # + # create_table(:tags, id: :string) do |t| + # t.column :label, :string + # end + # + # generates: + # + # CREATE TABLE tags ( + # id varchar PRIMARY KEY, + # label varchar + # ) + # + # ====== Create a composite primary key + # + # create_table(:orders, primary_key: [:product_id, :client_id]) do |t| + # t.belongs_to :product + # t.belongs_to :client + # end + # + # generates: + # + # CREATE TABLE order ( + # product_id bigint NOT NULL, + # client_id bigint NOT NULL + # ); + # + # ALTER TABLE ONLY "orders" + # ADD CONSTRAINT orders_pkey PRIMARY KEY (product_id, client_id); + # + # ====== Do not add a primary key column + # + # create_table(:categories_suppliers, id: false) do |t| + # t.column :category_id, :bigint + # t.column :supplier_id, :bigint + # end + # + # generates: + # + # CREATE TABLE categories_suppliers ( + # category_id bigint, + # supplier_id bigint + # ) + # + # ====== Create a temporary table based on a query + # + # create_table(:long_query, temporary: true, + # as: "SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id") + # + # generates: + # + # CREATE TEMPORARY TABLE long_query AS + # SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id + # + # See also TableDefinition#column for details on how to create columns. + def create_table(table_name, comment: nil, **options) + td = create_table_definition table_name, options[:temporary], options[:options], options[:as], comment: comment + + if options[:id] != false && !options[:as] + pk = options.fetch(:primary_key) do + Base.get_primary_key table_name.to_s.singularize + end + + if pk.is_a?(Array) + td.primary_keys pk + else + td.primary_key pk, options.fetch(:id, :primary_key), options + end + end + + yield td if block_given? + + if options[:force] + drop_table(table_name, options.merge(if_exists: true)) + end + + result = execute schema_creation.accept td + + unless supports_indexes_in_create? + td.indexes.each do |column_name, index_options| + add_index(table_name, column_name, index_options) + end + end + + if supports_comments? && !supports_comments_in_create? + change_table_comment(table_name, comment) if comment.present? + + td.columns.each do |column| + change_column_comment(table_name, column.name, column.comment) if column.comment.present? + end + end + + result + end + + # Creates a new join table with the name created using the lexical order of the first two + # arguments. These arguments can be a String or a Symbol. + # + # # Creates a table called 'assemblies_parts' with no id. + # create_join_table(:assemblies, :parts) + # + # You can pass an +options+ hash which can include the following keys: + # [:table_name] + # Sets the table name, overriding the default. + # [:column_options] + # Any extra options you want appended to the columns definition. + # [:options] + # Any extra options you want appended to the table definition. + # [:temporary] + # Make a temporary table. + # [:force] + # Set to true to drop the table before creating it. + # Defaults to false. + # + # Note that #create_join_table does not create any indices by default; you can use + # its block form to do so yourself: + # + # create_join_table :products, :categories do |t| + # t.index :product_id + # t.index :category_id + # end + # + # ====== Add a backend specific option to the generated SQL (MySQL) + # + # create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8') + # + # generates: + # + # CREATE TABLE assemblies_parts ( + # assembly_id bigint NOT NULL, + # part_id bigint NOT NULL, + # ) ENGINE=InnoDB DEFAULT CHARSET=utf8 + # + def create_join_table(table_1, table_2, column_options: {}, **options) + join_table_name = find_join_table_name(table_1, table_2, options) + + column_options.reverse_merge!(null: false, index: false) + + t1_ref, t2_ref = [table_1, table_2].map { |t| t.to_s.singularize } + + create_table(join_table_name, options.merge!(id: false)) do |td| + td.references t1_ref, column_options + td.references t2_ref, column_options + yield td if block_given? + end + end + + # Drops the join table specified by the given arguments. + # See #create_join_table for details. + # + # Although this command ignores the block if one is given, it can be helpful + # to provide one in a migration's +change+ method so it can be reverted. + # In that case, the block will be used by #create_join_table. + def drop_join_table(table_1, table_2, options = {}) + join_table_name = find_join_table_name(table_1, table_2, options) + drop_table(join_table_name) + end + + # A block for changing columns in +table+. + # + # # change_table() yields a Table instance + # change_table(:suppliers) do |t| + # t.column :name, :string, limit: 60 + # # Other column alterations here + # end + # + # The +options+ hash can include the following keys: + # [:bulk] + # Set this to true to make this a bulk alter query, such as + # + # ALTER TABLE `users` ADD COLUMN age INT, ADD COLUMN birthdate DATETIME ... + # + # Defaults to false. + # + # Only supported on the MySQL and PostgreSQL adapter, ignored elsewhere. + # + # ====== Add a column + # + # change_table(:suppliers) do |t| + # t.column :name, :string, limit: 60 + # end + # + # ====== Add 2 integer columns + # + # change_table(:suppliers) do |t| + # t.integer :width, :height, null: false, default: 0 + # end + # + # ====== Add created_at/updated_at columns + # + # change_table(:suppliers) do |t| + # t.timestamps + # end + # + # ====== Add a foreign key column + # + # change_table(:suppliers) do |t| + # t.references :company + # end + # + # Creates a company_id(bigint) column. + # + # ====== Add a polymorphic foreign key column + # + # change_table(:suppliers) do |t| + # t.belongs_to :company, polymorphic: true + # end + # + # Creates company_type(varchar) and company_id(bigint) columns. + # + # ====== Remove a column + # + # change_table(:suppliers) do |t| + # t.remove :company + # end + # + # ====== Remove several columns + # + # change_table(:suppliers) do |t| + # t.remove :company_id + # t.remove :width, :height + # end + # + # ====== Remove an index + # + # change_table(:suppliers) do |t| + # t.remove_index :company_id + # end + # + # See also Table for details on all of the various column transformations. + def change_table(table_name, options = {}) + if supports_bulk_alter? && options[:bulk] + recorder = ActiveRecord::Migration::CommandRecorder.new(self) + yield update_table_definition(table_name, recorder) + bulk_change_table(table_name, recorder.commands) + else + yield update_table_definition(table_name, self) + end + end + + # Renames a table. + # + # rename_table('octopuses', 'octopi') + # + def rename_table(table_name, new_name) + raise NotImplementedError, "rename_table is not implemented" + end + + # Drops a table from the database. + # + # [:force] + # Set to +:cascade+ to drop dependent objects as well. + # Defaults to false. + # [:if_exists] + # Set to +true+ to only drop the table if it exists. + # Defaults to false. + # + # Although this command ignores most +options+ and the block if one is given, + # it can be helpful to provide these in a migration's +change+ method so it can be reverted. + # In that case, +options+ and the block will be used by #create_table. + def drop_table(table_name, options = {}) + execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}" + end + + # Add a new +type+ column named +column_name+ to +table_name+. + # + # The +type+ parameter is normally one of the migrations native types, + # which is one of the following: + # :primary_key, :string, :text, + # :integer, :bigint, :float, :decimal, :numeric, + # :datetime, :time, :date, + # :binary, :boolean. + # + # You may use a type not in this list as long as it is supported by your + # database (for example, "polygon" in MySQL), but this will not be database + # agnostic and should usually be avoided. + # + # Available options are (none of these exists by default): + # * :limit - + # Requests a maximum column length. This is the number of characters for a :string column + # and number of bytes for :text, :binary and :integer columns. + # This option is ignored by some backends. + # * :default - + # The column's default value. Use +nil+ for +NULL+. + # * :null - + # Allows or disallows +NULL+ values in the column. + # * :precision - + # Specifies the precision for the :decimal and :numeric columns. + # * :scale - + # Specifies the scale for the :decimal and :numeric columns. + # * :comment - + # Specifies the comment for the column. This option is ignored by some backends. + # + # Note: The precision is the total number of significant digits, + # and the scale is the number of digits that can be stored following + # the decimal point. For example, the number 123.45 has a precision of 5 + # and a scale of 2. A decimal with a precision of 5 and a scale of 2 can + # range from -999.99 to 999.99. + # + # Please be aware of different RDBMS implementations behavior with + # :decimal columns: + # * The SQL standard says the default scale should be 0, :scale <= + # :precision, and makes no comments about the requirements of + # :precision. + # * MySQL: :precision [1..63], :scale [0..30]. + # Default is (10,0). + # * PostgreSQL: :precision [1..infinity], + # :scale [0..infinity]. No default. + # * SQLite3: No restrictions on :precision and :scale, + # but the maximum supported :precision is 16. No default. + # * Oracle: :precision [1..38], :scale [-84..127]. + # Default is (38,0). + # * DB2: :precision [1..63], :scale [0..62]. + # Default unknown. + # * SqlServer: :precision [1..38], :scale [0..38]. + # Default (38,0). + # + # == Examples + # + # add_column(:users, :picture, :binary, limit: 2.megabytes) + # # ALTER TABLE "users" ADD "picture" blob(2097152) + # + # add_column(:articles, :status, :string, limit: 20, default: 'draft', null: false) + # # ALTER TABLE "articles" ADD "status" varchar(20) DEFAULT 'draft' NOT NULL + # + # add_column(:answers, :bill_gates_money, :decimal, precision: 15, scale: 2) + # # ALTER TABLE "answers" ADD "bill_gates_money" decimal(15,2) + # + # add_column(:measurements, :sensor_reading, :decimal, precision: 30, scale: 20) + # # ALTER TABLE "measurements" ADD "sensor_reading" decimal(30,20) + # + # # While :scale defaults to zero on most databases, it + # # probably wouldn't hurt to include it. + # add_column(:measurements, :huge_integer, :decimal, precision: 30) + # # ALTER TABLE "measurements" ADD "huge_integer" decimal(30) + # + # # Defines a column that stores an array of a type. + # add_column(:users, :skills, :text, array: true) + # # ALTER TABLE "users" ADD "skills" text[] + # + # # Defines a column with a database-specific type. + # add_column(:shapes, :triangle, 'polygon') + # # ALTER TABLE "shapes" ADD "triangle" polygon + def add_column(table_name, column_name, type, options = {}) + at = create_alter_table table_name + at.add_column(column_name, type, options) + execute schema_creation.accept at + end + + # Removes the given columns from the table definition. + # + # remove_columns(:suppliers, :qualification, :experience) + # + def remove_columns(table_name, *column_names) + raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)") if column_names.empty? + column_names.each do |column_name| + remove_column(table_name, column_name) + end + end + + # Removes the column from the table definition. + # + # remove_column(:suppliers, :qualification) + # + # The +type+ and +options+ parameters will be ignored if present. It can be helpful + # to provide these in a migration's +change+ method so it can be reverted. + # In that case, +type+ and +options+ will be used by #add_column. + def remove_column(table_name, column_name, type = nil, options = {}) + execute "ALTER TABLE #{quote_table_name(table_name)} #{remove_column_for_alter(table_name, column_name, type, options)}" + end + + # Changes the column's definition according to the new options. + # See TableDefinition#column for details of the options you can use. + # + # change_column(:suppliers, :name, :string, limit: 80) + # change_column(:accounts, :description, :text) + # + def change_column(table_name, column_name, type, options = {}) + raise NotImplementedError, "change_column is not implemented" + end + + # Sets a new default value for a column: + # + # change_column_default(:suppliers, :qualification, 'new') + # change_column_default(:accounts, :authorized, 1) + # + # Setting the default to +nil+ effectively drops the default: + # + # change_column_default(:users, :email, nil) + # + # Passing a hash containing +:from+ and +:to+ will make this change + # reversible in migration: + # + # change_column_default(:posts, :state, from: nil, to: "draft") + # + def change_column_default(table_name, column_name, default_or_changes) + raise NotImplementedError, "change_column_default is not implemented" + end + + # Sets or removes a NOT NULL constraint on a column. The +null+ flag + # indicates whether the value can be +NULL+. For example + # + # change_column_null(:users, :nickname, false) + # + # says nicknames cannot be +NULL+ (adds the constraint), whereas + # + # change_column_null(:users, :nickname, true) + # + # allows them to be +NULL+ (drops the constraint). + # + # The method accepts an optional fourth argument to replace existing + # NULLs with some other value. Use that one when enabling the + # constraint if needed, since otherwise those rows would not be valid. + # + # Please note the fourth argument does not set a column's default. + def change_column_null(table_name, column_name, null, default = nil) + raise NotImplementedError, "change_column_null is not implemented" + end + + # Renames a column. + # + # rename_column(:suppliers, :description, :name) + # + def rename_column(table_name, column_name, new_column_name) + raise NotImplementedError, "rename_column is not implemented" + end + + # Adds a new index to the table. +column_name+ can be a single Symbol, or + # an Array of Symbols. + # + # The index will be named after the table and the column name(s), unless + # you pass :name as an option. + # + # ====== Creating a simple index + # + # add_index(:suppliers, :name) + # + # generates: + # + # CREATE INDEX suppliers_name_index ON suppliers(name) + # + # ====== Creating a unique index + # + # add_index(:accounts, [:branch_id, :party_id], unique: true) + # + # generates: + # + # CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id) + # + # ====== Creating a named index + # + # add_index(:accounts, [:branch_id, :party_id], unique: true, name: 'by_branch_party') + # + # generates: + # + # CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id) + # + # ====== Creating an index with specific key length + # + # add_index(:accounts, :name, name: 'by_name', length: 10) + # + # generates: + # + # CREATE INDEX by_name ON accounts(name(10)) + # + # ====== Creating an index with specific key lengths for multiple keys + # + # add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15}) + # + # generates: + # + # CREATE INDEX by_name_surname ON accounts(name(10), surname(15)) + # + # Note: SQLite doesn't support index length. + # + # ====== Creating an index with a sort order (desc or asc, asc is the default) + # + # add_index(:accounts, [:branch_id, :party_id, :surname], order: {branch_id: :desc, party_id: :asc}) + # + # generates: + # + # CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname) + # + # Note: MySQL only supports index order from 8.0.1 onwards (earlier versions accepted the syntax but ignored it). + # + # ====== Creating a partial index + # + # add_index(:accounts, [:branch_id, :party_id], unique: true, where: "active") + # + # generates: + # + # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active + # + # Note: Partial indexes are only supported for PostgreSQL and SQLite 3.8.0+. + # + # ====== Creating an index with a specific method + # + # add_index(:developers, :name, using: 'btree') + # + # generates: + # + # CREATE INDEX index_developers_on_name ON developers USING btree (name) -- PostgreSQL + # CREATE INDEX index_developers_on_name USING btree ON developers (name) -- MySQL + # + # Note: only supported by PostgreSQL and MySQL + # + # ====== Creating an index with a specific operator class + # + # add_index(:developers, :name, using: 'gist', opclass: :gist_trgm_ops) + # # CREATE INDEX developers_on_name ON developers USING gist (name gist_trgm_ops) -- PostgreSQL + # + # add_index(:developers, [:name, :city], using: 'gist', opclass: { city: :gist_trgm_ops }) + # # CREATE INDEX developers_on_name_and_city ON developers USING gist (name, city gist_trgm_ops) -- PostgreSQL + # + # add_index(:developers, [:name, :city], using: 'gist', opclass: :gist_trgm_ops) + # # CREATE INDEX developers_on_name_and_city ON developers USING gist (name gist_trgm_ops, city gist_trgm_ops) -- PostgreSQL + # + # Note: only supported by PostgreSQL + # + # ====== Creating an index with a specific type + # + # add_index(:developers, :name, type: :fulltext) + # + # generates: + # + # CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL + # + # Note: only supported by MySQL. + def add_index(table_name, column_name, options = {}) + index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options) + execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}" + end + + # Removes the given index from the table. + # + # Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists. + # + # remove_index :accounts, :branch_id + # + # Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists. + # + # remove_index :accounts, column: :branch_id + # + # Removes the index on +branch_id+ and +party_id+ in the +accounts+ table if exactly one such index exists. + # + # remove_index :accounts, column: [:branch_id, :party_id] + # + # Removes the index named +by_branch_party+ in the +accounts+ table. + # + # remove_index :accounts, name: :by_branch_party + # + def remove_index(table_name, options = {}) + index_name = index_name_for_remove(table_name, options) + execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}" + end + + # Renames an index. + # + # Rename the +index_people_on_last_name+ index to +index_users_on_last_name+: + # + # rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name' + # + def rename_index(table_name, old_name, new_name) + validate_index_length!(table_name, new_name) + + # this is a naive implementation; some DBs may support this more efficiently (PostgreSQL, for instance) + old_index_def = indexes(table_name).detect { |i| i.name == old_name } + return unless old_index_def + add_index(table_name, old_index_def.columns, name: new_name, unique: old_index_def.unique) + remove_index(table_name, name: old_name) + end + + def index_name(table_name, options) #:nodoc: + if Hash === options + if options[:column] + "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}" + elsif options[:name] + options[:name] + else + raise ArgumentError, "You must specify the index name" + end + else + index_name(table_name, index_name_options(options)) + end + end + + # Verifies the existence of an index with a given name. + def index_name_exists?(table_name, index_name) + index_name = index_name.to_s + indexes(table_name).detect { |i| i.name == index_name } + end + + # Adds a reference. The reference column is a bigint by default, + # the :type option can be used to specify a different type. + # Optionally adds a +_type+ column, if :polymorphic option is provided. + # #add_reference and #add_belongs_to are acceptable. + # + # The +options+ hash can include the following keys: + # [:type] + # The reference column type. Defaults to +:bigint+. + # [:index] + # Add an appropriate index. Defaults to true. + # See #add_index for usage of this option. + # [:foreign_key] + # Add an appropriate foreign key constraint. Defaults to false. + # [:polymorphic] + # Whether an additional +_type+ column should be added. Defaults to false. + # [:null] + # Whether the column allows nulls. Defaults to true. + # + # ====== Create a user_id bigint column + # + # add_reference(:products, :user) + # + # ====== Create a user_id string column + # + # add_reference(:products, :user, type: :string) + # + # ====== Create supplier_id, supplier_type columns and appropriate index + # + # add_reference(:products, :supplier, polymorphic: true, index: true) + # + # ====== Create a supplier_id column with a unique index + # + # add_reference(:products, :supplier, index: { unique: true }) + # + # ====== Create a supplier_id column with a named index + # + # add_reference(:products, :supplier, index: { name: "my_supplier_index" }) + # + # ====== Create a supplier_id column and appropriate foreign key + # + # add_reference(:products, :supplier, foreign_key: true) + # + # ====== Create a supplier_id column and a foreign key to the firms table + # + # add_reference(:products, :supplier, foreign_key: {to_table: :firms}) + # + def add_reference(table_name, ref_name, **options) + ReferenceDefinition.new(ref_name, options).add_to(update_table_definition(table_name, self)) + end + alias :add_belongs_to :add_reference + + # Removes the reference(s). Also removes a +type+ column if one exists. + # #remove_reference and #remove_belongs_to are acceptable. + # + # ====== Remove the reference + # + # remove_reference(:products, :user, index: true) + # + # ====== Remove polymorphic reference + # + # remove_reference(:products, :supplier, polymorphic: true) + # + # ====== Remove the reference with a foreign key + # + # remove_reference(:products, :user, index: true, foreign_key: true) + # + def remove_reference(table_name, ref_name, foreign_key: false, polymorphic: false, **options) + if foreign_key + reference_name = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name + if foreign_key.is_a?(Hash) + foreign_key_options = foreign_key + else + foreign_key_options = { to_table: reference_name } + end + foreign_key_options[:column] ||= "#{ref_name}_id" + remove_foreign_key(table_name, foreign_key_options) + end + + remove_column(table_name, "#{ref_name}_id") + remove_column(table_name, "#{ref_name}_type") if polymorphic + end + alias :remove_belongs_to :remove_reference + + # Returns an array of foreign keys for the given table. + # The foreign keys are represented as ForeignKeyDefinition objects. + def foreign_keys(table_name) + raise NotImplementedError, "foreign_keys is not implemented" + end + + # Adds a new foreign key. +from_table+ is the table with the key column, + # +to_table+ contains the referenced primary key. + # + # The foreign key will be named after the following pattern: fk_rails_. + # +identifier+ is a 10 character long string which is deterministically generated from the + # +from_table+ and +column+. A custom name can be specified with the :name option. + # + # ====== Creating a simple foreign key + # + # add_foreign_key :articles, :authors + # + # generates: + # + # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id") + # + # ====== Creating a foreign key on a specific column + # + # add_foreign_key :articles, :users, column: :author_id, primary_key: "lng_id" + # + # generates: + # + # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_58ca3d3a82 FOREIGN KEY ("author_id") REFERENCES "users" ("lng_id") + # + # ====== Creating a cascading foreign key + # + # add_foreign_key :articles, :authors, on_delete: :cascade + # + # generates: + # + # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE + # + # The +options+ hash can include the following keys: + # [:column] + # The foreign key column name on +from_table+. Defaults to to_table.singularize + "_id" + # [:primary_key] + # The primary key column name on +to_table+. Defaults to +id+. + # [:name] + # The constraint name. Defaults to fk_rails_. + # [:on_delete] + # Action that happens ON DELETE. Valid values are +:nullify+, +:cascade+ and +:restrict+ + # [:on_update] + # Action that happens ON UPDATE. Valid values are +:nullify+, +:cascade+ and +:restrict+ + # [:validate] + # (Postgres only) Specify whether or not the constraint should be validated. Defaults to +true+. + def add_foreign_key(from_table, to_table, options = {}) + return unless supports_foreign_keys? + + options = foreign_key_options(from_table, to_table, options) + at = create_alter_table from_table + at.add_foreign_key to_table, options + + execute schema_creation.accept(at) + end + + # Removes the given foreign key from the table. Any option parameters provided + # will be used to re-add the foreign key in case of a migration rollback. + # It is recommended that you provide any options used when creating the foreign + # key so that the migration can be reverted properly. + # + # Removes the foreign key on +accounts.branch_id+. + # + # remove_foreign_key :accounts, :branches + # + # Removes the foreign key on +accounts.owner_id+. + # + # remove_foreign_key :accounts, column: :owner_id + # + # Removes the foreign key named +special_fk_name+ on the +accounts+ table. + # + # remove_foreign_key :accounts, name: :special_fk_name + # + # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key. + def remove_foreign_key(from_table, options_or_to_table = {}) + return unless supports_foreign_keys? + + fk_name_to_delete = foreign_key_for!(from_table, options_or_to_table).name + + at = create_alter_table from_table + at.drop_foreign_key fk_name_to_delete + + execute schema_creation.accept(at) + end + + # Checks to see if a foreign key exists on a table for a given foreign key definition. + # + # # Checks to see if a foreign key exists. + # foreign_key_exists?(:accounts, :branches) + # + # # Checks to see if a foreign key on a specified column exists. + # foreign_key_exists?(:accounts, column: :owner_id) + # + # # Checks to see if a foreign key with a custom name exists. + # foreign_key_exists?(:accounts, name: "special_fk_name") + # + def foreign_key_exists?(from_table, options_or_to_table = {}) + foreign_key_for(from_table, options_or_to_table).present? + end + + def foreign_key_column_for(table_name) # :nodoc: + prefix = Base.table_name_prefix + suffix = Base.table_name_suffix + name = table_name.to_s =~ /#{prefix}(.+)#{suffix}/ ? $1 : table_name.to_s + "#{name.singularize}_id" + end + + def foreign_key_options(from_table, to_table, options) # :nodoc: + options = options.dup + options[:column] ||= foreign_key_column_for(to_table) + options[:name] ||= foreign_key_name(from_table, options) + options + end + + def dump_schema_information #:nodoc: + versions = ActiveRecord::SchemaMigration.all_versions + insert_versions_sql(versions) if versions.any? + end + + def internal_string_options_for_primary_key # :nodoc: + { primary_key: true } + end + + def assume_migrated_upto_version(version, migrations_paths) + migrations_paths = Array(migrations_paths) + version = version.to_i + sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name) + + migrated = ActiveRecord::SchemaMigration.all_versions.map(&:to_i) + versions = migration_context.migration_files.map do |file| + migration_context.parse_migration_filename(file).first.to_i + end + + unless migrated.include?(version) + execute "INSERT INTO #{sm_table} (version) VALUES (#{quote(version)})" + end + + inserting = (versions - migrated).select { |v| v < version } + if inserting.any? + if (duplicate = inserting.detect { |v| inserting.count(v) > 1 }) + raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict." + end + if supports_multi_insert? + execute insert_versions_sql(inserting) + else + inserting.each do |v| + execute insert_versions_sql(v) + end + end + end + end + + def type_to_sql(type, limit: nil, precision: nil, scale: nil, **) # :nodoc: + type = type.to_sym if type + if native = native_database_types[type] + column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup + + if type == :decimal # ignore limit, use precision and scale + scale ||= native[:scale] + + if precision ||= native[:precision] + if scale + column_type_sql << "(#{precision},#{scale})" + else + column_type_sql << "(#{precision})" + end + elsif scale + raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified" + end + + elsif [:datetime, :timestamp, :time, :interval].include?(type) && precision ||= native[:precision] + if (0..6) === precision + column_type_sql << "(#{precision})" + else + raise(ActiveRecordError, "No #{native[:name]} type has precision of #{precision}. The allowed range of precision is from 0 to 6") + end + elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit]) + column_type_sql << "(#{limit})" + end + + column_type_sql + else + type.to_s + end + end + + # Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT. + # PostgreSQL, MySQL, and Oracle override this for custom DISTINCT syntax - they + # require the order columns appear in the SELECT. + # + # columns_for_distinct("posts.id", ["posts.created_at desc"]) + # + def columns_for_distinct(columns, orders) # :nodoc: + columns + end + + # Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+. + # Additional options (like +:null+) are forwarded to #add_column. + # + # add_timestamps(:suppliers, null: true) + # + def add_timestamps(table_name, options = {}) + options[:null] = false if options[:null].nil? + + add_column table_name, :created_at, :datetime, options + add_column table_name, :updated_at, :datetime, options + end + + # Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition. + # + # remove_timestamps(:suppliers) + # + def remove_timestamps(table_name, options = {}) + remove_column table_name, :updated_at + remove_column table_name, :created_at + end + + def update_table_definition(table_name, base) #:nodoc: + Table.new(table_name, base) + end + + def add_index_options(table_name, column_name, comment: nil, **options) # :nodoc: + column_names = index_column_names(column_name) + + options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type, :opclass) + + index_type = options[:type].to_s if options.key?(:type) + index_type ||= options[:unique] ? "UNIQUE" : "" + index_name = options[:name].to_s if options.key?(:name) + index_name ||= index_name(table_name, column_names) + + if options.key?(:algorithm) + algorithm = index_algorithms.fetch(options[:algorithm]) { + raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}") + } + end + + using = "USING #{options[:using]}" if options[:using].present? + + if supports_partial_index? + index_options = options[:where] ? " WHERE #{options[:where]}" : "" + end + + validate_index_length!(table_name, index_name, options.fetch(:internal, false)) + + if data_source_exists?(table_name) && index_name_exists?(table_name, index_name) + raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists" + end + index_columns = quoted_columns_for_index(column_names, options).join(", ") + + [index_name, index_type, index_columns, index_options, algorithm, using, comment] + end + + def options_include_default?(options) + options.include?(:default) && !(options[:null] == false && options[:default].nil?) + end + + # Changes the comment for a table or removes it if +nil+. + def change_table_comment(table_name, comment) + raise NotImplementedError, "#{self.class} does not support changing table comments" + end + + # Changes the comment for a column or removes it if +nil+. + def change_column_comment(table_name, column_name, comment) + raise NotImplementedError, "#{self.class} does not support changing column comments" + end + + def create_schema_dumper(options) # :nodoc: + SchemaDumper.create(self, options) + end + + private + def column_options_keys + [:limit, :precision, :scale, :default, :null, :collation, :comment] + end + + def add_index_sort_order(quoted_columns, **options) + orders = options_for_index_columns(options[:order]) + quoted_columns.each do |name, column| + column << " #{orders[name].upcase}" if orders[name].present? + end + end + + def options_for_index_columns(options) + if options.is_a?(Hash) + options.symbolize_keys + else + Hash.new { |hash, column| hash[column] = options } + end + end + + # Overridden by the MySQL adapter for supporting index lengths and by + # the PostgreSQL adapter for supporting operator classes. + def add_options_for_index_columns(quoted_columns, **options) + if supports_index_sort_order? + quoted_columns = add_index_sort_order(quoted_columns, options) + end + + quoted_columns + end + + def quoted_columns_for_index(column_names, **options) + return [column_names] if column_names.is_a?(String) + + quoted_columns = Hash[column_names.map { |name| [name.to_sym, quote_column_name(name).dup] }] + add_options_for_index_columns(quoted_columns, options).values + end + + def index_name_for_remove(table_name, options = {}) + return options[:name] if can_remove_index_by_name?(options) + + checks = [] + + if options.is_a?(Hash) + checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name) + column_names = index_column_names(options[:column]) + else + column_names = index_column_names(options) + end + + if column_names.present? + checks << lambda { |i| index_name(table_name, i.columns) == index_name(table_name, column_names) } + end + + raise ArgumentError, "No name or columns specified" if checks.none? + + matching_indexes = indexes(table_name).select { |i| checks.all? { |check| check[i] } } + + if matching_indexes.count > 1 + raise ArgumentError, "Multiple indexes found on #{table_name} columns #{column_names}. " \ + "Specify an index name from #{matching_indexes.map(&:name).join(', ')}" + elsif matching_indexes.none? + raise ArgumentError, "No indexes found on #{table_name} with the options provided." + else + matching_indexes.first.name + end + end + + def rename_table_indexes(table_name, new_name) + indexes(new_name).each do |index| + generated_index_name = index_name(table_name, column: index.columns) + if generated_index_name == index.name + rename_index new_name, generated_index_name, index_name(new_name, column: index.columns) + end + end + end + + def rename_column_indexes(table_name, column_name, new_column_name) + column_name, new_column_name = column_name.to_s, new_column_name.to_s + indexes(table_name).each do |index| + next unless index.columns.include?(new_column_name) + old_columns = index.columns.dup + old_columns[old_columns.index(new_column_name)] = column_name + generated_index_name = index_name(table_name, column: old_columns) + if generated_index_name == index.name + rename_index table_name, generated_index_name, index_name(table_name, column: index.columns) + end + end + end + + def schema_creation + SchemaCreation.new(self) + end + + def create_table_definition(*args) + TableDefinition.new(*args) + end + + def create_alter_table(name) + AlterTable.new create_table_definition(name) + end + + def fetch_type_metadata(sql_type) + cast_type = lookup_cast_type(sql_type) + SqlTypeMetadata.new( + sql_type: sql_type, + type: cast_type.type, + limit: cast_type.limit, + precision: cast_type.precision, + scale: cast_type.scale, + ) + end + + def index_column_names(column_names) + if column_names.is_a?(String) && /\W/.match?(column_names) + column_names + else + Array(column_names) + end + end + + def index_name_options(column_names) + if column_names.is_a?(String) && /\W/.match?(column_names) + column_names = column_names.scan(/\w+/).join("_") + end + + { column: column_names } + end + + def foreign_key_name(table_name, options) + options.fetch(:name) do + identifier = "#{table_name}_#{options.fetch(:column)}_fk" + hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10) + + "fk_rails_#{hashed_identifier}" + end + end + + def foreign_key_for(from_table, options_or_to_table = {}) + return unless supports_foreign_keys? + foreign_keys(from_table).detect { |fk| fk.defined_for? options_or_to_table } + end + + def foreign_key_for!(from_table, options_or_to_table = {}) + foreign_key_for(from_table, options_or_to_table) || \ + raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{options_or_to_table}") + end + + def extract_foreign_key_action(specifier) + case specifier + when "CASCADE"; :cascade + when "SET NULL"; :nullify + when "RESTRICT"; :restrict + end + end + + def validate_index_length!(table_name, new_name, internal = false) + max_index_length = internal ? index_name_length : allowed_index_name_length + + if new_name.length > max_index_length + raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters" + end + end + + def extract_new_default_value(default_or_changes) + if default_or_changes.is_a?(Hash) && default_or_changes.has_key?(:from) && default_or_changes.has_key?(:to) + default_or_changes[:to] + else + default_or_changes + end + end + + def can_remove_index_by_name?(options) + options.is_a?(Hash) && options.key?(:name) && options.except(:name, :algorithm).empty? + end + + def add_column_for_alter(table_name, column_name, type, options = {}) + td = create_table_definition(table_name) + cd = td.new_column_definition(column_name, type, options) + schema_creation.accept(AddColumnDefinition.new(cd)) + end + + def remove_column_for_alter(table_name, column_name, type = nil, options = {}) + "DROP COLUMN #{quote_column_name(column_name)}" + end + + def remove_columns_for_alter(table_name, *column_names) + column_names.map { |column_name| remove_column_for_alter(table_name, column_name) } + end + + def insert_versions_sql(versions) + sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name) + + if versions.is_a?(Array) + sql = "INSERT INTO #{sm_table} (version) VALUES\n".dup + sql << versions.map { |v| "(#{quote(v)})" }.join(",\n") + sql << ";\n\n" + sql + else + "INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});" + end + end + + def data_source_sql(name = nil, type: nil) + raise NotImplementedError + end + + def quoted_scope(name = nil, type: nil) + raise NotImplementedError + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/transaction.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/transaction.rb new file mode 100644 index 00000000..eb2824d1 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract/transaction.rb @@ -0,0 +1,283 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + class TransactionState + def initialize(state = nil) + @state = state + @children = [] + end + + def add_child(state) + @children << state + end + + def finalized? + @state + end + + def committed? + @state == :committed || @state == :fully_committed + end + + def fully_committed? + @state == :fully_committed + end + + def rolledback? + @state == :rolledback || @state == :fully_rolledback + end + + def fully_rolledback? + @state == :fully_rolledback + end + + def fully_completed? + completed? + end + + def completed? + committed? || rolledback? + end + + def set_state(state) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + The set_state method is deprecated and will be removed in + Rails 6.0. Please use rollback! or commit! to set transaction + state directly. + MSG + case state + when :rolledback + rollback! + when :committed + commit! + when nil + nullify! + else + raise ArgumentError, "Invalid transaction state: #{state}" + end + end + + def rollback! + @children.each { |c| c.rollback! } + @state = :rolledback + end + + def full_rollback! + @children.each { |c| c.rollback! } + @state = :fully_rolledback + end + + def commit! + @state = :committed + end + + def full_commit! + @state = :fully_committed + end + + def nullify! + @state = nil + end + end + + class NullTransaction #:nodoc: + def initialize; end + def state; end + def closed?; true; end + def open?; false; end + def joinable?; false; end + def add_record(record); end + end + + class Transaction #:nodoc: + attr_reader :connection, :state, :records, :savepoint_name + attr_writer :joinable + + def initialize(connection, options, run_commit_callbacks: false) + @connection = connection + @state = TransactionState.new + @records = [] + @joinable = options.fetch(:joinable, true) + @run_commit_callbacks = run_commit_callbacks + end + + def add_record(record) + records << record + end + + def rollback_records + ite = records.uniq + while record = ite.shift + record.rolledback!(force_restore_state: full_rollback?) + end + ensure + ite.each do |i| + i.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: false) + end + end + + def before_commit_records + records.uniq.each(&:before_committed!) if @run_commit_callbacks + end + + def commit_records + ite = records.uniq + while record = ite.shift + if @run_commit_callbacks + record.committed! + else + # if not running callbacks, only adds the record to the parent transaction + record.add_to_transaction + end + end + ensure + ite.each { |i| i.committed!(should_run_callbacks: false) } + end + + def full_rollback?; true; end + def joinable?; @joinable; end + def closed?; false; end + def open?; !closed?; end + end + + class SavepointTransaction < Transaction + def initialize(connection, savepoint_name, parent_transaction, options, *args) + super(connection, options, *args) + + parent_transaction.state.add_child(@state) + + if options[:isolation] + raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction" + end + connection.create_savepoint(@savepoint_name = savepoint_name) + end + + def rollback + connection.rollback_to_savepoint(savepoint_name) + @state.rollback! + end + + def commit + connection.release_savepoint(savepoint_name) + @state.commit! + end + + def full_rollback?; false; end + end + + class RealTransaction < Transaction + def initialize(connection, options, *args) + super + if options[:isolation] + connection.begin_isolated_db_transaction(options[:isolation]) + else + connection.begin_db_transaction + end + end + + def rollback + connection.rollback_db_transaction + @state.full_rollback! + end + + def commit + connection.commit_db_transaction + @state.full_commit! + end + end + + class TransactionManager #:nodoc: + def initialize(connection) + @stack = [] + @connection = connection + end + + def begin_transaction(options = {}) + @connection.lock.synchronize do + run_commit_callbacks = !current_transaction.joinable? + transaction = + if @stack.empty? + RealTransaction.new(@connection, options, run_commit_callbacks: run_commit_callbacks) + else + SavepointTransaction.new(@connection, "active_record_#{@stack.size}", @stack.last, options, + run_commit_callbacks: run_commit_callbacks) + end + + @stack.push(transaction) + transaction + end + end + + def commit_transaction + @connection.lock.synchronize do + transaction = @stack.last + + begin + transaction.before_commit_records + ensure + @stack.pop + end + + transaction.commit + transaction.commit_records + end + end + + def rollback_transaction(transaction = nil) + @connection.lock.synchronize do + transaction ||= @stack.pop + transaction.rollback + transaction.rollback_records + end + end + + def within_new_transaction(options = {}) + @connection.lock.synchronize do + begin + transaction = begin_transaction options + yield + rescue Exception => error + if transaction + rollback_transaction + after_failure_actions(transaction, error) + end + raise + ensure + unless error + if Thread.current.status == "aborting" + rollback_transaction if transaction + else + begin + commit_transaction if transaction + rescue Exception + rollback_transaction(transaction) unless transaction.state.completed? + raise + end + end + end + end + end + end + + def open_transactions + @stack.size + end + + def current_transaction + @stack.last || NULL_TRANSACTION + end + + private + + NULL_TRANSACTION = NullTransaction.new + + # Deallocate invalidated prepared statements outside of the transaction + def after_failure_actions(transaction, error) + return unless transaction.is_a?(RealTransaction) + return unless error.is_a?(ActiveRecord::PreparedStatementCacheExpired) + @connection.clear_cache! + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract_adapter.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract_adapter.rb new file mode 100644 index 00000000..ed681fd4 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract_adapter.rb @@ -0,0 +1,628 @@ +# frozen_string_literal: true + +require "active_record/connection_adapters/determine_if_preparable_visitor" +require "active_record/connection_adapters/schema_cache" +require "active_record/connection_adapters/sql_type_metadata" +require "active_record/connection_adapters/abstract/schema_dumper" +require "active_record/connection_adapters/abstract/schema_creation" +require "active_support/concurrency/load_interlock_aware_monitor" +require "arel/collectors/bind" +require "arel/collectors/composite" +require "arel/collectors/sql_string" +require "arel/collectors/substitute_binds" + +module ActiveRecord + module ConnectionAdapters # :nodoc: + extend ActiveSupport::Autoload + + autoload :Column + autoload :ConnectionSpecification + + autoload_at "active_record/connection_adapters/abstract/schema_definitions" do + autoload :IndexDefinition + autoload :ColumnDefinition + autoload :ChangeColumnDefinition + autoload :ForeignKeyDefinition + autoload :TableDefinition + autoload :Table + autoload :AlterTable + autoload :ReferenceDefinition + end + + autoload_at "active_record/connection_adapters/abstract/connection_pool" do + autoload :ConnectionHandler + end + + autoload_under "abstract" do + autoload :SchemaStatements + autoload :DatabaseStatements + autoload :DatabaseLimits + autoload :Quoting + autoload :ConnectionPool + autoload :QueryCache + autoload :Savepoints + end + + autoload_at "active_record/connection_adapters/abstract/transaction" do + autoload :TransactionManager + autoload :NullTransaction + autoload :RealTransaction + autoload :SavepointTransaction + autoload :TransactionState + end + + # Active Record supports multiple database systems. AbstractAdapter and + # related classes form the abstraction layer which makes this possible. + # An AbstractAdapter represents a connection to a database, and provides an + # abstract interface for database-specific functionality such as establishing + # a connection, escaping values, building the right SQL fragments for +:offset+ + # and +:limit+ options, etc. + # + # All the concrete database adapters follow the interface laid down in this class. + # {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling#connection] returns an AbstractAdapter object, which + # you can use. + # + # Most of the methods in the adapter are useful during migrations. Most + # notably, the instance methods provided by SchemaStatements are very useful. + class AbstractAdapter + ADAPTER_NAME = "Abstract".freeze + include ActiveSupport::Callbacks + define_callbacks :checkout, :checkin + + include Quoting, DatabaseStatements, SchemaStatements + include DatabaseLimits + include QueryCache + include Savepoints + + SIMPLE_INT = /\A\d+\z/ + + attr_accessor :visitor, :pool + attr_reader :schema_cache, :owner, :logger, :prepared_statements, :lock + alias :in_use? :owner + + def self.type_cast_config_to_integer(config) + if config.is_a?(Integer) + config + elsif config =~ SIMPLE_INT + config.to_i + else + config + end + end + + def self.type_cast_config_to_boolean(config) + if config == "false" + false + else + config + end + end + + def initialize(connection, logger = nil, config = {}) # :nodoc: + super() + + @connection = connection + @owner = nil + @instrumenter = ActiveSupport::Notifications.instrumenter + @logger = logger + @config = config + @pool = nil + @idle_since = Concurrent.monotonic_time + @schema_cache = SchemaCache.new self + @quoted_column_names, @quoted_table_names = {}, {} + @visitor = arel_visitor + @lock = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new + + if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) + @prepared_statements = true + @visitor.extend(DetermineIfPreparableVisitor) + else + @prepared_statements = false + end + end + + def migrations_paths # :nodoc: + @config[:migrations_paths] || Migrator.migrations_paths + end + + def migration_context # :nodoc: + MigrationContext.new(migrations_paths) + end + + class Version + include Comparable + + def initialize(version_string) + @version = version_string.split(".").map(&:to_i) + end + + def <=>(version_string) + @version <=> version_string.split(".").map(&:to_i) + end + end + + def valid_type?(type) # :nodoc: + !native_database_types[type].nil? + end + + # this method must only be called while holding connection pool's mutex + def lease + if in_use? + msg = "Cannot lease connection, ".dup + if @owner == Thread.current + msg << "it is already leased by the current thread." + else + msg << "it is already in use by a different thread: #{@owner}. " \ + "Current thread: #{Thread.current}." + end + raise ActiveRecordError, msg + end + + @owner = Thread.current + end + + def schema_cache=(cache) + cache.connection = self + @schema_cache = cache + end + + # this method must only be called while holding connection pool's mutex + def expire + if in_use? + if @owner != Thread.current + raise ActiveRecordError, "Cannot expire connection, " \ + "it is owned by a different thread: #{@owner}. " \ + "Current thread: #{Thread.current}." + end + + @idle_since = Concurrent.monotonic_time + @owner = nil + else + raise ActiveRecordError, "Cannot expire connection, it is not currently leased." + end + end + + # this method must only be called while holding connection pool's mutex (and a desire for segfaults) + def steal! # :nodoc: + if in_use? + if @owner != Thread.current + pool.send :remove_connection_from_thread_cache, self, @owner + + @owner = Thread.current + end + else + raise ActiveRecordError, "Cannot steal connection, it is not currently leased." + end + end + + # Seconds since this connection was returned to the pool + def seconds_idle # :nodoc: + return 0 if in_use? + Concurrent.monotonic_time - @idle_since + end + + def unprepared_statement + old_prepared_statements, @prepared_statements = @prepared_statements, false + yield + ensure + @prepared_statements = old_prepared_statements + end + + # Returns the human-readable name of the adapter. Use mixed case - one + # can always use downcase if needed. + def adapter_name + self.class::ADAPTER_NAME + end + + # Does this adapter support DDL rollbacks in transactions? That is, would + # CREATE TABLE or ALTER TABLE get rolled back by a transaction? + def supports_ddl_transactions? + false + end + + def supports_bulk_alter? + false + end + + # Does this adapter support savepoints? + def supports_savepoints? + false + end + + # Does this adapter support application-enforced advisory locking? + def supports_advisory_locks? + false + end + + # Should primary key values be selected from their corresponding + # sequence before the insert statement? If true, next_sequence_value + # is called before each insert to set the record's primary key. + def prefetch_primary_key?(table_name = nil) + false + end + + # Does this adapter support index sort order? + def supports_index_sort_order? + false + end + + # Does this adapter support partial indices? + def supports_partial_index? + false + end + + # Does this adapter support expression indices? + def supports_expression_index? + false + end + + # Does this adapter support explain? + def supports_explain? + false + end + + # Does this adapter support setting the isolation level for a transaction? + def supports_transaction_isolation? + false + end + + # Does this adapter support database extensions? + def supports_extensions? + false + end + + # Does this adapter support creating indexes in the same statement as + # creating the table? + def supports_indexes_in_create? + false + end + + # Does this adapter support creating foreign key constraints? + def supports_foreign_keys? + false + end + + # Does this adapter support creating invalid constraints? + def supports_validate_constraints? + false + end + + # Does this adapter support creating foreign key constraints + # in the same statement as creating the table? + def supports_foreign_keys_in_create? + supports_foreign_keys? + end + + # Does this adapter support views? + def supports_views? + false + end + + # Does this adapter support datetime with precision? + def supports_datetime_with_precision? + false + end + + # Does this adapter support json data type? + def supports_json? + false + end + + # Does this adapter support metadata comments on database objects (tables, columns, indexes)? + def supports_comments? + false + end + + # Can comments for tables, columns, and indexes be specified in create/alter table statements? + def supports_comments_in_create? + false + end + + # Does this adapter support multi-value insert? + def supports_multi_insert? + true + end + + # Does this adapter support virtual columns? + def supports_virtual_columns? + false + end + + # Does this adapter support foreign/external tables? + def supports_foreign_tables? + false + end + + # This is meant to be implemented by the adapters that support extensions + def disable_extension(name) + end + + # This is meant to be implemented by the adapters that support extensions + def enable_extension(name) + end + + # This is meant to be implemented by the adapters that support advisory + # locks + # + # Return true if we got the lock, otherwise false + def get_advisory_lock(lock_id) # :nodoc: + end + + # This is meant to be implemented by the adapters that support advisory + # locks. + # + # Return true if we released the lock, otherwise false + def release_advisory_lock(lock_id) # :nodoc: + end + + # A list of extensions, to be filled in by adapters that support them. + def extensions + [] + end + + # A list of index algorithms, to be filled by adapters that support them. + def index_algorithms + {} + end + + # REFERENTIAL INTEGRITY ==================================== + + # Override to turn off referential integrity while executing &block. + def disable_referential_integrity + yield + end + + # CONNECTION MANAGEMENT ==================================== + + # Checks whether the connection to the database is still active. This includes + # checking whether the database is actually capable of responding, i.e. whether + # the connection isn't stale. + def active? + end + + # Disconnects from the database if already connected, and establishes a + # new connection with the database. Implementors should call super if they + # override the default implementation. + def reconnect! + clear_cache! + reset_transaction + end + + # Disconnects from the database if already connected. Otherwise, this + # method does nothing. + def disconnect! + clear_cache! + reset_transaction + end + + # Immediately forget this connection ever existed. Unlike disconnect!, + # this will not communicate with the server. + # + # After calling this method, the behavior of all other methods becomes + # undefined. This is called internally just before a forked process gets + # rid of a connection that belonged to its parent. + def discard! + # This should be overridden by concrete adapters. + # + # Prevent @connection's finalizer from touching the socket, or + # otherwise communicating with its server, when it is collected. + end + + # Reset the state of this connection, directing the DBMS to clear + # transactions and other connection-related server-side state. Usually a + # database-dependent operation. + # + # The default implementation does nothing; the implementation should be + # overridden by concrete adapters. + def reset! + # this should be overridden by concrete adapters + end + + ### + # Clear any caching the database adapter may be doing, for example + # clearing the prepared statement cache. This is database specific. + def clear_cache! + # this should be overridden by concrete adapters + end + + # Returns true if its required to reload the connection between requests for development mode. + def requires_reloading? + false + end + + # Checks whether the connection to the database is still active (i.e. not stale). + # This is done under the hood by calling #active?. If the connection + # is no longer active, then this method will reconnect to the database. + def verify! + reconnect! unless active? + end + + # Provides access to the underlying database driver for this adapter. For + # example, this method returns a Mysql2::Client object in case of Mysql2Adapter, + # and a PG::Connection object in case of PostgreSQLAdapter. + # + # This is useful for when you need to call a proprietary method such as + # PostgreSQL's lo_* methods. + def raw_connection + @connection + end + + def case_sensitive_comparison(table, attribute, column, value) # :nodoc: + table[attribute].eq(value) + end + + def case_insensitive_comparison(table, attribute, column, value) # :nodoc: + if can_perform_case_insensitive_comparison_for?(column) + table[attribute].lower.eq(table.lower(value)) + else + table[attribute].eq(value) + end + end + + def can_perform_case_insensitive_comparison_for?(column) + true + end + private :can_perform_case_insensitive_comparison_for? + + # Check the connection back in to the connection pool + def close + pool.checkin self + end + + def column_name_for_operation(operation, node) # :nodoc: + column_name_from_arel_node(node) + end + + def column_name_from_arel_node(node) # :nodoc: + visitor.accept(node, Arel::Collectors::SQLString.new).value + end + + def default_index_type?(index) # :nodoc: + index.using.nil? + end + + private + def type_map + @type_map ||= Type::TypeMap.new.tap do |mapping| + initialize_type_map(mapping) + end + end + + def initialize_type_map(m = type_map) + register_class_with_limit m, %r(boolean)i, Type::Boolean + register_class_with_limit m, %r(char)i, Type::String + register_class_with_limit m, %r(binary)i, Type::Binary + register_class_with_limit m, %r(text)i, Type::Text + register_class_with_precision m, %r(date)i, Type::Date + register_class_with_precision m, %r(time)i, Type::Time + register_class_with_precision m, %r(datetime)i, Type::DateTime + register_class_with_limit m, %r(float)i, Type::Float + register_class_with_limit m, %r(int)i, Type::Integer + + m.alias_type %r(blob)i, "binary" + m.alias_type %r(clob)i, "text" + m.alias_type %r(timestamp)i, "datetime" + m.alias_type %r(numeric)i, "decimal" + m.alias_type %r(number)i, "decimal" + m.alias_type %r(double)i, "float" + + m.register_type %r(^json)i, Type::Json.new + + m.register_type(%r(decimal)i) do |sql_type| + scale = extract_scale(sql_type) + precision = extract_precision(sql_type) + + if scale == 0 + # FIXME: Remove this class as well + Type::DecimalWithoutScale.new(precision: precision) + else + Type::Decimal.new(precision: precision, scale: scale) + end + end + end + + def reload_type_map + type_map.clear + initialize_type_map + end + + def register_class_with_limit(mapping, key, klass) + mapping.register_type(key) do |*args| + limit = extract_limit(args.last) + klass.new(limit: limit) + end + end + + def register_class_with_precision(mapping, key, klass) + mapping.register_type(key) do |*args| + precision = extract_precision(args.last) + klass.new(precision: precision) + end + end + + def extract_scale(sql_type) + case sql_type + when /\((\d+)\)/ then 0 + when /\((\d+)(,(\d+))\)/ then $3.to_i + end + end + + def extract_precision(sql_type) + $1.to_i if sql_type =~ /\((\d+)(,\d+)?\)/ + end + + def extract_limit(sql_type) + $1.to_i if sql_type =~ /\((.*)\)/ + end + + def translate_exception_class(e, sql) + begin + message = "#{e.class.name}: #{e.message}: #{sql}" + rescue Encoding::CompatibilityError + message = "#{e.class.name}: #{e.message.force_encoding sql.encoding}: #{sql}" + end + + exception = translate_exception(e, message) + exception.set_backtrace e.backtrace + exception + end + + def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil) # :doc: + @instrumenter.instrument( + "sql.active_record", + sql: sql, + name: name, + binds: binds, + type_casted_binds: type_casted_binds, + statement_name: statement_name, + connection_id: object_id) do + begin + @lock.synchronize do + yield + end + rescue => e + raise translate_exception_class(e, sql) + end + end + end + + def translate_exception(exception, message) + # override in derived class + case exception + when RuntimeError + exception + else + ActiveRecord::StatementInvalid.new(message) + end + end + + def without_prepared_statement?(binds) + !prepared_statements || binds.empty? + end + + def column_for(table_name, column_name) + column_name = column_name.to_s + columns(table_name).detect { |c| c.name == column_name } || + raise(ActiveRecordError, "No such column: #{table_name}.#{column_name}") + end + + def collector + if prepared_statements + Arel::Collectors::Composite.new( + Arel::Collectors::SQLString.new, + Arel::Collectors::Bind.new, + ) + else + Arel::Collectors::SubstituteBinds.new( + self, + Arel::Collectors::SQLString.new, + ) + end + end + + def arel_visitor + Arel::Visitors::ToSql.new(self) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract_mysql_adapter.rb new file mode 100644 index 00000000..db2be6bd --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -0,0 +1,887 @@ +# frozen_string_literal: true + +require "active_record/connection_adapters/abstract_adapter" +require "active_record/connection_adapters/statement_pool" +require "active_record/connection_adapters/mysql/column" +require "active_record/connection_adapters/mysql/explain_pretty_printer" +require "active_record/connection_adapters/mysql/quoting" +require "active_record/connection_adapters/mysql/schema_creation" +require "active_record/connection_adapters/mysql/schema_definitions" +require "active_record/connection_adapters/mysql/schema_dumper" +require "active_record/connection_adapters/mysql/schema_statements" +require "active_record/connection_adapters/mysql/type_metadata" + +require "active_support/core_ext/string/strip" + +module ActiveRecord + module ConnectionAdapters + class AbstractMysqlAdapter < AbstractAdapter + include MySQL::Quoting + include MySQL::SchemaStatements + + ## + # :singleton-method: + # By default, the Mysql2Adapter will consider all columns of type tinyint(1) + # as boolean. If you wish to disable this emulation you can add the following line + # to your application.rb file: + # + # ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans = false + class_attribute :emulate_booleans, default: true + + NATIVE_DATABASE_TYPES = { + primary_key: "bigint auto_increment PRIMARY KEY", + string: { name: "varchar", limit: 255 }, + text: { name: "text", limit: 65535 }, + integer: { name: "int", limit: 4 }, + float: { name: "float", limit: 24 }, + decimal: { name: "decimal" }, + datetime: { name: "datetime" }, + timestamp: { name: "timestamp" }, + time: { name: "time" }, + date: { name: "date" }, + binary: { name: "blob", limit: 65535 }, + boolean: { name: "tinyint", limit: 1 }, + json: { name: "json" }, + } + + class StatementPool < ConnectionAdapters::StatementPool # :nodoc: + private def dealloc(stmt) + stmt[:stmt].close + end + end + + def initialize(connection, logger, connection_options, config) + super(connection, logger, config) + + @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit])) + + if version < "5.1.10" + raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.1.10." + end + end + + def version #:nodoc: + @version ||= Version.new(version_string) + end + + def mariadb? # :nodoc: + /mariadb/i.match?(full_version) + end + + def supports_bulk_alter? #:nodoc: + true + end + + def supports_index_sort_order? + !mariadb? && version >= "8.0.1" + end + + def supports_transaction_isolation? + true + end + + def supports_explain? + true + end + + def supports_indexes_in_create? + true + end + + def supports_foreign_keys? + true + end + + def supports_views? + true + end + + def supports_datetime_with_precision? + if mariadb? + version >= "5.3.0" + else + version >= "5.6.4" + end + end + + def supports_virtual_columns? + if mariadb? + version >= "5.2.0" + else + version >= "5.7.5" + end + end + + def supports_advisory_locks? + true + end + + def get_advisory_lock(lock_name, timeout = 0) # :nodoc: + query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1 + end + + def release_advisory_lock(lock_name) # :nodoc: + query_value("SELECT RELEASE_LOCK(#{quote(lock_name.to_s)})") == 1 + end + + def native_database_types + NATIVE_DATABASE_TYPES + end + + def index_algorithms + { default: "ALGORITHM = DEFAULT".dup, copy: "ALGORITHM = COPY".dup, inplace: "ALGORITHM = INPLACE".dup } + end + + # HELPER METHODS =========================================== + + # The two drivers have slightly different ways of yielding hashes of results, so + # this method must be implemented to provide a uniform interface. + def each_hash(result) # :nodoc: + raise NotImplementedError + end + + # Must return the MySQL error number from the exception, if the exception has an + # error number. + def error_number(exception) # :nodoc: + raise NotImplementedError + end + + # REFERENTIAL INTEGRITY ==================================== + + def disable_referential_integrity #:nodoc: + old = query_value("SELECT @@FOREIGN_KEY_CHECKS") + + begin + update("SET FOREIGN_KEY_CHECKS = 0") + yield + ensure + update("SET FOREIGN_KEY_CHECKS = #{old}") + end + end + + # CONNECTION MANAGEMENT ==================================== + + # Clears the prepared statements cache. + def clear_cache! + reload_type_map + @statements.clear + end + + #-- + # DATABASE STATEMENTS ====================================== + #++ + + def explain(arel, binds = []) + sql = "EXPLAIN #{to_sql(arel, binds)}" + start = Time.now + result = exec_query(sql, "EXPLAIN", binds) + elapsed = Time.now - start + + MySQL::ExplainPrettyPrinter.new.pp(result, elapsed) + end + + # Executes the SQL statement in the context of this connection. + def execute(sql, name = nil) + log(sql, name) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @connection.query(sql) + end + end + end + + # Mysql2Adapter doesn't have to free a result after using it, but we use this method + # to write stuff in an abstract way without concerning ourselves about whether it + # needs to be explicitly freed or not. + def execute_and_free(sql, name = nil) # :nodoc: + yield execute(sql, name) + end + + def begin_db_transaction + execute "BEGIN" + end + + def begin_isolated_db_transaction(isolation) + execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}" + begin_db_transaction + end + + def commit_db_transaction #:nodoc: + execute "COMMIT" + end + + def exec_rollback_db_transaction #:nodoc: + execute "ROLLBACK" + end + + # In the simple case, MySQL allows us to place JOINs directly into the UPDATE + # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support + # these, we must use a subquery. + def join_to_update(update, select, key) # :nodoc: + if select.limit || select.offset || select.orders.any? + super + else + update.table select.source + update.wheres = select.constraints + end + end + + def empty_insert_statement_value + "VALUES ()" + end + + # SCHEMA STATEMENTS ======================================== + + # Drops the database specified on the +name+ attribute + # and creates it again using the provided +options+. + def recreate_database(name, options = {}) + drop_database(name) + sql = create_database(name, options) + reconnect! + sql + end + + # Create a new MySQL database with optional :charset and :collation. + # Charset defaults to utf8. + # + # Example: + # create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin' + # create_database 'matt_development' + # create_database 'matt_development', charset: :big5 + def create_database(name, options = {}) + if options[:collation] + execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT COLLATE #{quote_table_name(options[:collation])}" + else + execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}" + end + end + + # Drops a MySQL database. + # + # Example: + # drop_database('sebastian_development') + def drop_database(name) #:nodoc: + execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}" + end + + def current_database + query_value("SELECT database()", "SCHEMA") + end + + # Returns the database character set. + def charset + show_variable "character_set_database" + end + + # Returns the database collation strategy. + def collation + show_variable "collation_database" + end + + def truncate(table_name, name = nil) + execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name + end + + def table_comment(table_name) # :nodoc: + scope = quoted_scope(table_name) + + query_value(<<-SQL.strip_heredoc, "SCHEMA").presence + SELECT table_comment + FROM information_schema.tables + WHERE table_schema = #{scope[:schema]} + AND table_name = #{scope[:name]} + SQL + end + + def bulk_change_table(table_name, operations) #:nodoc: + sqls = operations.flat_map do |command, args| + table, arguments = args.shift, args + method = :"#{command}_for_alter" + + if respond_to?(method, true) + send(method, table, *arguments) + else + raise "Unknown method called : #{method}(#{arguments.inspect})" + end + end.join(", ") + + execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}") + end + + def change_table_comment(table_name, comment) #:nodoc: + comment = "" if comment.nil? + execute("ALTER TABLE #{quote_table_name(table_name)} COMMENT #{quote(comment)}") + end + + # Renames a table. + # + # Example: + # rename_table('octopuses', 'octopi') + def rename_table(table_name, new_name) + execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}" + rename_table_indexes(table_name, new_name) + end + + # Drops a table from the database. + # + # [:force] + # Set to +:cascade+ to drop dependent objects as well. + # Defaults to false. + # [:if_exists] + # Set to +true+ to only drop the table if it exists. + # Defaults to false. + # [:temporary] + # Set to +true+ to drop temporary table. + # Defaults to false. + # + # Although this command ignores most +options+ and the block if one is given, + # it can be helpful to provide these in a migration's +change+ method so it can be reverted. + # In that case, +options+ and the block will be used by create_table. + def drop_table(table_name, options = {}) + execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}" + end + + def rename_index(table_name, old_name, new_name) + if supports_rename_index? + validate_index_length!(table_name, new_name) + + execute "ALTER TABLE #{quote_table_name(table_name)} RENAME INDEX #{quote_table_name(old_name)} TO #{quote_table_name(new_name)}" + else + super + end + end + + def change_column_default(table_name, column_name, default_or_changes) #:nodoc: + default = extract_new_default_value(default_or_changes) + change_column table_name, column_name, nil, default: default + end + + def change_column_null(table_name, column_name, null, default = nil) #:nodoc: + unless null || default.nil? + execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL") + end + + change_column table_name, column_name, nil, null: null + end + + def change_column_comment(table_name, column_name, comment) #:nodoc: + change_column table_name, column_name, nil, comment: comment + end + + def change_column(table_name, column_name, type, options = {}) #:nodoc: + execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, options)}") + end + + def rename_column(table_name, column_name, new_column_name) #:nodoc: + execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_for_alter(table_name, column_name, new_column_name)}") + rename_column_indexes(table_name, column_name, new_column_name) + end + + def add_index(table_name, column_name, options = {}) #:nodoc: + index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options) + sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}".dup + execute add_sql_comment!(sql, comment) + end + + def add_sql_comment!(sql, comment) # :nodoc: + sql << " COMMENT #{quote(comment)}" if comment.present? + sql + end + + def foreign_keys(table_name) + raise ArgumentError unless table_name.present? + + scope = quoted_scope(table_name) + + fk_info = exec_query(<<-SQL.strip_heredoc, "SCHEMA") + SELECT fk.referenced_table_name AS 'to_table', + fk.referenced_column_name AS 'primary_key', + fk.column_name AS 'column', + fk.constraint_name AS 'name', + rc.update_rule AS 'on_update', + rc.delete_rule AS 'on_delete' + FROM information_schema.referential_constraints rc + JOIN information_schema.key_column_usage fk + USING (constraint_schema, constraint_name) + WHERE fk.referenced_column_name IS NOT NULL + AND fk.table_schema = #{scope[:schema]} + AND fk.table_name = #{scope[:name]} + AND rc.constraint_schema = #{scope[:schema]} + AND rc.table_name = #{scope[:name]} + SQL + + fk_info.map do |row| + options = { + column: row["column"], + name: row["name"], + primary_key: row["primary_key"] + } + + options[:on_update] = extract_foreign_key_action(row["on_update"]) + options[:on_delete] = extract_foreign_key_action(row["on_delete"]) + + ForeignKeyDefinition.new(table_name, row["to_table"], options) + end + end + + def table_options(table_name) # :nodoc: + table_options = {} + + create_table_info = create_table_info(table_name) + + # strip create_definitions and partition_options + raw_table_options = create_table_info.sub(/\A.*\n\) /m, "").sub(/\n\/\*!.*\*\/\n\z/m, "").strip + + # strip AUTO_INCREMENT + raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1') + + table_options[:options] = raw_table_options + + # strip COMMENT + if raw_table_options.sub!(/ COMMENT='.+'/, "") + table_options[:comment] = table_comment(table_name) + end + + table_options + end + + # Maps logical Rails types to MySQL-specific data types. + def type_to_sql(type, limit: nil, precision: nil, scale: nil, unsigned: nil, **) # :nodoc: + sql = \ + case type.to_s + when "integer" + integer_to_sql(limit) + when "text" + text_to_sql(limit) + when "blob" + binary_to_sql(limit) + when "binary" + if (0..0xfff) === limit + "varbinary(#{limit})" + else + binary_to_sql(limit) + end + else + super + end + + sql = "#{sql} unsigned" if unsigned && type != :primary_key + sql + end + + # SHOW VARIABLES LIKE 'name' + def show_variable(name) + query_value("SELECT @@#{name}", "SCHEMA") + rescue ActiveRecord::StatementInvalid + nil + end + + def primary_keys(table_name) # :nodoc: + raise ArgumentError unless table_name.present? + + scope = quoted_scope(table_name) + + query_values(<<-SQL.strip_heredoc, "SCHEMA") + SELECT column_name + FROM information_schema.key_column_usage + WHERE constraint_name = 'PRIMARY' + AND table_schema = #{scope[:schema]} + AND table_name = #{scope[:name]} + ORDER BY ordinal_position + SQL + end + + def case_sensitive_comparison(table, attribute, column, value) # :nodoc: + if column.collation && !column.case_sensitive? + table[attribute].eq(Arel::Nodes::Bin.new(value)) + else + super + end + end + + def can_perform_case_insensitive_comparison_for?(column) + column.case_sensitive? + end + private :can_perform_case_insensitive_comparison_for? + + # In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use + # DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for + # distinct queries, and requires that the ORDER BY include the distinct column. + # See https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html + def columns_for_distinct(columns, orders) # :nodoc: + order_columns = orders.reject(&:blank?).map { |s| + # Convert Arel node to string + s = s.to_sql unless s.is_a?(String) + # Remove any ASC/DESC modifiers + s.gsub(/\s+(?:ASC|DESC)\b/i, "") + }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" } + + (order_columns << super).join(", ") + end + + def strict_mode? + self.class.type_cast_config_to_boolean(@config.fetch(:strict, true)) + end + + def default_index_type?(index) # :nodoc: + index.using == :btree || super + end + + def insert_fixtures_set(fixture_set, tables_to_delete = []) + with_multi_statements do + super { discard_remaining_results } + end + end + + private + def combine_multi_statements(total_sql) + total_sql.each_with_object([]) do |sql, total_sql_chunks| + previous_packet = total_sql_chunks.last + sql << ";\n" + if max_allowed_packet_reached?(sql, previous_packet) || total_sql_chunks.empty? + total_sql_chunks << sql + else + previous_packet << sql + end + end + end + + def max_allowed_packet_reached?(current_packet, previous_packet) + if current_packet.bytesize > max_allowed_packet + raise ActiveRecordError, "Fixtures set is too large #{current_packet.bytesize}. Consider increasing the max_allowed_packet variable." + elsif previous_packet.nil? + false + else + (current_packet.bytesize + previous_packet.bytesize) > max_allowed_packet + end + end + + def max_allowed_packet + bytes_margin = 2 + @max_allowed_packet ||= (show_variable("max_allowed_packet") - bytes_margin) + end + + def initialize_type_map(m = type_map) + super + + register_class_with_limit m, %r(char)i, MysqlString + + m.register_type %r(tinytext)i, Type::Text.new(limit: 2**8 - 1) + m.register_type %r(tinyblob)i, Type::Binary.new(limit: 2**8 - 1) + m.register_type %r(text)i, Type::Text.new(limit: 2**16 - 1) + m.register_type %r(blob)i, Type::Binary.new(limit: 2**16 - 1) + m.register_type %r(mediumtext)i, Type::Text.new(limit: 2**24 - 1) + m.register_type %r(mediumblob)i, Type::Binary.new(limit: 2**24 - 1) + m.register_type %r(longtext)i, Type::Text.new(limit: 2**32 - 1) + m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1) + m.register_type %r(^float)i, Type::Float.new(limit: 24) + m.register_type %r(^double)i, Type::Float.new(limit: 53) + + register_integer_type m, %r(^bigint)i, limit: 8 + register_integer_type m, %r(^int)i, limit: 4 + register_integer_type m, %r(^mediumint)i, limit: 3 + register_integer_type m, %r(^smallint)i, limit: 2 + register_integer_type m, %r(^tinyint)i, limit: 1 + + m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans + m.alias_type %r(year)i, "integer" + m.alias_type %r(bit)i, "binary" + + m.register_type(%r(enum)i) do |sql_type| + limit = sql_type[/^enum\((.+)\)/i, 1] + .split(",").map { |enum| enum.strip.length - 2 }.max + MysqlString.new(limit: limit) + end + + m.register_type(%r(^set)i) do |sql_type| + limit = sql_type[/^set\((.+)\)/i, 1] + .split(",").map { |set| set.strip.length - 1 }.sum - 1 + MysqlString.new(limit: limit) + end + end + + def register_integer_type(mapping, key, options) + mapping.register_type(key) do |sql_type| + if /\bunsigned\b/.match?(sql_type) + Type::UnsignedInteger.new(options) + else + Type::Integer.new(options) + end + end + end + + def extract_precision(sql_type) + if /\A(?:date)?time(?:stamp)?\b/.match?(sql_type) + super || 0 + else + super + end + end + + # See https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html + ER_DUP_ENTRY = 1062 + ER_NOT_NULL_VIOLATION = 1048 + ER_DO_NOT_HAVE_DEFAULT = 1364 + ER_NO_REFERENCED_ROW_2 = 1452 + ER_DATA_TOO_LONG = 1406 + ER_OUT_OF_RANGE = 1264 + ER_LOCK_DEADLOCK = 1213 + ER_CANNOT_ADD_FOREIGN = 1215 + ER_CANNOT_CREATE_TABLE = 1005 + ER_LOCK_WAIT_TIMEOUT = 1205 + ER_QUERY_INTERRUPTED = 1317 + ER_QUERY_TIMEOUT = 3024 + + def translate_exception(exception, message) + case error_number(exception) + when ER_DUP_ENTRY + RecordNotUnique.new(message) + when ER_NO_REFERENCED_ROW_2 + InvalidForeignKey.new(message) + when ER_CANNOT_ADD_FOREIGN + mismatched_foreign_key(message) + when ER_CANNOT_CREATE_TABLE + if message.include?("errno: 150") + mismatched_foreign_key(message) + else + super + end + when ER_DATA_TOO_LONG + ValueTooLong.new(message) + when ER_OUT_OF_RANGE + RangeError.new(message) + when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT + NotNullViolation.new(message) + when ER_LOCK_DEADLOCK + Deadlocked.new(message) + when ER_LOCK_WAIT_TIMEOUT + LockWaitTimeout.new(message) + when ER_QUERY_TIMEOUT + StatementTimeout.new(message) + when ER_QUERY_INTERRUPTED + QueryCanceled.new(message) + else + super + end + end + + def change_column_for_alter(table_name, column_name, type, options = {}) + column = column_for(table_name, column_name) + type ||= column.sql_type + + unless options.key?(:default) + options[:default] = column.default + end + + unless options.key?(:null) + options[:null] = column.null + end + + unless options.key?(:comment) + options[:comment] = column.comment + end + + td = create_table_definition(table_name) + cd = td.new_column_definition(column.name, type, options) + schema_creation.accept(ChangeColumnDefinition.new(cd, column.name)) + end + + def rename_column_for_alter(table_name, column_name, new_column_name) + column = column_for(table_name, column_name) + options = { + default: column.default, + null: column.null, + auto_increment: column.auto_increment? + } + + current_type = exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"] + td = create_table_definition(table_name) + cd = td.new_column_definition(new_column_name, current_type, options) + schema_creation.accept(ChangeColumnDefinition.new(cd, column.name)) + end + + def add_index_for_alter(table_name, column_name, options = {}) + index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options) + index_algorithm[0, 0] = ", " if index_algorithm.present? + "ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}" + end + + def remove_index_for_alter(table_name, options = {}) + index_name = index_name_for_remove(table_name, options) + "DROP INDEX #{quote_column_name(index_name)}" + end + + def add_timestamps_for_alter(table_name, options = {}) + [add_column_for_alter(table_name, :created_at, :datetime, options), add_column_for_alter(table_name, :updated_at, :datetime, options)] + end + + def remove_timestamps_for_alter(table_name, options = {}) + [remove_column_for_alter(table_name, :updated_at), remove_column_for_alter(table_name, :created_at)] + end + + # MySQL is too stupid to create a temporary table for use subquery, so we have + # to give it some prompting in the form of a subsubquery. Ugh! + def subquery_for(key, select) + subselect = select.clone + subselect.projections = [key] + + # Materialize subquery by adding distinct + # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on' + subselect.distinct unless select.limit || select.offset || select.orders.any? + + key_name = quote_column_name(key.name) + Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key_name)) + end + + def supports_rename_index? + mariadb? ? false : version >= "5.7.6" + end + + def configure_connection + variables = @config.fetch(:variables, {}).stringify_keys + + # By default, MySQL 'where id is null' selects the last inserted id; Turn this off. + variables["sql_auto_is_null"] = 0 + + # Increase timeout so the server doesn't disconnect us. + wait_timeout = self.class.type_cast_config_to_integer(@config[:wait_timeout]) + wait_timeout = 2147483 unless wait_timeout.is_a?(Integer) + variables["wait_timeout"] = wait_timeout + + defaults = [":default", :default].to_set + + # Make MySQL reject illegal values rather than truncating or blanking them, see + # https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables + # If the user has provided another value for sql_mode, don't replace it. + if sql_mode = variables.delete("sql_mode") + sql_mode = quote(sql_mode) + elsif !defaults.include?(strict_mode?) + if strict_mode? + sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')" + else + sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')" + sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')" + sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')" + end + sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')" + end + sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode + + # NAMES does not have an equals sign, see + # https://dev.mysql.com/doc/refman/5.7/en/set-names.html + # (trailing comma because variable_assignments will always have content) + if @config[:encoding] + encoding = "NAMES #{@config[:encoding]}".dup + encoding << " COLLATE #{@config[:collation]}" if @config[:collation] + encoding << ", " + end + + # Gather up all of the SET variables... + variable_assignments = variables.map do |k, v| + if defaults.include?(v) + "@@SESSION.#{k} = DEFAULT" # Sets the value to the global or compile default + elsif !v.nil? + "@@SESSION.#{k} = #{quote(v)}" + end + # or else nil; compact to clear nils out + end.compact.join(", ") + + # ...and send them all in one query + execute "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}" + end + + def column_definitions(table_name) # :nodoc: + execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result| + each_hash(result) + end + end + + def create_table_info(table_name) # :nodoc: + exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"] + end + + def arel_visitor + Arel::Visitors::MySQL.new(self) + end + + def mismatched_foreign_key(message) + match = %r/ + (?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?\w+)`?.+? + FOREIGN\s+KEY\s*\(`?(?\w+)`?\)\s* + REFERENCES\s*(`?(?\w+)`?)\s*\(`?(?\w+)`?\) + /xmi.match(message) + + options = { + message: message, + } + + if match + options[:table] = match[:table] + options[:foreign_key] = match[:foreign_key] + options[:target_table] = match[:target_table] + options[:primary_key] = match[:primary_key] + options[:primary_key_column] = column_for(match[:target_table], match[:primary_key]) + end + + MismatchedForeignKey.new(options) + end + + def integer_to_sql(limit) # :nodoc: + case limit + when 1; "tinyint" + when 2; "smallint" + when 3; "mediumint" + when nil, 4; "int" + when 5..8; "bigint" + else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a decimal with scale 0 instead.") + end + end + + def text_to_sql(limit) # :nodoc: + case limit + when 0..0xff; "tinytext" + when nil, 0x100..0xffff; "text" + when 0x10000..0xffffff; "mediumtext" + when 0x1000000..0xffffffff; "longtext" + else raise(ActiveRecordError, "No text type has byte length #{limit}") + end + end + + def binary_to_sql(limit) # :nodoc: + case limit + when 0..0xff; "tinyblob" + when nil, 0x100..0xffff; "blob" + when 0x10000..0xffffff; "mediumblob" + when 0x1000000..0xffffffff; "longblob" + else raise(ActiveRecordError, "No binary type has byte length #{limit}") + end + end + + def version_string + full_version.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1] + end + + class MysqlString < Type::String # :nodoc: + def serialize(value) + case value + when true then "1" + when false then "0" + else super + end + end + + private + + def cast_value(value) + case value + when true then "1" + when false then "0" + else super + end + end + end + + ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2) + ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2) + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/column.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/column.rb new file mode 100644 index 00000000..5d81de9f --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/column.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +module ActiveRecord + # :stopdoc: + module ConnectionAdapters + # An abstract definition of a column in a table. + class Column + attr_reader :name, :default, :sql_type_metadata, :null, :table_name, :default_function, :collation, :comment + + delegate :precision, :scale, :limit, :type, :sql_type, to: :sql_type_metadata, allow_nil: true + + # Instantiates a new column in the table. + # + # +name+ is the column's name, such as supplier_id in supplier_id bigint. + # +default+ is the type-casted default value, such as +new+ in sales_stage varchar(20) default 'new'. + # +sql_type_metadata+ is various information about the type of the column + # +null+ determines if this column allows +NULL+ values. + def initialize(name, default, sql_type_metadata = nil, null = true, table_name = nil, default_function = nil, collation = nil, comment: nil, **) + @name = name.freeze + @table_name = table_name + @sql_type_metadata = sql_type_metadata + @null = null + @default = default + @default_function = default_function + @collation = collation + @comment = comment + end + + def has_default? + !default.nil? || default_function + end + + def bigint? + /\Abigint\b/.match?(sql_type) + end + + # Returns the human name of the column name. + # + # ===== Examples + # Column.new('sales_stage', ...).human_name # => 'Sales stage' + def human_name + Base.human_attribute_name(@name) + end + + def init_with(coder) + @name = coder["name"] + @table_name = coder["table_name"] + @sql_type_metadata = coder["sql_type_metadata"] + @null = coder["null"] + @default = coder["default"] + @default_function = coder["default_function"] + @collation = coder["collation"] + @comment = coder["comment"] + end + + def encode_with(coder) + coder["name"] = @name + coder["table_name"] = @table_name + coder["sql_type_metadata"] = @sql_type_metadata + coder["null"] = @null + coder["default"] = @default + coder["default_function"] = @default_function + coder["collation"] = @collation + coder["comment"] = @comment + end + + def ==(other) + other.is_a?(Column) && + attributes_for_hash == other.attributes_for_hash + end + alias :eql? :== + + def hash + attributes_for_hash.hash + end + + protected + + def attributes_for_hash + [self.class, name, default, sql_type_metadata, null, table_name, default_function, collation] + end + end + + class NullColumn < Column + def initialize(name) + super(name, nil) + end + end + end + # :startdoc: +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/connection_specification.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/connection_specification.rb new file mode 100644 index 00000000..f0d931c1 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/connection_specification.rb @@ -0,0 +1,287 @@ +# frozen_string_literal: true + +require "uri" + +module ActiveRecord + module ConnectionAdapters + class ConnectionSpecification #:nodoc: + attr_reader :name, :config, :adapter_method + + def initialize(name, config, adapter_method) + @name, @config, @adapter_method = name, config, adapter_method + end + + def initialize_dup(original) + @config = original.config.dup + end + + def to_hash + @config.merge(name: @name) + end + + # Expands a connection string into a hash. + class ConnectionUrlResolver # :nodoc: + # == Example + # + # url = "postgresql://foo:bar@localhost:9000/foo_test?pool=5&timeout=3000" + # ConnectionUrlResolver.new(url).to_hash + # # => { + # "adapter" => "postgresql", + # "host" => "localhost", + # "port" => 9000, + # "database" => "foo_test", + # "username" => "foo", + # "password" => "bar", + # "pool" => "5", + # "timeout" => "3000" + # } + def initialize(url) + raise "Database URL cannot be empty" if url.blank? + @uri = uri_parser.parse(url) + @adapter = @uri.scheme && @uri.scheme.tr("-", "_") + @adapter = "postgresql" if @adapter == "postgres" + + if @uri.opaque + @uri.opaque, @query = @uri.opaque.split("?", 2) + else + @query = @uri.query + end + end + + # Converts the given URL to a full connection hash. + def to_hash + config = raw_config.reject { |_, value| value.blank? } + config.map { |key, value| config[key] = uri_parser.unescape(value) if value.is_a? String } + config + end + + private + + def uri + @uri + end + + def uri_parser + @uri_parser ||= URI::Parser.new + end + + # Converts the query parameters of the URI into a hash. + # + # "localhost?pool=5&reaping_frequency=2" + # # => { "pool" => "5", "reaping_frequency" => "2" } + # + # returns empty hash if no query present. + # + # "localhost" + # # => {} + def query_hash + Hash[(@query || "").split("&").map { |pair| pair.split("=") }] + end + + def raw_config + if uri.opaque + query_hash.merge( + "adapter" => @adapter, + "database" => uri.opaque) + else + query_hash.merge( + "adapter" => @adapter, + "username" => uri.user, + "password" => uri.password, + "port" => uri.port, + "database" => database_from_path, + "host" => uri.hostname) + end + end + + # Returns name of the database. + def database_from_path + if @adapter == "sqlite3" + # 'sqlite3:/foo' is absolute, because that makes sense. The + # corresponding relative version, 'sqlite3:foo', is handled + # elsewhere, as an "opaque". + + uri.path + else + # Only SQLite uses a filename as the "database" name; for + # anything else, a leading slash would be silly. + + uri.path.sub(%r{^/}, "") + end + end + end + + ## + # Builds a ConnectionSpecification from user input. + class Resolver # :nodoc: + attr_reader :configurations + + # Accepts a hash two layers deep, keys on the first layer represent + # environments such as "production". Keys must be strings. + def initialize(configurations) + @configurations = configurations + end + + # Returns a hash with database connection information. + # + # == Examples + # + # Full hash Configuration. + # + # configurations = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } } + # Resolver.new(configurations).resolve(:production) + # # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3"} + # + # Initialized with URL configuration strings. + # + # configurations = { "production" => "postgresql://localhost/foo" } + # Resolver.new(configurations).resolve(:production) + # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" } + # + def resolve(config) + if config + resolve_connection config + elsif env = ActiveRecord::ConnectionHandling::RAILS_ENV.call + resolve_symbol_connection env.to_sym + else + raise AdapterNotSpecified + end + end + + # Expands each key in @configurations hash into fully resolved hash + def resolve_all + config = configurations.dup + + if env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call + env_config = config[env] if config[env].is_a?(Hash) && !(config[env].key?("adapter") || config[env].key?("url")) + end + + config.reject! { |k, v| v.is_a?(Hash) && !(v.key?("adapter") || v.key?("url")) } + config.merge! env_config if env_config + + config.each do |key, value| + config[key] = resolve(value) if value + end + + config + end + + # Returns an instance of ConnectionSpecification for a given adapter. + # Accepts a hash one layer deep that contains all connection information. + # + # == Example + # + # config = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } } + # spec = Resolver.new(config).spec(:production) + # spec.adapter_method + # # => "sqlite3_connection" + # spec.config + # # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } + # + def spec(config) + spec = resolve(config).symbolize_keys + + raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter) + + # Require the adapter itself and give useful feedback about + # 1. Missing adapter gems and + # 2. Adapter gems' missing dependencies. + path_to_adapter = "active_record/connection_adapters/#{spec[:adapter]}_adapter" + begin + require path_to_adapter + rescue LoadError => e + # We couldn't require the adapter itself. Raise an exception that + # points out config typos and missing gems. + if e.path == path_to_adapter + # We can assume that a non-builtin adapter was specified, so it's + # either misspelled or missing from Gemfile. + raise LoadError, "Could not load the '#{spec[:adapter]}' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace + + # Bubbled up from the adapter require. Prefix the exception message + # with some guidance about how to address it and reraise. + else + raise LoadError, "Error loading the '#{spec[:adapter]}' Active Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace + end + end + + adapter_method = "#{spec[:adapter]}_connection" + + unless ActiveRecord::Base.respond_to?(adapter_method) + raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter" + end + + ConnectionSpecification.new(spec.delete(:name) || "primary", spec, adapter_method) + end + + private + + # Returns fully resolved connection, accepts hash, string or symbol. + # Always returns a hash. + # + # == Examples + # + # Symbol representing current environment. + # + # Resolver.new("production" => {}).resolve_connection(:production) + # # => {} + # + # One layer deep hash of connection values. + # + # Resolver.new({}).resolve_connection("adapter" => "sqlite3") + # # => { "adapter" => "sqlite3" } + # + # Connection URL. + # + # Resolver.new({}).resolve_connection("postgresql://localhost/foo") + # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" } + # + def resolve_connection(spec) + case spec + when Symbol + resolve_symbol_connection spec + when String + resolve_url_connection spec + when Hash + resolve_hash_connection spec + end + end + + # Takes the environment such as +:production+ or +:development+. + # This requires that the @configurations was initialized with a key that + # matches. + # + # Resolver.new("production" => {}).resolve_symbol_connection(:production) + # # => {} + # + def resolve_symbol_connection(spec) + if config = configurations[spec.to_s] + resolve_connection(config).merge("name" => spec.to_s) + else + raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available: #{configurations.keys.inspect}") + end + end + + # Accepts a hash. Expands the "url" key that contains a + # URL database connection to a full connection + # hash and merges with the rest of the hash. + # Connection details inside of the "url" key win any merge conflicts + def resolve_hash_connection(spec) + if spec["url"] && spec["url"] !~ /^jdbc:/ + connection_hash = resolve_url_connection(spec.delete("url")) + spec.merge!(connection_hash) + end + spec + end + + # Takes a connection URL. + # + # Resolver.new({}).resolve_url_connection("postgresql://localhost/foo") + # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" } + # + def resolve_url_connection(url) + ConnectionUrlResolver.new(url).to_hash + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb new file mode 100644 index 00000000..7e9e7dea --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module DetermineIfPreparableVisitor + attr_accessor :preparable + + def accept(*) + @preparable = true + super + end + + def visit_Arel_Nodes_In(o, collector) + @preparable = false + + if Array === o.right && !o.right.empty? + o.right.delete_if do |bind| + if Arel::Nodes::BindParam === bind && Relation::QueryAttribute === bind.value + !bind.value.boundable? + end + end + end + + super + end + + def visit_Arel_Nodes_SqlLiteral(*) + @preparable = false + super + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql/column.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql/column.rb new file mode 100644 index 00000000..fa154101 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql/column.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module MySQL + class Column < ConnectionAdapters::Column # :nodoc: + delegate :extra, to: :sql_type_metadata, allow_nil: true + + def unsigned? + /\bunsigned(?: zerofill)?\z/.match?(sql_type) + end + + def case_sensitive? + collation && !/_ci\z/.match?(collation) + end + + def auto_increment? + extra == "auto_increment" + end + + def virtual? + /\b(?:VIRTUAL|STORED|PERSISTENT)\b/.match?(extra) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql/database_statements.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql/database_statements.rb new file mode 100644 index 00000000..ebc1c9d1 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql/database_statements.rb @@ -0,0 +1,140 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module MySQL + module DatabaseStatements + # Returns an ActiveRecord::Result instance. + def select_all(*) # :nodoc: + result = if ExplainRegistry.collect? && prepared_statements + unprepared_statement { super } + else + super + end + discard_remaining_results + result + end + + def query(sql, name = nil) # :nodoc: + execute(sql, name).to_a + end + + # Executes the SQL statement in the context of this connection. + def execute(sql, name = nil) + # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been + # made since we established the connection + @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone + + super + end + + def exec_query(sql, name = "SQL", binds = [], prepare: false) + if without_prepared_statement?(binds) + execute_and_free(sql, name) do |result| + ActiveRecord::Result.new(result.fields, result.to_a) if result + end + else + exec_stmt_and_free(sql, name, binds, cache_stmt: prepare) do |_, result| + ActiveRecord::Result.new(result.fields, result.to_a) if result + end + end + end + + def exec_delete(sql, name = nil, binds = []) + if without_prepared_statement?(binds) + execute_and_free(sql, name) { @connection.affected_rows } + else + exec_stmt_and_free(sql, name, binds) { |stmt| stmt.affected_rows } + end + end + alias :exec_update :exec_delete + + private + def default_insert_value(column) + Arel.sql("DEFAULT") unless column.auto_increment? + end + + def last_inserted_id(result) + @connection.last_id + end + + def discard_remaining_results + @connection.next_result while @connection.more_results? + end + + def supports_set_server_option? + @connection.respond_to?(:set_server_option) + end + + def multi_statements_enabled?(flags) + if flags.is_a?(Array) + flags.include?("MULTI_STATEMENTS") + else + (flags & Mysql2::Client::MULTI_STATEMENTS) != 0 + end + end + + def with_multi_statements + previous_flags = @config[:flags] + + unless multi_statements_enabled?(previous_flags) + if supports_set_server_option? + @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON) + else + @config[:flags] = Mysql2::Client::MULTI_STATEMENTS + reconnect! + end + end + + yield + ensure + unless multi_statements_enabled?(previous_flags) + if supports_set_server_option? + @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF) + else + @config[:flags] = previous_flags + reconnect! + end + end + end + + def exec_stmt_and_free(sql, name, binds, cache_stmt: false) + # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been + # made since we established the connection + @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone + + type_casted_binds = type_casted_binds(binds) + + log(sql, name, binds, type_casted_binds) do + if cache_stmt + cache = @statements[sql] ||= { + stmt: @connection.prepare(sql) + } + stmt = cache[:stmt] + else + stmt = @connection.prepare(sql) + end + + begin + result = ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + stmt.execute(*type_casted_binds) + end + rescue Mysql2::Error => e + if cache_stmt + @statements.delete(sql) + else + stmt.close + end + raise e + end + + ret = yield stmt, result + result.free if result + stmt.close unless cache_stmt + ret + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb new file mode 100644 index 00000000..20c3c836 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module MySQL + class ExplainPrettyPrinter # :nodoc: + # Pretty prints the result of an EXPLAIN in a way that resembles the output of the + # MySQL shell: + # + # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ + # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | + # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ + # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | | + # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where | + # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ + # 2 rows in set (0.00 sec) + # + # This is an exercise in Ruby hyperrealism :). + def pp(result, elapsed) + widths = compute_column_widths(result) + separator = build_separator(widths) + + pp = [] + + pp << separator + pp << build_cells(result.columns, widths) + pp << separator + + result.rows.each do |row| + pp << build_cells(row, widths) + end + + pp << separator + pp << build_footer(result.rows.length, elapsed) + + pp.join("\n") + "\n" + end + + private + + def compute_column_widths(result) + [].tap do |widths| + result.columns.each_with_index do |column, i| + cells_in_column = [column] + result.rows.map { |r| r[i].nil? ? "NULL" : r[i].to_s } + widths << cells_in_column.map(&:length).max + end + end + end + + def build_separator(widths) + padding = 1 + "+" + widths.map { |w| "-" * (w + (padding * 2)) }.join("+") + "+" + end + + def build_cells(items, widths) + cells = [] + items.each_with_index do |item, i| + item = "NULL" if item.nil? + justifier = item.is_a?(Numeric) ? "rjust" : "ljust" + cells << item.to_s.send(justifier, widths[i]) + end + "| " + cells.join(" | ") + " |" + end + + def build_footer(nrows, elapsed) + rows_label = nrows == 1 ? "row" : "rows" + "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql/quoting.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql/quoting.rb new file mode 100644 index 00000000..be038403 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql/quoting.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module MySQL + module Quoting # :nodoc: + def quote_column_name(name) + @quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`".freeze + end + + def quote_table_name(name) + @quoted_table_names[name] ||= super.gsub(".", "`.`").freeze + end + + def unquoted_true + 1 + end + + def unquoted_false + 0 + end + + def quoted_date(value) + if supports_datetime_with_precision? + super + else + super.sub(/\.\d{6}\z/, "") + end + end + + def quoted_binary(value) + "x'#{value.hex}'" + end + + def _type_cast(value) + case value + when Date, Time then value + else super + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql/schema_creation.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql/schema_creation.rb new file mode 100644 index 00000000..75377693 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql/schema_creation.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module MySQL + class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc: + delegate :add_sql_comment!, :mariadb?, to: :@conn + private :add_sql_comment!, :mariadb? + + private + + def visit_DropForeignKey(name) + "DROP FOREIGN KEY #{name}" + end + + def visit_AddColumnDefinition(o) + add_column_position!(super, column_options(o.column)) + end + + def visit_ChangeColumnDefinition(o) + change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}".dup + add_column_position!(change_column_sql, column_options(o.column)) + end + + def add_table_options!(create_sql, options) + add_sql_comment!(super, options[:comment]) + end + + def add_column_options!(sql, options) + # By default, TIMESTAMP columns are NOT NULL, cannot contain NULL values, + # and assigning NULL assigns the current timestamp. To permit a TIMESTAMP + # column to contain NULL, explicitly declare it with the NULL attribute. + # See https://dev.mysql.com/doc/refman/5.7/en/timestamp-initialization.html + if /\Atimestamp\b/.match?(options[:column].sql_type) && !options[:primary_key] + sql << " NULL" unless options[:null] == false || options_include_default?(options) + end + + if charset = options[:charset] + sql << " CHARACTER SET #{charset}" + end + + if collation = options[:collation] + sql << " COLLATE #{collation}" + end + + if as = options[:as] + sql << " AS (#{as})" + if options[:stored] + sql << (mariadb? ? " PERSISTENT" : " STORED") + end + end + + add_sql_comment!(super, options[:comment]) + end + + def add_column_position!(sql, options) + if options[:first] + sql << " FIRST" + elsif options[:after] + sql << " AFTER #{quote_column_name(options[:after])}" + end + + sql + end + + def index_in_create(table_name, column_name, options) + index_name, index_type, index_columns, _, _, index_using, comment = @conn.add_index_options(table_name, column_name, options) + add_sql_comment!("#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})".dup, comment) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql/schema_definitions.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql/schema_definitions.rb new file mode 100644 index 00000000..2ed4ad16 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql/schema_definitions.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module MySQL + module ColumnMethods + def blob(*args, **options) + args.each { |name| column(name, :blob, options) } + end + + def tinyblob(*args, **options) + args.each { |name| column(name, :tinyblob, options) } + end + + def mediumblob(*args, **options) + args.each { |name| column(name, :mediumblob, options) } + end + + def longblob(*args, **options) + args.each { |name| column(name, :longblob, options) } + end + + def tinytext(*args, **options) + args.each { |name| column(name, :tinytext, options) } + end + + def mediumtext(*args, **options) + args.each { |name| column(name, :mediumtext, options) } + end + + def longtext(*args, **options) + args.each { |name| column(name, :longtext, options) } + end + + def unsigned_integer(*args, **options) + args.each { |name| column(name, :unsigned_integer, options) } + end + + def unsigned_bigint(*args, **options) + args.each { |name| column(name, :unsigned_bigint, options) } + end + + def unsigned_float(*args, **options) + args.each { |name| column(name, :unsigned_float, options) } + end + + def unsigned_decimal(*args, **options) + args.each { |name| column(name, :unsigned_decimal, options) } + end + end + + class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition + include ColumnMethods + + def new_column_definition(name, type, **options) # :nodoc: + case type + when :virtual + type = options[:type] + when :primary_key + type = :integer + options[:limit] ||= 8 + options[:primary_key] = true + when /\Aunsigned_(?.+)\z/ + type = $~[:type].to_sym + options[:unsigned] = true + end + + super + end + + private + def aliased_types(name, fallback) + fallback + end + + def integer_like_primary_key_type(type, options) + options[:auto_increment] = true + type + end + end + + class Table < ActiveRecord::ConnectionAdapters::Table + include ColumnMethods + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql/schema_dumper.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql/schema_dumper.rb new file mode 100644 index 00000000..d23178e4 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql/schema_dumper.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module MySQL + class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc: + private + def prepare_column_options(column) + spec = super + spec[:unsigned] = "true" if column.unsigned? + spec[:auto_increment] = "true" if column.auto_increment? + + if @connection.supports_virtual_columns? && column.virtual? + spec[:as] = extract_expression_for_virtual_column(column) + spec[:stored] = "true" if /\b(?:STORED|PERSISTENT)\b/.match?(column.extra) + spec = { type: schema_type(column).inspect }.merge!(spec) + end + + spec + end + + def column_spec_for_primary_key(column) + spec = super + spec.delete(:auto_increment) if column.type == :integer && column.auto_increment? + spec + end + + def default_primary_key?(column) + super && column.auto_increment? && !column.unsigned? + end + + def explicit_primary_key_default?(column) + column.type == :integer && !column.auto_increment? + end + + def schema_type(column) + case column.sql_type + when /\Atimestamp\b/ + :timestamp + when "tinyblob" + :blob + else + super + end + end + + def schema_precision(column) + super unless /\A(?:date)?time(?:stamp)?\b/.match?(column.sql_type) && column.precision == 0 + end + + def schema_collation(column) + if column.collation && table_name = column.table_name + @table_collation_cache ||= {} + @table_collation_cache[table_name] ||= + @connection.exec_query("SHOW TABLE STATUS LIKE #{@connection.quote(table_name)}", "SCHEMA").first["Collation"] + column.collation.inspect if column.collation != @table_collation_cache[table_name] + end + end + + def extract_expression_for_virtual_column(column) + if @connection.mariadb? && @connection.version < "10.2.5" + create_table_info = @connection.send(:create_table_info, column.table_name) + column_name = @connection.quote_column_name(column.name) + if %r/#{column_name} #{Regexp.quote(column.sql_type)}(?: COLLATE \w+)? AS \((?.+?)\) #{column.extra}/ =~ create_table_info + $~[:expression].inspect + end + else + scope = @connection.send(:quoted_scope, column.table_name) + column_name = @connection.quote(column.name) + sql = "SELECT generation_expression FROM information_schema.columns" \ + " WHERE table_schema = #{scope[:schema]}" \ + " AND table_name = #{scope[:name]}" \ + " AND column_name = #{column_name}" + @connection.query_value(sql, "SCHEMA").inspect + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql/schema_statements.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql/schema_statements.rb new file mode 100644 index 00000000..2087938d --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql/schema_statements.rb @@ -0,0 +1,148 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module MySQL + module SchemaStatements # :nodoc: + # Returns an array of indexes for the given table. + def indexes(table_name) + indexes = [] + current_index = nil + execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result| + each_hash(result) do |row| + if current_index != row[:Key_name] + next if row[:Key_name] == "PRIMARY" # skip the primary key + current_index = row[:Key_name] + + mysql_index_type = row[:Index_type].downcase.to_sym + case mysql_index_type + when :fulltext, :spatial + index_type = mysql_index_type + when :btree, :hash + index_using = mysql_index_type + end + + indexes << [ + row[:Table], + row[:Key_name], + row[:Non_unique].to_i == 0, + [], + lengths: {}, + orders: {}, + type: index_type, + using: index_using, + comment: row[:Index_comment].presence + ] + end + + indexes.last[-2] << row[:Column_name] + indexes.last[-1][:lengths].merge!(row[:Column_name] => row[:Sub_part].to_i) if row[:Sub_part] + indexes.last[-1][:orders].merge!(row[:Column_name] => :desc) if row[:Collation] == "D" + end + end + + indexes.map { |index| IndexDefinition.new(*index) } + end + + def remove_column(table_name, column_name, type = nil, options = {}) + if foreign_key_exists?(table_name, column: column_name) + remove_foreign_key(table_name, column: column_name) + end + super + end + + def internal_string_options_for_primary_key + super.tap do |options| + if CHARSETS_OF_4BYTES_MAXLEN.include?(charset) && (mariadb? || version < "8.0.0") + options[:collation] = collation.sub(/\A[^_]+/, "utf8") + end + end + end + + def update_table_definition(table_name, base) + MySQL::Table.new(table_name, base) + end + + def create_schema_dumper(options) + MySQL::SchemaDumper.create(self, options) + end + + private + CHARSETS_OF_4BYTES_MAXLEN = ["utf8mb4", "utf16", "utf16le", "utf32"] + + def schema_creation + MySQL::SchemaCreation.new(self) + end + + def create_table_definition(*args) + MySQL::TableDefinition.new(*args) + end + + def new_column_from_field(table_name, field) + type_metadata = fetch_type_metadata(field[:Type], field[:Extra]) + if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(field[:Default]) + default, default_function = nil, field[:Default] + else + default, default_function = field[:Default], nil + end + + MySQL::Column.new( + field[:Field], + default, + type_metadata, + field[:Null] == "YES", + table_name, + default_function, + field[:Collation], + comment: field[:Comment].presence + ) + end + + def fetch_type_metadata(sql_type, extra = "") + MySQL::TypeMetadata.new(super(sql_type), extra: extra) + end + + def extract_foreign_key_action(specifier) + super unless specifier == "RESTRICT" + end + + def add_index_length(quoted_columns, **options) + lengths = options_for_index_columns(options[:length]) + quoted_columns.each do |name, column| + column << "(#{lengths[name]})" if lengths[name].present? + end + end + + def add_options_for_index_columns(quoted_columns, **options) + quoted_columns = add_index_length(quoted_columns, options) + super + end + + def data_source_sql(name = nil, type: nil) + scope = quoted_scope(name, type: type) + + sql = "SELECT table_name FROM information_schema.tables".dup + sql << " WHERE table_schema = #{scope[:schema]}" + sql << " AND table_name = #{scope[:name]}" if scope[:name] + sql << " AND table_type = #{scope[:type]}" if scope[:type] + sql + end + + def quoted_scope(name = nil, type: nil) + schema, name = extract_schema_qualified_name(name) + scope = {} + scope[:schema] = schema ? quote(schema) : "database()" + scope[:name] = quote(name) if name + scope[:type] = quote(type) if type + scope + end + + def extract_schema_qualified_name(string) + schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/) + schema, name = nil, schema unless name + [schema, name] + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql/type_metadata.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql/type_metadata.rb new file mode 100644 index 00000000..7ad0944d --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql/type_metadata.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module MySQL + class TypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc: + undef to_yaml if method_defined?(:to_yaml) + + attr_reader :extra + + def initialize(type_metadata, extra: "") + super(type_metadata) + @type_metadata = type_metadata + @extra = extra + end + + def ==(other) + other.is_a?(MySQL::TypeMetadata) && + attributes_for_hash == other.attributes_for_hash + end + alias eql? == + + def hash + attributes_for_hash.hash + end + + protected + + def attributes_for_hash + [self.class, @type_metadata, extra] + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql2_adapter.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql2_adapter.rb new file mode 100644 index 00000000..4c57bd48 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -0,0 +1,129 @@ +# frozen_string_literal: true + +require "active_record/connection_adapters/abstract_mysql_adapter" +require "active_record/connection_adapters/mysql/database_statements" + +gem "mysql2", ">= 0.4.4", "< 0.6.0" +require "mysql2" + +module ActiveRecord + module ConnectionHandling # :nodoc: + # Establishes a connection to the database that's used by all Active Record objects. + def mysql2_connection(config) + config = config.symbolize_keys + config[:flags] ||= 0 + + if config[:flags].kind_of? Array + config[:flags].push "FOUND_ROWS".freeze + else + config[:flags] |= Mysql2::Client::FOUND_ROWS + end + + client = Mysql2::Client.new(config) + ConnectionAdapters::Mysql2Adapter.new(client, logger, nil, config) + rescue Mysql2::Error => error + if error.message.include?("Unknown database") + raise ActiveRecord::NoDatabaseError + else + raise + end + end + end + + module ConnectionAdapters + class Mysql2Adapter < AbstractMysqlAdapter + ADAPTER_NAME = "Mysql2".freeze + + include MySQL::DatabaseStatements + + def initialize(connection, logger, connection_options, config) + super + @prepared_statements = false unless config.key?(:prepared_statements) + configure_connection + end + + def supports_json? + !mariadb? && version >= "5.7.8" + end + + def supports_comments? + true + end + + def supports_comments_in_create? + true + end + + def supports_savepoints? + true + end + + # HELPER METHODS =========================================== + + def each_hash(result) # :nodoc: + if block_given? + result.each(as: :hash, symbolize_keys: true) do |row| + yield row + end + else + to_enum(:each_hash, result) + end + end + + def error_number(exception) + exception.error_number if exception.respond_to?(:error_number) + end + + #-- + # QUOTING ================================================== + #++ + + def quote_string(string) + @connection.escape(string) + end + + #-- + # CONNECTION MANAGEMENT ==================================== + #++ + + def active? + @connection.ping + end + + def reconnect! + super + disconnect! + connect + end + alias :reset! :reconnect! + + # Disconnects from the database if already connected. + # Otherwise, this method does nothing. + def disconnect! + super + @connection.close + end + + def discard! # :nodoc: + @connection.automatic_close = false + @connection = nil + end + + private + + def connect + @connection = Mysql2::Client.new(@config) + configure_connection + end + + def configure_connection + @connection.query_options.merge!(as: :array) + super + end + + def full_version + @full_version ||= @connection.server_info[:version] + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/column.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/column.rb new file mode 100644 index 00000000..469ef3f5 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/column.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + # PostgreSQL-specific extensions to column definitions in a table. + class PostgreSQLColumn < Column #:nodoc: + delegate :array, :oid, :fmod, to: :sql_type_metadata + alias :array? :array + + def initialize(*, max_identifier_length: 63, **) + super + @max_identifier_length = max_identifier_length + end + + def serial? + return unless default_function + + if %r{\Anextval\('"?(?.+_(?seq\d*))"?'::regclass\)\z} =~ default_function + sequence_name_from_parts(table_name, name, suffix) == sequence_name + end + end + + protected + attr_reader :max_identifier_length + + private + def sequence_name_from_parts(table_name, column_name, suffix) + over_length = [table_name, column_name, suffix].map(&:length).sum + 2 - max_identifier_length + + if over_length > 0 + column_name_length = [(max_identifier_length - suffix.length - 2) / 2, column_name.length].min + over_length -= column_name.length - column_name_length + column_name = column_name[0, column_name_length - [over_length, 0].min] + end + + if over_length > 0 + table_name = table_name[0, table_name.length - over_length] + end + + "#{table_name}_#{column_name}_#{suffix}" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/database_statements.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/database_statements.rb new file mode 100644 index 00000000..8db2a645 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -0,0 +1,163 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module DatabaseStatements + def explain(arel, binds = []) + sql = "EXPLAIN #{to_sql(arel, binds)}" + PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", binds)) + end + + # The internal PostgreSQL identifier of the money data type. + MONEY_COLUMN_TYPE_OID = 790 #:nodoc: + # The internal PostgreSQL identifier of the BYTEA data type. + BYTEA_COLUMN_TYPE_OID = 17 #:nodoc: + + # create a 2D array representing the result set + def result_as_array(res) #:nodoc: + # check if we have any binary column and if they need escaping + ftypes = Array.new(res.nfields) do |i| + [i, res.ftype(i)] + end + + rows = res.values + return rows unless ftypes.any? { |_, x| + x == BYTEA_COLUMN_TYPE_OID || x == MONEY_COLUMN_TYPE_OID + } + + typehash = ftypes.group_by { |_, type| type } + binaries = typehash[BYTEA_COLUMN_TYPE_OID] || [] + monies = typehash[MONEY_COLUMN_TYPE_OID] || [] + + rows.each do |row| + # unescape string passed BYTEA field (OID == 17) + binaries.each do |index, _| + row[index] = unescape_bytea(row[index]) + end + + # If this is a money type column and there are any currency symbols, + # then strip them off. Indeed it would be prettier to do this in + # PostgreSQLColumn.string_to_decimal but would break form input + # fields that call value_before_type_cast. + monies.each do |index, _| + data = row[index] + # Because money output is formatted according to the locale, there are two + # cases to consider (note the decimal separators): + # (1) $12,345,678.12 + # (2) $12.345.678,12 + case data + when /^-?\D+[\d,]+\.\d{2}$/ # (1) + data.gsub!(/[^-\d.]/, "") + when /^-?\D+[\d.]+,\d{2}$/ # (2) + data.gsub!(/[^-\d,]/, "").sub!(/,/, ".") + end + end + end + end + + # Queries the database and returns the results in an Array-like object + def query(sql, name = nil) #:nodoc: + log(sql, name) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + result_as_array @connection.async_exec(sql) + end + end + end + + # Executes an SQL statement, returning a PG::Result object on success + # or raising a PG::Error exception otherwise. + # Note: the PG::Result object is manually memory managed; if you don't + # need it specifically, you may want consider the exec_query wrapper. + def execute(sql, name = nil) + log(sql, name) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @connection.async_exec(sql) + end + end + end + + def exec_query(sql, name = "SQL", binds = [], prepare: false) + execute_and_clear(sql, name, binds, prepare: prepare) do |result| + types = {} + fields = result.fields + fields.each_with_index do |fname, i| + ftype = result.ftype i + fmod = result.fmod i + types[fname] = get_oid_type(ftype, fmod, fname) + end + ActiveRecord::Result.new(fields, result.values, types) + end + end + + def exec_delete(sql, name = nil, binds = []) + execute_and_clear(sql, name, binds) { |result| result.cmd_tuples } + end + alias :exec_update :exec_delete + + def sql_for_insert(sql, pk, id_value, sequence_name, binds) # :nodoc: + if pk.nil? + # Extract the table from the insert sql. Yuck. + table_ref = extract_table_ref_from_insert_sql(sql) + pk = primary_key(table_ref) if table_ref + end + + if pk = suppress_composite_primary_key(pk) + sql = "#{sql} RETURNING #{quote_column_name(pk)}" + end + + super + end + private :sql_for_insert + + def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil) + if use_insert_returning? || pk == false + super + else + result = exec_query(sql, name, binds) + unless sequence_name + table_ref = extract_table_ref_from_insert_sql(sql) + if table_ref + pk = primary_key(table_ref) if pk.nil? + pk = suppress_composite_primary_key(pk) + sequence_name = default_sequence_name(table_ref, pk) + end + return result unless sequence_name + end + last_insert_id_result(sequence_name) + end + end + + # Begins a transaction. + def begin_db_transaction + execute "BEGIN" + end + + def begin_isolated_db_transaction(isolation) + begin_db_transaction + execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}" + end + + # Commits a transaction. + def commit_db_transaction + execute "COMMIT" + end + + # Aborts a transaction. + def exec_rollback_db_transaction + execute "ROLLBACK" + end + + private + # Returns the current ID of a table's sequence. + def last_insert_id_result(sequence_name) + exec_query("SELECT currval(#{quote(sequence_name)})", "SQL") + end + + def suppress_composite_primary_key(pk) + pk unless pk.is_a?(Array) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb new file mode 100644 index 00000000..086a5dcc --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + class ExplainPrettyPrinter # :nodoc: + # Pretty prints the result of an EXPLAIN in a way that resembles the output of the + # PostgreSQL shell: + # + # QUERY PLAN + # ------------------------------------------------------------------------------ + # Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0) + # Join Filter: (posts.user_id = users.id) + # -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4) + # Index Cond: (id = 1) + # -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4) + # Filter: (posts.user_id = 1) + # (6 rows) + # + def pp(result) + header = result.columns.first + lines = result.rows.map(&:first) + + # We add 2 because there's one char of padding at both sides, note + # the extra hyphens in the example above. + width = [header, *lines].map(&:length).max + 2 + + pp = [] + + pp << header.center(width).rstrip + pp << "-" * width + + pp += lines.map { |line| " #{line}" } + + nrows = result.rows.length + rows_label = nrows == 1 ? "row" : "rows" + pp << "(#{nrows} #{rows_label})" + + pp.join("\n") + "\n" + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid.rb new file mode 100644 index 00000000..247a2505 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "active_record/connection_adapters/postgresql/oid/array" +require "active_record/connection_adapters/postgresql/oid/bit" +require "active_record/connection_adapters/postgresql/oid/bit_varying" +require "active_record/connection_adapters/postgresql/oid/bytea" +require "active_record/connection_adapters/postgresql/oid/cidr" +require "active_record/connection_adapters/postgresql/oid/date" +require "active_record/connection_adapters/postgresql/oid/date_time" +require "active_record/connection_adapters/postgresql/oid/decimal" +require "active_record/connection_adapters/postgresql/oid/enum" +require "active_record/connection_adapters/postgresql/oid/hstore" +require "active_record/connection_adapters/postgresql/oid/inet" +require "active_record/connection_adapters/postgresql/oid/jsonb" +require "active_record/connection_adapters/postgresql/oid/money" +require "active_record/connection_adapters/postgresql/oid/oid" +require "active_record/connection_adapters/postgresql/oid/point" +require "active_record/connection_adapters/postgresql/oid/legacy_point" +require "active_record/connection_adapters/postgresql/oid/range" +require "active_record/connection_adapters/postgresql/oid/specialized_string" +require "active_record/connection_adapters/postgresql/oid/uuid" +require "active_record/connection_adapters/postgresql/oid/vector" +require "active_record/connection_adapters/postgresql/oid/xml" + +require "active_record/connection_adapters/postgresql/oid/type_map_initializer" + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/array.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/array.rb new file mode 100644 index 00000000..6fbeaa2b --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/array.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Array < Type::Value # :nodoc: + include Type::Helpers::Mutable + + Data = Struct.new(:encoder, :values) # :nodoc: + + attr_reader :subtype, :delimiter + delegate :type, :user_input_in_time_zone, :limit, :precision, :scale, to: :subtype + + def initialize(subtype, delimiter = ",") + @subtype = subtype + @delimiter = delimiter + + @pg_encoder = PG::TextEncoder::Array.new name: "#{type}[]", delimiter: delimiter + @pg_decoder = PG::TextDecoder::Array.new name: "#{type}[]", delimiter: delimiter + end + + def deserialize(value) + case value + when ::String + type_cast_array(@pg_decoder.decode(value), :deserialize) + when Data + type_cast_array(value.values, :deserialize) + else + super + end + end + + def cast(value) + if value.is_a?(::String) + value = begin + @pg_decoder.decode(value) + rescue TypeError + # malformed array string is treated as [], will raise in PG 2.0 gem + # this keeps a consistent implementation + [] + end + end + type_cast_array(value, :cast) + end + + def serialize(value) + if value.is_a?(::Array) + casted_values = type_cast_array(value, :serialize) + Data.new(@pg_encoder, casted_values) + else + super + end + end + + def ==(other) + other.is_a?(Array) && + subtype == other.subtype && + delimiter == other.delimiter + end + + def type_cast_for_schema(value) + return super unless value.is_a?(::Array) + "[" + value.map { |v| subtype.type_cast_for_schema(v) }.join(", ") + "]" + end + + def map(value, &block) + value.map(&block) + end + + def changed_in_place?(raw_old_value, new_value) + deserialize(raw_old_value) != new_value + end + + def force_equality?(value) + value.is_a?(::Array) + end + + private + + def type_cast_array(value, method) + if value.is_a?(::Array) + value.map { |item| type_cast_array(item, method) } + else + @subtype.public_send(method, value) + end + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/bit.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/bit.rb new file mode 100644 index 00000000..587e95d1 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/bit.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Bit < Type::Value # :nodoc: + def type + :bit + end + + def cast_value(value) + if ::String === value + case value + when /^0x/i + value[2..-1].hex.to_s(2) # Hexadecimal notation + else + value # Bit-string notation + end + else + value.to_s + end + end + + def serialize(value) + Data.new(super) if value + end + + class Data + def initialize(value) + @value = value + end + + def to_s + value + end + + def binary? + /\A[01]*\Z/.match?(value) + end + + def hex? + /\A[0-9A-F]*\Z/i.match?(value) + end + + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected + + attr_reader :value + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb new file mode 100644 index 00000000..dc7079dd --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class BitVarying < OID::Bit # :nodoc: + def type + :bit_varying + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/bytea.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/bytea.rb new file mode 100644 index 00000000..a3c60ece --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/bytea.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Bytea < Type::Binary # :nodoc: + def deserialize(value) + return if value.nil? + return value.to_s if value.is_a?(Type::Binary::Data) + PG::Connection.unescape_bytea(super) + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/cidr.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/cidr.rb new file mode 100644 index 00000000..66e99d94 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/cidr.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "ipaddr" + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Cidr < Type::Value # :nodoc: + def type + :cidr + end + + def type_cast_for_schema(value) + subnet_mask = value.instance_variable_get(:@mask_addr) + + # If the subnet mask is equal to /32, don't output it + if subnet_mask == (2**32 - 1) + "\"#{value}\"" + else + "\"#{value}/#{subnet_mask.to_s(2).count('1')}\"" + end + end + + def serialize(value) + if IPAddr === value + "#{value}/#{value.instance_variable_get(:@mask_addr).to_s(2).count('1')}" + else + value + end + end + + def cast_value(value) + if value.nil? + nil + elsif String === value + begin + IPAddr.new(value) + rescue ArgumentError + nil + end + else + value + end + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/date.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/date.rb new file mode 100644 index 00000000..24a1daa9 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/date.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Date < Type::Date # :nodoc: + def cast_value(value) + case value + when "infinity" then ::Float::INFINITY + when "-infinity" then -::Float::INFINITY + when / BC$/ + astronomical_year = format("%04d", -value[/^\d+/].to_i + 1) + super(value.sub(/ BC$/, "").sub(/^\d+/, astronomical_year)) + else + super + end + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/date_time.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/date_time.rb new file mode 100644 index 00000000..cd667422 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/date_time.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class DateTime < Type::DateTime # :nodoc: + def cast_value(value) + case value + when "infinity" then ::Float::INFINITY + when "-infinity" then -::Float::INFINITY + when / BC$/ + astronomical_year = format("%04d", -value[/^\d+/].to_i + 1) + super(value.sub(/ BC$/, "").sub(/^\d+/, astronomical_year)) + else + super + end + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/decimal.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/decimal.rb new file mode 100644 index 00000000..e7d33855 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/decimal.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Decimal < Type::Decimal # :nodoc: + def infinity(options = {}) + BigDecimal("Infinity") * (options[:negative] ? -1 : 1) + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/enum.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/enum.rb new file mode 100644 index 00000000..f70f09ad --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/enum.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Enum < Type::Value # :nodoc: + def type + :enum + end + + private + + def cast_value(value) + value.to_s + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/hstore.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/hstore.rb new file mode 100644 index 00000000..aabe83b8 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/hstore.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Hstore < Type::Value # :nodoc: + include Type::Helpers::Mutable + + def type + :hstore + end + + def deserialize(value) + if value.is_a?(::String) + ::Hash[value.scan(HstorePair).map { |k, v| + v = v.upcase == "NULL" ? nil : v.gsub(/\A"(.*)"\Z/m, '\1').gsub(/\\(.)/, '\1') + k = k.gsub(/\A"(.*)"\Z/m, '\1').gsub(/\\(.)/, '\1') + [k, v] + }] + else + value + end + end + + def serialize(value) + if value.is_a?(::Hash) + value.map { |k, v| "#{escape_hstore(k)}=>#{escape_hstore(v)}" }.join(", ") + elsif value.respond_to?(:to_unsafe_h) + serialize(value.to_unsafe_h) + else + value + end + end + + def accessor + ActiveRecord::Store::StringKeyedHashAccessor + end + + # Will compare the Hash equivalents of +raw_old_value+ and +new_value+. + # By comparing hashes, this avoids an edge case where the order of + # the keys change between the two hashes, and they would not be marked + # as equal. + def changed_in_place?(raw_old_value, new_value) + deserialize(raw_old_value) != new_value + end + + private + + HstorePair = begin + quoted_string = /"[^"\\]*(?:\\.[^"\\]*)*"/ + unquoted_string = /(?:\\.|[^\s,])[^\s=,\\]*(?:\\.[^\s=,\\]*|=[^,>])*/ + /(#{quoted_string}|#{unquoted_string})\s*=>\s*(#{quoted_string}|#{unquoted_string})/ + end + + def escape_hstore(value) + if value.nil? + "NULL" + else + if value == "" + '""' + else + '"%s"' % value.to_s.gsub(/(["\\])/, '\\\\\1') + end + end + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/inet.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/inet.rb new file mode 100644 index 00000000..55be71fd --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/inet.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Inet < Cidr # :nodoc: + def type + :inet + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb new file mode 100644 index 00000000..e0216f10 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Jsonb < Type::Json # :nodoc: + def type + :jsonb + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb new file mode 100644 index 00000000..7b057a84 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class LegacyPoint < Type::Value # :nodoc: + include Type::Helpers::Mutable + + def type + :point + end + + def cast(value) + case value + when ::String + if value[0] == "(" && value[-1] == ")" + value = value[1...-1] + end + cast(value.split(",")) + when ::Array + value.map { |v| Float(v) } + else + value + end + end + + def serialize(value) + if value.is_a?(::Array) + "(#{number_for_point(value[0])},#{number_for_point(value[1])})" + else + super + end + end + + private + + def number_for_point(number) + number.to_s.gsub(/\.0$/, "") + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/money.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/money.rb new file mode 100644 index 00000000..6434377b --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/money.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Money < Type::Decimal # :nodoc: + def type + :money + end + + def scale + 2 + end + + def cast_value(value) + return value unless ::String === value + + # Because money output is formatted according to the locale, there are two + # cases to consider (note the decimal separators): + # (1) $12,345,678.12 + # (2) $12.345.678,12 + # Negative values are represented as follows: + # (3) -$2.55 + # (4) ($2.55) + + value = value.sub(/^\((.+)\)$/, '-\1') # (4) + case value + when /^-?\D+[\d,]+\.\d{2}$/ # (1) + value.gsub!(/[^-\d.]/, "") + when /^-?\D+[\d.]+,\d{2}$/ # (2) + value.gsub!(/[^-\d,]/, "").sub!(/,/, ".") + end + + super(value) + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/oid.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/oid.rb new file mode 100644 index 00000000..d8c04432 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/oid.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Oid < Type::Integer # :nodoc: + def type + :oid + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/point.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/point.rb new file mode 100644 index 00000000..02a9c506 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/point.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +module ActiveRecord + Point = Struct.new(:x, :y) + + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Point < Type::Value # :nodoc: + include Type::Helpers::Mutable + + def type + :point + end + + def cast(value) + case value + when ::String + return if value.blank? + + if value[0] == "(" && value[-1] == ")" + value = value[1...-1] + end + x, y = value.split(",") + build_point(x, y) + when ::Array + build_point(*value) + else + value + end + end + + def serialize(value) + case value + when ActiveRecord::Point + "(#{number_for_point(value.x)},#{number_for_point(value.y)})" + when ::Array + serialize(build_point(*value)) + else + super + end + end + + def type_cast_for_schema(value) + if ActiveRecord::Point === value + [value.x, value.y] + else + super + end + end + + private + + def number_for_point(number) + number.to_s.gsub(/\.0$/, "") + end + + def build_point(x, y) + ActiveRecord::Point.new(Float(x), Float(y)) + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/range.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/range.rb new file mode 100644 index 00000000..d85f9ab3 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/range.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Range < Type::Value # :nodoc: + attr_reader :subtype, :type + delegate :user_input_in_time_zone, to: :subtype + + def initialize(subtype, type = :range) + @subtype = subtype + @type = type + end + + def type_cast_for_schema(value) + value.inspect.gsub("Infinity", "::Float::INFINITY") + end + + def cast_value(value) + return if value == "empty" + return value unless value.is_a?(::String) + + extracted = extract_bounds(value) + from = type_cast_single extracted[:from] + to = type_cast_single extracted[:to] + + if !infinity?(from) && extracted[:exclude_start] + raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')" + end + ::Range.new(from, to, extracted[:exclude_end]) + end + + def serialize(value) + if value.is_a?(::Range) + from = type_cast_single_for_database(value.begin) + to = type_cast_single_for_database(value.end) + ::Range.new(from, to, value.exclude_end?) + else + super + end + end + + def ==(other) + other.is_a?(Range) && + other.subtype == subtype && + other.type == type + end + + def map(value) # :nodoc: + new_begin = yield(value.begin) + new_end = yield(value.end) + ::Range.new(new_begin, new_end, value.exclude_end?) + end + + def force_equality?(value) + value.is_a?(::Range) + end + + private + + def type_cast_single(value) + infinity?(value) ? value : @subtype.deserialize(value) + end + + def type_cast_single_for_database(value) + infinity?(value) ? value : @subtype.serialize(value) + end + + def extract_bounds(value) + from, to = value[1..-2].split(",") + { + from: (value[1] == "," || from == "-infinity") ? infinity(negative: true) : from, + to: (value[-2] == "," || to == "infinity") ? infinity : to, + exclude_start: (value[0] == "("), + exclude_end: (value[-1] == ")") + } + end + + def infinity(negative: false) + if subtype.respond_to?(:infinity) + subtype.infinity(negative: negative) + elsif negative + -::Float::INFINITY + else + ::Float::INFINITY + end + end + + def infinity?(value) + value.respond_to?(:infinite?) && value.infinite? + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb new file mode 100644 index 00000000..4ad1344f --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class SpecializedString < Type::String # :nodoc: + attr_reader :type + + def initialize(type, **options) + @type = type + super(options) + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb new file mode 100644 index 00000000..231278c1 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + # This class uses the data from PostgreSQL pg_type table to build + # the OID -> Type mapping. + # - OID is an integer representing the type. + # - Type is an OID::Type object. + # This class has side effects on the +store+ passed during initialization. + class TypeMapInitializer # :nodoc: + def initialize(store) + @store = store + end + + def run(records) + nodes = records.reject { |row| @store.key? row["oid"].to_i } + mapped, nodes = nodes.partition { |row| @store.key? row["typname"] } + ranges, nodes = nodes.partition { |row| row["typtype"] == "r".freeze } + enums, nodes = nodes.partition { |row| row["typtype"] == "e".freeze } + domains, nodes = nodes.partition { |row| row["typtype"] == "d".freeze } + arrays, nodes = nodes.partition { |row| row["typinput"] == "array_in".freeze } + composites, nodes = nodes.partition { |row| row["typelem"].to_i != 0 } + + mapped.each { |row| register_mapped_type(row) } + enums.each { |row| register_enum_type(row) } + domains.each { |row| register_domain_type(row) } + arrays.each { |row| register_array_type(row) } + ranges.each { |row| register_range_type(row) } + composites.each { |row| register_composite_type(row) } + end + + def query_conditions_for_initial_load + known_type_names = @store.keys.map { |n| "'#{n}'" } + known_type_types = %w('r' 'e' 'd') + <<-SQL % [known_type_names.join(", "), known_type_types.join(", ")] + WHERE + t.typname IN (%s) + OR t.typtype IN (%s) + OR t.typinput = 'array_in(cstring,oid,integer)'::regprocedure + OR t.typelem != 0 + SQL + end + + private + def register_mapped_type(row) + alias_type row["oid"], row["typname"] + end + + def register_enum_type(row) + register row["oid"], OID::Enum.new + end + + def register_array_type(row) + register_with_subtype(row["oid"], row["typelem"].to_i) do |subtype| + OID::Array.new(subtype, row["typdelim"]) + end + end + + def register_range_type(row) + register_with_subtype(row["oid"], row["rngsubtype"].to_i) do |subtype| + OID::Range.new(subtype, row["typname"].to_sym) + end + end + + def register_domain_type(row) + if base_type = @store.lookup(row["typbasetype"].to_i) + register row["oid"], base_type + else + warn "unknown base type (OID: #{row["typbasetype"]}) for domain #{row["typname"]}." + end + end + + def register_composite_type(row) + if subtype = @store.lookup(row["typelem"].to_i) + register row["oid"], OID::Vector.new(row["typdelim"], subtype) + end + end + + def register(oid, oid_type = nil, &block) + oid = assert_valid_registration(oid, oid_type || block) + if block_given? + @store.register_type(oid, &block) + else + @store.register_type(oid, oid_type) + end + end + + def alias_type(oid, target) + oid = assert_valid_registration(oid, target) + @store.alias_type(oid, target) + end + + def register_with_subtype(oid, target_oid) + if @store.key?(target_oid) + register(oid) do |_, *args| + yield @store.lookup(target_oid, *args) + end + end + end + + def assert_valid_registration(oid, oid_type) + raise ArgumentError, "can't register nil type for OID #{oid}" if oid_type.nil? + oid.to_i + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/uuid.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/uuid.rb new file mode 100644 index 00000000..bc9b8dbf --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/uuid.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Uuid < Type::Value # :nodoc: + ACCEPTABLE_UUID = %r{\A(\{)?([a-fA-F0-9]{4}-?){8}(?(1)\}|)\z} + + alias_method :serialize, :deserialize + + def type + :uuid + end + + def cast(value) + value.to_s[ACCEPTABLE_UUID, 0] + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/vector.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/vector.rb new file mode 100644 index 00000000..88ef626a --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/vector.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Vector < Type::Value # :nodoc: + attr_reader :delim, :subtype + + # +delim+ corresponds to the `typdelim` column in the pg_types + # table. +subtype+ is derived from the `typelem` column in the + # pg_types table. + def initialize(delim, subtype) + @delim = delim + @subtype = subtype + end + + # FIXME: this should probably split on +delim+ and use +subtype+ + # to cast the values. Unfortunately, the current Rails behavior + # is to just return the string. + def cast(value) + value + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/xml.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/xml.rb new file mode 100644 index 00000000..042f32fd --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/oid/xml.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Xml < Type::String # :nodoc: + def type + :xml + end + + def serialize(value) + return unless value + Data.new(super) + end + + class Data # :nodoc: + def initialize(value) + @value = value + end + + def to_s + @value + end + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/quoting.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/quoting.rb new file mode 100644 index 00000000..e75202b0 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -0,0 +1,168 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module Quoting + # Escapes binary strings for bytea input to the database. + def escape_bytea(value) + @connection.escape_bytea(value) if value + end + + # Unescapes bytea output from a database to the binary string it represents. + # NOTE: This is NOT an inverse of escape_bytea! This is only to be used + # on escaped binary output from database drive. + def unescape_bytea(value) + @connection.unescape_bytea(value) if value + end + + # Quotes strings for use in SQL input. + def quote_string(s) #:nodoc: + @connection.escape(s) + end + + # Checks the following cases: + # + # - table_name + # - "table.name" + # - schema_name.table_name + # - schema_name."table.name" + # - "schema.name".table_name + # - "schema.name"."table.name" + def quote_table_name(name) # :nodoc: + @quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze + end + + # Quotes schema names for use in SQL queries. + def quote_schema_name(name) + PG::Connection.quote_ident(name) + end + + def quote_table_name_for_assignment(table, attr) + quote_column_name(attr) + end + + # Quotes column names for use in SQL queries. + def quote_column_name(name) # :nodoc: + @quoted_column_names[name] ||= PG::Connection.quote_ident(super).freeze + end + + # Quote date/time values for use in SQL input. + def quoted_date(value) #:nodoc: + if value.year <= 0 + bce_year = format("%04d", -value.year + 1) + super.sub(/^-?\d+/, bce_year) + " BC" + else + super + end + end + + def quoted_binary(value) # :nodoc: + "'#{escape_bytea(value.to_s)}'" + end + + def quote_default_expression(value, column) # :nodoc: + if value.is_a?(Proc) + value.call + elsif column.type == :uuid && value.is_a?(String) && /\(\)/.match?(value) + value # Does not quote function default values for UUID columns + elsif column.respond_to?(:array?) + value = type_cast_from_column(column, value) + quote(value) + else + super + end + end + + def lookup_cast_type_from_column(column) # :nodoc: + type_map.lookup(column.oid, column.fmod, column.sql_type) + end + + private + def lookup_cast_type(sql_type) + super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i) + end + + def _quote(value) + case value + when OID::Xml::Data + "xml '#{quote_string(value.to_s)}'" + when OID::Bit::Data + if value.binary? + "B'#{value}'" + elsif value.hex? + "X'#{value}'" + end + when Float + if value.infinite? || value.nan? + "'#{value}'" + else + super + end + when OID::Array::Data + _quote(encode_array(value)) + when Range + _quote(encode_range(value)) + else + super + end + end + + def _type_cast(value) + case value + when Type::Binary::Data + # Return a bind param hash with format as binary. + # See https://deveiate.org/code/pg/PG/Connection.html#method-i-exec_prepared-doc + # for more information + { value: value.to_s, format: 1 } + when OID::Xml::Data, OID::Bit::Data + value.to_s + when OID::Array::Data + encode_array(value) + when Range + encode_range(value) + else + super + end + end + + def encode_array(array_data) + encoder = array_data.encoder + values = type_cast_array(array_data.values) + + result = encoder.encode(values) + if encoding = determine_encoding_of_strings_in_array(values) + result.force_encoding(encoding) + end + result + end + + def encode_range(range) + "[#{type_cast_range_value(range.first)},#{type_cast_range_value(range.last)}#{range.exclude_end? ? ')' : ']'}" + end + + def determine_encoding_of_strings_in_array(value) + case value + when ::Array then determine_encoding_of_strings_in_array(value.first) + when ::String then value.encoding + end + end + + def type_cast_array(values) + case values + when ::Array then values.map { |item| type_cast_array(item) } + else _type_cast(values) + end + end + + def type_cast_range_value(value) + infinity?(value) ? "" : type_cast(value) + end + + def infinity?(value) + value.respond_to?(:infinite?) && value.infinite? + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/referential_integrity.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/referential_integrity.rb new file mode 100644 index 00000000..8df91c98 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/referential_integrity.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module ReferentialIntegrity # :nodoc: + def disable_referential_integrity # :nodoc: + original_exception = nil + + begin + transaction(requires_new: true) do + execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";")) + end + rescue ActiveRecord::ActiveRecordError => e + original_exception = e + end + + begin + yield + rescue ActiveRecord::InvalidForeignKey => e + warn <<-WARNING +WARNING: Rails was not able to disable referential integrity. + +This is most likely caused due to missing permissions. +Rails needs superuser privileges to disable referential integrity. + + cause: #{original_exception.try(:message)} + + WARNING + raise e + end + + begin + transaction(requires_new: true) do + execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";")) + end + rescue ActiveRecord::ActiveRecordError + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/schema_creation.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/schema_creation.rb new file mode 100644 index 00000000..11922449 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/schema_creation.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc: + private + def visit_AlterTable(o) + super << o.constraint_validations.map { |fk| visit_ValidateConstraint fk }.join(" ") + end + + def visit_AddForeignKey(o) + super.dup.tap { |sql| sql << " NOT VALID" unless o.validate? } + end + + def visit_ValidateConstraint(name) + "VALIDATE CONSTRAINT #{quote_column_name(name)}" + end + + def visit_ChangeColumnDefinition(o) + column = o.column + column.sql_type = type_to_sql(column.type, column.options) + quoted_column_name = quote_column_name(o.name) + + change_column_sql = "ALTER COLUMN #{quoted_column_name} TYPE #{column.sql_type}".dup + + options = column_options(column) + + if options[:collation] + change_column_sql << " COLLATE \"#{options[:collation]}\"" + end + + if options[:using] + change_column_sql << " USING #{options[:using]}" + elsif options[:cast_as] + cast_as_type = type_to_sql(options[:cast_as], options) + change_column_sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})" + end + + if options.key?(:default) + if options[:default].nil? + change_column_sql << ", ALTER COLUMN #{quoted_column_name} DROP DEFAULT" + else + quoted_default = quote_default_expression(options[:default], column) + change_column_sql << ", ALTER COLUMN #{quoted_column_name} SET DEFAULT #{quoted_default}" + end + end + + if options.key?(:null) + change_column_sql << ", ALTER COLUMN #{quoted_column_name} #{options[:null] ? 'DROP' : 'SET'} NOT NULL" + end + + change_column_sql + end + + def add_column_options!(sql, options) + if options[:collation] + sql << " COLLATE \"#{options[:collation]}\"" + end + super + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/schema_definitions.rb new file mode 100644 index 00000000..6047217f --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/schema_definitions.rb @@ -0,0 +1,206 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module ColumnMethods + # Defines the primary key field. + # Use of the native PostgreSQL UUID type is supported, and can be used + # by defining your tables as such: + # + # create_table :stuffs, id: :uuid do |t| + # t.string :content + # t.timestamps + # end + # + # By default, this will use the +gen_random_uuid()+ function from the + # +pgcrypto+ extension. As that extension is only available in + # PostgreSQL 9.4+, for earlier versions an explicit default can be set + # to use +uuid_generate_v4()+ from the +uuid-ossp+ extension instead: + # + # create_table :stuffs, id: false do |t| + # t.primary_key :id, :uuid, default: "uuid_generate_v4()" + # t.uuid :foo_id + # t.timestamps + # end + # + # To enable the appropriate extension, which is a requirement, use + # the +enable_extension+ method in your migrations. + # + # To use a UUID primary key without any of the extensions, set the + # +:default+ option to +nil+: + # + # create_table :stuffs, id: false do |t| + # t.primary_key :id, :uuid, default: nil + # t.uuid :foo_id + # t.timestamps + # end + # + # You may also pass a custom stored procedure that returns a UUID or use a + # different UUID generation function from another library. + # + # Note that setting the UUID primary key default value to +nil+ will + # require you to assure that you always provide a UUID value before saving + # a record (as primary keys cannot be +nil+). This might be done via the + # +SecureRandom.uuid+ method and a +before_save+ callback, for instance. + def primary_key(name, type = :primary_key, **options) + if type == :uuid + options[:default] = options.fetch(:default, "gen_random_uuid()") + end + + super + end + + def bigserial(*args, **options) + args.each { |name| column(name, :bigserial, options) } + end + + def bit(*args, **options) + args.each { |name| column(name, :bit, options) } + end + + def bit_varying(*args, **options) + args.each { |name| column(name, :bit_varying, options) } + end + + def cidr(*args, **options) + args.each { |name| column(name, :cidr, options) } + end + + def citext(*args, **options) + args.each { |name| column(name, :citext, options) } + end + + def daterange(*args, **options) + args.each { |name| column(name, :daterange, options) } + end + + def hstore(*args, **options) + args.each { |name| column(name, :hstore, options) } + end + + def inet(*args, **options) + args.each { |name| column(name, :inet, options) } + end + + def interval(*args, **options) + args.each { |name| column(name, :interval, options) } + end + + def int4range(*args, **options) + args.each { |name| column(name, :int4range, options) } + end + + def int8range(*args, **options) + args.each { |name| column(name, :int8range, options) } + end + + def jsonb(*args, **options) + args.each { |name| column(name, :jsonb, options) } + end + + def ltree(*args, **options) + args.each { |name| column(name, :ltree, options) } + end + + def macaddr(*args, **options) + args.each { |name| column(name, :macaddr, options) } + end + + def money(*args, **options) + args.each { |name| column(name, :money, options) } + end + + def numrange(*args, **options) + args.each { |name| column(name, :numrange, options) } + end + + def oid(*args, **options) + args.each { |name| column(name, :oid, options) } + end + + def point(*args, **options) + args.each { |name| column(name, :point, options) } + end + + def line(*args, **options) + args.each { |name| column(name, :line, options) } + end + + def lseg(*args, **options) + args.each { |name| column(name, :lseg, options) } + end + + def box(*args, **options) + args.each { |name| column(name, :box, options) } + end + + def path(*args, **options) + args.each { |name| column(name, :path, options) } + end + + def polygon(*args, **options) + args.each { |name| column(name, :polygon, options) } + end + + def circle(*args, **options) + args.each { |name| column(name, :circle, options) } + end + + def serial(*args, **options) + args.each { |name| column(name, :serial, options) } + end + + def tsrange(*args, **options) + args.each { |name| column(name, :tsrange, options) } + end + + def tstzrange(*args, **options) + args.each { |name| column(name, :tstzrange, options) } + end + + def tsvector(*args, **options) + args.each { |name| column(name, :tsvector, options) } + end + + def uuid(*args, **options) + args.each { |name| column(name, :uuid, options) } + end + + def xml(*args, **options) + args.each { |name| column(name, :xml, options) } + end + end + + class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition + include ColumnMethods + + private + def integer_like_primary_key_type(type, options) + if type == :bigint || options[:limit] == 8 + :bigserial + else + :serial + end + end + end + + class Table < ActiveRecord::ConnectionAdapters::Table + include ColumnMethods + end + + class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable + attr_reader :constraint_validations + + def initialize(td) + super + @constraint_validations = [] + end + + def validate_constraint(name) + @constraint_validations << name + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/schema_dumper.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/schema_dumper.rb new file mode 100644 index 00000000..84643d20 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/schema_dumper.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc: + private + + def extensions(stream) + extensions = @connection.extensions + if extensions.any? + stream.puts " # These are extensions that must be enabled in order to support this database" + extensions.sort.each do |extension| + stream.puts " enable_extension #{extension.inspect}" + end + stream.puts + end + end + + def prepare_column_options(column) + spec = super + spec[:array] = "true" if column.array? + spec + end + + def default_primary_key?(column) + schema_type(column) == :bigserial + end + + def explicit_primary_key_default?(column) + column.type == :uuid || (column.type == :integer && !column.serial?) + end + + def schema_type(column) + return super unless column.serial? + + if column.bigint? + :bigserial + else + :serial + end + end + + def schema_expression(column) + super unless column.serial? + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/schema_statements.rb new file mode 100644 index 00000000..fc72e0df --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -0,0 +1,774 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module SchemaStatements + # Drops the database specified on the +name+ attribute + # and creates it again using the provided +options+. + def recreate_database(name, options = {}) #:nodoc: + drop_database(name) + create_database(name, options) + end + + # Create a new PostgreSQL database. Options include :owner, :template, + # :encoding (defaults to utf8), :collation, :ctype, + # :tablespace, and :connection_limit (note that MySQL uses + # :charset while PostgreSQL uses :encoding). + # + # Example: + # create_database config[:database], config + # create_database 'foo_development', encoding: 'unicode' + def create_database(name, options = {}) + options = { encoding: "utf8" }.merge!(options.symbolize_keys) + + option_string = options.inject("") do |memo, (key, value)| + memo += case key + when :owner + " OWNER = \"#{value}\"" + when :template + " TEMPLATE = \"#{value}\"" + when :encoding + " ENCODING = '#{value}'" + when :collation + " LC_COLLATE = '#{value}'" + when :ctype + " LC_CTYPE = '#{value}'" + when :tablespace + " TABLESPACE = \"#{value}\"" + when :connection_limit + " CONNECTION LIMIT = #{value}" + else + "" + end + end + + execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}" + end + + # Drops a PostgreSQL database. + # + # Example: + # drop_database 'matt_development' + def drop_database(name) #:nodoc: + execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}" + end + + def drop_table(table_name, options = {}) # :nodoc: + execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}" + end + + # Returns true if schema exists. + def schema_exists?(name) + query_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = #{quote(name)}", "SCHEMA").to_i > 0 + end + + # Verifies existence of an index with a given name. + def index_name_exists?(table_name, index_name) + table = quoted_scope(table_name) + index = quoted_scope(index_name) + + query_value(<<-SQL, "SCHEMA").to_i > 0 + SELECT COUNT(*) + FROM pg_class t + INNER JOIN pg_index d ON t.oid = d.indrelid + INNER JOIN pg_class i ON d.indexrelid = i.oid + LEFT JOIN pg_namespace n ON n.oid = i.relnamespace + WHERE i.relkind = 'i' + AND i.relname = #{index[:name]} + AND t.relname = #{table[:name]} + AND n.nspname = #{index[:schema]} + SQL + end + + # Returns an array of indexes for the given table. + def indexes(table_name) # :nodoc: + scope = quoted_scope(table_name) + + result = query(<<-SQL, "SCHEMA") + SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid, + pg_catalog.obj_description(i.oid, 'pg_class') AS comment + FROM pg_class t + INNER JOIN pg_index d ON t.oid = d.indrelid + INNER JOIN pg_class i ON d.indexrelid = i.oid + LEFT JOIN pg_namespace n ON n.oid = i.relnamespace + WHERE i.relkind = 'i' + AND d.indisprimary = 'f' + AND t.relname = #{scope[:name]} + AND n.nspname = #{scope[:schema]} + ORDER BY i.relname + SQL + + result.map do |row| + index_name = row[0] + unique = row[1] + indkey = row[2].split(" ").map(&:to_i) + inddef = row[3] + oid = row[4] + comment = row[5] + + using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/m).flatten + + orders = {} + opclasses = {} + + if indkey.include?(0) + columns = expressions + else + columns = Hash[query(<<-SQL.strip_heredoc, "SCHEMA")].values_at(*indkey).compact + SELECT a.attnum, a.attname + FROM pg_attribute a + WHERE a.attrelid = #{oid} + AND a.attnum IN (#{indkey.join(",")}) + SQL + + # add info on sort order (only desc order is explicitly specified, asc is the default) + # and non-default opclasses + expressions.scan(/(?\w+)"?\s?(?\w+_ops)?\s?(?DESC)?\s?(?NULLS (?:FIRST|LAST))?/).each do |column, opclass, desc, nulls| + opclasses[column] = opclass.to_sym if opclass + if nulls + orders[column] = [desc, nulls].compact.join(" ") + else + orders[column] = :desc if desc + end + end + end + + IndexDefinition.new( + table_name, + index_name, + unique, + columns, + orders: orders, + opclasses: opclasses, + where: where, + using: using.to_sym, + comment: comment.presence + ) + end + end + + def table_options(table_name) # :nodoc: + if comment = table_comment(table_name) + { comment: comment } + end + end + + # Returns a comment stored in database for given table + def table_comment(table_name) # :nodoc: + scope = quoted_scope(table_name, type: "BASE TABLE") + if scope[:name] + query_value(<<-SQL.strip_heredoc, "SCHEMA") + SELECT pg_catalog.obj_description(c.oid, 'pg_class') + FROM pg_catalog.pg_class c + LEFT JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relname = #{scope[:name]} + AND c.relkind IN (#{scope[:type]}) + AND n.nspname = #{scope[:schema]} + SQL + end + end + + # Returns the current database name. + def current_database + query_value("SELECT current_database()", "SCHEMA") + end + + # Returns the current schema name. + def current_schema + query_value("SELECT current_schema", "SCHEMA") + end + + # Returns the current database encoding format. + def encoding + query_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = current_database()", "SCHEMA") + end + + # Returns the current database collation. + def collation + query_value("SELECT datcollate FROM pg_database WHERE datname = current_database()", "SCHEMA") + end + + # Returns the current database ctype. + def ctype + query_value("SELECT datctype FROM pg_database WHERE datname = current_database()", "SCHEMA") + end + + # Returns an array of schema names. + def schema_names + query_values(<<-SQL, "SCHEMA") + SELECT nspname + FROM pg_namespace + WHERE nspname !~ '^pg_.*' + AND nspname NOT IN ('information_schema') + ORDER by nspname; + SQL + end + + # Creates a schema for the given schema name. + def create_schema(schema_name) + execute "CREATE SCHEMA #{quote_schema_name(schema_name)}" + end + + # Drops the schema for the given schema name. + def drop_schema(schema_name, options = {}) + execute "DROP SCHEMA#{' IF EXISTS' if options[:if_exists]} #{quote_schema_name(schema_name)} CASCADE" + end + + # Sets the schema search path to a string of comma-separated schema names. + # Names beginning with $ have to be quoted (e.g. $user => '$user'). + # See: https://www.postgresql.org/docs/current/static/ddl-schemas.html + # + # This should be not be called manually but set in database.yml. + def schema_search_path=(schema_csv) + if schema_csv + execute("SET search_path TO #{schema_csv}", "SCHEMA") + @schema_search_path = schema_csv + end + end + + # Returns the active schema search path. + def schema_search_path + @schema_search_path ||= query_value("SHOW search_path", "SCHEMA") + end + + # Returns the current client message level. + def client_min_messages + query_value("SHOW client_min_messages", "SCHEMA") + end + + # Set the client message level. + def client_min_messages=(level) + execute("SET client_min_messages TO '#{level}'", "SCHEMA") + end + + # Returns the sequence name for a table's primary key or some other specified key. + def default_sequence_name(table_name, pk = "id") #:nodoc: + result = serial_sequence(table_name, pk) + return nil unless result + Utils.extract_schema_qualified_name(result).to_s + rescue ActiveRecord::StatementInvalid + PostgreSQL::Name.new(nil, "#{table_name}_#{pk}_seq").to_s + end + + def serial_sequence(table, column) + query_value("SELECT pg_get_serial_sequence(#{quote(table)}, #{quote(column)})", "SCHEMA") + end + + # Sets the sequence of a table's primary key to the specified value. + def set_pk_sequence!(table, value) #:nodoc: + pk, sequence = pk_and_sequence_for(table) + + if pk + if sequence + quoted_sequence = quote_table_name(sequence) + + query_value("SELECT setval(#{quote(quoted_sequence)}, #{value})", "SCHEMA") + else + @logger.warn "#{table} has primary key #{pk} with no default sequence." if @logger + end + end + end + + # Resets the sequence of a table's primary key to the maximum value. + def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc: + unless pk && sequence + default_pk, default_sequence = pk_and_sequence_for(table) + + pk ||= default_pk + sequence ||= default_sequence + end + + if @logger && pk && !sequence + @logger.warn "#{table} has primary key #{pk} with no default sequence." + end + + if pk && sequence + quoted_sequence = quote_table_name(sequence) + max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA") + if max_pk.nil? + if postgresql_version >= 100000 + minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA") + else + minvalue = query_value("SELECT min_value FROM #{quoted_sequence}", "SCHEMA") + end + end + + query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})", "SCHEMA") + end + end + + # Returns a table's primary key and belonging sequence. + def pk_and_sequence_for(table) #:nodoc: + # First try looking for a sequence with a dependency on the + # given table's primary key. + result = query(<<-end_sql, "SCHEMA")[0] + SELECT attr.attname, nsp.nspname, seq.relname + FROM pg_class seq, + pg_attribute attr, + pg_depend dep, + pg_constraint cons, + pg_namespace nsp + WHERE seq.oid = dep.objid + AND seq.relkind = 'S' + AND attr.attrelid = dep.refobjid + AND attr.attnum = dep.refobjsubid + AND attr.attrelid = cons.conrelid + AND attr.attnum = cons.conkey[1] + AND seq.relnamespace = nsp.oid + AND cons.contype = 'p' + AND dep.classid = 'pg_class'::regclass + AND dep.refobjid = #{quote(quote_table_name(table))}::regclass + end_sql + + if result.nil? || result.empty? + result = query(<<-end_sql, "SCHEMA")[0] + SELECT attr.attname, nsp.nspname, + CASE + WHEN pg_get_expr(def.adbin, def.adrelid) !~* 'nextval' THEN NULL + WHEN split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) ~ '.' THEN + substr(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), + strpos(split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2), '.')+1) + ELSE split_part(pg_get_expr(def.adbin, def.adrelid), '''', 2) + END + FROM pg_class t + JOIN pg_attribute attr ON (t.oid = attrelid) + JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum) + JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1]) + JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid) + WHERE t.oid = #{quote(quote_table_name(table))}::regclass + AND cons.contype = 'p' + AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate' + end_sql + end + + pk = result.shift + if result.last + [pk, PostgreSQL::Name.new(*result)] + else + [pk, nil] + end + rescue + nil + end + + def primary_keys(table_name) # :nodoc: + query_values(<<-SQL.strip_heredoc, "SCHEMA") + SELECT a.attname + FROM ( + SELECT indrelid, indkey, generate_subscripts(indkey, 1) idx + FROM pg_index + WHERE indrelid = #{quote(quote_table_name(table_name))}::regclass + AND indisprimary + ) i + JOIN pg_attribute a + ON a.attrelid = i.indrelid + AND a.attnum = i.indkey[i.idx] + ORDER BY i.idx + SQL + end + + def bulk_change_table(table_name, operations) + sql_fragments = [] + non_combinable_operations = [] + + operations.each do |command, args| + table, arguments = args.shift, args + method = :"#{command}_for_alter" + + if respond_to?(method, true) + sqls, procs = Array(send(method, table, *arguments)).partition { |v| v.is_a?(String) } + sql_fragments << sqls + non_combinable_operations.concat(procs) + else + execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty? + non_combinable_operations.each(&:call) + sql_fragments = [] + non_combinable_operations = [] + send(command, table, *arguments) + end + end + + execute "ALTER TABLE #{quote_table_name(table_name)} #{sql_fragments.join(", ")}" unless sql_fragments.empty? + non_combinable_operations.each(&:call) + end + + # Renames a table. + # Also renames a table's primary key sequence if the sequence name exists and + # matches the Active Record default. + # + # Example: + # rename_table('octopuses', 'octopi') + def rename_table(table_name, new_name) + clear_cache! + execute "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}" + pk, seq = pk_and_sequence_for(new_name) + if pk + idx = "#{table_name}_pkey" + new_idx = "#{new_name}_pkey" + execute "ALTER INDEX #{quote_table_name(idx)} RENAME TO #{quote_table_name(new_idx)}" + if seq && seq.identifier == "#{table_name}_#{pk}_seq" + new_seq = "#{new_name}_#{pk}_seq" + execute "ALTER TABLE #{seq.quoted} RENAME TO #{quote_table_name(new_seq)}" + end + end + rename_table_indexes(table_name, new_name) + end + + def add_column(table_name, column_name, type, options = {}) #:nodoc: + clear_cache! + super + change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment) + end + + def change_column(table_name, column_name, type, options = {}) #:nodoc: + clear_cache! + sqls, procs = Array(change_column_for_alter(table_name, column_name, type, options)).partition { |v| v.is_a?(String) } + execute "ALTER TABLE #{quote_table_name(table_name)} #{sqls.join(", ")}" + procs.each(&:call) + end + + # Changes the default value of a table column. + def change_column_default(table_name, column_name, default_or_changes) # :nodoc: + execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}" + end + + def change_column_null(table_name, column_name, null, default = nil) #:nodoc: + clear_cache! + unless null || default.nil? + column = column_for(table_name, column_name) + execute "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(default, column)} WHERE #{quote_column_name(column_name)} IS NULL" if column + end + execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_null_for_alter(table_name, column_name, null, default)}" + end + + # Adds comment for given table column or drops it if +comment+ is a +nil+ + def change_column_comment(table_name, column_name, comment) # :nodoc: + clear_cache! + execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} IS #{quote(comment)}" + end + + # Adds comment for given table or drops it if +comment+ is a +nil+ + def change_table_comment(table_name, comment) # :nodoc: + clear_cache! + execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS #{quote(comment)}" + end + + # Renames a column in a table. + def rename_column(table_name, column_name, new_column_name) #:nodoc: + clear_cache! + execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}" + rename_column_indexes(table_name, column_name, new_column_name) + end + + def add_index(table_name, column_name, options = {}) #:nodoc: + index_name, index_type, index_columns_and_opclasses, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options) + execute("CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns_and_opclasses})#{index_options}").tap do + execute "COMMENT ON INDEX #{quote_column_name(index_name)} IS #{quote(comment)}" if comment + end + end + + def remove_index(table_name, options = {}) #:nodoc: + table = Utils.extract_schema_qualified_name(table_name.to_s) + + if options.is_a?(Hash) && options.key?(:name) + provided_index = Utils.extract_schema_qualified_name(options[:name].to_s) + + options[:name] = provided_index.identifier + table = PostgreSQL::Name.new(provided_index.schema, table.identifier) unless table.schema.present? + + if provided_index.schema.present? && table.schema != provided_index.schema + raise ArgumentError.new("Index schema '#{provided_index.schema}' does not match table schema '#{table.schema}'") + end + end + + index_to_remove = PostgreSQL::Name.new(table.schema, index_name_for_remove(table.to_s, options)) + algorithm = + if options.is_a?(Hash) && options.key?(:algorithm) + index_algorithms.fetch(options[:algorithm]) do + raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}") + end + end + execute "DROP INDEX #{algorithm} #{quote_table_name(index_to_remove)}" + end + + # Renames an index of a table. Raises error if length of new + # index name is greater than allowed limit. + def rename_index(table_name, old_name, new_name) + validate_index_length!(table_name, new_name) + + execute "ALTER INDEX #{quote_column_name(old_name)} RENAME TO #{quote_table_name(new_name)}" + end + + def foreign_keys(table_name) + scope = quoted_scope(table_name) + fk_info = exec_query(<<-SQL.strip_heredoc, "SCHEMA") + SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid + FROM pg_constraint c + JOIN pg_class t1 ON c.conrelid = t1.oid + JOIN pg_class t2 ON c.confrelid = t2.oid + JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid + JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid + JOIN pg_namespace t3 ON c.connamespace = t3.oid + WHERE c.contype = 'f' + AND t1.relname = #{scope[:name]} + AND t3.nspname = #{scope[:schema]} + ORDER BY c.conname + SQL + + fk_info.map do |row| + options = { + column: row["column"], + name: row["name"], + primary_key: row["primary_key"] + } + + options[:on_delete] = extract_foreign_key_action(row["on_delete"]) + options[:on_update] = extract_foreign_key_action(row["on_update"]) + options[:validate] = row["valid"] + + ForeignKeyDefinition.new(table_name, row["to_table"], options) + end + end + + def foreign_tables + query_values(data_source_sql(type: "FOREIGN TABLE"), "SCHEMA") + end + + def foreign_table_exists?(table_name) + query_values(data_source_sql(table_name, type: "FOREIGN TABLE"), "SCHEMA").any? if table_name.present? + end + + # Maps logical Rails types to PostgreSQL-specific data types. + def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc: + sql = \ + case type.to_s + when "binary" + # PostgreSQL doesn't support limits on binary (bytea) columns. + # The hard limit is 1GB, because of a 32-bit size field, and TOAST. + case limit + when nil, 0..0x3fffffff; super(type) + else raise(ActiveRecordError, "No binary type has byte size #{limit}.") + end + when "text" + # PostgreSQL doesn't support limits on text columns. + # The hard limit is 1GB, according to section 8.3 in the manual. + case limit + when nil, 0..0x3fffffff; super(type) + else raise(ActiveRecordError, "The limit on text can be at most 1GB - 1byte.") + end + when "integer" + case limit + when 1, 2; "smallint" + when nil, 3, 4; "integer" + when 5..8; "bigint" + else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead.") + end + else + super + end + + sql = "#{sql}[]" if array && type != :primary_key + sql + end + + # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and + # requires that the ORDER BY include the distinct column. + def columns_for_distinct(columns, orders) #:nodoc: + order_columns = orders.reject(&:blank?).map { |s| + # Convert Arel node to string + s = s.to_sql unless s.is_a?(String) + # Remove any ASC/DESC modifiers + s.gsub(/\s+(?:ASC|DESC)\b/i, "") + .gsub(/\s+NULLS\s+(?:FIRST|LAST)\b/i, "") + }.reject(&:blank?).map.with_index { |column, i| "#{column} AS alias_#{i}" } + + (order_columns << super).join(", ") + end + + def update_table_definition(table_name, base) # :nodoc: + PostgreSQL::Table.new(table_name, base) + end + + def create_schema_dumper(options) # :nodoc: + PostgreSQL::SchemaDumper.create(self, options) + end + + # Validates the given constraint. + # + # Validates the constraint named +constraint_name+ on +accounts+. + # + # validate_constraint :accounts, :constraint_name + def validate_constraint(table_name, constraint_name) + return unless supports_validate_constraints? + + at = create_alter_table table_name + at.validate_constraint constraint_name + + execute schema_creation.accept(at) + end + + # Validates the given foreign key. + # + # Validates the foreign key on +accounts.branch_id+. + # + # validate_foreign_key :accounts, :branches + # + # Validates the foreign key on +accounts.owner_id+. + # + # validate_foreign_key :accounts, column: :owner_id + # + # Validates the foreign key named +special_fk_name+ on the +accounts+ table. + # + # validate_foreign_key :accounts, name: :special_fk_name + # + # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key. + def validate_foreign_key(from_table, options_or_to_table = {}) + return unless supports_validate_constraints? + + fk_name_to_validate = foreign_key_for!(from_table, options_or_to_table).name + + validate_constraint from_table, fk_name_to_validate + end + + private + def schema_creation + PostgreSQL::SchemaCreation.new(self) + end + + def create_table_definition(*args) + PostgreSQL::TableDefinition.new(*args) + end + + def create_alter_table(name) + PostgreSQL::AlterTable.new create_table_definition(name) + end + + def new_column_from_field(table_name, field) + column_name, type, default, notnull, oid, fmod, collation, comment = field + type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i) + default_value = extract_value_from_default(default) + default_function = extract_default_function(default_value, default) + + PostgreSQLColumn.new( + column_name, + default_value, + type_metadata, + !notnull, + table_name, + default_function, + collation, + comment: comment.presence, + max_identifier_length: max_identifier_length + ) + end + + def fetch_type_metadata(column_name, sql_type, oid, fmod) + cast_type = get_oid_type(oid, fmod, column_name, sql_type) + simple_type = SqlTypeMetadata.new( + sql_type: sql_type, + type: cast_type.type, + limit: cast_type.limit, + precision: cast_type.precision, + scale: cast_type.scale, + ) + PostgreSQLTypeMetadata.new(simple_type, oid: oid, fmod: fmod) + end + + def extract_foreign_key_action(specifier) + case specifier + when "c"; :cascade + when "n"; :nullify + when "r"; :restrict + end + end + + def add_column_for_alter(table_name, column_name, type, options = {}) + return super unless options.key?(:comment) + [super, Proc.new { change_column_comment(table_name, column_name, options[:comment]) }] + end + + def change_column_for_alter(table_name, column_name, type, options = {}) + td = create_table_definition(table_name) + cd = td.new_column_definition(column_name, type, options) + sqls = [schema_creation.accept(ChangeColumnDefinition.new(cd, column_name))] + sqls << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment) + sqls + end + + def change_column_default_for_alter(table_name, column_name, default_or_changes) + column = column_for(table_name, column_name) + return unless column + + default = extract_new_default_value(default_or_changes) + alter_column_query = "ALTER COLUMN #{quote_column_name(column_name)} %s" + if default.nil? + # DEFAULT NULL results in the same behavior as DROP DEFAULT. However, PostgreSQL will + # cast the default to the columns type, which leaves us with a default like "default NULL::character varying". + alter_column_query % "DROP DEFAULT" + else + alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}" + end + end + + def change_column_null_for_alter(table_name, column_name, null, default = nil) + "ALTER COLUMN #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL" + end + + def add_timestamps_for_alter(table_name, options = {}) + [add_column_for_alter(table_name, :created_at, :datetime, options), add_column_for_alter(table_name, :updated_at, :datetime, options)] + end + + def remove_timestamps_for_alter(table_name, options = {}) + [remove_column_for_alter(table_name, :updated_at), remove_column_for_alter(table_name, :created_at)] + end + + def add_index_opclass(quoted_columns, **options) + opclasses = options_for_index_columns(options[:opclass]) + quoted_columns.each do |name, column| + column << " #{opclasses[name]}" if opclasses[name].present? + end + end + + def add_options_for_index_columns(quoted_columns, **options) + quoted_columns = add_index_opclass(quoted_columns, options) + super + end + + def data_source_sql(name = nil, type: nil) + scope = quoted_scope(name, type: type) + scope[:type] ||= "'r','v','m','p','f'" # (r)elation/table, (v)iew, (m)aterialized view, (p)artitioned table, (f)oreign table + + sql = "SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace".dup + sql << " WHERE n.nspname = #{scope[:schema]}" + sql << " AND c.relname = #{scope[:name]}" if scope[:name] + sql << " AND c.relkind IN (#{scope[:type]})" + sql + end + + def quoted_scope(name = nil, type: nil) + schema, name = extract_schema_qualified_name(name) + type = \ + case type + when "BASE TABLE" + "'r','p'" + when "VIEW" + "'v','m'" + when "FOREIGN TABLE" + "'f'" + end + scope = {} + scope[:schema] = schema ? quote(schema) : "ANY (current_schemas(false))" + scope[:name] = quote(name) if name + scope[:type] = type if type + scope + end + + def extract_schema_qualified_name(string) + name = Utils.extract_schema_qualified_name(string.to_s) + [name.schema, name.identifier] + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/type_metadata.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/type_metadata.rb new file mode 100644 index 00000000..b252a76c --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/type_metadata.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + class PostgreSQLTypeMetadata < DelegateClass(SqlTypeMetadata) + undef to_yaml if method_defined?(:to_yaml) + + attr_reader :oid, :fmod, :array + + def initialize(type_metadata, oid: nil, fmod: nil) + super(type_metadata) + @type_metadata = type_metadata + @oid = oid + @fmod = fmod + @array = /\[\]$/.match?(type_metadata.sql_type) + end + + def sql_type + super.gsub(/\[\]$/, "".freeze) + end + + def ==(other) + other.is_a?(PostgreSQLTypeMetadata) && + attributes_for_hash == other.attributes_for_hash + end + alias eql? == + + def hash + attributes_for_hash.hash + end + + protected + + def attributes_for_hash + [self.class, @type_metadata, oid, fmod] + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/utils.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/utils.rb new file mode 100644 index 00000000..f2f47015 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql/utils.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + # Value Object to hold a schema qualified name. + # This is usually the name of a PostgreSQL relation but it can also represent + # schema qualified type names. +schema+ and +identifier+ are unquoted to prevent + # double quoting. + class Name # :nodoc: + SEPARATOR = "." + attr_reader :schema, :identifier + + def initialize(schema, identifier) + @schema, @identifier = unquote(schema), unquote(identifier) + end + + def to_s + parts.join SEPARATOR + end + + def quoted + if schema + PG::Connection.quote_ident(schema) << SEPARATOR << PG::Connection.quote_ident(identifier) + else + PG::Connection.quote_ident(identifier) + end + end + + def ==(o) + o.class == self.class && o.parts == parts + end + alias_method :eql?, :== + + def hash + parts.hash + end + + protected + + def parts + @parts ||= [@schema, @identifier].compact + end + + private + def unquote(part) + if part && part.start_with?('"') + part[1..-2] + else + part + end + end + end + + module Utils # :nodoc: + extend self + + # Returns an instance of ActiveRecord::ConnectionAdapters::PostgreSQL::Name + # extracted from +string+. + # +schema+ is +nil+ if not specified in +string+. + # +schema+ and +identifier+ exclude surrounding quotes (regardless of whether provided in +string+) + # +string+ supports the range of schema/table references understood by PostgreSQL, for example: + # + # * table_name + # * "table.name" + # * schema_name.table_name + # * schema_name."table.name" + # * "schema_name".table_name + # * "schema.name"."table name" + def extract_schema_qualified_name(string) + schema, table = string.scan(/[^".]+|"[^"]*"/) + if table.nil? + table = schema + schema = nil + end + PostgreSQL::Name.new(schema, table) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql_adapter.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql_adapter.rb new file mode 100644 index 00000000..57921cee --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -0,0 +1,863 @@ +# frozen_string_literal: true + +# Make sure we're using pg high enough for type casts and Ruby 2.2+ compatibility +gem "pg", ">= 0.18", "< 2.0" +require "pg" + +# Use async_exec instead of exec_params on pg versions before 1.1 +class ::PG::Connection + unless self.public_method_defined?(:async_exec_params) + remove_method :exec_params + alias exec_params async_exec + end +end + +require "active_record/connection_adapters/abstract_adapter" +require "active_record/connection_adapters/statement_pool" +require "active_record/connection_adapters/postgresql/column" +require "active_record/connection_adapters/postgresql/database_statements" +require "active_record/connection_adapters/postgresql/explain_pretty_printer" +require "active_record/connection_adapters/postgresql/oid" +require "active_record/connection_adapters/postgresql/quoting" +require "active_record/connection_adapters/postgresql/referential_integrity" +require "active_record/connection_adapters/postgresql/schema_creation" +require "active_record/connection_adapters/postgresql/schema_definitions" +require "active_record/connection_adapters/postgresql/schema_dumper" +require "active_record/connection_adapters/postgresql/schema_statements" +require "active_record/connection_adapters/postgresql/type_metadata" +require "active_record/connection_adapters/postgresql/utils" + +module ActiveRecord + module ConnectionHandling # :nodoc: + # Establishes a connection to the database that's used by all Active Record objects + def postgresql_connection(config) + conn_params = config.symbolize_keys + + conn_params.delete_if { |_, v| v.nil? } + + # Map ActiveRecords param names to PGs. + conn_params[:user] = conn_params.delete(:username) if conn_params[:username] + conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database] + + # Forward only valid config params to PG::Connection.connect. + valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl] + conn_params.slice!(*valid_conn_param_keys) + + # The postgres drivers don't allow the creation of an unconnected PG::Connection object, + # so just pass a nil connection object for the time being. + ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, conn_params, config) + end + end + + module ConnectionAdapters + # The PostgreSQL adapter works with the native C (https://bitbucket.org/ged/ruby-pg) driver. + # + # Options: + # + # * :host - Defaults to a Unix-domain socket in /tmp. On machines without Unix-domain sockets, + # the default is to connect to localhost. + # * :port - Defaults to 5432. + # * :username - Defaults to be the same as the operating system name of the user running the application. + # * :password - Password to be used if the server demands password authentication. + # * :database - Defaults to be the same as the user name. + # * :schema_search_path - An optional schema search path for the connection given + # as a string of comma-separated schema names. This is backward-compatible with the :schema_order option. + # * :encoding - An optional client encoding that is used in a SET client_encoding TO + # call on the connection. + # * :min_messages - An optional client min messages that is used in a + # SET client_min_messages TO call on the connection. + # * :variables - An optional hash of additional parameters that + # will be used in SET SESSION key = val calls on the connection. + # * :insert_returning - An optional boolean to control the use of RETURNING for INSERT statements + # defaults to true. + # + # Any further options are used as connection parameters to libpq. See + # https://www.postgresql.org/docs/current/static/libpq-connect.html for the + # list of parameters. + # + # In addition, default connection parameters of libpq can be set per environment variables. + # See https://www.postgresql.org/docs/current/static/libpq-envars.html . + class PostgreSQLAdapter < AbstractAdapter + ADAPTER_NAME = "PostgreSQL".freeze + + NATIVE_DATABASE_TYPES = { + primary_key: "bigserial primary key", + string: { name: "character varying" }, + text: { name: "text" }, + integer: { name: "integer", limit: 4 }, + float: { name: "float" }, + decimal: { name: "decimal" }, + datetime: { name: "timestamp" }, + time: { name: "time" }, + date: { name: "date" }, + daterange: { name: "daterange" }, + numrange: { name: "numrange" }, + tsrange: { name: "tsrange" }, + tstzrange: { name: "tstzrange" }, + int4range: { name: "int4range" }, + int8range: { name: "int8range" }, + binary: { name: "bytea" }, + boolean: { name: "boolean" }, + xml: { name: "xml" }, + tsvector: { name: "tsvector" }, + hstore: { name: "hstore" }, + inet: { name: "inet" }, + cidr: { name: "cidr" }, + macaddr: { name: "macaddr" }, + uuid: { name: "uuid" }, + json: { name: "json" }, + jsonb: { name: "jsonb" }, + ltree: { name: "ltree" }, + citext: { name: "citext" }, + point: { name: "point" }, + line: { name: "line" }, + lseg: { name: "lseg" }, + box: { name: "box" }, + path: { name: "path" }, + polygon: { name: "polygon" }, + circle: { name: "circle" }, + bit: { name: "bit" }, + bit_varying: { name: "bit varying" }, + money: { name: "money" }, + interval: { name: "interval" }, + oid: { name: "oid" }, + } + + OID = PostgreSQL::OID #:nodoc: + + include PostgreSQL::Quoting + include PostgreSQL::ReferentialIntegrity + include PostgreSQL::SchemaStatements + include PostgreSQL::DatabaseStatements + + def supports_bulk_alter? + true + end + + def supports_index_sort_order? + true + end + + def supports_partial_index? + true + end + + def supports_expression_index? + true + end + + def supports_transaction_isolation? + true + end + + def supports_foreign_keys? + true + end + + def supports_validate_constraints? + true + end + + def supports_views? + true + end + + def supports_datetime_with_precision? + true + end + + def supports_json? + postgresql_version >= 90200 + end + + def supports_comments? + true + end + + def supports_savepoints? + true + end + + def index_algorithms + { concurrently: "CONCURRENTLY" } + end + + class StatementPool < ConnectionAdapters::StatementPool # :nodoc: + def initialize(connection, max) + super(max) + @connection = connection + @counter = 0 + end + + def next_key + "a#{@counter + 1}" + end + + def []=(sql, key) + super.tap { @counter += 1 } + end + + private + def dealloc(key) + @connection.query "DEALLOCATE #{key}" if connection_active? + rescue PG::Error + end + + def connection_active? + @connection.status == PG::CONNECTION_OK + rescue PG::Error + false + end + end + + # Initializes and connects a PostgreSQL adapter. + def initialize(connection, logger, connection_parameters, config) + super(connection, logger, config) + + @connection_parameters = connection_parameters + + # @local_tz is initialized as nil to avoid warnings when connect tries to use it + @local_tz = nil + @max_identifier_length = nil + + connect + add_pg_encoders + @statements = StatementPool.new @connection, + self.class.type_cast_config_to_integer(config[:statement_limit]) + + if postgresql_version < 90100 + raise "Your version of PostgreSQL (#{postgresql_version}) is too old. Active Record supports PostgreSQL >= 9.1." + end + + add_pg_decoders + + @type_map = Type::HashLookupTypeMap.new + initialize_type_map + @local_tz = execute("SHOW TIME ZONE", "SCHEMA").first["TimeZone"] + @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true + end + + # Clears the prepared statements cache. + def clear_cache! + @lock.synchronize do + @statements.clear + end + end + + def truncate(table_name, name = nil) + exec_query "TRUNCATE TABLE #{quote_table_name(table_name)}", name, [] + end + + # Is this connection alive and ready for queries? + def active? + @lock.synchronize do + @connection.query "SELECT 1" + end + true + rescue PG::Error + false + end + + # Close then reopen the connection. + def reconnect! + @lock.synchronize do + super + @connection.reset + configure_connection + end + end + + def reset! + @lock.synchronize do + clear_cache! + reset_transaction + unless @connection.transaction_status == ::PG::PQTRANS_IDLE + @connection.query "ROLLBACK" + end + @connection.query "DISCARD ALL" + configure_connection + end + end + + # Disconnects from the database if already connected. Otherwise, this + # method does nothing. + def disconnect! + @lock.synchronize do + super + @connection.close rescue nil + end + end + + def discard! # :nodoc: + @connection.socket_io.reopen(IO::NULL) rescue nil + @connection = nil + end + + def native_database_types #:nodoc: + NATIVE_DATABASE_TYPES + end + + def set_standard_conforming_strings + execute("SET standard_conforming_strings = on", "SCHEMA") + end + + def supports_ddl_transactions? + true + end + + def supports_advisory_locks? + true + end + + def supports_explain? + true + end + + def supports_extensions? + true + end + + def supports_ranges? + # Range datatypes weren't introduced until PostgreSQL 9.2 + postgresql_version >= 90200 + end + + def supports_materialized_views? + postgresql_version >= 90300 + end + + def supports_foreign_tables? + postgresql_version >= 90300 + end + + def supports_pgcrypto_uuid? + postgresql_version >= 90400 + end + + def get_advisory_lock(lock_id) # :nodoc: + unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63 + raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer") + end + query_value("SELECT pg_try_advisory_lock(#{lock_id})") + end + + def release_advisory_lock(lock_id) # :nodoc: + unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63 + raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer") + end + query_value("SELECT pg_advisory_unlock(#{lock_id})") + end + + def enable_extension(name) + exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap { + reload_type_map + } + end + + def disable_extension(name) + exec_query("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE").tap { + reload_type_map + } + end + + def extension_enabled?(name) + res = exec_query("SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled", "SCHEMA") + res.cast_values.first + end + + def extensions + exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values + end + + # Returns the configured supported identifier length supported by PostgreSQL + def max_identifier_length + @max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i + end + alias table_alias_length max_identifier_length + alias index_name_length max_identifier_length + + # Set the authorized user for this session + def session_auth=(user) + clear_cache! + execute("SET SESSION AUTHORIZATION #{user}") + end + + def use_insert_returning? + @use_insert_returning + end + + def column_name_for_operation(operation, node) # :nodoc: + OPERATION_ALIASES.fetch(operation) { operation.downcase } + end + + OPERATION_ALIASES = { # :nodoc: + "maximum" => "max", + "minimum" => "min", + "average" => "avg", + } + + # Returns the version of the connected PostgreSQL server. + def postgresql_version + @connection.server_version + end + + def default_index_type?(index) # :nodoc: + index.using == :btree || super + end + + private + # See https://www.postgresql.org/docs/current/static/errcodes-appendix.html + VALUE_LIMIT_VIOLATION = "22001" + NUMERIC_VALUE_OUT_OF_RANGE = "22003" + NOT_NULL_VIOLATION = "23502" + FOREIGN_KEY_VIOLATION = "23503" + UNIQUE_VIOLATION = "23505" + SERIALIZATION_FAILURE = "40001" + DEADLOCK_DETECTED = "40P01" + LOCK_NOT_AVAILABLE = "55P03" + QUERY_CANCELED = "57014" + + def translate_exception(exception, message) + return exception unless exception.respond_to?(:result) + + case exception.result.try(:error_field, PG::PG_DIAG_SQLSTATE) + when UNIQUE_VIOLATION + RecordNotUnique.new(message) + when FOREIGN_KEY_VIOLATION + InvalidForeignKey.new(message) + when VALUE_LIMIT_VIOLATION + ValueTooLong.new(message) + when NUMERIC_VALUE_OUT_OF_RANGE + RangeError.new(message) + when NOT_NULL_VIOLATION + NotNullViolation.new(message) + when SERIALIZATION_FAILURE + SerializationFailure.new(message) + when DEADLOCK_DETECTED + Deadlocked.new(message) + when LOCK_NOT_AVAILABLE + LockWaitTimeout.new(message) + when QUERY_CANCELED + QueryCanceled.new(message) + else + super + end + end + + def get_oid_type(oid, fmod, column_name, sql_type = "".freeze) + if !type_map.key?(oid) + load_additional_types([oid]) + end + + type_map.fetch(oid, fmod, sql_type) { + warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String." + Type.default_value.tap do |cast_type| + type_map.register_type(oid, cast_type) + end + } + end + + def initialize_type_map(m = type_map) + m.register_type "int2", Type::Integer.new(limit: 2) + m.register_type "int4", Type::Integer.new(limit: 4) + m.register_type "int8", Type::Integer.new(limit: 8) + m.register_type "oid", OID::Oid.new + m.register_type "float4", Type::Float.new + m.alias_type "float8", "float4" + m.register_type "text", Type::Text.new + register_class_with_limit m, "varchar", Type::String + m.alias_type "char", "varchar" + m.alias_type "name", "varchar" + m.alias_type "bpchar", "varchar" + m.register_type "bool", Type::Boolean.new + register_class_with_limit m, "bit", OID::Bit + register_class_with_limit m, "varbit", OID::BitVarying + m.alias_type "timestamptz", "timestamp" + m.register_type "date", OID::Date.new + + m.register_type "money", OID::Money.new + m.register_type "bytea", OID::Bytea.new + m.register_type "point", OID::Point.new + m.register_type "hstore", OID::Hstore.new + m.register_type "json", Type::Json.new + m.register_type "jsonb", OID::Jsonb.new + m.register_type "cidr", OID::Cidr.new + m.register_type "inet", OID::Inet.new + m.register_type "uuid", OID::Uuid.new + m.register_type "xml", OID::Xml.new + m.register_type "tsvector", OID::SpecializedString.new(:tsvector) + m.register_type "macaddr", OID::SpecializedString.new(:macaddr) + m.register_type "citext", OID::SpecializedString.new(:citext) + m.register_type "ltree", OID::SpecializedString.new(:ltree) + m.register_type "line", OID::SpecializedString.new(:line) + m.register_type "lseg", OID::SpecializedString.new(:lseg) + m.register_type "box", OID::SpecializedString.new(:box) + m.register_type "path", OID::SpecializedString.new(:path) + m.register_type "polygon", OID::SpecializedString.new(:polygon) + m.register_type "circle", OID::SpecializedString.new(:circle) + + m.register_type "interval" do |_, _, sql_type| + precision = extract_precision(sql_type) + OID::SpecializedString.new(:interval, precision: precision) + end + + register_class_with_precision m, "time", Type::Time + register_class_with_precision m, "timestamp", OID::DateTime + + m.register_type "numeric" do |_, fmod, sql_type| + precision = extract_precision(sql_type) + scale = extract_scale(sql_type) + + # The type for the numeric depends on the width of the field, + # so we'll do something special here. + # + # When dealing with decimal columns: + # + # places after decimal = fmod - 4 & 0xffff + # places before decimal = (fmod - 4) >> 16 & 0xffff + if fmod && (fmod - 4 & 0xffff).zero? + # FIXME: Remove this class, and the second argument to + # lookups on PG + Type::DecimalWithoutScale.new(precision: precision) + else + OID::Decimal.new(precision: precision, scale: scale) + end + end + + load_additional_types + end + + # Extracts the value from a PostgreSQL column default definition. + def extract_value_from_default(default) + case default + # Quoted types + when /\A[\(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m + # The default 'now'::date is CURRENT_DATE + if $1 == "now".freeze && $2 == "date".freeze + nil + else + $1.gsub("''".freeze, "'".freeze) + end + # Boolean types + when "true".freeze, "false".freeze + default + # Numeric types + when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/ + $1 + # Object identifier types + when /\A-?\d+\z/ + $1 + else + # Anything else is blank, some user type, or some function + # and we can't know the value of that, so return nil. + nil + end + end + + def extract_default_function(default_value, default) + default if has_default_function?(default_value, default) + end + + def has_default_function?(default_value, default) + !default_value && %r{\w+\(.*\)|\(.*\)::\w+|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default) + end + + def load_additional_types(oids = nil) + initializer = OID::TypeMapInitializer.new(type_map) + + if supports_ranges? + query = <<-SQL + SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype + FROM pg_type as t + LEFT JOIN pg_range as r ON oid = rngtypid + SQL + else + query = <<-SQL + SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, t.typtype, t.typbasetype + FROM pg_type as t + SQL + end + + if oids + query += "WHERE t.oid::integer IN (%s)" % oids.join(", ") + else + query += initializer.query_conditions_for_initial_load + end + + execute_and_clear(query, "SCHEMA", []) do |records| + initializer.run(records) + end + end + + FEATURE_NOT_SUPPORTED = "0A000" #:nodoc: + + def execute_and_clear(sql, name, binds, prepare: false) + if without_prepared_statement?(binds) + result = exec_no_cache(sql, name, []) + elsif !prepare + result = exec_no_cache(sql, name, binds) + else + result = exec_cache(sql, name, binds) + end + ret = yield result + result.clear + ret + end + + def exec_no_cache(sql, name, binds) + type_casted_binds = type_casted_binds(binds) + log(sql, name, binds, type_casted_binds) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @connection.exec_params(sql, type_casted_binds) + end + end + end + + def exec_cache(sql, name, binds) + stmt_key = prepare_statement(sql) + type_casted_binds = type_casted_binds(binds) + + log(sql, name, binds, type_casted_binds, stmt_key) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @connection.exec_prepared(stmt_key, type_casted_binds) + end + end + rescue ActiveRecord::StatementInvalid => e + raise unless is_cached_plan_failure?(e) + + # Nothing we can do if we are in a transaction because all commands + # will raise InFailedSQLTransaction + if in_transaction? + raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message) + else + @lock.synchronize do + # outside of transactions we can simply flush this query and retry + @statements.delete sql_key(sql) + end + retry + end + end + + # Annoyingly, the code for prepared statements whose return value may + # have changed is FEATURE_NOT_SUPPORTED. + # + # This covers various different error types so we need to do additional + # work to classify the exception definitively as a + # ActiveRecord::PreparedStatementCacheExpired + # + # Check here for more details: + # https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573 + CACHED_PLAN_HEURISTIC = "cached plan must not change result type".freeze + def is_cached_plan_failure?(e) + pgerror = e.cause + code = pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE) + code == FEATURE_NOT_SUPPORTED && pgerror.message.include?(CACHED_PLAN_HEURISTIC) + rescue + false + end + + def in_transaction? + open_transactions > 0 + end + + # Returns the statement identifier for the client side cache + # of statements + def sql_key(sql) + "#{schema_search_path}-#{sql}" + end + + # Prepare the statement if it hasn't been prepared, return + # the statement key. + def prepare_statement(sql) + @lock.synchronize do + sql_key = sql_key(sql) + unless @statements.key? sql_key + nextkey = @statements.next_key + begin + @connection.prepare nextkey, sql + rescue => e + raise translate_exception_class(e, sql) + end + # Clear the queue + @connection.get_last_result + @statements[sql_key] = nextkey + end + @statements[sql_key] + end + end + + # Connects to a PostgreSQL server and sets up the adapter depending on the + # connected server's characteristics. + def connect + @connection = PG.connect(@connection_parameters) + configure_connection + rescue ::PG::Error => error + if error.message.include?("does not exist") + raise ActiveRecord::NoDatabaseError + else + raise + end + end + + # Configures the encoding, verbosity, schema search path, and time zone of the connection. + # This is called by #connect and should not be called manually. + def configure_connection + if @config[:encoding] + @connection.set_client_encoding(@config[:encoding]) + end + self.client_min_messages = @config[:min_messages] || "warning" + self.schema_search_path = @config[:schema_search_path] || @config[:schema_order] + + # Use standard-conforming strings so we don't have to do the E'...' dance. + set_standard_conforming_strings + + variables = @config.fetch(:variables, {}).stringify_keys + + # If using Active Record's time zone support configure the connection to return + # TIMESTAMP WITH ZONE types in UTC. + unless variables["timezone"] + if ActiveRecord::Base.default_timezone == :utc + variables["timezone"] = "UTC" + elsif @local_tz + variables["timezone"] = @local_tz + end + end + + # SET statements from :variables config hash + # https://www.postgresql.org/docs/current/static/sql-set.html + variables.map do |k, v| + if v == ":default" || v == :default + # Sets the value to the global or compile default + execute("SET SESSION #{k} TO DEFAULT", "SCHEMA") + elsif !v.nil? + execute("SET SESSION #{k} TO #{quote(v)}", "SCHEMA") + end + end + end + + # Returns the list of a table's column names, data types, and default values. + # + # The underlying query is roughly: + # SELECT column.name, column.type, default.value, column.comment + # FROM column LEFT JOIN default + # ON column.table_id = default.table_id + # AND column.num = default.column_num + # WHERE column.table_id = get_table_id('table_name') + # AND column.num > 0 + # AND NOT column.is_dropped + # ORDER BY column.num + # + # If the table name is not prefixed with a schema, the database will + # take the first match from the schema search path. + # + # Query implementation notes: + # - format_type includes the column size constraint, e.g. varchar(50) + # - ::regclass is a function that gives the id for a table name + def column_definitions(table_name) + query(<<-end_sql, "SCHEMA") + SELECT a.attname, format_type(a.atttypid, a.atttypmod), + pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod, + c.collname, col_description(a.attrelid, a.attnum) AS comment + FROM pg_attribute a + LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum + LEFT JOIN pg_type t ON a.atttypid = t.oid + LEFT JOIN pg_collation c ON a.attcollation = c.oid AND a.attcollation <> t.typcollation + WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass + AND a.attnum > 0 AND NOT a.attisdropped + ORDER BY a.attnum + end_sql + end + + def extract_table_ref_from_insert_sql(sql) + sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im] + $1.strip if $1 + end + + def arel_visitor + Arel::Visitors::PostgreSQL.new(self) + end + + def can_perform_case_insensitive_comparison_for?(column) + @case_insensitive_cache ||= {} + @case_insensitive_cache[column.sql_type] ||= begin + sql = <<-end_sql + SELECT exists( + SELECT * FROM pg_proc + WHERE proname = 'lower' + AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector + ) OR exists( + SELECT * FROM pg_proc + INNER JOIN pg_cast + ON ARRAY[casttarget]::oidvector = proargtypes + WHERE proname = 'lower' + AND castsource = #{quote column.sql_type}::regtype + ) + end_sql + execute_and_clear(sql, "SCHEMA", []) do |result| + result.getvalue(0, 0) + end + end + end + + def add_pg_encoders + map = PG::TypeMapByClass.new + map[Integer] = PG::TextEncoder::Integer.new + map[TrueClass] = PG::TextEncoder::Boolean.new + map[FalseClass] = PG::TextEncoder::Boolean.new + @connection.type_map_for_queries = map + end + + def add_pg_decoders + coders_by_name = { + "int2" => PG::TextDecoder::Integer, + "int4" => PG::TextDecoder::Integer, + "int8" => PG::TextDecoder::Integer, + "oid" => PG::TextDecoder::Integer, + "float4" => PG::TextDecoder::Float, + "float8" => PG::TextDecoder::Float, + "bool" => PG::TextDecoder::Boolean, + } + known_coder_types = coders_by_name.keys.map { |n| quote(n) } + query = <<-SQL % known_coder_types.join(", ") + SELECT t.oid, t.typname + FROM pg_type as t + WHERE t.typname IN (%s) + SQL + coders = execute_and_clear(query, "SCHEMA", []) do |result| + result + .map { |row| construct_coder(row, coders_by_name[row["typname"]]) } + .compact + end + + map = PG::TypeMapByOid.new + coders.each { |coder| map.add_coder(coder) } + @connection.type_map_for_results = map + end + + def construct_coder(row, coder_class) + return unless coder_class + coder_class.new(oid: row["oid"].to_i, name: row["typname"]) + end + + ActiveRecord::Type.add_modifier({ array: true }, OID::Array, adapter: :postgresql) + ActiveRecord::Type.add_modifier({ range: true }, OID::Range, adapter: :postgresql) + ActiveRecord::Type.register(:bit, OID::Bit, adapter: :postgresql) + ActiveRecord::Type.register(:bit_varying, OID::BitVarying, adapter: :postgresql) + ActiveRecord::Type.register(:binary, OID::Bytea, adapter: :postgresql) + ActiveRecord::Type.register(:cidr, OID::Cidr, adapter: :postgresql) + ActiveRecord::Type.register(:date, OID::Date, adapter: :postgresql) + ActiveRecord::Type.register(:datetime, OID::DateTime, adapter: :postgresql) + ActiveRecord::Type.register(:decimal, OID::Decimal, adapter: :postgresql) + ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql) + ActiveRecord::Type.register(:hstore, OID::Hstore, adapter: :postgresql) + ActiveRecord::Type.register(:inet, OID::Inet, adapter: :postgresql) + ActiveRecord::Type.register(:jsonb, OID::Jsonb, adapter: :postgresql) + ActiveRecord::Type.register(:money, OID::Money, adapter: :postgresql) + ActiveRecord::Type.register(:point, OID::Point, adapter: :postgresql) + ActiveRecord::Type.register(:legacy_point, OID::LegacyPoint, adapter: :postgresql) + ActiveRecord::Type.register(:uuid, OID::Uuid, adapter: :postgresql) + ActiveRecord::Type.register(:vector, OID::Vector, adapter: :postgresql) + ActiveRecord::Type.register(:xml, OID::Xml, adapter: :postgresql) + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/schema_cache.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/schema_cache.rb new file mode 100644 index 00000000..c29cf1f9 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/schema_cache.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + class SchemaCache + attr_reader :version + attr_accessor :connection + + def initialize(conn) + @connection = conn + + @columns = {} + @columns_hash = {} + @primary_keys = {} + @data_sources = {} + end + + def initialize_dup(other) + super + @columns = @columns.dup + @columns_hash = @columns_hash.dup + @primary_keys = @primary_keys.dup + @data_sources = @data_sources.dup + end + + def encode_with(coder) + coder["columns"] = @columns + coder["columns_hash"] = @columns_hash + coder["primary_keys"] = @primary_keys + coder["data_sources"] = @data_sources + coder["version"] = connection.migration_context.current_version + end + + def init_with(coder) + @columns = coder["columns"] + @columns_hash = coder["columns_hash"] + @primary_keys = coder["primary_keys"] + @data_sources = coder["data_sources"] + @version = coder["version"] + end + + def primary_keys(table_name) + @primary_keys[table_name] ||= data_source_exists?(table_name) ? connection.primary_key(table_name) : nil + end + + # A cached lookup for table existence. + def data_source_exists?(name) + prepare_data_sources if @data_sources.empty? + return @data_sources[name] if @data_sources.key? name + + @data_sources[name] = connection.data_source_exists?(name) + end + + # Add internal cache for table with +table_name+. + def add(table_name) + if data_source_exists?(table_name) + primary_keys(table_name) + columns(table_name) + columns_hash(table_name) + end + end + + def data_sources(name) + @data_sources[name] + end + + # Get the columns for a table + def columns(table_name) + @columns[table_name] ||= connection.columns(table_name) + end + + # Get the columns for a table as a hash, key is the column name + # value is the column object. + def columns_hash(table_name) + @columns_hash[table_name] ||= Hash[columns(table_name).map { |col| + [col.name, col] + }] + end + + # Clears out internal caches + def clear! + @columns.clear + @columns_hash.clear + @primary_keys.clear + @data_sources.clear + @version = nil + end + + def size + [@columns, @columns_hash, @primary_keys, @data_sources].map(&:size).inject :+ + end + + # Clear out internal caches for the data source +name+. + def clear_data_source_cache!(name) + @columns.delete name + @columns_hash.delete name + @primary_keys.delete name + @data_sources.delete name + end + + def marshal_dump + # if we get current version during initialization, it happens stack over flow. + @version = connection.migration_context.current_version + [@version, @columns, @columns_hash, @primary_keys, @data_sources] + end + + def marshal_load(array) + @version, @columns, @columns_hash, @primary_keys, @data_sources = array + end + + private + + def prepare_data_sources + connection.data_sources.each { |source| @data_sources[source] = true } + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/sql_type_metadata.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/sql_type_metadata.rb new file mode 100644 index 00000000..8489bcbf --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/sql_type_metadata.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module ActiveRecord + # :stopdoc: + module ConnectionAdapters + class SqlTypeMetadata + attr_reader :sql_type, :type, :limit, :precision, :scale + + def initialize(sql_type: nil, type: nil, limit: nil, precision: nil, scale: nil) + @sql_type = sql_type + @type = type + @limit = limit + @precision = precision + @scale = scale + end + + def ==(other) + other.is_a?(SqlTypeMetadata) && + attributes_for_hash == other.attributes_for_hash + end + alias eql? == + + def hash + attributes_for_hash.hash + end + + protected + + def attributes_for_hash + [self.class, sql_type, type, limit, precision, scale] + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb new file mode 100644 index 00000000..832fdfe5 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module SQLite3 + class ExplainPrettyPrinter # :nodoc: + # Pretty prints the result of an EXPLAIN QUERY PLAN in a way that resembles + # the output of the SQLite shell: + # + # 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows) + # 0|1|1|SCAN TABLE posts (~100000 rows) + # + def pp(result) + result.rows.map do |row| + row.join("|") + end.join("\n") + "\n" + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/sqlite3/quoting.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/sqlite3/quoting.rb new file mode 100644 index 00000000..07c5d31a --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/sqlite3/quoting.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module SQLite3 + module Quoting # :nodoc: + def quote_string(s) + @connection.class.quote(s) + end + + def quote_table_name_for_assignment(table, attr) + quote_column_name(attr) + end + + def quote_table_name(name) + @quoted_table_names[name] ||= super.gsub(".", "\".\"").freeze + end + + def quote_column_name(name) + @quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}").freeze + end + + def quoted_time(value) + value = value.change(year: 2000, month: 1, day: 1) + quoted_date(value).sub(/\A\d\d\d\d-\d\d-\d\d /, "2000-01-01 ") + end + + def quoted_binary(value) + "x'#{value.hex}'" + end + + def quoted_true + ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? "1".freeze : "'t'".freeze + end + + def unquoted_true + ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? 1 : "t".freeze + end + + def quoted_false + ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? "0".freeze : "'f'".freeze + end + + def unquoted_false + ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer ? 0 : "f".freeze + end + + private + + def _type_cast(value) + case value + when BigDecimal + value.to_f + when String + if value.encoding == Encoding::ASCII_8BIT + super(value.encode(Encoding::UTF_8)) + else + super + end + else + super + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/sqlite3/schema_creation.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/sqlite3/schema_creation.rb new file mode 100644 index 00000000..b8425613 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/sqlite3/schema_creation.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module SQLite3 + class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc: + private + def add_column_options!(sql, options) + if options[:collation] + sql << " COLLATE \"#{options[:collation]}\"" + end + super + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb new file mode 100644 index 00000000..c9855019 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module SQLite3 + class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition + def references(*args, **options) + super(*args, type: :integer, **options) + end + alias :belongs_to :references + + private + def integer_like_primary_key_type(type, options) + :primary_key + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb new file mode 100644 index 00000000..621678ec --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module SQLite3 + class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc: + private + def default_primary_key?(column) + schema_type(column) == :integer + end + + def explicit_primary_key_default?(column) + column.bigint? + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/sqlite3/schema_statements.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/sqlite3/schema_statements.rb new file mode 100644 index 00000000..24e7bc65 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/sqlite3/schema_statements.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + module SQLite3 + module SchemaStatements # :nodoc: + # Returns an array of indexes for the given table. + def indexes(table_name) + exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row| + # Indexes SQLite creates implicitly for internal use start with "sqlite_". + # See https://www.sqlite.org/fileformat2.html#intschema + next if row["name"].starts_with?("sqlite_") + + index_sql = query_value(<<-SQL, "SCHEMA") + SELECT sql + FROM sqlite_master + WHERE name = #{quote(row['name'])} AND type = 'index' + UNION ALL + SELECT sql + FROM sqlite_temp_master + WHERE name = #{quote(row['name'])} AND type = 'index' + SQL + + /\sWHERE\s+(?.+)$/i =~ index_sql + + columns = exec_query("PRAGMA index_info(#{quote(row['name'])})", "SCHEMA").map do |col| + col["name"] + end + + # Add info on sort order for columns (only desc order is explicitly specified, asc is + # the default) + orders = {} + if index_sql # index_sql can be null in case of primary key indexes + index_sql.scan(/"(\w+)" DESC/).flatten.each { |order_column| + orders[order_column] = :desc + } + end + + IndexDefinition.new( + table_name, + row["name"], + row["unique"] != 0, + columns, + where: where, + orders: orders + ) + end.compact + end + + def create_schema_dumper(options) + SQLite3::SchemaDumper.create(self, options) + end + + private + def schema_creation + SQLite3::SchemaCreation.new(self) + end + + def create_table_definition(*args) + SQLite3::TableDefinition.new(*args) + end + + def new_column_from_field(table_name, field) + default = \ + case field["dflt_value"] + when /^null$/i + nil + when /^'(.*)'$/m + $1.gsub("''", "'") + when /^"(.*)"$/m + $1.gsub('""', '"') + else + field["dflt_value"] + end + + type_metadata = fetch_type_metadata(field["type"]) + Column.new(field["name"], default, type_metadata, field["notnull"].to_i == 0, table_name, nil, field["collation"]) + end + + def data_source_sql(name = nil, type: nil) + scope = quoted_scope(name, type: type) + scope[:type] ||= "'table','view'" + + sql = "SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence'".dup + sql << " AND name = #{scope[:name]}" if scope[:name] + sql << " AND type IN (#{scope[:type]})" + sql + end + + def quoted_scope(name = nil, type: nil) + type = \ + case type + when "BASE TABLE" + "'table'" + when "VIEW" + "'view'" + end + scope = {} + scope[:name] = quote(name) if name + scope[:type] = type if type + scope + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/sqlite3_adapter.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/sqlite3_adapter.rb new file mode 100644 index 00000000..133e3114 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -0,0 +1,573 @@ +# frozen_string_literal: true + +require "active_record/connection_adapters/abstract_adapter" +require "active_record/connection_adapters/statement_pool" +require "active_record/connection_adapters/sqlite3/explain_pretty_printer" +require "active_record/connection_adapters/sqlite3/quoting" +require "active_record/connection_adapters/sqlite3/schema_creation" +require "active_record/connection_adapters/sqlite3/schema_definitions" +require "active_record/connection_adapters/sqlite3/schema_dumper" +require "active_record/connection_adapters/sqlite3/schema_statements" + +gem "sqlite3", "~> 1.3", ">= 1.3.6" +require "sqlite3" + +module ActiveRecord + module ConnectionHandling # :nodoc: + def sqlite3_connection(config) + # Require database. + unless config[:database] + raise ArgumentError, "No database file specified. Missing argument: database" + end + + # Allow database path relative to Rails.root, but only if the database + # path is not the special path that tells sqlite to build a database only + # in memory. + if ":memory:" != config[:database] + config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root) + dirname = File.dirname(config[:database]) + Dir.mkdir(dirname) unless File.directory?(dirname) + end + + db = SQLite3::Database.new( + config[:database].to_s, + results_as_hash: true + ) + + db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout] + + ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config) + rescue Errno::ENOENT => error + if error.message.include?("No such file or directory") + raise ActiveRecord::NoDatabaseError + else + raise + end + end + end + + module ConnectionAdapters #:nodoc: + # The SQLite3 adapter works SQLite 3.6.16 or newer + # with the sqlite3-ruby drivers (available as gem from https://rubygems.org/gems/sqlite3). + # + # Options: + # + # * :database - Path to the database file. + class SQLite3Adapter < AbstractAdapter + ADAPTER_NAME = "SQLite".freeze + + include SQLite3::Quoting + include SQLite3::SchemaStatements + + NATIVE_DATABASE_TYPES = { + primary_key: "integer PRIMARY KEY AUTOINCREMENT NOT NULL", + string: { name: "varchar" }, + text: { name: "text" }, + integer: { name: "integer" }, + float: { name: "float" }, + decimal: { name: "decimal" }, + datetime: { name: "datetime" }, + time: { name: "time" }, + date: { name: "date" }, + binary: { name: "blob" }, + boolean: { name: "boolean" }, + json: { name: "json" }, + } + + ## + # :singleton-method: + # Indicates whether boolean values are stored in sqlite3 databases as 1 + # and 0 or 't' and 'f'. Leaving ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer + # set to false is deprecated. SQLite databases have used 't' and 'f' to + # serialize boolean values and must have old data converted to 1 and 0 + # (its native boolean serialization) before setting this flag to true. + # Conversion can be accomplished by setting up a rake task which runs + # + # ExampleModel.where("boolean_column = 't'").update_all(boolean_column: 1) + # ExampleModel.where("boolean_column = 'f'").update_all(boolean_column: 0) + # for all models and all boolean columns, after which the flag must be set + # to true by adding the following to your application.rb file: + # + # Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true + class_attribute :represent_boolean_as_integer, default: false + + class StatementPool < ConnectionAdapters::StatementPool # :nodoc: + private + def dealloc(stmt) + stmt[:stmt].close unless stmt[:stmt].closed? + end + end + + def initialize(connection, logger, connection_options, config) + super(connection, logger, config) + + @active = true + @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit])) + + configure_connection + end + + def supports_ddl_transactions? + true + end + + def supports_savepoints? + true + end + + def supports_partial_index? + sqlite_version >= "3.8.0" + end + + def requires_reloading? + true + end + + def supports_foreign_keys_in_create? + sqlite_version >= "3.6.19" + end + + def supports_views? + true + end + + def supports_datetime_with_precision? + true + end + + def supports_json? + true + end + + def supports_multi_insert? + sqlite_version >= "3.7.11" + end + + def active? + @active + end + + # Disconnects from the database if already connected. Otherwise, this + # method does nothing. + def disconnect! + super + @active = false + @connection.close rescue nil + end + + # Clears the prepared statements cache. + def clear_cache! + @statements.clear + end + + def supports_index_sort_order? + true + end + + # Returns 62. SQLite supports index names up to 64 + # characters. The rest is used by Rails internally to perform + # temporary rename operations + def allowed_index_name_length + index_name_length - 2 + end + + def native_database_types #:nodoc: + NATIVE_DATABASE_TYPES + end + + # Returns the current database encoding format as a string, eg: 'UTF-8' + def encoding + @connection.encoding.to_s + end + + def supports_explain? + true + end + + # REFERENTIAL INTEGRITY ==================================== + + def disable_referential_integrity # :nodoc: + old = query_value("PRAGMA foreign_keys") + + begin + execute("PRAGMA foreign_keys = OFF") + yield + ensure + execute("PRAGMA foreign_keys = #{old}") + end + end + + #-- + # DATABASE STATEMENTS ====================================== + #++ + + def explain(arel, binds = []) + sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}" + SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", [])) + end + + def exec_query(sql, name = nil, binds = [], prepare: false) + type_casted_binds = type_casted_binds(binds) + + log(sql, name, binds, type_casted_binds) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + # Don't cache statements if they are not prepared + unless prepare + stmt = @connection.prepare(sql) + begin + cols = stmt.columns + unless without_prepared_statement?(binds) + stmt.bind_params(type_casted_binds) + end + records = stmt.to_a + ensure + stmt.close + end + else + cache = @statements[sql] ||= { + stmt: @connection.prepare(sql) + } + stmt = cache[:stmt] + cols = cache[:cols] ||= stmt.columns + stmt.reset! + stmt.bind_params(type_casted_binds) + records = stmt.to_a + end + + ActiveRecord::Result.new(cols, records) + end + end + end + + def exec_delete(sql, name = "SQL", binds = []) + exec_query(sql, name, binds) + @connection.changes + end + alias :exec_update :exec_delete + + def last_inserted_id(result) + @connection.last_insert_row_id + end + + def execute(sql, name = nil) #:nodoc: + log(sql, name) do + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @connection.execute(sql) + end + end + end + + def begin_db_transaction #:nodoc: + log("begin transaction", nil) { @connection.transaction } + end + + def commit_db_transaction #:nodoc: + log("commit transaction", nil) { @connection.commit } + end + + def exec_rollback_db_transaction #:nodoc: + log("rollback transaction", nil) { @connection.rollback } + end + + # SCHEMA STATEMENTS ======================================== + + def primary_keys(table_name) # :nodoc: + pks = table_structure(table_name).select { |f| f["pk"] > 0 } + pks.sort_by { |f| f["pk"] }.map { |f| f["name"] } + end + + def remove_index(table_name, options = {}) #:nodoc: + index_name = index_name_for_remove(table_name, options) + exec_query "DROP INDEX #{quote_column_name(index_name)}" + end + + # Renames a table. + # + # Example: + # rename_table('octopuses', 'octopi') + def rename_table(table_name, new_name) + exec_query "ALTER TABLE #{quote_table_name(table_name)} RENAME TO #{quote_table_name(new_name)}" + rename_table_indexes(table_name, new_name) + end + + def valid_alter_table_type?(type, options = {}) + !invalid_alter_table_type?(type, options) + end + deprecate :valid_alter_table_type? + + def add_column(table_name, column_name, type, options = {}) #:nodoc: + if invalid_alter_table_type?(type, options) + alter_table(table_name) do |definition| + definition.column(column_name, type, options) + end + else + super + end + end + + def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc: + alter_table(table_name) do |definition| + definition.remove_column column_name + end + end + + def change_column_default(table_name, column_name, default_or_changes) #:nodoc: + default = extract_new_default_value(default_or_changes) + + alter_table(table_name) do |definition| + definition[column_name].default = default + end + end + + def change_column_null(table_name, column_name, null, default = nil) #:nodoc: + unless null || default.nil? + exec_query("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL") + end + alter_table(table_name) do |definition| + definition[column_name].null = null + end + end + + def change_column(table_name, column_name, type, options = {}) #:nodoc: + alter_table(table_name) do |definition| + definition[column_name].instance_eval do + self.type = type + self.limit = options[:limit] if options.include?(:limit) + self.default = options[:default] if options.include?(:default) + self.null = options[:null] if options.include?(:null) + self.precision = options[:precision] if options.include?(:precision) + self.scale = options[:scale] if options.include?(:scale) + self.collation = options[:collation] if options.include?(:collation) + end + end + end + + def rename_column(table_name, column_name, new_column_name) #:nodoc: + column = column_for(table_name, column_name) + alter_table(table_name, rename: { column.name => new_column_name.to_s }) + rename_column_indexes(table_name, column.name, new_column_name) + end + + def add_reference(table_name, ref_name, **options) # :nodoc: + super(table_name, ref_name, type: :integer, **options) + end + alias :add_belongs_to :add_reference + + def foreign_keys(table_name) + fk_info = exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA") + fk_info.map do |row| + options = { + column: row["from"], + primary_key: row["to"], + on_delete: extract_foreign_key_action(row["on_delete"]), + on_update: extract_foreign_key_action(row["on_update"]) + } + ForeignKeyDefinition.new(table_name, row["table"], options) + end + end + + def insert_fixtures(rows, table_name) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + `insert_fixtures` is deprecated and will be removed in the next version of Rails. + Consider using `insert_fixtures_set` for performance improvement. + MSG + insert_fixtures_set(table_name => rows) + end + + def insert_fixtures_set(fixture_set, tables_to_delete = []) + disable_referential_integrity do + transaction(requires_new: true) do + tables_to_delete.each { |table| delete "DELETE FROM #{quote_table_name(table)}", "Fixture Delete" } + + fixture_set.each do |table_name, rows| + rows.each { |row| insert_fixture(row, table_name) } + end + end + end + end + + private + def initialize_type_map(m = type_map) + super + register_class_with_limit m, %r(int)i, SQLite3Integer + end + + def table_structure(table_name) + structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA") + raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty? + table_structure_with_collation(table_name, structure) + end + alias column_definitions table_structure + + # See: https://www.sqlite.org/lang_altertable.html + # SQLite has an additional restriction on the ALTER TABLE statement + def invalid_alter_table_type?(type, options) + type.to_sym == :primary_key || options[:primary_key] + end + + def alter_table(table_name, options = {}) + altered_table_name = "a#{table_name}" + caller = lambda { |definition| yield definition if block_given? } + + transaction do + move_table(table_name, altered_table_name, + options.merge(temporary: true)) + move_table(altered_table_name, table_name, &caller) + end + end + + def move_table(from, to, options = {}, &block) + copy_table(from, to, options, &block) + drop_table(from) + end + + def copy_table(from, to, options = {}) + from_primary_key = primary_key(from) + options[:id] = false + create_table(to, options) do |definition| + @definition = definition + if from_primary_key.is_a?(Array) + @definition.primary_keys from_primary_key + end + columns(from).each do |column| + column_name = options[:rename] ? + (options[:rename][column.name] || + options[:rename][column.name.to_sym] || + column.name) : column.name + + @definition.column(column_name, column.type, + limit: column.limit, default: column.default, + precision: column.precision, scale: column.scale, + null: column.null, collation: column.collation, + primary_key: column_name == from_primary_key + ) + end + yield @definition if block_given? + end + copy_table_indexes(from, to, options[:rename] || {}) + copy_table_contents(from, to, + @definition.columns.map(&:name), + options[:rename] || {}) + end + + def copy_table_indexes(from, to, rename = {}) + indexes(from).each do |index| + name = index.name + if to == "a#{from}" + name = "t#{name}" + elsif from == "a#{to}" + name = name[1..-1] + end + + to_column_names = columns(to).map(&:name) + columns = index.columns.map { |c| rename[c] || c }.select do |column| + to_column_names.include?(column) + end + + unless columns.empty? + # index name can't be the same + opts = { name: name.gsub(/(^|_)(#{from})_/, "\\1#{to}_"), internal: true } + opts[:unique] = true if index.unique + opts[:where] = index.where if index.where + add_index(to, columns, opts) + end + end + end + + def copy_table_contents(from, to, columns, rename = {}) + column_mappings = Hash[columns.map { |name| [name, name] }] + rename.each { |a| column_mappings[a.last] = a.first } + from_columns = columns(from).collect(&:name) + columns = columns.find_all { |col| from_columns.include?(column_mappings[col]) } + from_columns_to_copy = columns.map { |col| column_mappings[col] } + quoted_columns = columns.map { |col| quote_column_name(col) } * "," + quoted_from_columns = from_columns_to_copy.map { |col| quote_column_name(col) } * "," + + exec_query("INSERT INTO #{quote_table_name(to)} (#{quoted_columns}) + SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}") + end + + def sqlite_version + @sqlite_version ||= SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)")) + end + + def translate_exception(exception, message) + case exception.message + # SQLite 3.8.2 returns a newly formatted error message: + # UNIQUE constraint failed: *table_name*.*column_name* + # Older versions of SQLite return: + # column *column_name* is not unique + when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/ + RecordNotUnique.new(message) + when /.* may not be NULL/, /NOT NULL constraint failed: .*/ + NotNullViolation.new(message) + when /FOREIGN KEY constraint failed/i + InvalidForeignKey.new(message) + else + super + end + end + + COLLATE_REGEX = /.*\"(\w+)\".*collate\s+\"(\w+)\".*/i.freeze + + def table_structure_with_collation(table_name, basic_structure) + collation_hash = {} + sql = <<-SQL + SELECT sql FROM + (SELECT * FROM sqlite_master UNION ALL + SELECT * FROM sqlite_temp_master) + WHERE type = 'table' AND name = #{quote(table_name)} + SQL + + # Result will have following sample string + # CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + # "password_digest" varchar COLLATE "NOCASE"); + result = exec_query(sql, "SCHEMA").first + + if result + # Splitting with left parentheses and picking up last will return all + # columns separated with comma(,). + columns_string = result["sql"].split("(").last + + columns_string.split(",").each do |column_string| + # This regex will match the column name and collation type and will save + # the value in $1 and $2 respectively. + collation_hash[$1] = $2 if COLLATE_REGEX =~ column_string + end + + basic_structure.map! do |column| + column_name = column["name"] + + if collation_hash.has_key? column_name + column["collation"] = collation_hash[column_name] + end + + column + end + else + basic_structure.to_hash + end + end + + def arel_visitor + Arel::Visitors::SQLite.new(self) + end + + def configure_connection + execute("PRAGMA foreign_keys = ON", "SCHEMA") + end + + class SQLite3Integer < Type::Integer # :nodoc: + private + def _limit + # INTEGER storage class can be stored 8 bytes value. + # See https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes + limit || 8 + end + end + + ActiveRecord::Type.register(:integer, SQLite3Integer, adapter: :sqlite3) + end + ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter) + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/statement_pool.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/statement_pool.rb new file mode 100644 index 00000000..46bd831d --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_adapters/statement_pool.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionAdapters + class StatementPool # :nodoc: + include Enumerable + + DEFAULT_STATEMENT_LIMIT = 1000 + + def initialize(statement_limit = nil) + @cache = Hash.new { |h, pid| h[pid] = {} } + @statement_limit = statement_limit || DEFAULT_STATEMENT_LIMIT + end + + def each(&block) + cache.each(&block) + end + + def key?(key) + cache.key?(key) + end + + def [](key) + cache[key] + end + + def length + cache.length + end + + def []=(sql, stmt) + while @statement_limit <= cache.size + dealloc(cache.shift.last) + end + cache[sql] = stmt + end + + def clear + cache.each_value do |stmt| + dealloc stmt + end + cache.clear + end + + def delete(key) + dealloc cache[key] + cache.delete(key) + end + + private + + def cache + @cache[Process.pid] + end + + def dealloc(stmt) + raise NotImplementedError + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_handling.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_handling.rb new file mode 100644 index 00000000..88d28dc5 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/connection_handling.rb @@ -0,0 +1,145 @@ +# frozen_string_literal: true + +module ActiveRecord + module ConnectionHandling + RAILS_ENV = -> { (Rails.env if defined?(Rails.env)) || ENV["RAILS_ENV"].presence || ENV["RACK_ENV"].presence } + DEFAULT_ENV = -> { RAILS_ENV.call || "default_env" } + + # Establishes the connection to the database. Accepts a hash as input where + # the :adapter key must be specified with the name of a database adapter (in lower-case) + # example for regular databases (MySQL, PostgreSQL, etc): + # + # ActiveRecord::Base.establish_connection( + # adapter: "mysql2", + # host: "localhost", + # username: "myuser", + # password: "mypass", + # database: "somedatabase" + # ) + # + # Example for SQLite database: + # + # ActiveRecord::Base.establish_connection( + # adapter: "sqlite3", + # database: "path/to/dbfile" + # ) + # + # Also accepts keys as strings (for parsing from YAML for example): + # + # ActiveRecord::Base.establish_connection( + # "adapter" => "sqlite3", + # "database" => "path/to/dbfile" + # ) + # + # Or a URL: + # + # ActiveRecord::Base.establish_connection( + # "postgres://myuser:mypass@localhost/somedatabase" + # ) + # + # In case {ActiveRecord::Base.configurations}[rdoc-ref:Core.configurations] + # is set (Rails automatically loads the contents of config/database.yml into it), + # a symbol can also be given as argument, representing a key in the + # configuration hash: + # + # ActiveRecord::Base.establish_connection(:production) + # + # The exceptions AdapterNotSpecified, AdapterNotFound and +ArgumentError+ + # may be returned on an error. + def establish_connection(config = nil) + raise "Anonymous class is not allowed." unless name + + config ||= DEFAULT_ENV.call.to_sym + spec_name = self == Base ? "primary" : name + self.connection_specification_name = spec_name + + resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations) + spec = resolver.resolve(config).symbolize_keys + spec[:name] = spec_name + + connection_handler.establish_connection(spec) + end + + class MergeAndResolveDefaultUrlConfig # :nodoc: + def initialize(raw_configurations) + @raw_config = raw_configurations.dup + @env = DEFAULT_ENV.call.to_s + end + + # Returns fully resolved connection hashes. + # Merges connection information from `ENV['DATABASE_URL']` if available. + def resolve + ConnectionAdapters::ConnectionSpecification::Resolver.new(config).resolve_all + end + + private + def config + @raw_config.dup.tap do |cfg| + if url = ENV["DATABASE_URL"] + cfg[@env] ||= {} + cfg[@env]["url"] ||= url + end + end + end + end + + # Returns the connection currently associated with the class. This can + # also be used to "borrow" the connection to do database work unrelated + # to any of the specific Active Records. + def connection + retrieve_connection + end + + attr_writer :connection_specification_name + + # Return the specification name from the current class or its parent. + def connection_specification_name + if !defined?(@connection_specification_name) || @connection_specification_name.nil? + return self == Base ? "primary" : superclass.connection_specification_name + end + @connection_specification_name + end + + # Returns the configuration of the associated connection as a hash: + # + # ActiveRecord::Base.connection_config + # # => {pool: 5, timeout: 5000, database: "db/development.sqlite3", adapter: "sqlite3"} + # + # Please use only for reading. + def connection_config + connection_pool.spec.config + end + + def connection_pool + connection_handler.retrieve_connection_pool(connection_specification_name) || raise(ConnectionNotEstablished) + end + + def retrieve_connection + connection_handler.retrieve_connection(connection_specification_name) + end + + # Returns +true+ if Active Record is connected. + def connected? + connection_handler.connected?(connection_specification_name) + end + + def remove_connection(name = nil) + name ||= @connection_specification_name if defined?(@connection_specification_name) + # if removing a connection that has a pool, we reset the + # connection_specification_name so it will use the parent + # pool. + if connection_handler.retrieve_connection_pool(name) + self.connection_specification_name = nil + end + + connection_handler.remove_connection(name) + end + + def clear_cache! # :nodoc: + connection.schema_cache.clear! + end + + delegate :clear_active_connections!, :clear_reloadable_connections!, + :clear_all_connections!, :flush_idle_connections!, to: :connection_handler + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/core.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/core.rb new file mode 100644 index 00000000..48fc7992 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/core.rb @@ -0,0 +1,559 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/indifferent_access" +require "active_support/core_ext/string/filters" +require "concurrent/map" + +module ActiveRecord + module Core + extend ActiveSupport::Concern + + included do + ## + # :singleton-method: + # + # Accepts a logger conforming to the interface of Log4r which is then + # passed on to any new database connections made and which can be + # retrieved on both a class and instance level by calling +logger+. + mattr_accessor :logger, instance_writer: false + + ## + # :singleton-method: + # + # Specifies if the methods calling database queries should be logged below + # their relevant queries. Defaults to false. + mattr_accessor :verbose_query_logs, instance_writer: false, default: false + + ## + # Contains the database configuration - as is typically stored in config/database.yml - + # as a Hash. + # + # For example, the following database.yml... + # + # development: + # adapter: sqlite3 + # database: db/development.sqlite3 + # + # production: + # adapter: sqlite3 + # database: db/production.sqlite3 + # + # ...would result in ActiveRecord::Base.configurations to look like this: + # + # { + # 'development' => { + # 'adapter' => 'sqlite3', + # 'database' => 'db/development.sqlite3' + # }, + # 'production' => { + # 'adapter' => 'sqlite3', + # 'database' => 'db/production.sqlite3' + # } + # } + def self.configurations=(config) + @@configurations = ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig.new(config).resolve + end + self.configurations = {} + + # Returns fully resolved configurations hash + def self.configurations + @@configurations + end + + ## + # :singleton-method: + # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling + # dates and times from the database. This is set to :utc by default. + mattr_accessor :default_timezone, instance_writer: false, default: :utc + + ## + # :singleton-method: + # Specifies the format to use when dumping the database schema with Rails' + # Rakefile. If :sql, the schema is dumped as (potentially database- + # specific) SQL statements. If :ruby, the schema is dumped as an + # ActiveRecord::Schema file which can be loaded into any database that + # supports migrations. Use :ruby if you want to have different database + # adapters for, e.g., your development and test environments. + mattr_accessor :schema_format, instance_writer: false, default: :ruby + + ## + # :singleton-method: + # Specifies if an error should be raised if the query has an order being + # ignored when doing batch queries. Useful in applications where the + # scope being ignored is error-worthy, rather than a warning. + mattr_accessor :error_on_ignored_order, instance_writer: false, default: false + + # :singleton-method: + # Specify the behavior for unsafe raw query methods. Values are as follows + # deprecated - Warnings are logged when unsafe raw SQL is passed to + # query methods. + # disabled - Unsafe raw SQL passed to query methods results in + # UnknownAttributeReference exception. + mattr_accessor :allow_unsafe_raw_sql, instance_writer: false, default: :deprecated + + ## + # :singleton-method: + # Specify whether or not to use timestamps for migration versions + mattr_accessor :timestamped_migrations, instance_writer: false, default: true + + ## + # :singleton-method: + # Specify whether schema dump should happen at the end of the + # db:migrate rake task. This is true by default, which is useful for the + # development environment. This should ideally be false in the production + # environment where dumping schema is rarely needed. + mattr_accessor :dump_schema_after_migration, instance_writer: false, default: true + + ## + # :singleton-method: + # Specifies which database schemas to dump when calling db:structure:dump. + # If the value is :schema_search_path (the default), any schemas listed in + # schema_search_path are dumped. Use :all to dump all schemas regardless + # of schema_search_path, or a string of comma separated schemas for a + # custom list. + mattr_accessor :dump_schemas, instance_writer: false, default: :schema_search_path + + ## + # :singleton-method: + # Specify a threshold for the size of query result sets. If the number of + # records in the set exceeds the threshold, a warning is logged. This can + # be used to identify queries which load thousands of records and + # potentially cause memory bloat. + mattr_accessor :warn_on_records_fetched_greater_than, instance_writer: false + + mattr_accessor :maintain_test_schema, instance_accessor: false + + mattr_accessor :belongs_to_required_by_default, instance_accessor: false + + class_attribute :default_connection_handler, instance_writer: false + + def self.connection_handler + ActiveRecord::RuntimeRegistry.connection_handler || default_connection_handler + end + + def self.connection_handler=(handler) + ActiveRecord::RuntimeRegistry.connection_handler = handler + end + + self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new + end + + module ClassMethods # :nodoc: + def allocate + define_attribute_methods + super + end + + def initialize_find_by_cache # :nodoc: + @find_by_statement_cache = { true => Concurrent::Map.new, false => Concurrent::Map.new } + end + + def inherited(child_class) # :nodoc: + # initialize cache at class definition for thread safety + child_class.initialize_find_by_cache + super + end + + def find(*ids) # :nodoc: + # We don't have cache keys for this stuff yet + return super unless ids.length == 1 + return super if block_given? || + primary_key.nil? || + scope_attributes? || + columns_hash.include?(inheritance_column) + + id = ids.first + + return super if StatementCache.unsupported_value?(id) + + key = primary_key + + statement = cached_find_by_statement(key) { |params| + where(key => params.bind).limit(1) + } + + record = statement.execute([id], connection).first + unless record + raise RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}", + name, primary_key, id) + end + record + rescue ::RangeError + raise RecordNotFound.new("Couldn't find #{name} with an out of range value for '#{primary_key}'", + name, primary_key) + end + + def find_by(*args) # :nodoc: + return super if scope_attributes? || reflect_on_all_aggregations.any? || + columns_hash.key?(inheritance_column) && base_class != self + + hash = args.first + + return super if !(Hash === hash) || hash.values.any? { |v| + StatementCache.unsupported_value?(v) + } + + # We can't cache Post.find_by(author: david) ...yet + return super unless hash.keys.all? { |k| columns_hash.has_key?(k.to_s) } + + keys = hash.keys + + statement = cached_find_by_statement(keys) { |params| + wheres = keys.each_with_object({}) { |param, o| + o[param] = params.bind + } + where(wheres).limit(1) + } + begin + statement.execute(hash.values, connection).first + rescue TypeError + raise ActiveRecord::StatementInvalid + rescue ::RangeError + nil + end + end + + def find_by!(*args) # :nodoc: + find_by(*args) || raise(RecordNotFound.new("Couldn't find #{name}", name)) + end + + def initialize_generated_modules # :nodoc: + generated_association_methods + end + + def generated_association_methods + @generated_association_methods ||= begin + mod = const_set(:GeneratedAssociationMethods, Module.new) + private_constant :GeneratedAssociationMethods + include mod + + mod + end + end + + # Returns a string like 'Post(id:integer, title:string, body:text)' + def inspect + if self == Base + super + elsif abstract_class? + "#{super}(abstract)" + elsif !connected? + "#{super} (call '#{super}.connection' to establish a connection)" + elsif table_exists? + attr_list = attribute_types.map { |name, type| "#{name}: #{type.type}" } * ", " + "#{super}(#{attr_list})" + else + "#{super}(Table doesn't exist)" + end + end + + # Overwrite the default class equality method to provide support for decorated models. + def ===(object) + object.is_a?(self) + end + + # Returns an instance of Arel::Table loaded with the current table name. + # + # class Post < ActiveRecord::Base + # scope :published_and_commented, -> { published.and(arel_table[:comments_count].gt(0)) } + # end + def arel_table # :nodoc: + @arel_table ||= Arel::Table.new(table_name, type_caster: type_caster) + end + + def arel_attribute(name, table = arel_table) # :nodoc: + name = attribute_alias(name) if attribute_alias?(name) + table[name] + end + + def predicate_builder # :nodoc: + @predicate_builder ||= PredicateBuilder.new(table_metadata) + end + + def type_caster # :nodoc: + TypeCaster::Map.new(self) + end + + private + + def cached_find_by_statement(key, &block) + cache = @find_by_statement_cache[connection.prepared_statements] + cache.compute_if_absent(key) { StatementCache.create(connection, &block) } + end + + def relation + relation = Relation.create(self) + + if finder_needs_type_condition? && !ignore_default_scope? + relation.where!(type_condition) + relation.create_with!(inheritance_column.to_s => sti_name) + else + relation + end + end + + def table_metadata + TableMetadata.new(self, arel_table) + end + end + + # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with + # attributes but not yet saved (pass a hash with key names matching the associated table column names). + # In both instances, valid attribute keys are determined by the column names of the associated table -- + # hence you can't have attributes that aren't part of the table columns. + # + # ==== Example: + # # Instantiates a single new object + # User.new(first_name: 'Jamie') + def initialize(attributes = nil) + self.class.define_attribute_methods + @attributes = self.class._default_attributes.deep_dup + + init_internals + initialize_internals_callback + + assign_attributes(attributes) if attributes + + yield self if block_given? + _run_initialize_callbacks + end + + # Initialize an empty model object from +coder+. +coder+ should be + # the result of previously encoding an Active Record model, using + # #encode_with. + # + # class Post < ActiveRecord::Base + # end + # + # old_post = Post.new(title: "hello world") + # coder = {} + # old_post.encode_with(coder) + # + # post = Post.allocate + # post.init_with(coder) + # post.title # => 'hello world' + def init_with(coder) + coder = LegacyYamlAdapter.convert(self.class, coder) + @attributes = self.class.yaml_encoder.decode(coder) + + init_internals + + @new_record = coder["new_record"] + + self.class.define_attribute_methods + + yield self if block_given? + + _run_find_callbacks + _run_initialize_callbacks + + self + end + + ## + # :method: clone + # Identical to Ruby's clone method. This is a "shallow" copy. Be warned that your attributes are not copied. + # That means that modifying attributes of the clone will modify the original, since they will both point to the + # same attributes hash. If you need a copy of your attributes hash, please use the #dup method. + # + # user = User.first + # new_user = user.clone + # user.name # => "Bob" + # new_user.name = "Joe" + # user.name # => "Joe" + # + # user.object_id == new_user.object_id # => false + # user.name.object_id == new_user.name.object_id # => true + # + # user.name.object_id == user.dup.name.object_id # => false + + ## + # :method: dup + # Duped objects have no id assigned and are treated as new records. Note + # that this is a "shallow" copy as it copies the object's attributes + # only, not its associations. The extent of a "deep" copy is application + # specific and is therefore left to the application to implement according + # to its need. + # The dup method does not preserve the timestamps (created|updated)_(at|on). + + ## + def initialize_dup(other) # :nodoc: + @attributes = @attributes.deep_dup + @attributes.reset(self.class.primary_key) + + _run_initialize_callbacks + + @new_record = true + @destroyed = false + @_start_transaction_state = {} + @transaction_state = nil + + super + end + + # Populate +coder+ with attributes about this record that should be + # serialized. The structure of +coder+ defined in this method is + # guaranteed to match the structure of +coder+ passed to the #init_with + # method. + # + # Example: + # + # class Post < ActiveRecord::Base + # end + # coder = {} + # Post.new.encode_with(coder) + # coder # => {"attributes" => {"id" => nil, ... }} + def encode_with(coder) + self.class.yaml_encoder.encode(@attributes, coder) + coder["new_record"] = new_record? + coder["active_record_yaml_version"] = 2 + end + + # Returns true if +comparison_object+ is the same exact object, or +comparison_object+ + # is of the same type and +self+ has an ID and it is equal to +comparison_object.id+. + # + # Note that new records are different from any other record by definition, unless the + # other record is the receiver itself. Besides, if you fetch existing records with + # +select+ and leave the ID out, you're on your own, this predicate will return false. + # + # Note also that destroying a record preserves its ID in the model instance, so deleted + # models are still comparable. + def ==(comparison_object) + super || + comparison_object.instance_of?(self.class) && + !id.nil? && + comparison_object.id == id + end + alias :eql? :== + + # Delegates to id in order to allow two records of the same type and id to work with something like: + # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ] + def hash + if id + self.class.hash ^ id.hash + else + super + end + end + + # Clone and freeze the attributes hash such that associations are still + # accessible, even on destroyed records, but cloned models will not be + # frozen. + def freeze + @attributes = @attributes.clone.freeze + self + end + + # Returns +true+ if the attributes hash has been frozen. + def frozen? + @attributes.frozen? + end + + # Allows sort on objects + def <=>(other_object) + if other_object.is_a?(self.class) + to_key <=> other_object.to_key + else + super + end + end + + # Returns +true+ if the record is read only. Records loaded through joins with piggy-back + # attributes will be marked as read only since they cannot be saved. + def readonly? + @readonly + end + + # Marks this record as read only. + def readonly! + @readonly = true + end + + def connection_handler + self.class.connection_handler + end + + # Returns the contents of the record as a nicely formatted string. + def inspect + # We check defined?(@attributes) not to issue warnings if the object is + # allocated but not initialized. + inspection = if defined?(@attributes) && @attributes + self.class.attribute_names.collect do |name| + if has_attribute?(name) + "#{name}: #{attribute_for_inspect(name)}" + end + end.compact.join(", ") + else + "not initialized" + end + + "#<#{self.class} #{inspection}>" + end + + # Takes a PP and prettily prints this record to it, allowing you to get a nice result from pp record + # when pp is required. + def pretty_print(pp) + return super if custom_inspect_method_defined? + pp.object_address_group(self) do + if defined?(@attributes) && @attributes + column_names = self.class.column_names.select { |name| has_attribute?(name) || new_record? } + pp.seplist(column_names, proc { pp.text "," }) do |column_name| + column_value = read_attribute(column_name) + pp.breakable " " + pp.group(1) do + pp.text column_name + pp.text ":" + pp.breakable + pp.pp column_value + end + end + else + pp.breakable " " + pp.text "not initialized" + end + end + end + + # Returns a hash of the given methods with their names as keys and returned values as values. + def slice(*methods) + Hash[methods.flatten.map! { |method| [method, public_send(method)] }].with_indifferent_access + end + + private + + # +Array#flatten+ will call +#to_ary+ (recursively) on each of the elements of + # the array, and then rescues from the possible +NoMethodError+. If those elements are + # +ActiveRecord::Base+'s, then this triggers the various +method_missing+'s that we have, + # which significantly impacts upon performance. + # + # So we can avoid the +method_missing+ hit by explicitly defining +#to_ary+ as +nil+ here. + # + # See also https://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary.html + def to_ary + nil + end + + def init_internals + @readonly = false + @destroyed = false + @marked_for_destruction = false + @destroyed_by_association = nil + @new_record = true + @_start_transaction_state = {} + @transaction_state = nil + end + + def initialize_internals_callback + end + + def thaw + if frozen? + @attributes = @attributes.dup + end + end + + def custom_inspect_method_defined? + self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/counter_cache.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/counter_cache.rb new file mode 100644 index 00000000..0d8748d7 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/counter_cache.rb @@ -0,0 +1,218 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record Counter Cache + module CounterCache + extend ActiveSupport::Concern + + module ClassMethods + # Resets one or more counter caches to their correct value using an SQL + # count query. This is useful when adding new counter caches, or if the + # counter has been corrupted or modified directly by SQL. + # + # ==== Parameters + # + # * +id+ - The id of the object you wish to reset a counter on. + # * +counters+ - One or more association counters to reset. Association name or counter name can be given. + # * :touch - Touch timestamp columns when updating. + # Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to + # touch that column or an array of symbols to touch just those ones. + # + # ==== Examples + # + # # For the Post with id #1, reset the comments_count + # Post.reset_counters(1, :comments) + # + # # Like above, but also touch the +updated_at+ and/or +updated_on+ + # # attributes. + # Post.reset_counters(1, :comments, touch: true) + def reset_counters(id, *counters, touch: nil) + object = find(id) + + counters.each do |counter_association| + has_many_association = _reflect_on_association(counter_association) + unless has_many_association + has_many = reflect_on_all_associations(:has_many) + has_many_association = has_many.find { |association| association.counter_cache_column && association.counter_cache_column.to_sym == counter_association.to_sym } + counter_association = has_many_association.plural_name if has_many_association + end + raise ArgumentError, "'#{name}' has no association called '#{counter_association}'" unless has_many_association + + if has_many_association.is_a? ActiveRecord::Reflection::ThroughReflection + has_many_association = has_many_association.through_reflection + end + + foreign_key = has_many_association.foreign_key.to_s + child_class = has_many_association.klass + reflection = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? } + counter_name = reflection.counter_cache_column + + updates = { counter_name => object.send(counter_association).count(:all) } + + if touch + names = touch if touch != true + updates.merge!(touch_attributes_with_time(*names)) + end + + unscoped.where(primary_key => object.id).update_all(updates) + end + + true + end + + # A generic "counter updater" implementation, intended primarily to be + # used by #increment_counter and #decrement_counter, but which may also + # be useful on its own. It simply does a direct SQL update for the record + # with the given ID, altering the given hash of counters by the amount + # given by the corresponding value: + # + # ==== Parameters + # + # * +id+ - The id of the object you wish to update a counter on or an array of ids. + # * +counters+ - A Hash containing the names of the fields + # to update as keys and the amount to update the field by as values. + # * :touch option - Touch timestamp columns when updating. + # If attribute names are passed, they are updated along with updated_at/on + # attributes. + # + # ==== Examples + # + # # For the Post with id of 5, decrement the comment_count by 1, and + # # increment the action_count by 1 + # Post.update_counters 5, comment_count: -1, action_count: 1 + # # Executes the following SQL: + # # UPDATE posts + # # SET comment_count = COALESCE(comment_count, 0) - 1, + # # action_count = COALESCE(action_count, 0) + 1 + # # WHERE id = 5 + # + # # For the Posts with id of 10 and 15, increment the comment_count by 1 + # Post.update_counters [10, 15], comment_count: 1 + # # Executes the following SQL: + # # UPDATE posts + # # SET comment_count = COALESCE(comment_count, 0) + 1 + # # WHERE id IN (10, 15) + # + # # For the Posts with id of 10 and 15, increment the comment_count by 1 + # # and update the updated_at value for each counter. + # Post.update_counters [10, 15], comment_count: 1, touch: true + # # Executes the following SQL: + # # UPDATE posts + # # SET comment_count = COALESCE(comment_count, 0) + 1, + # # `updated_at` = '2016-10-13T09:59:23-05:00' + # # WHERE id IN (10, 15) + def update_counters(id, counters) + touch = counters.delete(:touch) + + updates = counters.map do |counter_name, value| + operator = value < 0 ? "-" : "+" + quoted_column = connection.quote_column_name(counter_name) + "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}" + end + + if touch + names = touch if touch != true + touch_updates = touch_attributes_with_time(*names) + updates << sanitize_sql_for_assignment(touch_updates) unless touch_updates.empty? + end + + if id.is_a?(Relation) && self == id.klass + relation = id + else + relation = unscoped.where!(primary_key => id) + end + + relation.update_all updates.join(", ") + end + + # Increment a numeric field by one, via a direct SQL update. + # + # This method is used primarily for maintaining counter_cache columns that are + # used to store aggregate values. For example, a +DiscussionBoard+ may cache + # posts_count and comments_count to avoid running an SQL query to calculate the + # number of posts and comments there are, each time it is displayed. + # + # ==== Parameters + # + # * +counter_name+ - The name of the field that should be incremented. + # * +id+ - The id of the object that should be incremented or an array of ids. + # * :touch - Touch timestamp columns when updating. + # Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to + # touch that column or an array of symbols to touch just those ones. + # + # ==== Examples + # + # # Increment the posts_count column for the record with an id of 5 + # DiscussionBoard.increment_counter(:posts_count, 5) + # + # # Increment the posts_count column for the record with an id of 5 + # # and update the updated_at value. + # DiscussionBoard.increment_counter(:posts_count, 5, touch: true) + def increment_counter(counter_name, id, touch: nil) + update_counters(id, counter_name => 1, touch: touch) + end + + # Decrement a numeric field by one, via a direct SQL update. + # + # This works the same as #increment_counter but reduces the column value by + # 1 instead of increasing it. + # + # ==== Parameters + # + # * +counter_name+ - The name of the field that should be decremented. + # * +id+ - The id of the object that should be decremented or an array of ids. + # * :touch - Touch timestamp columns when updating. + # Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to + # touch that column or an array of symbols to touch just those ones. + # + # ==== Examples + # + # # Decrement the posts_count column for the record with an id of 5 + # DiscussionBoard.decrement_counter(:posts_count, 5) + # + # # Decrement the posts_count column for the record with an id of 5 + # # and update the updated_at value. + # DiscussionBoard.decrement_counter(:posts_count, 5, touch: true) + def decrement_counter(counter_name, id, touch: nil) + update_counters(id, counter_name => -1, touch: touch) + end + end + + private + + def _create_record(*) + id = super + + each_counter_cached_associations do |association| + if send(association.reflection.name) + association.increment_counters + end + end + + id + end + + def destroy_row + affected_rows = super + + if affected_rows > 0 + each_counter_cached_associations do |association| + foreign_key = association.reflection.foreign_key.to_sym + unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key + if send(association.reflection.name) + association.decrement_counters + end + end + end + end + + affected_rows + end + + def each_counter_cached_associations + _reflections.each do |name, reflection| + yield association(name.to_sym) if reflection.belongs_to? && reflection.counter_cache_column + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/define_callbacks.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/define_callbacks.rb new file mode 100644 index 00000000..87ecd7ce --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/define_callbacks.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module ActiveRecord + # This module exists because ActiveRecord::AttributeMethods::Dirty needs to + # define callbacks, but continue to have its version of +save+ be the super + # method of ActiveRecord::Callbacks. This will be removed when the removal + # of deprecated code removes this need. + module DefineCallbacks + extend ActiveSupport::Concern + + module ClassMethods # :nodoc: + include ActiveModel::Callbacks + end + + included do + include ActiveModel::Validations::Callbacks + + define_model_callbacks :initialize, :find, :touch, only: :after + define_model_callbacks :save, :create, :update, :destroy + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/dynamic_matchers.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/dynamic_matchers.rb new file mode 100644 index 00000000..3bb8c6f4 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/dynamic_matchers.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +module ActiveRecord + module DynamicMatchers #:nodoc: + private + def respond_to_missing?(name, _) + if self == Base + super + else + match = Method.match(self, name) + match && match.valid? || super + end + end + + def method_missing(name, *arguments, &block) + match = Method.match(self, name) + + if match && match.valid? + match.define + send(name, *arguments, &block) + else + super + end + end + + class Method + @matchers = [] + + class << self + attr_reader :matchers + + def match(model, name) + klass = matchers.find { |k| k.pattern.match?(name) } + klass.new(model, name) if klass + end + + def pattern + @pattern ||= /\A#{prefix}_([_a-zA-Z]\w*)#{suffix}\Z/ + end + + def prefix + raise NotImplementedError + end + + def suffix + "" + end + end + + attr_reader :model, :name, :attribute_names + + def initialize(model, name) + @model = model + @name = name.to_s + @attribute_names = @name.match(self.class.pattern)[1].split("_and_") + @attribute_names.map! { |n| @model.attribute_aliases[n] || n } + end + + def valid? + attribute_names.all? { |name| model.columns_hash[name] || model.reflect_on_aggregation(name.to_sym) } + end + + def define + model.class_eval <<-CODE, __FILE__, __LINE__ + 1 + def self.#{name}(#{signature}) + #{body} + end + CODE + end + + private + + def body + "#{finder}(#{attributes_hash})" + end + + # The parameters in the signature may have reserved Ruby words, in order + # to prevent errors, we start each param name with `_`. + def signature + attribute_names.map { |name| "_#{name}" }.join(", ") + end + + # Given that the parameters starts with `_`, the finder needs to use the + # same parameter name. + def attributes_hash + "{" + attribute_names.map { |name| ":#{name} => _#{name}" }.join(",") + "}" + end + + def finder + raise NotImplementedError + end + end + + class FindBy < Method + Method.matchers << self + + def self.prefix + "find_by" + end + + def finder + "find_by" + end + end + + class FindByBang < Method + Method.matchers << self + + def self.prefix + "find_by" + end + + def self.suffix + "!" + end + + def finder + "find_by!" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/enum.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/enum.rb new file mode 100644 index 00000000..fcadf757 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/enum.rb @@ -0,0 +1,244 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/deep_dup" + +module ActiveRecord + # Declare an enum attribute where the values map to integers in the database, + # but can be queried by name. Example: + # + # class Conversation < ActiveRecord::Base + # enum status: [ :active, :archived ] + # end + # + # # conversation.update! status: 0 + # conversation.active! + # conversation.active? # => true + # conversation.status # => "active" + # + # # conversation.update! status: 1 + # conversation.archived! + # conversation.archived? # => true + # conversation.status # => "archived" + # + # # conversation.status = 1 + # conversation.status = "archived" + # + # conversation.status = nil + # conversation.status.nil? # => true + # conversation.status # => nil + # + # Scopes based on the allowed values of the enum field will be provided + # as well. With the above example: + # + # Conversation.active + # Conversation.archived + # + # Of course, you can also query them directly if the scopes don't fit your + # needs: + # + # Conversation.where(status: [:active, :archived]) + # Conversation.where.not(status: :active) + # + # You can set the default value from the database declaration, like: + # + # create_table :conversations do |t| + # t.column :status, :integer, default: 0 + # end + # + # Good practice is to let the first declared status be the default. + # + # Finally, it's also possible to explicitly map the relation between attribute and + # database integer with a hash: + # + # class Conversation < ActiveRecord::Base + # enum status: { active: 0, archived: 1 } + # end + # + # Note that when an array is used, the implicit mapping from the values to database + # integers is derived from the order the values appear in the array. In the example, + # :active is mapped to +0+ as it's the first element, and :archived + # is mapped to +1+. In general, the +i+-th element is mapped to i-1 in the + # database. + # + # Therefore, once a value is added to the enum array, its position in the array must + # be maintained, and new values should only be added to the end of the array. To + # remove unused values, the explicit hash syntax should be used. + # + # In rare circumstances you might need to access the mapping directly. + # The mappings are exposed through a class method with the pluralized attribute + # name, which return the mapping in a +HashWithIndifferentAccess+: + # + # Conversation.statuses[:active] # => 0 + # Conversation.statuses["archived"] # => 1 + # + # Use that class method when you need to know the ordinal value of an enum. + # For example, you can use that when manually building SQL strings: + # + # Conversation.where("status <> ?", Conversation.statuses[:archived]) + # + # You can use the +:_prefix+ or +:_suffix+ options when you need to define + # multiple enums with same values. If the passed value is +true+, the methods + # are prefixed/suffixed with the name of the enum. It is also possible to + # supply a custom value: + # + # class Conversation < ActiveRecord::Base + # enum status: [:active, :archived], _suffix: true + # enum comments_status: [:active, :inactive], _prefix: :comments + # end + # + # With the above example, the bang and predicate methods along with the + # associated scopes are now prefixed and/or suffixed accordingly: + # + # conversation.active_status! + # conversation.archived_status? # => false + # + # conversation.comments_inactive! + # conversation.comments_active? # => false + + module Enum + def self.extended(base) # :nodoc: + base.class_attribute(:defined_enums, instance_writer: false, default: {}) + end + + def inherited(base) # :nodoc: + base.defined_enums = defined_enums.deep_dup + super + end + + class EnumType < Type::Value # :nodoc: + delegate :type, to: :subtype + + def initialize(name, mapping, subtype) + @name = name + @mapping = mapping + @subtype = subtype + end + + def cast(value) + return if value.blank? + + if mapping.has_key?(value) + value.to_s + elsif mapping.has_value?(value) + mapping.key(value) + else + assert_valid_value(value) + end + end + + def deserialize(value) + return if value.nil? + mapping.key(subtype.deserialize(value)) + end + + def serialize(value) + mapping.fetch(value, value) + end + + def assert_valid_value(value) + unless value.blank? || mapping.has_key?(value) || mapping.has_value?(value) + raise ArgumentError, "'#{value}' is not a valid #{name}" + end + end + + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected + + attr_reader :name, :mapping, :subtype + end + + def enum(definitions) + klass = self + enum_prefix = definitions.delete(:_prefix) + enum_suffix = definitions.delete(:_suffix) + definitions.each do |name, values| + # statuses = { } + enum_values = ActiveSupport::HashWithIndifferentAccess.new + name = name.to_s + + # def self.statuses() statuses end + detect_enum_conflict!(name, name.pluralize, true) + singleton_class.send(:define_method, name.pluralize) { enum_values } + defined_enums[name] = enum_values + + detect_enum_conflict!(name, name) + detect_enum_conflict!(name, "#{name}=") + + attr = attribute_alias?(name) ? attribute_alias(name) : name + decorate_attribute_type(attr, :enum) do |subtype| + EnumType.new(attr, enum_values, subtype) + end + + _enum_methods_module.module_eval do + pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index + pairs.each do |label, value| + if enum_prefix == true + prefix = "#{name}_" + elsif enum_prefix + prefix = "#{enum_prefix}_" + end + if enum_suffix == true + suffix = "_#{name}" + elsif enum_suffix + suffix = "_#{enum_suffix}" + end + + value_method_name = "#{prefix}#{label}#{suffix}" + enum_values[label] = value + label = label.to_s + + # def active?() status == "active" end + klass.send(:detect_enum_conflict!, name, "#{value_method_name}?") + define_method("#{value_method_name}?") { self[attr] == label } + + # def active!() update!(status: 0) end + klass.send(:detect_enum_conflict!, name, "#{value_method_name}!") + define_method("#{value_method_name}!") { update!(attr => value) } + + # scope :active, -> { where(status: 0) } + klass.send(:detect_enum_conflict!, name, value_method_name, true) + klass.scope value_method_name, -> { where(attr => value) } + end + end + enum_values.freeze + end + end + + private + def _enum_methods_module + @_enum_methods_module ||= begin + mod = Module.new + include mod + mod + end + end + + ENUM_CONFLICT_MESSAGE = \ + "You tried to define an enum named \"%{enum}\" on the model \"%{klass}\", but " \ + "this will generate a %{type} method \"%{method}\", which is already defined " \ + "by %{source}." + + def detect_enum_conflict!(enum_name, method_name, klass_method = false) + if klass_method && dangerous_class_method?(method_name) + raise_conflict_error(enum_name, method_name, type: "class") + elsif klass_method && method_defined_within?(method_name, Relation) + raise_conflict_error(enum_name, method_name, type: "class", source: Relation.name) + elsif !klass_method && dangerous_attribute_method?(method_name) + raise_conflict_error(enum_name, method_name) + elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module) + raise_conflict_error(enum_name, method_name, source: "another enum") + end + end + + def raise_conflict_error(enum_name, method_name, type: "instance", source: "Active Record") + raise ArgumentError, ENUM_CONFLICT_MESSAGE % { + enum: enum_name, + klass: name, + type: type, + method: method_name, + source: source + } + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/errors.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/errors.rb new file mode 100644 index 00000000..6f4eb651 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/errors.rb @@ -0,0 +1,380 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record Errors + # + # Generic Active Record exception class. + class ActiveRecordError < StandardError + end + + # Raised when the single-table inheritance mechanism fails to locate the subclass + # (for example due to improper usage of column that + # {ActiveRecord::Base.inheritance_column}[rdoc-ref:ModelSchema::ClassMethods#inheritance_column] + # points to). + class SubclassNotFound < ActiveRecordError + end + + # Raised when an object assigned to an association has an incorrect type. + # + # class Ticket < ActiveRecord::Base + # has_many :patches + # end + # + # class Patch < ActiveRecord::Base + # belongs_to :ticket + # end + # + # # Comments are not patches, this assignment raises AssociationTypeMismatch. + # @ticket.patches << Comment.new(content: "Please attach tests to your patch.") + class AssociationTypeMismatch < ActiveRecordError + end + + # Raised when unserialized object's type mismatches one specified for serializable field. + class SerializationTypeMismatch < ActiveRecordError + end + + # Raised when adapter not specified on connection (or configuration file + # +config/database.yml+ misses adapter field). + class AdapterNotSpecified < ActiveRecordError + end + + # Raised when Active Record cannot find database adapter specified in + # +config/database.yml+ or programmatically. + class AdapterNotFound < ActiveRecordError + end + + # Raised when connection to the database could not been established (for example when + # {ActiveRecord::Base.connection=}[rdoc-ref:ConnectionHandling#connection] + # is given a +nil+ object). + class ConnectionNotEstablished < ActiveRecordError + end + + # Raised when Active Record cannot find a record by given id or set of ids. + class RecordNotFound < ActiveRecordError + attr_reader :model, :primary_key, :id + + def initialize(message = nil, model = nil, primary_key = nil, id = nil) + @primary_key = primary_key + @model = model + @id = id + + super(message) + end + end + + # Raised by {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] and + # {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!] + # methods when a record is invalid and can not be saved. + class RecordNotSaved < ActiveRecordError + attr_reader :record + + def initialize(message = nil, record = nil) + @record = record + super(message) + end + end + + # Raised by {ActiveRecord::Base#destroy!}[rdoc-ref:Persistence#destroy!] + # when a call to {#destroy}[rdoc-ref:Persistence#destroy!] + # would return false. + # + # begin + # complex_operation_that_internally_calls_destroy! + # rescue ActiveRecord::RecordNotDestroyed => invalid + # puts invalid.record.errors + # end + # + class RecordNotDestroyed < ActiveRecordError + attr_reader :record + + def initialize(message = nil, record = nil) + @record = record + super(message) + end + end + + # Superclass for all database execution errors. + # + # Wraps the underlying database error as +cause+. + class StatementInvalid < ActiveRecordError + def initialize(message = nil) + super(message || $!.try(:message)) + end + end + + # Defunct wrapper class kept for compatibility. + # StatementInvalid wraps the original exception now. + class WrappedDatabaseException < StatementInvalid + end + + # Raised when a record cannot be inserted or updated because it would violate a uniqueness constraint. + class RecordNotUnique < WrappedDatabaseException + end + + # Raised when a record cannot be inserted or updated because it references a non-existent record. + class InvalidForeignKey < WrappedDatabaseException + end + + # Raised when a foreign key constraint cannot be added because the column type does not match the referenced column type. + class MismatchedForeignKey < StatementInvalid + def initialize( + adapter = nil, + message: nil, + sql: nil, + binds: nil, + table: nil, + foreign_key: nil, + target_table: nil, + primary_key: nil, + primary_key_column: nil + ) + if table + type = primary_key_column.bigint? ? :bigint : primary_key_column.type + msg = <<-EOM.squish + Column `#{foreign_key}` on table `#{table}` does not match column `#{primary_key}` on `#{target_table}`, + which has type `#{primary_key_column.sql_type}`. + To resolve this issue, change the type of the `#{foreign_key}` column on `#{table}` to be :#{type}. + (For example `t.#{type} :#{foreign_key}`). + EOM + else + msg = <<-EOM.squish + There is a mismatch between the foreign key and primary key column types. + Verify that the foreign key column type and the primary key of the associated table match types. + EOM + end + if message + msg << "\nOriginal message: #{message}" + end + super(msg) + end + end + + # Raised when a record cannot be inserted or updated because it would violate a not null constraint. + class NotNullViolation < StatementInvalid + end + + # Raised when a record cannot be inserted or updated because a value too long for a column type. + class ValueTooLong < StatementInvalid + end + + # Raised when values that executed are out of range. + class RangeError < StatementInvalid + end + + # Raised when number of bind variables in statement given to +:condition+ key + # (for example, when using {ActiveRecord::Base.find}[rdoc-ref:FinderMethods#find] method) + # does not match number of expected values supplied. + # + # For example, when there are two placeholders with only one value supplied: + # + # Location.where("lat = ? AND lng = ?", 53.7362) + class PreparedStatementInvalid < ActiveRecordError + end + + # Raised when a given database does not exist. + class NoDatabaseError < StatementInvalid + end + + # Raised when PostgreSQL returns 'cached plan must not change result type' and + # we cannot retry gracefully (e.g. inside a transaction) + class PreparedStatementCacheExpired < StatementInvalid + end + + # Raised on attempt to save stale record. Record is stale when it's being saved in another query after + # instantiation, for example, when two users edit the same wiki page and one starts editing and saves + # the page before the other. + # + # Read more about optimistic locking in ActiveRecord::Locking module + # documentation. + class StaleObjectError < ActiveRecordError + attr_reader :record, :attempted_action + + def initialize(record = nil, attempted_action = nil) + if record && attempted_action + @record = record + @attempted_action = attempted_action + super("Attempted to #{attempted_action} a stale object: #{record.class.name}.") + else + super("Stale object error.") + end + end + end + + # Raised when association is being configured improperly or user tries to use + # offset and limit together with + # {ActiveRecord::Base.has_many}[rdoc-ref:Associations::ClassMethods#has_many] or + # {ActiveRecord::Base.has_and_belongs_to_many}[rdoc-ref:Associations::ClassMethods#has_and_belongs_to_many] + # associations. + class ConfigurationError < ActiveRecordError + end + + # Raised on attempt to update record that is instantiated as read only. + class ReadOnlyRecord < ActiveRecordError + end + + # {ActiveRecord::Base.transaction}[rdoc-ref:Transactions::ClassMethods#transaction] + # uses this exception to distinguish a deliberate rollback from other exceptional situations. + # Normally, raising an exception will cause the + # {.transaction}[rdoc-ref:Transactions::ClassMethods#transaction] method to rollback + # the database transaction *and* pass on the exception. But if you raise an + # ActiveRecord::Rollback exception, then the database transaction will be rolled back, + # without passing on the exception. + # + # For example, you could do this in your controller to rollback a transaction: + # + # class BooksController < ActionController::Base + # def create + # Book.transaction do + # book = Book.new(params[:book]) + # book.save! + # if today_is_friday? + # # The system must fail on Friday so that our support department + # # won't be out of job. We silently rollback this transaction + # # without telling the user. + # raise ActiveRecord::Rollback, "Call tech support!" + # end + # end + # # ActiveRecord::Rollback is the only exception that won't be passed on + # # by ActiveRecord::Base.transaction, so this line will still be reached + # # even on Friday. + # redirect_to root_url + # end + # end + class Rollback < ActiveRecordError + end + + # Raised when attribute has a name reserved by Active Record (when attribute + # has name of one of Active Record instance methods). + class DangerousAttributeError < ActiveRecordError + end + + # Raised when unknown attributes are supplied via mass assignment. + UnknownAttributeError = ActiveModel::UnknownAttributeError + + # Raised when an error occurred while doing a mass assignment to an attribute through the + # {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method. + # The exception has an +attribute+ property that is the name of the offending attribute. + class AttributeAssignmentError < ActiveRecordError + attr_reader :exception, :attribute + + def initialize(message = nil, exception = nil, attribute = nil) + super(message) + @exception = exception + @attribute = attribute + end + end + + # Raised when there are multiple errors while doing a mass assignment through the + # {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] + # method. The exception has an +errors+ property that contains an array of AttributeAssignmentError + # objects, each corresponding to the error while assigning to an attribute. + class MultiparameterAssignmentErrors < ActiveRecordError + attr_reader :errors + + def initialize(errors = nil) + @errors = errors + end + end + + # Raised when a primary key is needed, but not specified in the schema or model. + class UnknownPrimaryKey < ActiveRecordError + attr_reader :model + + def initialize(model = nil, description = nil) + if model + message = "Unknown primary key for table #{model.table_name} in model #{model}." + message += "\n#{description}" if description + @model = model + super(message) + else + super("Unknown primary key.") + end + end + end + + # Raised when a relation cannot be mutated because it's already loaded. + # + # class Task < ActiveRecord::Base + # end + # + # relation = Task.all + # relation.loaded? # => true + # + # # Methods which try to mutate a loaded relation fail. + # relation.where!(title: 'TODO') # => ActiveRecord::ImmutableRelation + # relation.limit!(5) # => ActiveRecord::ImmutableRelation + class ImmutableRelation < ActiveRecordError + end + + # TransactionIsolationError will be raised under the following conditions: + # + # * The adapter does not support setting the isolation level + # * You are joining an existing open transaction + # * You are creating a nested (savepoint) transaction + # + # The mysql2 and postgresql adapters support setting the transaction isolation level. + class TransactionIsolationError < ActiveRecordError + end + + # TransactionRollbackError will be raised when a transaction is rolled + # back by the database due to a serialization failure or a deadlock. + # + # See the following: + # + # * https://www.postgresql.org/docs/current/static/transaction-iso.html + # * https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html#error_er_lock_deadlock + class TransactionRollbackError < StatementInvalid + end + + # SerializationFailure will be raised when a transaction is rolled + # back by the database due to a serialization failure. + class SerializationFailure < TransactionRollbackError + end + + # Deadlocked will be raised when a transaction is rolled + # back by the database when a deadlock is encountered. + class Deadlocked < TransactionRollbackError + end + + # IrreversibleOrderError is raised when a relation's order is too complex for + # +reverse_order+ to automatically reverse. + class IrreversibleOrderError < ActiveRecordError + end + + # LockWaitTimeout will be raised when lock wait timeout exceeded. + class LockWaitTimeout < StatementInvalid + end + + # StatementTimeout will be raised when statement timeout exceeded. + class StatementTimeout < StatementInvalid + end + + # QueryCanceled will be raised when canceling statement due to user request. + class QueryCanceled < StatementInvalid + end + + # UnknownAttributeReference is raised when an unknown and potentially unsafe + # value is passed to a query method when allow_unsafe_raw_sql is set to + # :disabled. For example, passing a non column name value to a relation's + # #order method might cause this exception. + # + # When working around this exception, caution should be taken to avoid SQL + # injection vulnerabilities when passing user-provided values to query + # methods. Known-safe values can be passed to query methods by wrapping them + # in Arel.sql. + # + # For example, with allow_unsafe_raw_sql set to :disabled, the following + # code would raise this exception: + # + # Post.order("length(title)").first + # + # The desired result can be accomplished by wrapping the known-safe string + # in Arel.sql: + # + # Post.order(Arel.sql("length(title)")).first + # + # Again, such a workaround should *not* be used when passing user-provided + # values, such as request parameters or model attributes to query methods. + class UnknownAttributeReference < ActiveRecordError + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/explain.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/explain.rb new file mode 100644 index 00000000..7ccb9388 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/explain.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "active_record/explain_registry" + +module ActiveRecord + module Explain + # Executes the block with the collect flag enabled. Queries are collected + # asynchronously by the subscriber and returned. + def collecting_queries_for_explain # :nodoc: + ExplainRegistry.collect = true + yield + ExplainRegistry.queries + ensure + ExplainRegistry.reset + end + + # Makes the adapter execute EXPLAIN for the tuples of queries and bindings. + # Returns a formatted string ready to be logged. + def exec_explain(queries) # :nodoc: + str = queries.map do |sql, binds| + msg = "EXPLAIN for: #{sql}".dup + unless binds.empty? + msg << " " + msg << binds.map { |attr| render_bind(attr) }.inspect + end + msg << "\n" + msg << connection.explain(sql, binds) + end.join("\n") + + # Overriding inspect to be more human readable, especially in the console. + def str.inspect + self + end + + str + end + + private + + def render_bind(attr) + value = if attr.type.binary? && attr.value + "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>" + else + connection.type_cast(attr.value_for_database) + end + + [attr.name, value] + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/explain_registry.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/explain_registry.rb new file mode 100644 index 00000000..7fd07894 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/explain_registry.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require "active_support/per_thread_registry" + +module ActiveRecord + # This is a thread locals registry for EXPLAIN. For example + # + # ActiveRecord::ExplainRegistry.queries + # + # returns the collected queries local to the current thread. + # + # See the documentation of ActiveSupport::PerThreadRegistry + # for further details. + class ExplainRegistry # :nodoc: + extend ActiveSupport::PerThreadRegistry + + attr_accessor :queries, :collect + + def initialize + reset + end + + def collect? + @collect + end + + def reset + @collect = false + @queries = [] + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/explain_subscriber.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/explain_subscriber.rb new file mode 100644 index 00000000..a86217ab --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/explain_subscriber.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "active_support/notifications" +require "active_record/explain_registry" + +module ActiveRecord + class ExplainSubscriber # :nodoc: + def start(name, id, payload) + # unused + end + + def finish(name, id, payload) + if ExplainRegistry.collect? && !ignore_payload?(payload) + ExplainRegistry.queries << payload.values_at(:sql, :binds) + end + end + + # SCHEMA queries cannot be EXPLAINed, also we do not want to run EXPLAIN on + # our own EXPLAINs no matter how loopingly beautiful that would be. + # + # On the other hand, we want to monitor the performance of our real database + # queries, not the performance of the access to the query cache. + IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN) + EXPLAINED_SQLS = /\A\s*(with|select|update|delete|insert)\b/i + def ignore_payload?(payload) + payload[:exception] || + payload[:cached] || + IGNORED_PAYLOADS.include?(payload[:name]) || + payload[:sql] !~ EXPLAINED_SQLS + end + + ActiveSupport::Notifications.subscribe("sql.active_record", new) + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/fixture_set/file.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/fixture_set/file.rb new file mode 100644 index 00000000..f1ea0e02 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/fixture_set/file.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require "erb" +require "yaml" + +module ActiveRecord + class FixtureSet + class File # :nodoc: + include Enumerable + + ## + # Open a fixture file named +file+. When called with a block, the block + # is called with the filehandle and the filehandle is automatically closed + # when the block finishes. + def self.open(file) + x = new file + block_given? ? yield(x) : x + end + + def initialize(file) + @file = file + end + + def each(&block) + rows.each(&block) + end + + def model_class + config_row["model_class"] + end + + private + def rows + @rows ||= raw_rows.reject { |fixture_name, _| fixture_name == "_fixture" } + end + + def config_row + @config_row ||= begin + row = raw_rows.find { |fixture_name, _| fixture_name == "_fixture" } + if row + row.last + else + { 'model_class': nil } + end + end + end + + def raw_rows + @raw_rows ||= begin + data = YAML.load(render(IO.read(@file))) + data ? validate(data).to_a : [] + rescue ArgumentError, Psych::SyntaxError => error + raise Fixture::FormatError, "a YAML error occurred parsing #{@file}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}", error.backtrace + end + end + + def prepare_erb(content) + erb = ERB.new(content) + erb.filename = @file + erb + end + + def render(content) + context = ActiveRecord::FixtureSet::RenderContext.create_subclass.new + prepare_erb(content).result(context.get_binding) + end + + # Validate our unmarshalled data. + def validate(data) + unless Hash === data || YAML::Omap === data + raise Fixture::FormatError, "fixture is not a hash: #{@file}" + end + + invalid = data.reject { |_, row| Hash === row } + if invalid.any? + raise Fixture::FormatError, "fixture key is not a hash: #{@file}, keys: #{invalid.keys.inspect}" + end + data + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/fixtures.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/fixtures.rb new file mode 100644 index 00000000..d9a75d9a --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/fixtures.rb @@ -0,0 +1,1065 @@ +# frozen_string_literal: true + +require "erb" +require "yaml" +require "zlib" +require "set" +require "active_support/dependencies" +require "active_support/core_ext/digest/uuid" +require "active_record/fixture_set/file" +require "active_record/errors" + +module ActiveRecord + class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc: + end + + # \Fixtures are a way of organizing data that you want to test against; in short, sample data. + # + # They are stored in YAML files, one file per model, which are placed in the directory + # appointed by ActiveSupport::TestCase.fixture_path=(path) (this is automatically + # configured for Rails, so you can just put your files in /test/fixtures/). + # The fixture file ends with the +.yml+ file extension, for example: + # /test/fixtures/web_sites.yml). + # + # The format of a fixture file looks like this: + # + # rubyonrails: + # id: 1 + # name: Ruby on Rails + # url: http://www.rubyonrails.org + # + # google: + # id: 2 + # name: Google + # url: http://www.google.com + # + # This fixture file includes two fixtures. Each YAML fixture (ie. record) is given a name and + # is followed by an indented list of key/value pairs in the "key: value" format. Records are + # separated by a blank line for your viewing pleasure. + # + # Note: Fixtures are unordered. If you want ordered fixtures, use the omap YAML type. + # See http://yaml.org/type/omap.html + # for the specification. You will need ordered fixtures when you have foreign key constraints + # on keys in the same table. This is commonly needed for tree structures. Example: + # + # --- !omap + # - parent: + # id: 1 + # parent_id: NULL + # title: Parent + # - child: + # id: 2 + # parent_id: 1 + # title: Child + # + # = Using Fixtures in Test Cases + # + # Since fixtures are a testing construct, we use them in our unit and functional tests. There + # are two ways to use the fixtures, but first let's take a look at a sample unit test: + # + # require 'test_helper' + # + # class WebSiteTest < ActiveSupport::TestCase + # test "web_site_count" do + # assert_equal 2, WebSite.count + # end + # end + # + # By default, +test_helper.rb+ will load all of your fixtures into your test + # database, so this test will succeed. + # + # The testing environment will automatically load all the fixtures into the database before each + # test. To ensure consistent data, the environment deletes the fixtures before running the load. + # + # In addition to being available in the database, the fixture's data may also be accessed by + # using a special dynamic method, which has the same name as the model. + # + # Passing in a fixture name to this dynamic method returns the fixture matching this name: + # + # test "find one" do + # assert_equal "Ruby on Rails", web_sites(:rubyonrails).name + # end + # + # Passing in multiple fixture names returns all fixtures matching these names: + # + # test "find all by name" do + # assert_equal 2, web_sites(:rubyonrails, :google).length + # end + # + # Passing in no arguments returns all fixtures: + # + # test "find all" do + # assert_equal 2, web_sites.length + # end + # + # Passing in any fixture name that does not exist will raise StandardError: + # + # test "find by name that does not exist" do + # assert_raise(StandardError) { web_sites(:reddit) } + # end + # + # Alternatively, you may enable auto-instantiation of the fixture data. For instance, take the + # following tests: + # + # test "find_alt_method_1" do + # assert_equal "Ruby on Rails", @web_sites['rubyonrails']['name'] + # end + # + # test "find_alt_method_2" do + # assert_equal "Ruby on Rails", @rubyonrails.name + # end + # + # In order to use these methods to access fixtured data within your test cases, you must specify one of the + # following in your ActiveSupport::TestCase-derived class: + # + # - to fully enable instantiated fixtures (enable alternate methods #1 and #2 above) + # self.use_instantiated_fixtures = true + # + # - create only the hash for the fixtures, do not 'find' each instance (enable alternate method #1 only) + # self.use_instantiated_fixtures = :no_instances + # + # Using either of these alternate methods incurs a performance hit, as the fixtured data must be fully + # traversed in the database to create the fixture hash and/or instance variables. This is expensive for + # large sets of fixtured data. + # + # = Dynamic fixtures with ERB + # + # Sometimes you don't care about the content of the fixtures as much as you care about the volume. + # In these cases, you can mix ERB in with your YAML fixtures to create a bunch of fixtures for load + # testing, like: + # + # <% 1.upto(1000) do |i| %> + # fix_<%= i %>: + # id: <%= i %> + # name: guy_<%= i %> + # <% end %> + # + # This will create 1000 very simple fixtures. + # + # Using ERB, you can also inject dynamic values into your fixtures with inserts like + # <%= Date.today.strftime("%Y-%m-%d") %>. + # This is however a feature to be used with some caution. The point of fixtures are that they're + # stable units of predictable sample data. If you feel that you need to inject dynamic values, then + # perhaps you should reexamine whether your application is properly testable. Hence, dynamic values + # in fixtures are to be considered a code smell. + # + # Helper methods defined in a fixture will not be available in other fixtures, to prevent against + # unwanted inter-test dependencies. Methods used by multiple fixtures should be defined in a module + # that is included in ActiveRecord::FixtureSet.context_class. + # + # - define a helper method in test_helper.rb + # module FixtureFileHelpers + # def file_sha(path) + # Digest::SHA2.hexdigest(File.read(Rails.root.join('test/fixtures', path))) + # end + # end + # ActiveRecord::FixtureSet.context_class.include FixtureFileHelpers + # + # - use the helper method in a fixture + # photo: + # name: kitten.png + # sha: <%= file_sha 'files/kitten.png' %> + # + # = Transactional Tests + # + # Test cases can use begin+rollback to isolate their changes to the database instead of having to + # delete+insert for every test case. + # + # class FooTest < ActiveSupport::TestCase + # self.use_transactional_tests = true + # + # test "godzilla" do + # assert_not_empty Foo.all + # Foo.destroy_all + # assert_empty Foo.all + # end + # + # test "godzilla aftermath" do + # assert_not_empty Foo.all + # end + # end + # + # If you preload your test database with all fixture data (probably in the rake task) and use + # transactional tests, then you may omit all fixtures declarations in your test cases since + # all the data's already there and every case rolls back its changes. + # + # In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to + # true. This will provide access to fixture data for every table that has been loaded through + # fixtures (depending on the value of +use_instantiated_fixtures+). + # + # When *not* to use transactional tests: + # + # 1. You're testing whether a transaction works correctly. Nested transactions don't commit until + # all parent transactions commit, particularly, the fixtures transaction which is begun in setup + # and rolled back in teardown. Thus, you won't be able to verify + # the results of your transaction until Active Record supports nested transactions or savepoints (in progress). + # 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM. + # Use InnoDB, MaxDB, or NDB instead. + # + # = Advanced Fixtures + # + # Fixtures that don't specify an ID get some extra features: + # + # * Stable, autogenerated IDs + # * Label references for associations (belongs_to, has_one, has_many) + # * HABTM associations as inline lists + # + # There are some more advanced features available even if the id is specified: + # + # * Autofilled timestamp columns + # * Fixture label interpolation + # * Support for YAML defaults + # + # == Stable, Autogenerated IDs + # + # Here, have a monkey fixture: + # + # george: + # id: 1 + # name: George the Monkey + # + # reginald: + # id: 2 + # name: Reginald the Pirate + # + # Each of these fixtures has two unique identifiers: one for the database + # and one for the humans. Why don't we generate the primary key instead? + # Hashing each fixture's label yields a consistent ID: + # + # george: # generated id: 503576764 + # name: George the Monkey + # + # reginald: # generated id: 324201669 + # name: Reginald the Pirate + # + # Active Record looks at the fixture's model class, discovers the correct + # primary key, and generates it right before inserting the fixture + # into the database. + # + # The generated ID for a given label is constant, so we can discover + # any fixture's ID without loading anything, as long as we know the label. + # + # == Label references for associations (belongs_to, has_one, has_many) + # + # Specifying foreign keys in fixtures can be very fragile, not to + # mention difficult to read. Since Active Record can figure out the ID of + # any fixture from its label, you can specify FK's by label instead of ID. + # + # === belongs_to + # + # Let's break out some more monkeys and pirates. + # + # ### in pirates.yml + # + # reginald: + # id: 1 + # name: Reginald the Pirate + # monkey_id: 1 + # + # ### in monkeys.yml + # + # george: + # id: 1 + # name: George the Monkey + # pirate_id: 1 + # + # Add a few more monkeys and pirates and break this into multiple files, + # and it gets pretty hard to keep track of what's going on. Let's + # use labels instead of IDs: + # + # ### in pirates.yml + # + # reginald: + # name: Reginald the Pirate + # monkey: george + # + # ### in monkeys.yml + # + # george: + # name: George the Monkey + # pirate: reginald + # + # Pow! All is made clear. Active Record reflects on the fixture's model class, + # finds all the +belongs_to+ associations, and allows you to specify + # a target *label* for the *association* (monkey: george) rather than + # a target *id* for the *FK* (monkey_id: 1). + # + # ==== Polymorphic belongs_to + # + # Supporting polymorphic relationships is a little bit more complicated, since + # Active Record needs to know what type your association is pointing at. Something + # like this should look familiar: + # + # ### in fruit.rb + # + # belongs_to :eater, polymorphic: true + # + # ### in fruits.yml + # + # apple: + # id: 1 + # name: apple + # eater_id: 1 + # eater_type: Monkey + # + # Can we do better? You bet! + # + # apple: + # eater: george (Monkey) + # + # Just provide the polymorphic target type and Active Record will take care of the rest. + # + # === has_and_belongs_to_many + # + # Time to give our monkey some fruit. + # + # ### in monkeys.yml + # + # george: + # id: 1 + # name: George the Monkey + # + # ### in fruits.yml + # + # apple: + # id: 1 + # name: apple + # + # orange: + # id: 2 + # name: orange + # + # grape: + # id: 3 + # name: grape + # + # ### in fruits_monkeys.yml + # + # apple_george: + # fruit_id: 1 + # monkey_id: 1 + # + # orange_george: + # fruit_id: 2 + # monkey_id: 1 + # + # grape_george: + # fruit_id: 3 + # monkey_id: 1 + # + # Let's make the HABTM fixture go away. + # + # ### in monkeys.yml + # + # george: + # id: 1 + # name: George the Monkey + # fruits: apple, orange, grape + # + # ### in fruits.yml + # + # apple: + # name: apple + # + # orange: + # name: orange + # + # grape: + # name: grape + # + # Zap! No more fruits_monkeys.yml file. We've specified the list of fruits + # on George's fixture, but we could've just as easily specified a list + # of monkeys on each fruit. As with +belongs_to+, Active Record reflects on + # the fixture's model class and discovers the +has_and_belongs_to_many+ + # associations. + # + # == Autofilled Timestamp Columns + # + # If your table/model specifies any of Active Record's + # standard timestamp columns (+created_at+, +created_on+, +updated_at+, +updated_on+), + # they will automatically be set to Time.now. + # + # If you've set specific values, they'll be left alone. + # + # == Fixture label interpolation + # + # The label of the current fixture is always available as a column value: + # + # geeksomnia: + # name: Geeksomnia's Account + # subdomain: $LABEL + # email: $LABEL@email.com + # + # Also, sometimes (like when porting older join table fixtures) you'll need + # to be able to get a hold of the identifier for a given label. ERB + # to the rescue: + # + # george_reginald: + # monkey_id: <%= ActiveRecord::FixtureSet.identify(:reginald) %> + # pirate_id: <%= ActiveRecord::FixtureSet.identify(:george) %> + # + # == Support for YAML defaults + # + # You can set and reuse defaults in your fixtures YAML file. + # This is the same technique used in the +database.yml+ file to specify + # defaults: + # + # DEFAULTS: &DEFAULTS + # created_on: <%= 3.weeks.ago.to_s(:db) %> + # + # first: + # name: Smurf + # <<: *DEFAULTS + # + # second: + # name: Fraggle + # <<: *DEFAULTS + # + # Any fixture labeled "DEFAULTS" is safely ignored. + # + # == Configure the fixture model class + # + # It's possible to set the fixture's model class directly in the YAML file. + # This is helpful when fixtures are loaded outside tests and + # +set_fixture_class+ is not available (e.g. + # when running rails db:fixtures:load). + # + # _fixture: + # model_class: User + # david: + # name: David + # + # Any fixtures labeled "_fixture" are safely ignored. + class FixtureSet + #-- + # An instance of FixtureSet is normally stored in a single YAML file and + # possibly in a folder with the same name. + #++ + + MAX_ID = 2**30 - 1 + + @@all_cached_fixtures = Hash.new { |h, k| h[k] = {} } + + def self.default_fixture_model_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc: + config.pluralize_table_names ? + fixture_set_name.singularize.camelize : + fixture_set_name.camelize + end + + def self.default_fixture_table_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc: + "#{ config.table_name_prefix }"\ + "#{ fixture_set_name.tr('/', '_') }"\ + "#{ config.table_name_suffix }".to_sym + end + + def self.reset_cache + @@all_cached_fixtures.clear + end + + def self.cache_for_connection(connection) + @@all_cached_fixtures[connection] + end + + def self.fixture_is_cached?(connection, table_name) + cache_for_connection(connection)[table_name] + end + + def self.cached_fixtures(connection, keys_to_fetch = nil) + if keys_to_fetch + cache_for_connection(connection).values_at(*keys_to_fetch) + else + cache_for_connection(connection).values + end + end + + def self.cache_fixtures(connection, fixtures_map) + cache_for_connection(connection).update(fixtures_map) + end + + def self.instantiate_fixtures(object, fixture_set, load_instances = true) + if load_instances + fixture_set.each do |fixture_name, fixture| + begin + object.instance_variable_set "@#{fixture_name}", fixture.find + rescue FixtureClassNotFound + nil + end + end + end + end + + def self.instantiate_all_loaded_fixtures(object, load_instances = true) + all_loaded_fixtures.each_value do |fixture_set| + instantiate_fixtures(object, fixture_set, load_instances) + end + end + + cattr_accessor :all_loaded_fixtures, default: {} + + class ClassCache + def initialize(class_names, config) + @class_names = class_names.stringify_keys + @config = config + + # Remove string values that aren't constants or subclasses of AR + @class_names.delete_if { |klass_name, klass| !insert_class(@class_names, klass_name, klass) } + end + + def [](fs_name) + @class_names.fetch(fs_name) { + klass = default_fixture_model(fs_name, @config).safe_constantize + insert_class(@class_names, fs_name, klass) + } + end + + private + + def insert_class(class_names, name, klass) + # We only want to deal with AR objects. + if klass && klass < ActiveRecord::Base + class_names[name] = klass + else + class_names[name] = nil + end + end + + def default_fixture_model(fs_name, config) + ActiveRecord::FixtureSet.default_fixture_model_name(fs_name, config) + end + end + + def self.create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base) + fixture_set_names = Array(fixture_set_names).map(&:to_s) + class_names = ClassCache.new class_names, config + + # FIXME: Apparently JK uses this. + connection = block_given? ? yield : ActiveRecord::Base.connection + + files_to_read = fixture_set_names.reject { |fs_name| + fixture_is_cached?(connection, fs_name) + } + + unless files_to_read.empty? + fixtures_map = {} + + fixture_sets = files_to_read.map do |fs_name| + klass = class_names[fs_name] + conn = klass ? klass.connection : connection + fixtures_map[fs_name] = new( # ActiveRecord::FixtureSet.new + conn, + fs_name, + klass, + ::File.join(fixtures_directory, fs_name)) + end + + update_all_loaded_fixtures fixtures_map + fixture_sets_by_connection = fixture_sets.group_by { |fs| fs.model_class ? fs.model_class.connection : connection } + + fixture_sets_by_connection.each do |conn, set| + table_rows_for_connection = Hash.new { |h, k| h[k] = [] } + + set.each do |fs| + fs.table_rows.each do |table, rows| + table_rows_for_connection[table].unshift(*rows) + end + end + conn.insert_fixtures_set(table_rows_for_connection, table_rows_for_connection.keys) + + # Cap primary key sequences to max(pk). + if conn.respond_to?(:reset_pk_sequence!) + set.each { |fs| conn.reset_pk_sequence!(fs.table_name) } + end + end + + cache_fixtures(connection, fixtures_map) + end + cached_fixtures(connection, fixture_set_names) + end + + # Returns a consistent, platform-independent identifier for +label+. + # Integer identifiers are values less than 2^30. UUIDs are RFC 4122 version 5 SHA-1 hashes. + def self.identify(label, column_type = :integer) + if column_type == :uuid + Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, label.to_s) + else + Zlib.crc32(label.to_s) % MAX_ID + end + end + + # Superclass for the evaluation contexts used by ERB fixtures. + def self.context_class + @context_class ||= Class.new + end + + def self.update_all_loaded_fixtures(fixtures_map) # :nodoc: + all_loaded_fixtures.update(fixtures_map) + end + + attr_reader :table_name, :name, :fixtures, :model_class, :config + + def initialize(connection, name, class_name, path, config = ActiveRecord::Base) + @name = name + @path = path + @config = config + + self.model_class = class_name + + @fixtures = read_fixture_files(path) + + @connection = connection + + @table_name = (model_class.respond_to?(:table_name) ? + model_class.table_name : + self.class.default_fixture_table_name(name, config)) + end + + def [](x) + fixtures[x] + end + + def []=(k, v) + fixtures[k] = v + end + + def each(&block) + fixtures.each(&block) + end + + def size + fixtures.size + end + + # Returns a hash of rows to be inserted. The key is the table, the value is + # a list of rows to insert to that table. + def table_rows + now = config.default_timezone == :utc ? Time.now.utc : Time.now + + # allow a standard key to be used for doing defaults in YAML + fixtures.delete("DEFAULTS") + + # track any join tables we need to insert later + rows = Hash.new { |h, table| h[table] = [] } + + rows[table_name] = fixtures.map do |label, fixture| + row = fixture.to_hash + + if model_class + # fill in timestamp columns if they aren't specified and the model is set to record_timestamps + if model_class.record_timestamps + timestamp_column_names.each do |c_name| + row[c_name] = now unless row.key?(c_name) + end + end + + # interpolate the fixture label + row.each do |key, value| + row[key] = value.gsub("$LABEL", label.to_s) if value.is_a?(String) + end + + # generate a primary key if necessary + if has_primary_key_column? && !row.include?(primary_key_name) + row[primary_key_name] = ActiveRecord::FixtureSet.identify(label, primary_key_type) + end + + # Resolve enums + model_class.defined_enums.each do |name, values| + if row.include?(name) + row[name] = values.fetch(row[name], row[name]) + end + end + + # If STI is used, find the correct subclass for association reflection + reflection_class = + if row.include?(inheritance_column_name) + row[inheritance_column_name].constantize rescue model_class + else + model_class + end + + reflection_class._reflections.each_value do |association| + case association.macro + when :belongs_to + # Do not replace association name with association foreign key if they are named the same + fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s + + if association.name.to_s != fk_name && value = row.delete(association.name.to_s) + if association.polymorphic? && value.sub!(/\s*\(([^\)]*)\)\s*$/, "") + # support polymorphic belongs_to as "label (Type)" + row[association.foreign_type] = $1 + end + + fk_type = reflection_class.type_for_attribute(fk_name).type + row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type) + end + when :has_many + if association.options[:through] + add_join_records(rows, row, HasManyThroughProxy.new(association)) + end + end + end + end + + row + end + rows + end + + class ReflectionProxy # :nodoc: + def initialize(association) + @association = association + end + + def join_table + @association.join_table + end + + def name + @association.name + end + + def primary_key_type + @association.klass.type_for_attribute(@association.klass.primary_key).type + end + end + + class HasManyThroughProxy < ReflectionProxy # :nodoc: + def rhs_key + @association.foreign_key + end + + def lhs_key + @association.through_reflection.foreign_key + end + + def join_table + @association.through_reflection.table_name + end + end + + private + def primary_key_name + @primary_key_name ||= model_class && model_class.primary_key + end + + def primary_key_type + @primary_key_type ||= model_class && model_class.type_for_attribute(model_class.primary_key).type + end + + def add_join_records(rows, row, association) + # This is the case when the join table has no fixtures file + if (targets = row.delete(association.name.to_s)) + table_name = association.join_table + column_type = association.primary_key_type + lhs_key = association.lhs_key + rhs_key = association.rhs_key + + targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/) + rows[table_name].concat targets.map { |target| + { lhs_key => row[primary_key_name], + rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) } + } + end + end + + def has_primary_key_column? + @has_primary_key_column ||= primary_key_name && + model_class.columns.any? { |c| c.name == primary_key_name } + end + + def timestamp_column_names + @timestamp_column_names ||= + %w(created_at created_on updated_at updated_on) & column_names + end + + def inheritance_column_name + @inheritance_column_name ||= model_class && model_class.inheritance_column + end + + def column_names + @column_names ||= @connection.columns(@table_name).collect(&:name) + end + + def model_class=(class_name) + if class_name.is_a?(Class) # TODO: Should be an AR::Base type class, or any? + @model_class = class_name + else + @model_class = class_name.safe_constantize if class_name + end + end + + # Loads the fixtures from the YAML file at +path+. + # If the file sets the +model_class+ and current instance value is not set, + # it uses the file value. + def read_fixture_files(path) + yaml_files = Dir["#{path}/{**,*}/*.yml"].select { |f| + ::File.file?(f) + } + [yaml_file_path(path)] + + yaml_files.each_with_object({}) do |file, fixtures| + FixtureSet::File.open(file) do |fh| + self.model_class ||= fh.model_class if fh.model_class + fh.each do |fixture_name, row| + fixtures[fixture_name] = ActiveRecord::Fixture.new(row, model_class) + end + end + end + end + + def yaml_file_path(path) + "#{path}.yml" + end + end + + class Fixture #:nodoc: + include Enumerable + + class FixtureError < StandardError #:nodoc: + end + + class FormatError < FixtureError #:nodoc: + end + + attr_reader :model_class, :fixture + + def initialize(fixture, model_class) + @fixture = fixture + @model_class = model_class + end + + def class_name + model_class.name if model_class + end + + def each + fixture.each { |item| yield item } + end + + def [](key) + fixture[key] + end + + alias :to_hash :fixture + + def find + if model_class + model_class.unscoped do + model_class.find(fixture[model_class.primary_key]) + end + else + raise FixtureClassNotFound, "No class attached to find." + end + end + end +end + +module ActiveRecord + module TestFixtures + extend ActiveSupport::Concern + + def before_setup # :nodoc: + setup_fixtures + super + end + + def after_teardown # :nodoc: + super + teardown_fixtures + end + + included do + class_attribute :fixture_path, instance_writer: false + class_attribute :fixture_table_names, default: [] + class_attribute :fixture_class_names, default: {} + class_attribute :use_transactional_tests, default: true + class_attribute :use_instantiated_fixtures, default: false # true, false, or :no_instances + class_attribute :pre_loaded_fixtures, default: false + class_attribute :config, default: ActiveRecord::Base + end + + module ClassMethods + # Sets the model class for a fixture when the class name cannot be inferred from the fixture name. + # + # Examples: + # + # set_fixture_class some_fixture: SomeModel, + # 'namespaced/fixture' => Another::Model + # + # The keys must be the fixture names, that coincide with the short paths to the fixture files. + def set_fixture_class(class_names = {}) + self.fixture_class_names = fixture_class_names.merge(class_names.stringify_keys) + end + + def fixtures(*fixture_set_names) + if fixture_set_names.first == :all + fixture_set_names = Dir["#{fixture_path}/{**,*}/*.{yml}"].uniq + fixture_set_names.map! { |f| f[(fixture_path.to_s.size + 1)..-5] } + else + fixture_set_names = fixture_set_names.flatten.map(&:to_s) + end + + self.fixture_table_names |= fixture_set_names + setup_fixture_accessors(fixture_set_names) + end + + def setup_fixture_accessors(fixture_set_names = nil) + fixture_set_names = Array(fixture_set_names || fixture_table_names) + methods = Module.new do + fixture_set_names.each do |fs_name| + fs_name = fs_name.to_s + accessor_name = fs_name.tr("/", "_").to_sym + + define_method(accessor_name) do |*fixture_names| + force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload + return_single_record = fixture_names.size == 1 + fixture_names = @loaded_fixtures[fs_name].fixtures.keys if fixture_names.empty? + + @fixture_cache[fs_name] ||= {} + + instances = fixture_names.map do |f_name| + f_name = f_name.to_s if f_name.is_a?(Symbol) + @fixture_cache[fs_name].delete(f_name) if force_reload + + if @loaded_fixtures[fs_name][f_name] + @fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find + else + raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'" + end + end + + return_single_record ? instances.first : instances + end + private accessor_name + end + end + include methods + end + + def uses_transaction(*methods) + @uses_transaction = [] unless defined?(@uses_transaction) + @uses_transaction.concat methods.map(&:to_s) + end + + def uses_transaction?(method) + @uses_transaction = [] unless defined?(@uses_transaction) + @uses_transaction.include?(method.to_s) + end + end + + def run_in_transaction? + use_transactional_tests && + !self.class.uses_transaction?(method_name) + end + + def setup_fixtures(config = ActiveRecord::Base) + if pre_loaded_fixtures && !use_transactional_tests + raise RuntimeError, "pre_loaded_fixtures requires use_transactional_tests" + end + + @fixture_cache = {} + @fixture_connections = [] + @@already_loaded_fixtures ||= {} + @connection_subscriber = nil + + # Load fixtures once and begin transaction. + if run_in_transaction? + if @@already_loaded_fixtures[self.class] + @loaded_fixtures = @@already_loaded_fixtures[self.class] + else + @loaded_fixtures = load_fixtures(config) + @@already_loaded_fixtures[self.class] = @loaded_fixtures + end + + # Begin transactions for connections already established + @fixture_connections = enlist_fixture_connections + @fixture_connections.each do |connection| + connection.begin_transaction joinable: false + connection.pool.lock_thread = true + end + + # When connections are established in the future, begin a transaction too + @connection_subscriber = ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload| + spec_name = payload[:spec_name] if payload.key?(:spec_name) + + if spec_name + begin + connection = ActiveRecord::Base.connection_handler.retrieve_connection(spec_name) + rescue ConnectionNotEstablished + connection = nil + end + + if connection && !@fixture_connections.include?(connection) + connection.begin_transaction joinable: false + connection.pool.lock_thread = true + @fixture_connections << connection + end + end + end + + # Load fixtures for every test. + else + ActiveRecord::FixtureSet.reset_cache + @@already_loaded_fixtures[self.class] = nil + @loaded_fixtures = load_fixtures(config) + end + + # Instantiate fixtures for every test if requested. + instantiate_fixtures if use_instantiated_fixtures + end + + def teardown_fixtures + # Rollback changes if a transaction is active. + if run_in_transaction? + ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber + @fixture_connections.each do |connection| + connection.rollback_transaction if connection.transaction_open? + connection.pool.lock_thread = false + end + @fixture_connections.clear + else + ActiveRecord::FixtureSet.reset_cache + end + + ActiveRecord::Base.clear_active_connections! + end + + def enlist_fixture_connections + ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection) + end + + private + def load_fixtures(config) + fixtures = ActiveRecord::FixtureSet.create_fixtures(fixture_path, fixture_table_names, fixture_class_names, config) + Hash[fixtures.map { |f| [f.name, f] }] + end + + def instantiate_fixtures + if pre_loaded_fixtures + raise RuntimeError, "Load fixtures before instantiating them." if ActiveRecord::FixtureSet.all_loaded_fixtures.empty? + ActiveRecord::FixtureSet.instantiate_all_loaded_fixtures(self, load_instances?) + else + raise RuntimeError, "Load fixtures before instantiating them." if @loaded_fixtures.nil? + @loaded_fixtures.each_value do |fixture_set| + ActiveRecord::FixtureSet.instantiate_fixtures(self, fixture_set, load_instances?) + end + end + end + + def load_instances? + use_instantiated_fixtures != :no_instances + end + end +end + +class ActiveRecord::FixtureSet::RenderContext # :nodoc: + def self.create_subclass + Class.new ActiveRecord::FixtureSet.context_class do + def get_binding + binding() + end + + def binary(path) + %(!!binary "#{Base64.strict_encode64(File.read(path))}") + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/gem_version.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/gem_version.rb new file mode 100644 index 00000000..8a803f78 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/gem_version.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActiveRecord + # Returns the version of the currently loaded Active Record as a Gem::Version + def self.gem_version + Gem::Version.new VERSION::STRING + end + + module VERSION + MAJOR = 5 + MINOR = 2 + TINY = 3 + PRE = nil + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/inheritance.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/inheritance.rb new file mode 100644 index 00000000..208ba95c --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/inheritance.rb @@ -0,0 +1,283 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/indifferent_access" + +module ActiveRecord + # == Single table inheritance + # + # Active Record allows inheritance by storing the name of the class in a column that by + # default is named "type" (can be changed by overwriting Base.inheritance_column). + # This means that an inheritance looking like this: + # + # class Company < ActiveRecord::Base; end + # class Firm < Company; end + # class Client < Company; end + # class PriorityClient < Client; end + # + # When you do Firm.create(name: "37signals"), this record will be saved in + # the companies table with type = "Firm". You can then fetch this row again using + # Company.where(name: '37signals').first and it will return a Firm object. + # + # Be aware that because the type column is an attribute on the record every new + # subclass will instantly be marked as dirty and the type column will be included + # in the list of changed attributes on the record. This is different from non + # Single Table Inheritance(STI) classes: + # + # Company.new.changed? # => false + # Firm.new.changed? # => true + # Firm.new.changes # => {"type"=>["","Firm"]} + # + # If you don't have a type column defined in your table, single-table inheritance won't + # be triggered. In that case, it'll work just like normal subclasses with no special magic + # for differentiating between them or reloading the right type with find. + # + # Note, all the attributes for all the cases are kept in the same table. Read more: + # https://www.martinfowler.com/eaaCatalog/singleTableInheritance.html + # + module Inheritance + extend ActiveSupport::Concern + + included do + # Determines whether to store the full constant name including namespace when using STI. + # This is true, by default. + class_attribute :store_full_sti_class, instance_writer: false, default: true + end + + module ClassMethods + # Determines if one of the attributes passed in is the inheritance column, + # and if the inheritance column is attr accessible, it initializes an + # instance of the given subclass instead of the base class. + def new(attributes = nil, &block) + if abstract_class? || self == Base + raise NotImplementedError, "#{self} is an abstract class and cannot be instantiated." + end + + if has_attribute?(inheritance_column) + subclass = subclass_from_attributes(attributes) + + if subclass.nil? && base_class == self + subclass = subclass_from_attributes(column_defaults) + end + end + + if subclass && subclass != self + subclass.new(attributes, &block) + else + super + end + end + + # Returns +true+ if this does not need STI type condition. Returns + # +false+ if STI type condition needs to be applied. + def descends_from_active_record? + if self == Base + false + elsif superclass.abstract_class? + superclass.descends_from_active_record? + else + superclass == Base || !columns_hash.include?(inheritance_column) + end + end + + def finder_needs_type_condition? #:nodoc: + # This is like this because benchmarking justifies the strange :false stuff + :true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true) + end + + # Returns the class descending directly from ActiveRecord::Base, or + # an abstract class, if any, in the inheritance hierarchy. + # + # If A extends ActiveRecord::Base, A.base_class will return A. If B descends from A + # through some arbitrarily deep hierarchy, B.base_class will return A. + # + # If B < A and C < B and if A is an abstract_class then both B.base_class + # and C.base_class would return B as the answer since A is an abstract_class. + def base_class + unless self < Base + raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord" + end + + if superclass == Base || superclass.abstract_class? + self + else + superclass.base_class + end + end + + # Set this to +true+ if this is an abstract class (see + # abstract_class?). + # If you are using inheritance with Active Record and don't want a class + # to be considered as part of the STI hierarchy, you must set this to + # true. + # +ApplicationRecord+, for example, is generated as an abstract class. + # + # Consider the following default behaviour: + # + # Shape = Class.new(ActiveRecord::Base) + # Polygon = Class.new(Shape) + # Square = Class.new(Polygon) + # + # Shape.table_name # => "shapes" + # Polygon.table_name # => "shapes" + # Square.table_name # => "shapes" + # Shape.create! # => # + # Polygon.create! # => # + # Square.create! # => # + # + # However, when using abstract_class, +Shape+ is omitted from + # the hierarchy: + # + # class Shape < ActiveRecord::Base + # self.abstract_class = true + # end + # Polygon = Class.new(Shape) + # Square = Class.new(Polygon) + # + # Shape.table_name # => nil + # Polygon.table_name # => "polygons" + # Square.table_name # => "polygons" + # Shape.create! # => NotImplementedError: Shape is an abstract class and cannot be instantiated. + # Polygon.create! # => # + # Square.create! # => # + # + # Note that in the above example, to disallow the creation of a plain + # +Polygon+, you should use validates :type, presence: true, + # instead of setting it as an abstract class. This way, +Polygon+ will + # stay in the hierarchy, and Active Record will continue to correctly + # derive the table name. + attr_accessor :abstract_class + + # Returns whether this class is an abstract class or not. + def abstract_class? + defined?(@abstract_class) && @abstract_class == true + end + + def sti_name + store_full_sti_class ? name : name.demodulize + end + + def polymorphic_name + base_class.name + end + + def inherited(subclass) + subclass.instance_variable_set(:@_type_candidates_cache, Concurrent::Map.new) + super + end + + protected + + # Returns the class type of the record using the current module as a prefix. So descendants of + # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass. + def compute_type(type_name) + if type_name.start_with?("::".freeze) + # If the type is prefixed with a scope operator then we assume that + # the type_name is an absolute reference. + ActiveSupport::Dependencies.constantize(type_name) + else + type_candidate = @_type_candidates_cache[type_name] + if type_candidate && type_constant = ActiveSupport::Dependencies.safe_constantize(type_candidate) + return type_constant + end + + # Build a list of candidates to search for + candidates = [] + name.scan(/::|$/) { candidates.unshift "#{$`}::#{type_name}" } + candidates << type_name + + candidates.each do |candidate| + constant = ActiveSupport::Dependencies.safe_constantize(candidate) + if candidate == constant.to_s + @_type_candidates_cache[type_name] = candidate + return constant + end + end + + raise NameError.new("uninitialized constant #{candidates.first}", candidates.first) + end + end + + private + + # Called by +instantiate+ to decide which class to use for a new + # record instance. For single-table inheritance, we check the record + # for a +type+ column and return the corresponding class. + def discriminate_class_for_record(record) + if using_single_table_inheritance?(record) + find_sti_class(record[inheritance_column]) + else + super + end + end + + def using_single_table_inheritance?(record) + record[inheritance_column].present? && has_attribute?(inheritance_column) + end + + def find_sti_class(type_name) + type_name = base_class.type_for_attribute(inheritance_column).cast(type_name) + subclass = begin + if store_full_sti_class + ActiveSupport::Dependencies.constantize(type_name) + else + compute_type(type_name) + end + rescue NameError + raise SubclassNotFound, + "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " \ + "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " \ + "Please rename this column if you didn't intend it to be used for storing the inheritance class " \ + "or overwrite #{name}.inheritance_column to use another column for that information." + end + unless subclass == self || descendants.include?(subclass) + raise SubclassNotFound, "Invalid single-table inheritance type: #{subclass.name} is not a subclass of #{name}" + end + subclass + end + + def type_condition(table = arel_table) + sti_column = arel_attribute(inheritance_column, table) + sti_names = ([self] + descendants).map(&:sti_name) + + sti_column.in(sti_names) + end + + # Detect the subclass from the inheritance column of attrs. If the inheritance column value + # is not self or a valid subclass, raises ActiveRecord::SubclassNotFound + def subclass_from_attributes(attrs) + attrs = attrs.to_h if attrs.respond_to?(:permitted?) + if attrs.is_a?(Hash) + subclass_name = attrs[inheritance_column] || attrs[inheritance_column.to_sym] + + if subclass_name.present? + find_sti_class(subclass_name) + end + end + end + end + + def initialize_dup(other) + super + ensure_proper_type + end + + private + + def initialize_internals_callback + super + ensure_proper_type + end + + # Sets the attribute used for single table inheritance to this class name if this is not the + # ActiveRecord::Base descendant. + # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to + # do Reply.new without having to set Reply[Reply.inheritance_column] = "Reply" yourself. + # No such attribute would be set for objects of the Message class in that example. + def ensure_proper_type + klass = self.class + if klass.finder_needs_type_condition? + _write_attribute(klass.inheritance_column, klass.sti_name) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/integration.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/integration.rb new file mode 100644 index 00000000..6cf26a97 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/integration.rb @@ -0,0 +1,155 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/filters" + +module ActiveRecord + module Integration + extend ActiveSupport::Concern + + included do + ## + # :singleton-method: + # Indicates the format used to generate the timestamp in the cache key, if + # versioning is off. Accepts any of the symbols in Time::DATE_FORMATS. + # + # This is +:usec+, by default. + class_attribute :cache_timestamp_format, instance_writer: false, default: :usec + + ## + # :singleton-method: + # Indicates whether to use a stable #cache_key method that is accompanied + # by a changing version in the #cache_version method. + # + # This is +false+, by default until Rails 6.0. + class_attribute :cache_versioning, instance_writer: false, default: false + end + + # Returns a +String+, which Action Pack uses for constructing a URL to this + # object. The default implementation returns this record's id as a +String+, + # or +nil+ if this record's unsaved. + # + # For example, suppose that you have a User model, and that you have a + # resources :users route. Normally, +user_path+ will + # construct a path with the user object's 'id' in it: + # + # user = User.find_by(name: 'Phusion') + # user_path(user) # => "/users/1" + # + # You can override +to_param+ in your model to make +user_path+ construct + # a path using the user's name instead of the user's id: + # + # class User < ActiveRecord::Base + # def to_param # overridden + # name + # end + # end + # + # user = User.find_by(name: 'Phusion') + # user_path(user) # => "/users/Phusion" + def to_param + # We can't use alias_method here, because method 'id' optimizes itself on the fly. + id && id.to_s # Be sure to stringify the id for routes + end + + # Returns a stable cache key that can be used to identify this record. + # + # Product.new.cache_key # => "products/new" + # Product.find(5).cache_key # => "products/5" + # + # If ActiveRecord::Base.cache_versioning is turned off, as it was in Rails 5.1 and earlier, + # the cache key will also include a version. + # + # Product.cache_versioning = false + # Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available) + def cache_key(*timestamp_names) + if new_record? + "#{model_name.cache_key}/new" + else + if cache_version && timestamp_names.none? + "#{model_name.cache_key}/#{id}" + else + timestamp = if timestamp_names.any? + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Specifying a timestamp name for #cache_key has been deprecated in favor of + the explicit #cache_version method that can be overwritten. + MSG + + max_updated_column_timestamp(timestamp_names) + else + max_updated_column_timestamp + end + + if timestamp + timestamp = timestamp.utc.to_s(cache_timestamp_format) + "#{model_name.cache_key}/#{id}-#{timestamp}" + else + "#{model_name.cache_key}/#{id}" + end + end + end + end + + # Returns a cache version that can be used together with the cache key to form + # a recyclable caching scheme. By default, the #updated_at column is used for the + # cache_version, but this method can be overwritten to return something else. + # + # Note, this method will return nil if ActiveRecord::Base.cache_versioning is set to + # +false+ (which it is by default until Rails 6.0). + def cache_version + if cache_versioning && timestamp = try(:updated_at) + timestamp.utc.to_s(:usec) + end + end + + # Returns a cache key along with the version. + def cache_key_with_version + if version = cache_version + "#{cache_key}-#{version}" + else + cache_key + end + end + + module ClassMethods + # Defines your model's +to_param+ method to generate "pretty" URLs + # using +method_name+, which can be any attribute or method that + # responds to +to_s+. + # + # class User < ActiveRecord::Base + # to_param :name + # end + # + # user = User.find_by(name: 'Fancy Pants') + # user.id # => 123 + # user_path(user) # => "/users/123-fancy-pants" + # + # Values longer than 20 characters will be truncated. The value + # is truncated word by word. + # + # user = User.find_by(name: 'David Heinemeier Hansson') + # user.id # => 125 + # user_path(user) # => "/users/125-david-heinemeier" + # + # Because the generated param begins with the record's +id+, it is + # suitable for passing to +find+. In a controller, for example: + # + # params[:id] # => "123-fancy-pants" + # User.find(params[:id]).id # => 123 + def to_param(method_name = nil) + if method_name.nil? + super() + else + define_method :to_param do + if (default = super()) && + (result = send(method_name).to_s).present? && + (param = result.squish.parameterize.truncate(20, separator: /-/, omission: "")).present? + "#{default}-#{param}" + else + default + end + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/internal_metadata.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/internal_metadata.rb new file mode 100644 index 00000000..5a65edf2 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/internal_metadata.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require "active_record/scoping/default" +require "active_record/scoping/named" + +module ActiveRecord + # This class is used to create a table that keeps track of values and keys such + # as which environment migrations were run in. + class InternalMetadata < ActiveRecord::Base # :nodoc: + class << self + def primary_key + "key" + end + + def table_name + "#{table_name_prefix}#{ActiveRecord::Base.internal_metadata_table_name}#{table_name_suffix}" + end + + def []=(key, value) + find_or_initialize_by(key: key).update_attributes!(value: value) + end + + def [](key) + where(key: key).pluck(:value).first + end + + def table_exists? + connection.table_exists?(table_name) + end + + # Creates an internal metadata table with columns +key+ and +value+ + def create_table + unless table_exists? + key_options = connection.internal_string_options_for_primary_key + + connection.create_table(table_name, id: false) do |t| + t.string :key, key_options + t.string :value + t.timestamps + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/legacy_yaml_adapter.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/legacy_yaml_adapter.rb new file mode 100644 index 00000000..ffa095dd --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/legacy_yaml_adapter.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module ActiveRecord + module LegacyYamlAdapter + def self.convert(klass, coder) + return coder unless coder.is_a?(Psych::Coder) + + case coder["active_record_yaml_version"] + when 1, 2 then coder + else + if coder["attributes"].is_a?(ActiveModel::AttributeSet) + Rails420.convert(klass, coder) + else + Rails41.convert(klass, coder) + end + end + end + + module Rails420 + def self.convert(klass, coder) + attribute_set = coder["attributes"] + + klass.attribute_names.each do |attr_name| + attribute = attribute_set[attr_name] + if attribute.type.is_a?(Delegator) + type_from_klass = klass.type_for_attribute(attr_name) + attribute_set[attr_name] = attribute.with_type(type_from_klass) + end + end + + coder + end + end + + module Rails41 + def self.convert(klass, coder) + attributes = klass.attributes_builder + .build_from_database(coder["attributes"]) + new_record = coder["attributes"][klass.primary_key].blank? + + { + "attributes" => attributes, + "new_record" => new_record, + } + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/locale/en.yml b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/locale/en.yml new file mode 100644 index 00000000..0b35027b --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/locale/en.yml @@ -0,0 +1,48 @@ +en: + # Attributes names common to most models + #attributes: + #created_at: "Created at" + #updated_at: "Updated at" + + # Default error messages + errors: + messages: + required: "must exist" + taken: "has already been taken" + + # Active Record models configuration + activerecord: + errors: + messages: + record_invalid: "Validation failed: %{errors}" + restrict_dependent_destroy: + has_one: "Cannot delete record because a dependent %{record} exists" + has_many: "Cannot delete record because dependent %{record} exist" + # Append your own errors here or at the model/attributes scope. + + # You can define own errors for models or model attributes. + # The values :model, :attribute and :value are always available for interpolation. + # + # For example, + # models: + # user: + # blank: "This is a custom blank message for %{model}: %{attribute}" + # attributes: + # login: + # blank: "This is a custom blank message for User login" + # Will define custom blank validation message for User model and + # custom blank validation message for login attribute of User model. + #models: + + # Translate model names. Used in Model.human_name(). + #models: + # For example, + # user: "Dude" + # will translate User model name to "Dude" + + # Translate model attribute names. Used in Model.human_attribute_name(attribute). + #attributes: + # For example, + # user: + # login: "Handle" + # will translate User attribute "login" as "Handle" diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/locking/optimistic.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/locking/optimistic.rb new file mode 100644 index 00000000..7f096bb5 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/locking/optimistic.rb @@ -0,0 +1,198 @@ +# frozen_string_literal: true + +module ActiveRecord + module Locking + # == What is Optimistic Locking + # + # Optimistic locking allows multiple users to access the same record for edits, and assumes a minimum of + # conflicts with the data. It does this by checking whether another process has made changes to a record since + # it was opened, an ActiveRecord::StaleObjectError exception is thrown if that has occurred + # and the update is ignored. + # + # Check out ActiveRecord::Locking::Pessimistic for an alternative. + # + # == Usage + # + # Active Record supports optimistic locking if the +lock_version+ field is present. Each update to the + # record increments the +lock_version+ column and the locking facilities ensure that records instantiated twice + # will let the last one saved raise a +StaleObjectError+ if the first was also updated. Example: + # + # p1 = Person.find(1) + # p2 = Person.find(1) + # + # p1.first_name = "Michael" + # p1.save + # + # p2.first_name = "should fail" + # p2.save # Raises an ActiveRecord::StaleObjectError + # + # Optimistic locking will also check for stale data when objects are destroyed. Example: + # + # p1 = Person.find(1) + # p2 = Person.find(1) + # + # p1.first_name = "Michael" + # p1.save + # + # p2.destroy # Raises an ActiveRecord::StaleObjectError + # + # You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging, + # or otherwise apply the business logic needed to resolve the conflict. + # + # This locking mechanism will function inside a single Ruby process. To make it work across all + # web requests, the recommended approach is to add +lock_version+ as a hidden field to your form. + # + # This behavior can be turned off by setting ActiveRecord::Base.lock_optimistically = false. + # To override the name of the +lock_version+ column, set the locking_column class attribute: + # + # class Person < ActiveRecord::Base + # self.locking_column = :lock_person + # end + # + module Optimistic + extend ActiveSupport::Concern + + included do + class_attribute :lock_optimistically, instance_writer: false, default: true + end + + def locking_enabled? #:nodoc: + self.class.locking_enabled? + end + + private + def _create_record(attribute_names = self.attribute_names, *) + if locking_enabled? + # We always want to persist the locking version, even if we don't detect + # a change from the default, since the database might have no default + attribute_names |= [self.class.locking_column] + end + super + end + + def _touch_row(attribute_names, time) + super + ensure + clear_attribute_change(self.class.locking_column) if locking_enabled? + end + + def _update_row(attribute_names, attempted_action = "update") + return super unless locking_enabled? + + begin + locking_column = self.class.locking_column + previous_lock_value = read_attribute_before_type_cast(locking_column) + attribute_names << locking_column + + self[locking_column] += 1 + + affected_rows = self.class._update_record( + attributes_with_values(attribute_names), + self.class.primary_key => id_in_database, + locking_column => previous_lock_value + ) + + if affected_rows != 1 + raise ActiveRecord::StaleObjectError.new(self, attempted_action) + end + + affected_rows + + # If something went wrong, revert the locking_column value. + rescue Exception + self[locking_column] = previous_lock_value.to_i + raise + end + end + + def destroy_row + return super unless locking_enabled? + + locking_column = self.class.locking_column + + affected_rows = self.class._delete_record( + self.class.primary_key => id_in_database, + locking_column => read_attribute_before_type_cast(locking_column) + ) + + if affected_rows != 1 + raise ActiveRecord::StaleObjectError.new(self, "destroy") + end + + affected_rows + end + + module ClassMethods + DEFAULT_LOCKING_COLUMN = "lock_version" + + # Returns true if the +lock_optimistically+ flag is set to true + # (which it is, by default) and the table includes the + # +locking_column+ column (defaults to +lock_version+). + def locking_enabled? + lock_optimistically && columns_hash[locking_column] + end + + # Set the column to use for optimistic locking. Defaults to +lock_version+. + def locking_column=(value) + reload_schema_from_cache + @locking_column = value.to_s + end + + # The version column used for optimistic locking. Defaults to +lock_version+. + def locking_column + @locking_column = DEFAULT_LOCKING_COLUMN unless defined?(@locking_column) + @locking_column + end + + # Reset the column used for optimistic locking back to the +lock_version+ default. + def reset_locking_column + self.locking_column = DEFAULT_LOCKING_COLUMN + end + + # Make sure the lock version column gets updated when counters are + # updated. + def update_counters(id, counters) + counters = counters.merge(locking_column => 1) if locking_enabled? + super + end + + private + + # We need to apply this decorator here, rather than on module inclusion. The closure + # created by the matcher would otherwise evaluate for `ActiveRecord::Base`, not the + # sub class being decorated. As such, changes to `lock_optimistically`, or + # `locking_column` would not be picked up. + def inherited(subclass) + subclass.class_eval do + is_lock_column = ->(name, _) { lock_optimistically && name == locking_column } + decorate_matching_attribute_types(is_lock_column, :_optimistic_locking) do |type| + LockingType.new(type) + end + end + super + end + end + end + + # In de/serialize we change `nil` to 0, so that we can allow passing + # `nil` values to `lock_version`, and not result in `ActiveRecord::StaleObjectError` + # during update record. + class LockingType < DelegateClass(Type::Value) # :nodoc: + def deserialize(value) + super.to_i + end + + def serialize(value) + super.to_i + end + + def init_with(coder) + __setobj__(coder["subtype"]) + end + + def encode_with(coder) + coder["subtype"] = __getobj__ + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/locking/pessimistic.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/locking/pessimistic.rb new file mode 100644 index 00000000..5d1d15c9 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/locking/pessimistic.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +module ActiveRecord + module Locking + # Locking::Pessimistic provides support for row-level locking using + # SELECT ... FOR UPDATE and other lock types. + # + # Chain ActiveRecord::Base#find to ActiveRecord::QueryMethods#lock to obtain an exclusive + # lock on the selected rows: + # # select * from accounts where id=1 for update + # Account.lock.find(1) + # + # Call lock('some locking clause') to use a database-specific locking clause + # of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'. Example: + # + # Account.transaction do + # # select * from accounts where name = 'shugo' limit 1 for update + # shugo = Account.where("name = 'shugo'").lock(true).first + # yuko = Account.where("name = 'yuko'").lock(true).first + # shugo.balance -= 100 + # shugo.save! + # yuko.balance += 100 + # yuko.save! + # end + # + # You can also use ActiveRecord::Base#lock! method to lock one record by id. + # This may be better if you don't need to lock every row. Example: + # + # Account.transaction do + # # select * from accounts where ... + # accounts = Account.where(...) + # account1 = accounts.detect { |account| ... } + # account2 = accounts.detect { |account| ... } + # # select * from accounts where id=? for update + # account1.lock! + # account2.lock! + # account1.balance -= 100 + # account1.save! + # account2.balance += 100 + # account2.save! + # end + # + # You can start a transaction and acquire the lock in one go by calling + # with_lock with a block. The block is called from within + # a transaction, the object is already locked. Example: + # + # account = Account.first + # account.with_lock do + # # This block is called within a transaction, + # # account is already locked. + # account.balance -= 100 + # account.save! + # end + # + # Database-specific information on row locking: + # MySQL: https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html + # PostgreSQL: https://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE + module Pessimistic + # Obtain a row lock on this record. Reloads the record to obtain the requested + # lock. Pass an SQL locking clause to append the end of the SELECT statement + # or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns + # the locked record. + def lock!(lock = true) + if persisted? + if has_changes_to_save? + raise(<<-MSG.squish) + Locking a record with unpersisted changes is not supported. Use + `save` to persist the changes, or `reload` to discard them + explicitly. + MSG + end + + reload(lock: lock) + end + self + end + + # Wraps the passed block in a transaction, locking the object + # before yielding. You can pass the SQL locking clause + # as argument (see lock!). + def with_lock(lock = true) + transaction do + lock!(lock) + yield + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/log_subscriber.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/log_subscriber.rb new file mode 100644 index 00000000..013c3765 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/log_subscriber.rb @@ -0,0 +1,137 @@ +# frozen_string_literal: true + +module ActiveRecord + class LogSubscriber < ActiveSupport::LogSubscriber + IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"] + + def self.runtime=(value) + ActiveRecord::RuntimeRegistry.sql_runtime = value + end + + def self.runtime + ActiveRecord::RuntimeRegistry.sql_runtime ||= 0 + end + + def self.reset_runtime + rt, self.runtime = runtime, 0 + rt + end + + def sql(event) + self.class.runtime += event.duration + return unless logger.debug? + + payload = event.payload + + return if IGNORE_PAYLOAD_NAMES.include?(payload[:name]) + + name = "#{payload[:name]} (#{event.duration.round(1)}ms)" + name = "CACHE #{name}" if payload[:cached] + sql = payload[:sql] + binds = nil + + unless (payload[:binds] || []).empty? + casted_params = type_casted_binds(payload[:type_casted_binds]) + binds = " " + payload[:binds].zip(casted_params).map { |attr, value| + render_bind(attr, value) + }.inspect + end + + name = colorize_payload_name(name, payload[:name]) + sql = color(sql, sql_color(sql), true) + + debug " #{name} #{sql}#{binds}" + end + + private + def type_casted_binds(casted_binds) + casted_binds.respond_to?(:call) ? casted_binds.call : casted_binds + end + + def render_bind(attr, value) + if attr.is_a?(Array) + attr = attr.first + elsif attr.type.binary? && attr.value + value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>" + end + + [attr && attr.name, value] + end + + def colorize_payload_name(name, payload_name) + if payload_name.blank? || payload_name == "SQL" # SQL vs Model Load/Exists + color(name, MAGENTA, true) + else + color(name, CYAN, true) + end + end + + def sql_color(sql) + case sql + when /\A\s*rollback/mi + RED + when /select .*for update/mi, /\A\s*lock/mi + WHITE + when /\A\s*select/i + BLUE + when /\A\s*insert/i + GREEN + when /\A\s*update/i + YELLOW + when /\A\s*delete/i + RED + when /transaction\s*\Z/i + CYAN + else + MAGENTA + end + end + + def logger + ActiveRecord::Base.logger + end + + def debug(progname = nil, &block) + return unless super + + if ActiveRecord::Base.verbose_query_logs + log_query_source + end + end + + def log_query_source + source_line, line_number = extract_callstack(caller_locations) + + if source_line + if defined?(::Rails.root) + app_root = "#{::Rails.root.to_s}/".freeze + source_line = source_line.sub(app_root, "") + end + + logger.debug(" ↳ #{ source_line }:#{ line_number }") + end + end + + def extract_callstack(callstack) + line = callstack.find do |frame| + frame.absolute_path && !ignored_callstack(frame.absolute_path) + end + + offending_line = line || callstack.first + + [ + offending_line.path, + offending_line.lineno + ] + end + + RAILS_GEM_ROOT = File.expand_path("../../..", __dir__) + "/" + + def ignored_callstack(path) + path.start_with?(RAILS_GEM_ROOT) || + path.start_with?(RbConfig::CONFIG["rubylibdir"]) + end + end +end + +ActiveRecord::LogSubscriber.attach_to :active_record diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/migration.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/migration.rb new file mode 100644 index 00000000..c9e0652a --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/migration.rb @@ -0,0 +1,1378 @@ +# frozen_string_literal: true + +require "set" +require "zlib" +require "active_support/core_ext/module/attribute_accessors" + +module ActiveRecord + class MigrationError < ActiveRecordError#:nodoc: + def initialize(message = nil) + message = "\n\n#{message}\n\n" if message + super + end + end + + # Exception that can be raised to stop migrations from being rolled back. + # For example the following migration is not reversible. + # Rolling back this migration will raise an ActiveRecord::IrreversibleMigration error. + # + # class IrreversibleMigrationExample < ActiveRecord::Migration[5.0] + # def change + # create_table :distributors do |t| + # t.string :zipcode + # end + # + # execute <<-SQL + # ALTER TABLE distributors + # ADD CONSTRAINT zipchk + # CHECK (char_length(zipcode) = 5) NO INHERIT; + # SQL + # end + # end + # + # There are two ways to mitigate this problem. + # + # 1. Define #up and #down methods instead of #change: + # + # class ReversibleMigrationExample < ActiveRecord::Migration[5.0] + # def up + # create_table :distributors do |t| + # t.string :zipcode + # end + # + # execute <<-SQL + # ALTER TABLE distributors + # ADD CONSTRAINT zipchk + # CHECK (char_length(zipcode) = 5) NO INHERIT; + # SQL + # end + # + # def down + # execute <<-SQL + # ALTER TABLE distributors + # DROP CONSTRAINT zipchk + # SQL + # + # drop_table :distributors + # end + # end + # + # 2. Use the #reversible method in #change method: + # + # class ReversibleMigrationExample < ActiveRecord::Migration[5.0] + # def change + # create_table :distributors do |t| + # t.string :zipcode + # end + # + # reversible do |dir| + # dir.up do + # execute <<-SQL + # ALTER TABLE distributors + # ADD CONSTRAINT zipchk + # CHECK (char_length(zipcode) = 5) NO INHERIT; + # SQL + # end + # + # dir.down do + # execute <<-SQL + # ALTER TABLE distributors + # DROP CONSTRAINT zipchk + # SQL + # end + # end + # end + # end + class IrreversibleMigration < MigrationError + end + + class DuplicateMigrationVersionError < MigrationError#:nodoc: + def initialize(version = nil) + if version + super("Multiple migrations have the version number #{version}.") + else + super("Duplicate migration version error.") + end + end + end + + class DuplicateMigrationNameError < MigrationError#:nodoc: + def initialize(name = nil) + if name + super("Multiple migrations have the name #{name}.") + else + super("Duplicate migration name.") + end + end + end + + class UnknownMigrationVersionError < MigrationError #:nodoc: + def initialize(version = nil) + if version + super("No migration with version number #{version}.") + else + super("Unknown migration version.") + end + end + end + + class IllegalMigrationNameError < MigrationError#:nodoc: + def initialize(name = nil) + if name + super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed).") + else + super("Illegal name for migration.") + end + end + end + + class PendingMigrationError < MigrationError#:nodoc: + def initialize(message = nil) + if !message && defined?(Rails.env) + super("Migrations are pending. To resolve this issue, run:\n\n bin/rails db:migrate RAILS_ENV=#{::Rails.env}") + elsif !message + super("Migrations are pending. To resolve this issue, run:\n\n bin/rails db:migrate") + else + super + end + end + end + + class ConcurrentMigrationError < MigrationError #:nodoc: + DEFAULT_MESSAGE = "Cannot run migrations because another migration process is currently running.".freeze + RELEASE_LOCK_FAILED_MESSAGE = "Failed to release advisory lock".freeze + + def initialize(message = DEFAULT_MESSAGE) + super + end + end + + class NoEnvironmentInSchemaError < MigrationError #:nodoc: + def initialize + msg = "Environment data not found in the schema. To resolve this issue, run: \n\n bin/rails db:environment:set" + if defined?(Rails.env) + super("#{msg} RAILS_ENV=#{::Rails.env}") + else + super(msg) + end + end + end + + class ProtectedEnvironmentError < ActiveRecordError #:nodoc: + def initialize(env = "production") + msg = "You are attempting to run a destructive action against your '#{env}' database.\n".dup + msg << "If you are sure you want to continue, run the same command with the environment variable:\n" + msg << "DISABLE_DATABASE_ENVIRONMENT_CHECK=1" + super(msg) + end + end + + class EnvironmentMismatchError < ActiveRecordError + def initialize(current: nil, stored: nil) + msg = "You are attempting to modify a database that was last run in `#{ stored }` environment.\n".dup + msg << "You are running in `#{ current }` environment. " + msg << "If you are sure you want to continue, first set the environment using:\n\n" + msg << " bin/rails db:environment:set" + if defined?(Rails.env) + super("#{msg} RAILS_ENV=#{::Rails.env}\n\n") + else + super("#{msg}\n\n") + end + end + end + + # = Active Record Migrations + # + # Migrations can manage the evolution of a schema used by several physical + # databases. It's a solution to the common problem of adding a field to make + # a new feature work in your local database, but being unsure of how to + # push that change to other developers and to the production server. With + # migrations, you can describe the transformations in self-contained classes + # that can be checked into version control systems and executed against + # another database that might be one, two, or five versions behind. + # + # Example of a simple migration: + # + # class AddSsl < ActiveRecord::Migration[5.0] + # def up + # add_column :accounts, :ssl_enabled, :boolean, default: true + # end + # + # def down + # remove_column :accounts, :ssl_enabled + # end + # end + # + # This migration will add a boolean flag to the accounts table and remove it + # if you're backing out of the migration. It shows how all migrations have + # two methods +up+ and +down+ that describes the transformations + # required to implement or remove the migration. These methods can consist + # of both the migration specific methods like +add_column+ and +remove_column+, + # but may also contain regular Ruby code for generating data needed for the + # transformations. + # + # Example of a more complex migration that also needs to initialize data: + # + # class AddSystemSettings < ActiveRecord::Migration[5.0] + # def up + # create_table :system_settings do |t| + # t.string :name + # t.string :label + # t.text :value + # t.string :type + # t.integer :position + # end + # + # SystemSetting.create name: 'notice', + # label: 'Use notice?', + # value: 1 + # end + # + # def down + # drop_table :system_settings + # end + # end + # + # This migration first adds the +system_settings+ table, then creates the very + # first row in it using the Active Record model that relies on the table. It + # also uses the more advanced +create_table+ syntax where you can specify a + # complete table schema in one block call. + # + # == Available transformations + # + # === Creation + # + # * create_join_table(table_1, table_2, options): Creates a join + # table having its name as the lexical order of the first two + # arguments. See + # ActiveRecord::ConnectionAdapters::SchemaStatements#create_join_table for + # details. + # * create_table(name, options): Creates a table called +name+ and + # makes the table object available to a block that can then add columns to it, + # following the same format as +add_column+. See example above. The options hash + # is for fragments like "DEFAULT CHARSET=UTF-8" that are appended to the create + # table definition. + # * add_column(table_name, column_name, type, options): Adds a new column + # to the table called +table_name+ + # named +column_name+ specified to be one of the following types: + # :string, :text, :integer, :float, + # :decimal, :datetime, :timestamp, :time, + # :date, :binary, :boolean. A default value can be + # specified by passing an +options+ hash like { default: 11 }. + # Other options include :limit and :null (e.g. + # { limit: 50, null: false }) -- see + # ActiveRecord::ConnectionAdapters::TableDefinition#column for details. + # * add_foreign_key(from_table, to_table, options): Adds a new + # foreign key. +from_table+ is the table with the key column, +to_table+ contains + # the referenced primary key. + # * add_index(table_name, column_names, options): Adds a new index + # with the name of the column. Other options include + # :name, :unique (e.g. + # { name: 'users_name_index', unique: true }) and :order + # (e.g. { order: { name: :desc } }). + # * add_reference(:table_name, :reference_name): Adds a new column + # +reference_name_id+ by default an integer. See + # ActiveRecord::ConnectionAdapters::SchemaStatements#add_reference for details. + # * add_timestamps(table_name, options): Adds timestamps (+created_at+ + # and +updated_at+) columns to +table_name+. + # + # === Modification + # + # * change_column(table_name, column_name, type, options): Changes + # the column to a different type using the same parameters as add_column. + # * change_column_default(table_name, column_name, default_or_changes): + # Sets a default value for +column_name+ defined by +default_or_changes+ on + # +table_name+. Passing a hash containing :from and :to + # as +default_or_changes+ will make this change reversible in the migration. + # * change_column_null(table_name, column_name, null, default = nil): + # Sets or removes a +NOT NULL+ constraint on +column_name+. The +null+ flag + # indicates whether the value can be +NULL+. See + # ActiveRecord::ConnectionAdapters::SchemaStatements#change_column_null for + # details. + # * change_table(name, options): Allows to make column alterations to + # the table called +name+. It makes the table object available to a block that + # can then add/remove columns, indexes or foreign keys to it. + # * rename_column(table_name, column_name, new_column_name): Renames + # a column but keeps the type and content. + # * rename_index(table_name, old_name, new_name): Renames an index. + # * rename_table(old_name, new_name): Renames the table called +old_name+ + # to +new_name+. + # + # === Deletion + # + # * drop_table(name): Drops the table called +name+. + # * drop_join_table(table_1, table_2, options): Drops the join table + # specified by the given arguments. + # * remove_column(table_name, column_name, type, options): Removes the column + # named +column_name+ from the table called +table_name+. + # * remove_columns(table_name, *column_names): Removes the given + # columns from the table definition. + # * remove_foreign_key(from_table, options_or_to_table): Removes the + # given foreign key from the table called +table_name+. + # * remove_index(table_name, column: column_names): Removes the index + # specified by +column_names+. + # * remove_index(table_name, name: index_name): Removes the index + # specified by +index_name+. + # * remove_reference(table_name, ref_name, options): Removes the + # reference(s) on +table_name+ specified by +ref_name+. + # * remove_timestamps(table_name, options): Removes the timestamp + # columns (+created_at+ and +updated_at+) from the table definition. + # + # == Irreversible transformations + # + # Some transformations are destructive in a manner that cannot be reversed. + # Migrations of that kind should raise an ActiveRecord::IrreversibleMigration + # exception in their +down+ method. + # + # == Running migrations from within Rails + # + # The Rails package has several tools to help create and apply migrations. + # + # To generate a new migration, you can use + # rails generate migration MyNewMigration + # + # where MyNewMigration is the name of your migration. The generator will + # create an empty migration file timestamp_my_new_migration.rb + # in the db/migrate/ directory where timestamp is the + # UTC formatted date and time that the migration was generated. + # + # There is a special syntactic shortcut to generate migrations that add fields to a table. + # + # rails generate migration add_fieldname_to_tablename fieldname:string + # + # This will generate the file timestamp_add_fieldname_to_tablename.rb, which will look like this: + # class AddFieldnameToTablename < ActiveRecord::Migration[5.0] + # def change + # add_column :tablenames, :fieldname, :string + # end + # end + # + # To run migrations against the currently configured database, use + # rails db:migrate. This will update the database by running all of the + # pending migrations, creating the schema_migrations table + # (see "About the schema_migrations table" section below) if missing. It will also + # invoke the db:schema:dump task, which will update your db/schema.rb file + # to match the structure of your database. + # + # To roll the database back to a previous migration version, use + # rails db:rollback VERSION=X where X is the version to which + # you wish to downgrade. Alternatively, you can also use the STEP option if you + # wish to rollback last few migrations. rails db:rollback STEP=2 will rollback + # the latest two migrations. + # + # If any of the migrations throw an ActiveRecord::IrreversibleMigration exception, + # that step will fail and you'll have some manual work to do. + # + # == Database support + # + # Migrations are currently supported in MySQL, PostgreSQL, SQLite, + # SQL Server, and Oracle (all supported databases except DB2). + # + # == More examples + # + # Not all migrations change the schema. Some just fix the data: + # + # class RemoveEmptyTags < ActiveRecord::Migration[5.0] + # def up + # Tag.all.each { |tag| tag.destroy if tag.pages.empty? } + # end + # + # def down + # # not much we can do to restore deleted data + # raise ActiveRecord::IrreversibleMigration, "Can't recover the deleted tags" + # end + # end + # + # Others remove columns when they migrate up instead of down: + # + # class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration[5.0] + # def up + # remove_column :items, :incomplete_items_count + # remove_column :items, :completed_items_count + # end + # + # def down + # add_column :items, :incomplete_items_count + # add_column :items, :completed_items_count + # end + # end + # + # And sometimes you need to do something in SQL not abstracted directly by migrations: + # + # class MakeJoinUnique < ActiveRecord::Migration[5.0] + # def up + # execute "ALTER TABLE `pages_linked_pages` ADD UNIQUE `page_id_linked_page_id` (`page_id`,`linked_page_id`)" + # end + # + # def down + # execute "ALTER TABLE `pages_linked_pages` DROP INDEX `page_id_linked_page_id`" + # end + # end + # + # == Using a model after changing its table + # + # Sometimes you'll want to add a column in a migration and populate it + # immediately after. In that case, you'll need to make a call to + # Base#reset_column_information in order to ensure that the model has the + # latest column data from after the new column was added. Example: + # + # class AddPeopleSalary < ActiveRecord::Migration[5.0] + # def up + # add_column :people, :salary, :integer + # Person.reset_column_information + # Person.all.each do |p| + # p.update_attribute :salary, SalaryCalculator.compute(p) + # end + # end + # end + # + # == Controlling verbosity + # + # By default, migrations will describe the actions they are taking, writing + # them to the console as they happen, along with benchmarks describing how + # long each step took. + # + # You can quiet them down by setting ActiveRecord::Migration.verbose = false. + # + # You can also insert your own messages and benchmarks by using the +say_with_time+ + # method: + # + # def up + # ... + # say_with_time "Updating salaries..." do + # Person.all.each do |p| + # p.update_attribute :salary, SalaryCalculator.compute(p) + # end + # end + # ... + # end + # + # The phrase "Updating salaries..." would then be printed, along with the + # benchmark for the block when the block completes. + # + # == Timestamped Migrations + # + # By default, Rails generates migrations that look like: + # + # 20080717013526_your_migration_name.rb + # + # The prefix is a generation timestamp (in UTC). + # + # If you'd prefer to use numeric prefixes, you can turn timestamped migrations + # off by setting: + # + # config.active_record.timestamped_migrations = false + # + # In application.rb. + # + # == Reversible Migrations + # + # Reversible migrations are migrations that know how to go +down+ for you. + # You simply supply the +up+ logic, and the Migration system figures out + # how to execute the down commands for you. + # + # To define a reversible migration, define the +change+ method in your + # migration like this: + # + # class TenderloveMigration < ActiveRecord::Migration[5.0] + # def change + # create_table(:horses) do |t| + # t.column :content, :text + # t.column :remind_at, :datetime + # end + # end + # end + # + # This migration will create the horses table for you on the way up, and + # automatically figure out how to drop the table on the way down. + # + # Some commands like +remove_column+ cannot be reversed. If you care to + # define how to move up and down in these cases, you should define the +up+ + # and +down+ methods as before. + # + # If a command cannot be reversed, an + # ActiveRecord::IrreversibleMigration exception will be raised when + # the migration is moving down. + # + # For a list of commands that are reversible, please see + # ActiveRecord::Migration::CommandRecorder. + # + # == Transactional Migrations + # + # If the database adapter supports DDL transactions, all migrations will + # automatically be wrapped in a transaction. There are queries that you + # can't execute inside a transaction though, and for these situations + # you can turn the automatic transactions off. + # + # class ChangeEnum < ActiveRecord::Migration[5.0] + # disable_ddl_transaction! + # + # def up + # execute "ALTER TYPE model_size ADD VALUE 'new_value'" + # end + # end + # + # Remember that you can still open your own transactions, even if you + # are in a Migration with self.disable_ddl_transaction!. + class Migration + autoload :CommandRecorder, "active_record/migration/command_recorder" + autoload :Compatibility, "active_record/migration/compatibility" + + # This must be defined before the inherited hook, below + class Current < Migration # :nodoc: + end + + def self.inherited(subclass) # :nodoc: + super + if subclass.superclass == Migration + raise StandardError, "Directly inheriting from ActiveRecord::Migration is not supported. " \ + "Please specify the Rails release the migration was written for:\n" \ + "\n" \ + " class #{subclass} < ActiveRecord::Migration[4.2]" + end + end + + def self.[](version) + Compatibility.find(version) + end + + def self.current_version + ActiveRecord::VERSION::STRING.to_f + end + + MigrationFilenameRegexp = /\A([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/ # :nodoc: + + # This class is used to verify that all migrations have been run before + # loading a web page if config.active_record.migration_error is set to :page_load + class CheckPending + def initialize(app) + @app = app + @last_check = 0 + end + + def call(env) + mtime = ActiveRecord::Base.connection.migration_context.last_migration.mtime.to_i + if @last_check < mtime + ActiveRecord::Migration.check_pending!(connection) + @last_check = mtime + end + @app.call(env) + end + + private + + def connection + ActiveRecord::Base.connection + end + end + + class << self + attr_accessor :delegate # :nodoc: + attr_accessor :disable_ddl_transaction # :nodoc: + + def nearest_delegate # :nodoc: + delegate || superclass.nearest_delegate + end + + # Raises ActiveRecord::PendingMigrationError error if any migrations are pending. + def check_pending!(connection = Base.connection) + raise ActiveRecord::PendingMigrationError if connection.migration_context.needs_migration? + end + + def load_schema_if_pending! + if Base.connection.migration_context.needs_migration? || !Base.connection.migration_context.any_migrations? + # Roundtrip to Rake to allow plugins to hook into database initialization. + root = defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root + FileUtils.cd(root) do + current_config = Base.connection_config + Base.clear_all_connections! + system("bin/rails db:test:prepare") + # Establish a new connection, the old database may be gone (db:test:prepare uses purge) + Base.establish_connection(current_config) + end + check_pending! + end + end + + def maintain_test_schema! # :nodoc: + if ActiveRecord::Base.maintain_test_schema + suppress_messages { load_schema_if_pending! } + end + end + + def method_missing(name, *args, &block) # :nodoc: + nearest_delegate.send(name, *args, &block) + end + + def migrate(direction) + new.migrate direction + end + + # Disable the transaction wrapping this migration. + # You can still create your own transactions even after calling #disable_ddl_transaction! + # + # For more details read the {"Transactional Migrations" section above}[rdoc-ref:Migration]. + def disable_ddl_transaction! + @disable_ddl_transaction = true + end + end + + def disable_ddl_transaction # :nodoc: + self.class.disable_ddl_transaction + end + + cattr_accessor :verbose + attr_accessor :name, :version + + def initialize(name = self.class.name, version = nil) + @name = name + @version = version + @connection = nil + end + + self.verbose = true + # instantiate the delegate object after initialize is defined + self.delegate = new + + # Reverses the migration commands for the given block and + # the given migrations. + # + # The following migration will remove the table 'horses' + # and create the table 'apples' on the way up, and the reverse + # on the way down. + # + # class FixTLMigration < ActiveRecord::Migration[5.0] + # def change + # revert do + # create_table(:horses) do |t| + # t.text :content + # t.datetime :remind_at + # end + # end + # create_table(:apples) do |t| + # t.string :variety + # end + # end + # end + # + # Or equivalently, if +TenderloveMigration+ is defined as in the + # documentation for Migration: + # + # require_relative '20121212123456_tenderlove_migration' + # + # class FixupTLMigration < ActiveRecord::Migration[5.0] + # def change + # revert TenderloveMigration + # + # create_table(:apples) do |t| + # t.string :variety + # end + # end + # end + # + # This command can be nested. + def revert(*migration_classes) + run(*migration_classes.reverse, revert: true) unless migration_classes.empty? + if block_given? + if connection.respond_to? :revert + connection.revert { yield } + else + recorder = CommandRecorder.new(connection) + @connection = recorder + suppress_messages do + connection.revert { yield } + end + @connection = recorder.delegate + recorder.commands.each do |cmd, args, block| + send(cmd, *args, &block) + end + end + end + end + + def reverting? + connection.respond_to?(:reverting) && connection.reverting + end + + ReversibleBlockHelper = Struct.new(:reverting) do # :nodoc: + def up + yield unless reverting + end + + def down + yield if reverting + end + end + + # Used to specify an operation that can be run in one direction or another. + # Call the methods +up+ and +down+ of the yielded object to run a block + # only in one given direction. + # The whole block will be called in the right order within the migration. + # + # In the following example, the looping on users will always be done + # when the three columns 'first_name', 'last_name' and 'full_name' exist, + # even when migrating down: + # + # class SplitNameMigration < ActiveRecord::Migration[5.0] + # def change + # add_column :users, :first_name, :string + # add_column :users, :last_name, :string + # + # reversible do |dir| + # User.reset_column_information + # User.all.each do |u| + # dir.up { u.first_name, u.last_name = u.full_name.split(' ') } + # dir.down { u.full_name = "#{u.first_name} #{u.last_name}" } + # u.save + # end + # end + # + # revert { add_column :users, :full_name, :string } + # end + # end + def reversible + helper = ReversibleBlockHelper.new(reverting?) + execute_block { yield helper } + end + + # Used to specify an operation that is only run when migrating up + # (for example, populating a new column with its initial values). + # + # In the following example, the new column +published+ will be given + # the value +true+ for all existing records. + # + # class AddPublishedToPosts < ActiveRecord::Migration[5.2] + # def change + # add_column :posts, :published, :boolean, default: false + # up_only do + # execute "update posts set published = 'true'" + # end + # end + # end + def up_only + execute_block { yield } unless reverting? + end + + # Runs the given migration classes. + # Last argument can specify options: + # - :direction (default is :up) + # - :revert (default is false) + def run(*migration_classes) + opts = migration_classes.extract_options! + dir = opts[:direction] || :up + dir = (dir == :down ? :up : :down) if opts[:revert] + if reverting? + # If in revert and going :up, say, we want to execute :down without reverting, so + revert { run(*migration_classes, direction: dir, revert: true) } + else + migration_classes.each do |migration_class| + migration_class.new.exec_migration(connection, dir) + end + end + end + + def up + self.class.delegate = self + return unless self.class.respond_to?(:up) + self.class.up + end + + def down + self.class.delegate = self + return unless self.class.respond_to?(:down) + self.class.down + end + + # Execute this migration in the named direction + def migrate(direction) + return unless respond_to?(direction) + + case direction + when :up then announce "migrating" + when :down then announce "reverting" + end + + time = nil + ActiveRecord::Base.connection_pool.with_connection do |conn| + time = Benchmark.measure do + exec_migration(conn, direction) + end + end + + case direction + when :up then announce "migrated (%.4fs)" % time.real; write + when :down then announce "reverted (%.4fs)" % time.real; write + end + end + + def exec_migration(conn, direction) + @connection = conn + if respond_to?(:change) + if direction == :down + revert { change } + else + change + end + else + send(direction) + end + ensure + @connection = nil + end + + def write(text = "") + puts(text) if verbose + end + + def announce(message) + text = "#{version} #{name}: #{message}" + length = [0, 75 - text.length].max + write "== %s %s" % [text, "=" * length] + end + + def say(message, subitem = false) + write "#{subitem ? " ->" : "--"} #{message}" + end + + def say_with_time(message) + say(message) + result = nil + time = Benchmark.measure { result = yield } + say "%.4fs" % time.real, :subitem + say("#{result} rows", :subitem) if result.is_a?(Integer) + result + end + + def suppress_messages + save, self.verbose = verbose, false + yield + ensure + self.verbose = save + end + + def connection + @connection || ActiveRecord::Base.connection + end + + def method_missing(method, *arguments, &block) + arg_list = arguments.map(&:inspect) * ", " + + say_with_time "#{method}(#{arg_list})" do + unless connection.respond_to? :revert + unless arguments.empty? || [:execute, :enable_extension, :disable_extension].include?(method) + arguments[0] = proper_table_name(arguments.first, table_name_options) + if [:rename_table, :add_foreign_key].include?(method) || + (method == :remove_foreign_key && !arguments.second.is_a?(Hash)) + arguments[1] = proper_table_name(arguments.second, table_name_options) + end + end + end + return super unless connection.respond_to?(method) + connection.send(method, *arguments, &block) + end + end + + def copy(destination, sources, options = {}) + copied = [] + + FileUtils.mkdir_p(destination) unless File.exist?(destination) + + destination_migrations = ActiveRecord::MigrationContext.new(destination).migrations + last = destination_migrations.last + sources.each do |scope, path| + source_migrations = ActiveRecord::MigrationContext.new(path).migrations + + source_migrations.each do |migration| + source = File.binread(migration.filename) + inserted_comment = "# This migration comes from #{scope} (originally #{migration.version})\n" + magic_comments = "".dup + loop do + # If we have a magic comment in the original migration, + # insert our comment after the first newline(end of the magic comment line) + # so the magic keep working. + # Note that magic comments must be at the first line(except sh-bang). + source.sub!(/\A(?:#.*\b(?:en)?coding:\s*\S+|#\s*frozen_string_literal:\s*(?:true|false)).*\n/) do |magic_comment| + magic_comments << magic_comment; "" + end || break + end + source = "#{magic_comments}#{inserted_comment}#{source}" + + if duplicate = destination_migrations.detect { |m| m.name == migration.name } + if options[:on_skip] && duplicate.scope != scope.to_s + options[:on_skip].call(scope, migration) + end + next + end + + migration.version = next_migration_number(last ? last.version + 1 : 0).to_i + new_path = File.join(destination, "#{migration.version}_#{migration.name.underscore}.#{scope}.rb") + old_path, migration.filename = migration.filename, new_path + last = migration + + File.binwrite(migration.filename, source) + copied << migration + options[:on_copy].call(scope, migration, old_path) if options[:on_copy] + destination_migrations << migration + end + end + + copied + end + + # Finds the correct table name given an Active Record object. + # Uses the Active Record object's own table_name, or pre/suffix from the + # options passed in. + def proper_table_name(name, options = {}) + if name.respond_to? :table_name + name.table_name + else + "#{options[:table_name_prefix]}#{name}#{options[:table_name_suffix]}" + end + end + + # Determines the version number of the next migration. + def next_migration_number(number) + if ActiveRecord::Base.timestamped_migrations + [Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % number].max + else + SchemaMigration.normalize_migration_number(number) + end + end + + # Builds a hash for use in ActiveRecord::Migration#proper_table_name using + # the Active Record object's table_name prefix and suffix + def table_name_options(config = ActiveRecord::Base) #:nodoc: + { + table_name_prefix: config.table_name_prefix, + table_name_suffix: config.table_name_suffix + } + end + + private + def execute_block + if connection.respond_to? :execute_block + super # use normal delegation to record the block + else + yield + end + end + end + + # MigrationProxy is used to defer loading of the actual migration classes + # until they are needed + MigrationProxy = Struct.new(:name, :version, :filename, :scope) do + def initialize(name, version, filename, scope) + super + @migration = nil + end + + def basename + File.basename(filename) + end + + def mtime + File.mtime filename + end + + delegate :migrate, :announce, :write, :disable_ddl_transaction, to: :migration + + private + + def migration + @migration ||= load_migration + end + + def load_migration + require(File.expand_path(filename)) + name.constantize.new(name, version) + end + end + + class NullMigration < MigrationProxy #:nodoc: + def initialize + super(nil, 0, nil, nil) + end + + def mtime + 0 + end + end + + class MigrationContext # :nodoc: + attr_reader :migrations_paths + + def initialize(migrations_paths) + @migrations_paths = migrations_paths + end + + def migrate(target_version = nil, &block) + case + when target_version.nil? + up(target_version, &block) + when current_version == 0 && target_version == 0 + [] + when current_version > target_version + down(target_version, &block) + else + up(target_version, &block) + end + end + + def rollback(steps = 1) + move(:down, steps) + end + + def forward(steps = 1) + move(:up, steps) + end + + def up(target_version = nil) + selected_migrations = if block_given? + migrations.select { |m| yield m } + else + migrations + end + + Migrator.new(:up, selected_migrations, target_version).migrate + end + + def down(target_version = nil) + selected_migrations = if block_given? + migrations.select { |m| yield m } + else + migrations + end + + Migrator.new(:down, selected_migrations, target_version).migrate + end + + def run(direction, target_version) + Migrator.new(direction, migrations, target_version).run + end + + def open + Migrator.new(:up, migrations, nil) + end + + def get_all_versions + if SchemaMigration.table_exists? + SchemaMigration.all_versions.map(&:to_i) + else + [] + end + end + + def current_version + get_all_versions.max || 0 + rescue ActiveRecord::NoDatabaseError + end + + def needs_migration? + (migrations.collect(&:version) - get_all_versions).size > 0 + end + + def any_migrations? + migrations.any? + end + + def last_migration #:nodoc: + migrations.last || NullMigration.new + end + + def parse_migration_filename(filename) # :nodoc: + File.basename(filename).scan(Migration::MigrationFilenameRegexp).first + end + + def migrations + migrations = migration_files.map do |file| + version, name, scope = parse_migration_filename(file) + raise IllegalMigrationNameError.new(file) unless version + version = version.to_i + name = name.camelize + + MigrationProxy.new(name, version, file, scope) + end + + migrations.sort_by(&:version) + end + + def migrations_status + db_list = ActiveRecord::SchemaMigration.normalized_versions + + file_list = migration_files.map do |file| + version, name, scope = parse_migration_filename(file) + raise IllegalMigrationNameError.new(file) unless version + version = ActiveRecord::SchemaMigration.normalize_migration_number(version) + status = db_list.delete(version) ? "up" : "down" + [status, version, (name + scope).humanize] + end.compact + + db_list.map! do |version| + ["up", version, "********** NO FILE **********"] + end + + (db_list + file_list).sort_by { |_, version, _| version } + end + + def migration_files + paths = Array(migrations_paths) + Dir[*paths.flat_map { |path| "#{path}/**/[0-9]*_*.rb" }] + end + + def current_environment + ActiveRecord::ConnectionHandling::DEFAULT_ENV.call + end + + def protected_environment? + ActiveRecord::Base.protected_environments.include?(last_stored_environment) if last_stored_environment + end + + def last_stored_environment + return nil if current_version == 0 + raise NoEnvironmentInSchemaError unless ActiveRecord::InternalMetadata.table_exists? + + environment = ActiveRecord::InternalMetadata[:environment] + raise NoEnvironmentInSchemaError unless environment + environment + end + + private + def move(direction, steps) + migrator = Migrator.new(direction, migrations) + + if current_version != 0 && !migrator.current_migration + raise UnknownMigrationVersionError.new(current_version) + end + + start_index = + if current_version == 0 + 0 + else + migrator.migrations.index(migrator.current_migration) + end + + finish = migrator.migrations[start_index + steps] + version = finish ? finish.version : 0 + send(direction, version) + end + end + + class Migrator # :nodoc: + class << self + attr_accessor :migrations_paths + + def migrations_path=(path) + ActiveSupport::Deprecation.warn \ + "`ActiveRecord::Migrator.migrations_path=` is now deprecated and will be removed in Rails 6.0. " \ + "You can set the `migrations_paths` on the `connection` instead through the `database.yml`." + self.migrations_paths = [path] + end + + # For cases where a table doesn't exist like loading from schema cache + def current_version + MigrationContext.new(migrations_paths).current_version + end + end + + self.migrations_paths = ["db/migrate"] + + def initialize(direction, migrations, target_version = nil) + @direction = direction + @target_version = target_version + @migrated_versions = nil + @migrations = migrations + + validate(@migrations) + + ActiveRecord::SchemaMigration.create_table + ActiveRecord::InternalMetadata.create_table + end + + def current_version + migrated.max || 0 + end + + def current_migration + migrations.detect { |m| m.version == current_version } + end + alias :current :current_migration + + def run + if use_advisory_lock? + with_advisory_lock { run_without_lock } + else + run_without_lock + end + end + + def migrate + if use_advisory_lock? + with_advisory_lock { migrate_without_lock } + else + migrate_without_lock + end + end + + def runnable + runnable = migrations[start..finish] + if up? + runnable.reject { |m| ran?(m) } + else + # skip the last migration if we're headed down, but not ALL the way down + runnable.pop if target + runnable.find_all { |m| ran?(m) } + end + end + + def migrations + down? ? @migrations.reverse : @migrations.sort_by(&:version) + end + + def pending_migrations + already_migrated = migrated + migrations.reject { |m| already_migrated.include?(m.version) } + end + + def migrated + @migrated_versions || load_migrated + end + + def load_migrated + @migrated_versions = Set.new(Base.connection.migration_context.get_all_versions) + end + + private + + # Used for running a specific migration. + def run_without_lock + migration = migrations.detect { |m| m.version == @target_version } + raise UnknownMigrationVersionError.new(@target_version) if migration.nil? + result = execute_migration_in_transaction(migration, @direction) + + record_environment + result + end + + # Used for running multiple migrations up to or down to a certain value. + def migrate_without_lock + if invalid_target? + raise UnknownMigrationVersionError.new(@target_version) + end + + result = runnable.each do |migration| + execute_migration_in_transaction(migration, @direction) + end + + record_environment + result + end + + # Stores the current environment in the database. + def record_environment + return if down? + ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Base.connection.migration_context.current_environment + end + + def ran?(migration) + migrated.include?(migration.version.to_i) + end + + # Return true if a valid version is not provided. + def invalid_target? + @target_version && @target_version != 0 && !target + end + + def execute_migration_in_transaction(migration, direction) + return if down? && !migrated.include?(migration.version.to_i) + return if up? && migrated.include?(migration.version.to_i) + + Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger + + ddl_transaction(migration) do + migration.migrate(direction) + record_version_state_after_migrating(migration.version) + end + rescue => e + msg = "An error has occurred, ".dup + msg << "this and " if use_transaction?(migration) + msg << "all later migrations canceled:\n\n#{e}" + raise StandardError, msg, e.backtrace + end + + def target + migrations.detect { |m| m.version == @target_version } + end + + def finish + migrations.index(target) || migrations.size - 1 + end + + def start + up? ? 0 : (migrations.index(current) || 0) + end + + def validate(migrations) + name, = migrations.group_by(&:name).find { |_, v| v.length > 1 } + raise DuplicateMigrationNameError.new(name) if name + + version, = migrations.group_by(&:version).find { |_, v| v.length > 1 } + raise DuplicateMigrationVersionError.new(version) if version + end + + def record_version_state_after_migrating(version) + if down? + migrated.delete(version) + ActiveRecord::SchemaMigration.where(version: version.to_s).delete_all + else + migrated << version + ActiveRecord::SchemaMigration.create!(version: version.to_s) + end + end + + def up? + @direction == :up + end + + def down? + @direction == :down + end + + # Wrap the migration in a transaction only if supported by the adapter. + def ddl_transaction(migration) + if use_transaction?(migration) + Base.transaction { yield } + else + yield + end + end + + def use_transaction?(migration) + !migration.disable_ddl_transaction && Base.connection.supports_ddl_transactions? + end + + def use_advisory_lock? + Base.connection.supports_advisory_locks? + end + + def with_advisory_lock + lock_id = generate_migrator_advisory_lock_id + connection = Base.connection + got_lock = connection.get_advisory_lock(lock_id) + raise ConcurrentMigrationError unless got_lock + load_migrated # reload schema_migrations to be sure it wasn't changed by another process before we got the lock + yield + ensure + if got_lock && !connection.release_advisory_lock(lock_id) + raise ConcurrentMigrationError.new( + ConcurrentMigrationError::RELEASE_LOCK_FAILED_MESSAGE + ) + end + end + + MIGRATOR_SALT = 2053462845 + def generate_migrator_advisory_lock_id + db_name_hash = Zlib.crc32(Base.connection.current_database) + MIGRATOR_SALT * db_name_hash + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/migration/command_recorder.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/migration/command_recorder.rb new file mode 100644 index 00000000..81ef4828 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/migration/command_recorder.rb @@ -0,0 +1,240 @@ +# frozen_string_literal: true + +module ActiveRecord + class Migration + # ActiveRecord::Migration::CommandRecorder records commands done during + # a migration and knows how to reverse those commands. The CommandRecorder + # knows how to invert the following commands: + # + # * add_column + # * add_foreign_key + # * add_index + # * add_reference + # * add_timestamps + # * change_column + # * change_column_default (must supply a :from and :to option) + # * change_column_null + # * create_join_table + # * create_table + # * disable_extension + # * drop_join_table + # * drop_table (must supply a block) + # * enable_extension + # * remove_column (must supply a type) + # * remove_columns (must specify at least one column name or more) + # * remove_foreign_key (must supply a second table) + # * remove_index + # * remove_reference + # * remove_timestamps + # * rename_column + # * rename_index + # * rename_table + class CommandRecorder + ReversibleAndIrreversibleMethods = [:create_table, :create_join_table, :rename_table, :add_column, :remove_column, + :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, + :change_column_default, :add_reference, :remove_reference, :transaction, + :drop_join_table, :drop_table, :execute_block, :enable_extension, :disable_extension, + :change_column, :execute, :remove_columns, :change_column_null, + :add_foreign_key, :remove_foreign_key + ] + include JoinTable + + attr_accessor :commands, :delegate, :reverting + + def initialize(delegate = nil) + @commands = [] + @delegate = delegate + @reverting = false + end + + # While executing the given block, the recorded will be in reverting mode. + # All commands recorded will end up being recorded reverted + # and in reverse order. + # For example: + # + # recorder.revert{ recorder.record(:rename_table, [:old, :new]) } + # # same effect as recorder.record(:rename_table, [:new, :old]) + def revert + @reverting = !@reverting + previous = @commands + @commands = [] + yield + ensure + @commands = previous.concat(@commands.reverse) + @reverting = !@reverting + end + + # Record +command+. +command+ should be a method name and arguments. + # For example: + # + # recorder.record(:method_name, [:arg1, :arg2]) + def record(*command, &block) + if @reverting + @commands << inverse_of(*command, &block) + else + @commands << (command << block) + end + end + + # Returns the inverse of the given command. For example: + # + # recorder.inverse_of(:rename_table, [:old, :new]) + # # => [:rename_table, [:new, :old]] + # + # This method will raise an +IrreversibleMigration+ exception if it cannot + # invert the +command+. + def inverse_of(command, args, &block) + method = :"invert_#{command}" + raise IrreversibleMigration, <<-MSG.strip_heredoc unless respond_to?(method, true) + This migration uses #{command}, which is not automatically reversible. + To make the migration reversible you can either: + 1. Define #up and #down methods in place of the #change method. + 2. Use the #reversible method to define reversible behavior. + MSG + send(method, args, &block) + end + + ReversibleAndIrreversibleMethods.each do |method| + class_eval <<-EOV, __FILE__, __LINE__ + 1 + def #{method}(*args, &block) # def create_table(*args, &block) + record(:"#{method}", args, &block) # record(:create_table, args, &block) + end # end + EOV + end + alias :add_belongs_to :add_reference + alias :remove_belongs_to :remove_reference + + def change_table(table_name, options = {}) # :nodoc: + yield delegate.update_table_definition(table_name, self) + end + + private + + module StraightReversions # :nodoc: + private + { transaction: :transaction, + execute_block: :execute_block, + create_table: :drop_table, + create_join_table: :drop_join_table, + add_column: :remove_column, + add_timestamps: :remove_timestamps, + add_reference: :remove_reference, + enable_extension: :disable_extension + }.each do |cmd, inv| + [[inv, cmd], [cmd, inv]].uniq.each do |method, inverse| + class_eval <<-EOV, __FILE__, __LINE__ + 1 + def invert_#{method}(args, &block) # def invert_create_table(args, &block) + [:#{inverse}, args, block] # [:drop_table, args, block] + end # end + EOV + end + end + end + + include StraightReversions + + def invert_drop_table(args, &block) + if args.size == 1 && block == nil + raise ActiveRecord::IrreversibleMigration, "To avoid mistakes, drop_table is only reversible if given options or a block (can be empty)." + end + super + end + + def invert_rename_table(args) + [:rename_table, args.reverse] + end + + def invert_remove_column(args) + raise ActiveRecord::IrreversibleMigration, "remove_column is only reversible if given a type." if args.size <= 2 + super + end + + def invert_rename_index(args) + [:rename_index, [args.first] + args.last(2).reverse] + end + + def invert_rename_column(args) + [:rename_column, [args.first] + args.last(2).reverse] + end + + def invert_add_index(args) + table, columns, options = *args + options ||= {} + + options_hash = options.slice(:name, :algorithm) + options_hash[:column] = columns if !options_hash[:name] + + [:remove_index, [table, options_hash]] + end + + def invert_remove_index(args) + table, options_or_column = *args + if (options = options_or_column).is_a?(Hash) + unless options[:column] + raise ActiveRecord::IrreversibleMigration, "remove_index is only reversible if given a :column option." + end + options = options.dup + [:add_index, [table, options.delete(:column), options]] + elsif (column = options_or_column).present? + [:add_index, [table, column]] + end + end + + alias :invert_add_belongs_to :invert_add_reference + alias :invert_remove_belongs_to :invert_remove_reference + + def invert_change_column_default(args) + table, column, options = *args + + unless options && options.is_a?(Hash) && options.has_key?(:from) && options.has_key?(:to) + raise ActiveRecord::IrreversibleMigration, "change_column_default is only reversible if given a :from and :to option." + end + + [:change_column_default, [table, column, from: options[:to], to: options[:from]]] + end + + def invert_change_column_null(args) + args[2] = !args[2] + [:change_column_null, args] + end + + def invert_add_foreign_key(args) + from_table, to_table, add_options = args + add_options ||= {} + + if add_options[:name] + options = { name: add_options[:name] } + elsif add_options[:column] + options = { column: add_options[:column] } + else + options = to_table + end + + [:remove_foreign_key, [from_table, options]] + end + + def invert_remove_foreign_key(args) + from_table, to_table, remove_options = args + raise ActiveRecord::IrreversibleMigration, "remove_foreign_key is only reversible if given a second table" if to_table.nil? || to_table.is_a?(Hash) + + reversed_args = [from_table, to_table] + reversed_args << remove_options if remove_options + + [:add_foreign_key, reversed_args] + end + + def respond_to_missing?(method, _) + super || delegate.respond_to?(method) + end + + # Forwards any missing method call to the \target. + def method_missing(method, *args, &block) + if delegate.respond_to?(method) + delegate.public_send(method, *args, &block) + else + super + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/migration/compatibility.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/migration/compatibility.rb new file mode 100644 index 00000000..cb26cecd --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/migration/compatibility.rb @@ -0,0 +1,217 @@ +# frozen_string_literal: true + +module ActiveRecord + class Migration + module Compatibility # :nodoc: all + def self.find(version) + version = version.to_s + name = "V#{version.tr('.', '_')}" + unless const_defined?(name) + versions = constants.grep(/\AV[0-9_]+\z/).map { |s| s.to_s.delete("V").tr("_", ".").inspect } + raise ArgumentError, "Unknown migration version #{version.inspect}; expected one of #{versions.sort.join(', ')}" + end + const_get(name) + end + + V5_2 = Current + + class V5_1 < V5_2 + def change_column(table_name, column_name, type, options = {}) + if connection.adapter_name == "PostgreSQL" + super(table_name, column_name, type, options.except(:default, :null, :comment)) + connection.change_column_default(table_name, column_name, options[:default]) if options.key?(:default) + connection.change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null) + connection.change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment) + else + super + end + end + + def create_table(table_name, options = {}) + if connection.adapter_name == "Mysql2" + super(table_name, options: "ENGINE=InnoDB", **options) + else + super + end + end + end + + class V5_0 < V5_1 + module TableDefinition + def primary_key(name, type = :primary_key, **options) + type = :integer if type == :primary_key + super + end + + def references(*args, **options) + super(*args, type: :integer, **options) + end + alias :belongs_to :references + end + + def create_table(table_name, options = {}) + if connection.adapter_name == "PostgreSQL" + if options[:id] == :uuid && !options.key?(:default) + options[:default] = "uuid_generate_v4()" + end + end + + unless connection.adapter_name == "Mysql2" && options[:id] == :bigint + if [:integer, :bigint].include?(options[:id]) && !options.key?(:default) + options[:default] = nil + end + end + + # Since 5.1 PostgreSQL adapter uses bigserial type for primary + # keys by default and MySQL uses bigint. This compat layer makes old migrations utilize + # serial/int type instead -- the way it used to work before 5.1. + unless options.key?(:id) + options[:id] = :integer + end + + if block_given? + super do |t| + yield compatible_table_definition(t) + end + else + super + end + end + + def change_table(table_name, options = {}) + if block_given? + super do |t| + yield compatible_table_definition(t) + end + else + super + end + end + + def create_join_table(table_1, table_2, column_options: {}, **options) + column_options.reverse_merge!(type: :integer) + + if block_given? + super do |t| + yield compatible_table_definition(t) + end + else + super + end + end + + def add_column(table_name, column_name, type, options = {}) + if type == :primary_key + type = :integer + options[:primary_key] = true + end + super + end + + def add_reference(table_name, ref_name, **options) + super(table_name, ref_name, type: :integer, **options) + end + alias :add_belongs_to :add_reference + + private + def compatible_table_definition(t) + class << t + prepend TableDefinition + end + t + end + end + + class V4_2 < V5_0 + module TableDefinition + def references(*, **options) + options[:index] ||= false + super + end + alias :belongs_to :references + + def timestamps(**options) + options[:null] = true if options[:null].nil? + super + end + end + + def create_table(table_name, options = {}) + if block_given? + super do |t| + yield compatible_table_definition(t) + end + else + super + end + end + + def change_table(table_name, options = {}) + if block_given? + super do |t| + yield compatible_table_definition(t) + end + else + super + end + end + + def add_reference(*, **options) + options[:index] ||= false + super + end + alias :add_belongs_to :add_reference + + def add_timestamps(_, **options) + options[:null] = true if options[:null].nil? + super + end + + def index_exists?(table_name, column_name, options = {}) + column_names = Array(column_name).map(&:to_s) + options[:name] = + if options[:name].present? + options[:name].to_s + else + connection.index_name(table_name, column: column_names) + end + super + end + + def remove_index(table_name, options = {}) + options = { column: options } unless options.is_a?(Hash) + options[:name] = index_name_for_remove(table_name, options) + super(table_name, options) + end + + private + def compatible_table_definition(t) + class << t + prepend TableDefinition + end + super + end + + def index_name_for_remove(table_name, options = {}) + index_name = connection.index_name(table_name, options) + + unless connection.index_name_exists?(table_name, index_name) + if options.is_a?(Hash) && options.has_key?(:name) + options_without_column = options.dup + options_without_column.delete :column + index_name_without_column = connection.index_name(table_name, options_without_column) + + if connection.index_name_exists?(table_name, index_name_without_column) + return index_name_without_column + end + end + + raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist" + end + + index_name + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/migration/join_table.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/migration/join_table.rb new file mode 100644 index 00000000..9abb289b --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/migration/join_table.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActiveRecord + class Migration + module JoinTable #:nodoc: + private + + def find_join_table_name(table_1, table_2, options = {}) + options.delete(:table_name) || join_table_name(table_1, table_2) + end + + def join_table_name(table_1, table_2) + ModelSchema.derive_join_table_name(table_1, table_2).to_sym + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/model_schema.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/model_schema.rb new file mode 100644 index 00000000..0f1cfb88 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/model_schema.rb @@ -0,0 +1,521 @@ +# frozen_string_literal: true + +require "monitor" + +module ActiveRecord + module ModelSchema + extend ActiveSupport::Concern + + ## + # :singleton-method: primary_key_prefix_type + # :call-seq: primary_key_prefix_type + # + # The prefix type that will be prepended to every primary key column name. + # The options are +:table_name+ and +:table_name_with_underscore+. If the first is specified, + # the Product class will look for "productid" instead of "id" as the primary column. If the + # latter is specified, the Product class will look for "product_id" instead of "id". Remember + # that this is a global setting for all Active Records. + + ## + # :singleton-method: primary_key_prefix_type= + # :call-seq: primary_key_prefix_type=(prefix_type) + # + # Sets the prefix type that will be prepended to every primary key column name. + # The options are +:table_name+ and +:table_name_with_underscore+. If the first is specified, + # the Product class will look for "productid" instead of "id" as the primary column. If the + # latter is specified, the Product class will look for "product_id" instead of "id". Remember + # that this is a global setting for all Active Records. + + ## + # :singleton-method: table_name_prefix + # :call-seq: table_name_prefix + # + # The prefix string to prepend to every table name. + + ## + # :singleton-method: table_name_prefix= + # :call-seq: table_name_prefix=(prefix) + # + # Sets the prefix string to prepend to every table name. So if set to "basecamp_", all table + # names will be named like "basecamp_projects", "basecamp_people", etc. This is a convenient + # way of creating a namespace for tables in a shared database. By default, the prefix is the + # empty string. + # + # If you are organising your models within modules you can add a prefix to the models within + # a namespace by defining a singleton method in the parent module called table_name_prefix which + # returns your chosen prefix. + + ## + # :singleton-method: table_name_suffix + # :call-seq: table_name_suffix + # + # The suffix string to append to every table name. + + ## + # :singleton-method: table_name_suffix= + # :call-seq: table_name_suffix=(suffix) + # + # Works like +table_name_prefix=+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp", + # "people_basecamp"). By default, the suffix is the empty string. + # + # If you are organising your models within modules, you can add a suffix to the models within + # a namespace by defining a singleton method in the parent module called table_name_suffix which + # returns your chosen suffix. + + ## + # :singleton-method: schema_migrations_table_name + # :call-seq: schema_migrations_table_name + # + # The name of the schema migrations table. By default, the value is "schema_migrations". + + ## + # :singleton-method: schema_migrations_table_name= + # :call-seq: schema_migrations_table_name=(table_name) + # + # Sets the name of the schema migrations table. + + ## + # :singleton-method: internal_metadata_table_name + # :call-seq: internal_metadata_table_name + # + # The name of the internal metadata table. By default, the value is "ar_internal_metadata". + + ## + # :singleton-method: internal_metadata_table_name= + # :call-seq: internal_metadata_table_name=(table_name) + # + # Sets the name of the internal metadata table. + + ## + # :singleton-method: pluralize_table_names + # :call-seq: pluralize_table_names + # + # Indicates whether table names should be the pluralized versions of the corresponding class names. + # If true, the default table name for a Product class will be "products". If false, it would just be "product". + # See table_name for the full rules on table/class naming. This is true, by default. + + ## + # :singleton-method: pluralize_table_names= + # :call-seq: pluralize_table_names=(value) + # + # Set whether table names should be the pluralized versions of the corresponding class names. + # If true, the default table name for a Product class will be "products". If false, it would just be "product". + # See table_name for the full rules on table/class naming. This is true, by default. + + included do + mattr_accessor :primary_key_prefix_type, instance_writer: false + + class_attribute :table_name_prefix, instance_writer: false, default: "" + class_attribute :table_name_suffix, instance_writer: false, default: "" + class_attribute :schema_migrations_table_name, instance_accessor: false, default: "schema_migrations" + class_attribute :internal_metadata_table_name, instance_accessor: false, default: "ar_internal_metadata" + class_attribute :pluralize_table_names, instance_writer: false, default: true + + self.protected_environments = ["production"] + self.inheritance_column = "type" + self.ignored_columns = [].freeze + + delegate :type_for_attribute, to: :class + + initialize_load_schema_monitor + end + + # Derives the join table name for +first_table+ and +second_table+. The + # table names appear in alphabetical order. A common prefix is removed + # (useful for namespaced models like Music::Artist and Music::Record): + # + # artists, records => artists_records + # records, artists => artists_records + # music_artists, music_records => music_artists_records + def self.derive_join_table_name(first_table, second_table) # :nodoc: + [first_table.to_s, second_table.to_s].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_") + end + + module ClassMethods + # Guesses the table name (in forced lower-case) based on the name of the class in the + # inheritance hierarchy descending directly from ActiveRecord::Base. So if the hierarchy + # looks like: Reply < Message < ActiveRecord::Base, then Message is used + # to guess the table name even when called on Reply. The rules used to do the guess + # are handled by the Inflector class in Active Support, which knows almost all common + # English inflections. You can add new inflections in config/initializers/inflections.rb. + # + # Nested classes are given table names prefixed by the singular form of + # the parent's table name. Enclosing modules are not considered. + # + # ==== Examples + # + # class Invoice < ActiveRecord::Base + # end + # + # file class table_name + # invoice.rb Invoice invoices + # + # class Invoice < ActiveRecord::Base + # class Lineitem < ActiveRecord::Base + # end + # end + # + # file class table_name + # invoice.rb Invoice::Lineitem invoice_lineitems + # + # module Invoice + # class Lineitem < ActiveRecord::Base + # end + # end + # + # file class table_name + # invoice/lineitem.rb Invoice::Lineitem lineitems + # + # Additionally, the class-level +table_name_prefix+ is prepended and the + # +table_name_suffix+ is appended. So if you have "myapp_" as a prefix, + # the table name guess for an Invoice class becomes "myapp_invoices". + # Invoice::Lineitem becomes "myapp_invoice_lineitems". + # + # You can also set your own table name explicitly: + # + # class Mouse < ActiveRecord::Base + # self.table_name = "mice" + # end + def table_name + reset_table_name unless defined?(@table_name) + @table_name + end + + # Sets the table name explicitly. Example: + # + # class Project < ActiveRecord::Base + # self.table_name = "project" + # end + def table_name=(value) + value = value && value.to_s + + if defined?(@table_name) + return if value == @table_name + reset_column_information if connected? + end + + @table_name = value + @quoted_table_name = nil + @arel_table = nil + @sequence_name = nil unless defined?(@explicit_sequence_name) && @explicit_sequence_name + @predicate_builder = nil + end + + # Returns a quoted version of the table name, used to construct SQL statements. + def quoted_table_name + @quoted_table_name ||= connection.quote_table_name(table_name) + end + + # Computes the table name, (re)sets it internally, and returns it. + def reset_table_name #:nodoc: + self.table_name = if abstract_class? + superclass == Base ? nil : superclass.table_name + elsif superclass.abstract_class? + superclass.table_name || compute_table_name + else + compute_table_name + end + end + + def full_table_name_prefix #:nodoc: + (parents.detect { |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix + end + + def full_table_name_suffix #:nodoc: + (parents.detect { |p| p.respond_to?(:table_name_suffix) } || self).table_name_suffix + end + + # The array of names of environments where destructive actions should be prohibited. By default, + # the value is ["production"]. + def protected_environments + if defined?(@protected_environments) + @protected_environments + else + superclass.protected_environments + end + end + + # Sets an array of names of environments where destructive actions should be prohibited. + def protected_environments=(environments) + @protected_environments = environments.map(&:to_s) + end + + # Defines the name of the table column which will store the class name on single-table + # inheritance situations. + # + # The default inheritance column name is +type+, which means it's a + # reserved word inside Active Record. To be able to use single-table + # inheritance with another column name, or to use the column +type+ in + # your own model for something else, you can set +inheritance_column+: + # + # self.inheritance_column = 'zoink' + def inheritance_column + (@inheritance_column ||= nil) || superclass.inheritance_column + end + + # Sets the value of inheritance_column + def inheritance_column=(value) + @inheritance_column = value.to_s + @explicit_inheritance_column = true + end + + # The list of columns names the model should ignore. Ignored columns won't have attribute + # accessors defined, and won't be referenced in SQL queries. + def ignored_columns + if defined?(@ignored_columns) + @ignored_columns + else + superclass.ignored_columns + end + end + + # Sets the columns names the model should ignore. Ignored columns won't have attribute + # accessors defined, and won't be referenced in SQL queries. + def ignored_columns=(columns) + @ignored_columns = columns.map(&:to_s) + end + + def sequence_name + if base_class == self + @sequence_name ||= reset_sequence_name + else + (@sequence_name ||= nil) || base_class.sequence_name + end + end + + def reset_sequence_name #:nodoc: + @explicit_sequence_name = false + @sequence_name = connection.default_sequence_name(table_name, primary_key) + end + + # Sets the name of the sequence to use when generating ids to the given + # value, or (if the value is +nil+ or +false+) to the value returned by the + # given block. This is required for Oracle and is useful for any + # database which relies on sequences for primary key generation. + # + # If a sequence name is not explicitly set when using Oracle, + # it will default to the commonly used pattern of: #{table_name}_seq + # + # If a sequence name is not explicitly set when using PostgreSQL, it + # will discover the sequence corresponding to your primary key for you. + # + # class Project < ActiveRecord::Base + # self.sequence_name = "projectseq" # default would have been "project_seq" + # end + def sequence_name=(value) + @sequence_name = value.to_s + @explicit_sequence_name = true + end + + # Determines if the primary key values should be selected from their + # corresponding sequence before the insert statement. + def prefetch_primary_key? + connection.prefetch_primary_key?(table_name) + end + + # Returns the next value that will be used as the primary key on + # an insert statement. + def next_sequence_value + connection.next_sequence_value(sequence_name) + end + + # Indicates whether the table associated with this class exists + def table_exists? + connection.schema_cache.data_source_exists?(table_name) + end + + def attributes_builder # :nodoc: + unless defined?(@attributes_builder) && @attributes_builder + defaults = _default_attributes.except(*(column_names - [primary_key])) + @attributes_builder = ActiveModel::AttributeSet::Builder.new(attribute_types, defaults) + end + @attributes_builder + end + + def columns_hash # :nodoc: + load_schema + @columns_hash + end + + def columns + load_schema + @columns ||= columns_hash.values + end + + def attribute_types # :nodoc: + load_schema + @attribute_types ||= Hash.new(Type.default_value) + end + + def yaml_encoder # :nodoc: + @yaml_encoder ||= ActiveModel::AttributeSet::YAMLEncoder.new(attribute_types) + end + + # Returns the type of the attribute with the given name, after applying + # all modifiers. This method is the only valid source of information for + # anything related to the types of a model's attributes. This method will + # access the database and load the model's schema if it is required. + # + # The return value of this method will implement the interface described + # by ActiveModel::Type::Value (though the object itself may not subclass + # it). + # + # +attr_name+ The name of the attribute to retrieve the type for. Must be + # a string or a symbol. + def type_for_attribute(attr_name, &block) + attr_name = attr_name.to_s + if block + attribute_types.fetch(attr_name, &block) + else + attribute_types[attr_name] + end + end + + # Returns a hash where the keys are column names and the values are + # default values when instantiating the Active Record object for this table. + def column_defaults + load_schema + @column_defaults ||= _default_attributes.deep_dup.to_hash + end + + def _default_attributes # :nodoc: + load_schema + @default_attributes ||= ActiveModel::AttributeSet.new({}) + end + + # Returns an array of column names as strings. + def column_names + @column_names ||= columns.map(&:name) + end + + # Returns an array of column objects where the primary id, all columns ending in "_id" or "_count", + # and columns used for single table inheritance have been removed. + def content_columns + @content_columns ||= columns.reject do |c| + c.name == primary_key || + c.name == inheritance_column || + c.name.end_with?("_id") || + c.name.end_with?("_count") + end + end + + # Resets all the cached information about columns, which will cause them + # to be reloaded on the next request. + # + # The most common usage pattern for this method is probably in a migration, + # when just after creating a table you want to populate it with some default + # values, eg: + # + # class CreateJobLevels < ActiveRecord::Migration[5.0] + # def up + # create_table :job_levels do |t| + # t.integer :id + # t.string :name + # + # t.timestamps + # end + # + # JobLevel.reset_column_information + # %w{assistant executive manager director}.each do |type| + # JobLevel.create(name: type) + # end + # end + # + # def down + # drop_table :job_levels + # end + # end + def reset_column_information + connection.clear_cache! + ([self] + descendants).each(&:undefine_attribute_methods) + connection.schema_cache.clear_data_source_cache!(table_name) + + reload_schema_from_cache + initialize_find_by_cache + end + + protected + + def initialize_load_schema_monitor + @load_schema_monitor = Monitor.new + end + + private + + def inherited(child_class) + super + child_class.initialize_load_schema_monitor + end + + def schema_loaded? + defined?(@schema_loaded) && @schema_loaded + end + + def load_schema + return if schema_loaded? + @load_schema_monitor.synchronize do + return if defined?(@columns_hash) && @columns_hash + + load_schema! + + @schema_loaded = true + end + end + + def load_schema! + @columns_hash = connection.schema_cache.columns_hash(table_name).except(*ignored_columns) + @columns_hash.each do |name, column| + define_attribute( + name, + connection.lookup_cast_type_from_column(column), + default: column.default, + user_provided_default: false + ) + end + end + + def reload_schema_from_cache + @arel_table = nil + @column_names = nil + @attribute_types = nil + @content_columns = nil + @default_attributes = nil + @column_defaults = nil + @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column + @attributes_builder = nil + @columns = nil + @columns_hash = nil + @schema_loaded = false + @attribute_names = nil + @yaml_encoder = nil + direct_descendants.each do |descendant| + descendant.send(:reload_schema_from_cache) + end + end + + # Guesses the table name, but does not decorate it with prefix and suffix information. + def undecorated_table_name(class_name = base_class.name) + table_name = class_name.to_s.demodulize.underscore + pluralize_table_names ? table_name.pluralize : table_name + end + + # Computes and returns a table name according to default conventions. + def compute_table_name + base = base_class + if self == base + # Nested classes are prefixed with singular parent table name. + if parent < Base && !parent.abstract_class? + contained = parent.table_name + contained = contained.singularize if parent.pluralize_table_names + contained += "_" + end + + "#{full_table_name_prefix}#{contained}#{undecorated_table_name(name)}#{full_table_name_suffix}" + else + # STI subclasses always use their superclass' table. + base.table_name + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/nested_attributes.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/nested_attributes.rb new file mode 100644 index 00000000..fa20bce3 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/nested_attributes.rb @@ -0,0 +1,600 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/except" +require "active_support/core_ext/module/redefine_method" +require "active_support/core_ext/object/try" +require "active_support/core_ext/hash/indifferent_access" + +module ActiveRecord + module NestedAttributes #:nodoc: + class TooManyRecords < ActiveRecordError + end + + extend ActiveSupport::Concern + + included do + class_attribute :nested_attributes_options, instance_writer: false, default: {} + end + + # = Active Record Nested Attributes + # + # Nested attributes allow you to save attributes on associated records + # through the parent. By default nested attribute updating is turned off + # and you can enable it using the accepts_nested_attributes_for class + # method. When you enable nested attributes an attribute writer is + # defined on the model. + # + # The attribute writer is named after the association, which means that + # in the following example, two new methods are added to your model: + # + # author_attributes=(attributes) and + # pages_attributes=(attributes). + # + # class Book < ActiveRecord::Base + # has_one :author + # has_many :pages + # + # accepts_nested_attributes_for :author, :pages + # end + # + # Note that the :autosave option is automatically enabled on every + # association that accepts_nested_attributes_for is used for. + # + # === One-to-one + # + # Consider a Member model that has one Avatar: + # + # class Member < ActiveRecord::Base + # has_one :avatar + # accepts_nested_attributes_for :avatar + # end + # + # Enabling nested attributes on a one-to-one association allows you to + # create the member and avatar in one go: + # + # params = { member: { name: 'Jack', avatar_attributes: { icon: 'smiling' } } } + # member = Member.create(params[:member]) + # member.avatar.id # => 2 + # member.avatar.icon # => 'smiling' + # + # It also allows you to update the avatar through the member: + # + # params = { member: { avatar_attributes: { id: '2', icon: 'sad' } } } + # member.update params[:member] + # member.avatar.icon # => 'sad' + # + # If you want to update the current avatar without providing the id, you must add :update_only option. + # + # class Member < ActiveRecord::Base + # has_one :avatar + # accepts_nested_attributes_for :avatar, update_only: true + # end + # + # params = { member: { avatar_attributes: { icon: 'sad' } } } + # member.update params[:member] + # member.avatar.id # => 2 + # member.avatar.icon # => 'sad' + # + # By default you will only be able to set and update attributes on the + # associated model. If you want to destroy the associated model through the + # attributes hash, you have to enable it first using the + # :allow_destroy option. + # + # class Member < ActiveRecord::Base + # has_one :avatar + # accepts_nested_attributes_for :avatar, allow_destroy: true + # end + # + # Now, when you add the _destroy key to the attributes hash, with a + # value that evaluates to +true+, you will destroy the associated model: + # + # member.avatar_attributes = { id: '2', _destroy: '1' } + # member.avatar.marked_for_destruction? # => true + # member.save + # member.reload.avatar # => nil + # + # Note that the model will _not_ be destroyed until the parent is saved. + # + # Also note that the model will not be destroyed unless you also specify + # its id in the updated hash. + # + # === One-to-many + # + # Consider a member that has a number of posts: + # + # class Member < ActiveRecord::Base + # has_many :posts + # accepts_nested_attributes_for :posts + # end + # + # You can now set or update attributes on the associated posts through + # an attribute hash for a member: include the key +:posts_attributes+ + # with an array of hashes of post attributes as a value. + # + # For each hash that does _not_ have an id key a new record will + # be instantiated, unless the hash also contains a _destroy key + # that evaluates to +true+. + # + # params = { member: { + # name: 'joe', posts_attributes: [ + # { title: 'Kari, the awesome Ruby documentation browser!' }, + # { title: 'The egalitarian assumption of the modern citizen' }, + # { title: '', _destroy: '1' } # this will be ignored + # ] + # }} + # + # member = Member.create(params[:member]) + # member.posts.length # => 2 + # member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!' + # member.posts.second.title # => 'The egalitarian assumption of the modern citizen' + # + # You may also set a +:reject_if+ proc to silently ignore any new record + # hashes if they fail to pass your criteria. For example, the previous + # example could be rewritten as: + # + # class Member < ActiveRecord::Base + # has_many :posts + # accepts_nested_attributes_for :posts, reject_if: proc { |attributes| attributes['title'].blank? } + # end + # + # params = { member: { + # name: 'joe', posts_attributes: [ + # { title: 'Kari, the awesome Ruby documentation browser!' }, + # { title: 'The egalitarian assumption of the modern citizen' }, + # { title: '' } # this will be ignored because of the :reject_if proc + # ] + # }} + # + # member = Member.create(params[:member]) + # member.posts.length # => 2 + # member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!' + # member.posts.second.title # => 'The egalitarian assumption of the modern citizen' + # + # Alternatively, +:reject_if+ also accepts a symbol for using methods: + # + # class Member < ActiveRecord::Base + # has_many :posts + # accepts_nested_attributes_for :posts, reject_if: :new_record? + # end + # + # class Member < ActiveRecord::Base + # has_many :posts + # accepts_nested_attributes_for :posts, reject_if: :reject_posts + # + # def reject_posts(attributes) + # attributes['title'].blank? + # end + # end + # + # If the hash contains an id key that matches an already + # associated record, the matching record will be modified: + # + # member.attributes = { + # name: 'Joe', + # posts_attributes: [ + # { id: 1, title: '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' }, + # { id: 2, title: '[UPDATED] other post' } + # ] + # } + # + # member.posts.first.title # => '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' + # member.posts.second.title # => '[UPDATED] other post' + # + # However, the above applies if the parent model is being updated as well. + # For example, If you wanted to create a +member+ named _joe_ and wanted to + # update the +posts+ at the same time, that would give an + # ActiveRecord::RecordNotFound error. + # + # By default the associated records are protected from being destroyed. If + # you want to destroy any of the associated records through the attributes + # hash, you have to enable it first using the :allow_destroy + # option. This will allow you to also use the _destroy key to + # destroy existing records: + # + # class Member < ActiveRecord::Base + # has_many :posts + # accepts_nested_attributes_for :posts, allow_destroy: true + # end + # + # params = { member: { + # posts_attributes: [{ id: '2', _destroy: '1' }] + # }} + # + # member.attributes = params[:member] + # member.posts.detect { |p| p.id == 2 }.marked_for_destruction? # => true + # member.posts.length # => 2 + # member.save + # member.reload.posts.length # => 1 + # + # Nested attributes for an associated collection can also be passed in + # the form of a hash of hashes instead of an array of hashes: + # + # Member.create( + # name: 'joe', + # posts_attributes: { + # first: { title: 'Foo' }, + # second: { title: 'Bar' } + # } + # ) + # + # has the same effect as + # + # Member.create( + # name: 'joe', + # posts_attributes: [ + # { title: 'Foo' }, + # { title: 'Bar' } + # ] + # ) + # + # The keys of the hash which is the value for +:posts_attributes+ are + # ignored in this case. + # However, it is not allowed to use 'id' or :id for one of + # such keys, otherwise the hash will be wrapped in an array and + # interpreted as an attribute hash for a single post. + # + # Passing attributes for an associated collection in the form of a hash + # of hashes can be used with hashes generated from HTTP/HTML parameters, + # where there may be no natural way to submit an array of hashes. + # + # === Saving + # + # All changes to models, including the destruction of those marked for + # destruction, are saved and destroyed automatically and atomically when + # the parent model is saved. This happens inside the transaction initiated + # by the parent's save method. See ActiveRecord::AutosaveAssociation. + # + # === Validating the presence of a parent model + # + # If you want to validate that a child record is associated with a parent + # record, you can use the +validates_presence_of+ method and the +:inverse_of+ + # key as this example illustrates: + # + # class Member < ActiveRecord::Base + # has_many :posts, inverse_of: :member + # accepts_nested_attributes_for :posts + # end + # + # class Post < ActiveRecord::Base + # belongs_to :member, inverse_of: :posts + # validates_presence_of :member + # end + # + # Note that if you do not specify the +:inverse_of+ option, then + # Active Record will try to automatically guess the inverse association + # based on heuristics. + # + # For one-to-one nested associations, if you build the new (in-memory) + # child object yourself before assignment, then this module will not + # overwrite it, e.g.: + # + # class Member < ActiveRecord::Base + # has_one :avatar + # accepts_nested_attributes_for :avatar + # + # def avatar + # super || build_avatar(width: 200) + # end + # end + # + # member = Member.new + # member.avatar_attributes = {icon: 'sad'} + # member.avatar.width # => 200 + module ClassMethods + REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == "_destroy" || value.blank? } } + + # Defines an attributes writer for the specified association(s). + # + # Supported options: + # [:allow_destroy] + # If true, destroys any members from the attributes hash with a + # _destroy key and a value that evaluates to +true+ + # (eg. 1, '1', true, or 'true'). This option is off by default. + # [:reject_if] + # Allows you to specify a Proc or a Symbol pointing to a method + # that checks whether a record should be built for a certain attribute + # hash. The hash is passed to the supplied Proc or the method + # and it should return either +true+ or +false+. When no +:reject_if+ + # is specified, a record will be built for all attribute hashes that + # do not have a _destroy value that evaluates to true. + # Passing :all_blank instead of a Proc will create a proc + # that will reject a record where all the attributes are blank excluding + # any value for +_destroy+. + # [:limit] + # Allows you to specify the maximum number of associated records that + # can be processed with the nested attributes. Limit also can be specified + # as a Proc or a Symbol pointing to a method that should return a number. + # If the size of the nested attributes array exceeds the specified limit, + # NestedAttributes::TooManyRecords exception is raised. If omitted, any + # number of associations can be processed. + # Note that the +:limit+ option is only applicable to one-to-many + # associations. + # [:update_only] + # For a one-to-one association, this option allows you to specify how + # nested attributes are going to be used when an associated record already + # exists. In general, an existing record may either be updated with the + # new set of attribute values or be replaced by a wholly new record + # containing those values. By default the +:update_only+ option is +false+ + # and the nested attributes are used to update the existing record only + # if they include the record's :id value. Otherwise a new + # record will be instantiated and used to replace the existing one. + # However if the +:update_only+ option is +true+, the nested attributes + # are used to update the record's attributes always, regardless of + # whether the :id is present. The option is ignored for collection + # associations. + # + # Examples: + # # creates avatar_attributes= + # accepts_nested_attributes_for :avatar, reject_if: proc { |attributes| attributes['name'].blank? } + # # creates avatar_attributes= + # accepts_nested_attributes_for :avatar, reject_if: :all_blank + # # creates avatar_attributes= and posts_attributes= + # accepts_nested_attributes_for :avatar, :posts, allow_destroy: true + def accepts_nested_attributes_for(*attr_names) + options = { allow_destroy: false, update_only: false } + options.update(attr_names.extract_options!) + options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only) + options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank + + attr_names.each do |association_name| + if reflection = _reflect_on_association(association_name) + reflection.autosave = true + define_autosave_validation_callbacks(reflection) + + nested_attributes_options = self.nested_attributes_options.dup + nested_attributes_options[association_name.to_sym] = options + self.nested_attributes_options = nested_attributes_options + + type = (reflection.collection? ? :collection : :one_to_one) + generate_association_writer(association_name, type) + else + raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?" + end + end + end + + private + + # Generates a writer method for this association. Serves as a point for + # accessing the objects in the association. For example, this method + # could generate the following: + # + # def pirate_attributes=(attributes) + # assign_nested_attributes_for_one_to_one_association(:pirate, attributes) + # end + # + # This redirects the attempts to write objects in an association through + # the helper methods defined below. Makes it seem like the nested + # associations are just regular associations. + def generate_association_writer(association_name, type) + generated_association_methods.module_eval <<-eoruby, __FILE__, __LINE__ + 1 + silence_redefinition_of_method :#{association_name}_attributes= + def #{association_name}_attributes=(attributes) + assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes) + end + eoruby + end + end + + # Returns ActiveRecord::AutosaveAssociation::marked_for_destruction? It's + # used in conjunction with fields_for to build a form element for the + # destruction of this association. + # + # See ActionView::Helpers::FormHelper::fields_for for more info. + def _destroy + marked_for_destruction? + end + + private + + # Attribute hash keys that should not be assigned as normal attributes. + # These hash keys are nested attributes implementation details. + UNASSIGNABLE_KEYS = %w( id _destroy ) + + # Assigns the given attributes to the association. + # + # If an associated record does not yet exist, one will be instantiated. If + # an associated record already exists, the method's behavior depends on + # the value of the update_only option. If update_only is +false+ and the + # given attributes include an :id that matches the existing record's + # id, then the existing record will be modified. If no :id is provided + # it will be replaced with a new record. If update_only is +true+ the existing + # record will be modified regardless of whether an :id is provided. + # + # If the given attributes include a matching :id attribute, or + # update_only is true, and a :_destroy key set to a truthy value, + # then the existing record will be marked for destruction. + def assign_nested_attributes_for_one_to_one_association(association_name, attributes) + options = nested_attributes_options[association_name] + if attributes.respond_to?(:permitted?) + attributes = attributes.to_h + end + attributes = attributes.with_indifferent_access + existing_record = send(association_name) + + if (options[:update_only] || !attributes["id"].blank?) && existing_record && + (options[:update_only] || existing_record.id.to_s == attributes["id"].to_s) + assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes) + + elsif attributes["id"].present? + raise_nested_attributes_record_not_found!(association_name, attributes["id"]) + + elsif !reject_new_record?(association_name, attributes) + assignable_attributes = attributes.except(*UNASSIGNABLE_KEYS) + + if existing_record && existing_record.new_record? + existing_record.assign_attributes(assignable_attributes) + association(association_name).initialize_attributes(existing_record) + else + method = "build_#{association_name}" + if respond_to?(method) + send(method, assignable_attributes) + else + raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?" + end + end + end + end + + # Assigns the given attributes to the collection association. + # + # Hashes with an :id value matching an existing associated record + # will update that record. Hashes without an :id value will build + # a new record for the association. Hashes with a matching :id + # value and a :_destroy key set to a truthy value will mark the + # matched record for destruction. + # + # For example: + # + # assign_nested_attributes_for_collection_association(:people, { + # '1' => { id: '1', name: 'Peter' }, + # '2' => { name: 'John' }, + # '3' => { id: '2', _destroy: true } + # }) + # + # Will update the name of the Person with ID 1, build a new associated + # person with the name 'John', and mark the associated Person with ID 2 + # for destruction. + # + # Also accepts an Array of attribute hashes: + # + # assign_nested_attributes_for_collection_association(:people, [ + # { id: '1', name: 'Peter' }, + # { name: 'John' }, + # { id: '2', _destroy: true } + # ]) + def assign_nested_attributes_for_collection_association(association_name, attributes_collection) + options = nested_attributes_options[association_name] + if attributes_collection.respond_to?(:permitted?) + attributes_collection = attributes_collection.to_h + end + + unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array) + raise ArgumentError, "Hash or Array expected for attribute `#{association_name}`, got #{attributes_collection.class.name} (#{attributes_collection.inspect})" + end + + check_record_limit!(options[:limit], attributes_collection) + + if attributes_collection.is_a? Hash + keys = attributes_collection.keys + attributes_collection = if keys.include?("id") || keys.include?(:id) + [attributes_collection] + else + attributes_collection.values + end + end + + association = association(association_name) + + existing_records = if association.loaded? + association.target + else + attribute_ids = attributes_collection.map { |a| a["id"] || a[:id] }.compact + attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids) + end + + attributes_collection.each do |attributes| + if attributes.respond_to?(:permitted?) + attributes = attributes.to_h + end + attributes = attributes.with_indifferent_access + + if attributes["id"].blank? + unless reject_new_record?(association_name, attributes) + association.build(attributes.except(*UNASSIGNABLE_KEYS)) + end + elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes["id"].to_s } + unless call_reject_if(association_name, attributes) + # Make sure we are operating on the actual object which is in the association's + # proxy_target array (either by finding it, or adding it if not found) + # Take into account that the proxy_target may have changed due to callbacks + target_record = association.target.detect { |record| record.id.to_s == attributes["id"].to_s } + if target_record + existing_record = target_record + else + association.add_to_target(existing_record, :skip_callbacks) + end + + assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) + end + else + raise_nested_attributes_record_not_found!(association_name, attributes["id"]) + end + end + end + + # Takes in a limit and checks if the attributes_collection has too many + # records. It accepts limit in the form of symbol, proc, or + # number-like object (anything that can be compared with an integer). + # + # Raises TooManyRecords error if the attributes_collection is + # larger than the limit. + def check_record_limit!(limit, attributes_collection) + if limit + limit = \ + case limit + when Symbol + send(limit) + when Proc + limit.call + else + limit + end + + if limit && attributes_collection.size > limit + raise TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead." + end + end + end + + # Updates a record with the +attributes+ or marks it for destruction if + # +allow_destroy+ is +true+ and has_destroy_flag? returns +true+. + def assign_to_or_mark_for_destruction(record, attributes, allow_destroy) + record.assign_attributes(attributes.except(*UNASSIGNABLE_KEYS)) + record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy + end + + # Determines if a hash contains a truthy _destroy key. + def has_destroy_flag?(hash) + Type::Boolean.new.cast(hash["_destroy"]) + end + + # Determines if a new record should be rejected by checking + # has_destroy_flag? or if a :reject_if proc exists for this + # association and evaluates to +true+. + def reject_new_record?(association_name, attributes) + will_be_destroyed?(association_name, attributes) || call_reject_if(association_name, attributes) + end + + # Determines if a record with the particular +attributes+ should be + # rejected by calling the reject_if Symbol or Proc (if defined). + # The reject_if option is defined by +accepts_nested_attributes_for+. + # + # Returns false if there is a +destroy_flag+ on the attributes. + def call_reject_if(association_name, attributes) + return false if will_be_destroyed?(association_name, attributes) + + case callback = nested_attributes_options[association_name][:reject_if] + when Symbol + method(callback).arity == 0 ? send(callback) : send(callback, attributes) + when Proc + callback.call(attributes) + end + end + + # Only take into account the destroy flag if :allow_destroy is true + def will_be_destroyed?(association_name, attributes) + allow_destroy?(association_name) && has_destroy_flag?(attributes) + end + + def allow_destroy?(association_name) + nested_attributes_options[association_name][:allow_destroy] + end + + def raise_nested_attributes_record_not_found!(association_name, record_id) + model = self.class._reflect_on_association(association_name).klass.name + raise RecordNotFound.new("Couldn't find #{model} with ID=#{record_id} for #{self.class.name} with ID=#{id}", + model, "id", record_id) + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/no_touching.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/no_touching.rb new file mode 100644 index 00000000..754c8918 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/no_touching.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record No Touching + module NoTouching + extend ActiveSupport::Concern + + module ClassMethods + # Lets you selectively disable calls to +touch+ for the + # duration of a block. + # + # ==== Examples + # ActiveRecord::Base.no_touching do + # Project.first.touch # does nothing + # Message.first.touch # does nothing + # end + # + # Project.no_touching do + # Project.first.touch # does nothing + # Message.first.touch # works, but does not touch the associated project + # end + # + def no_touching(&block) + NoTouching.apply_to(self, &block) + end + end + + class << self + def apply_to(klass) #:nodoc: + klasses.push(klass) + yield + ensure + klasses.pop + end + + def applied_to?(klass) #:nodoc: + klasses.any? { |k| k >= klass } + end + + private + def klasses + Thread.current[:no_touching_classes] ||= [] + end + end + + def no_touching? + NoTouching.applied_to?(self.class) + end + + def touch_later(*) # :nodoc: + super unless no_touching? + end + + def touch(*) # :nodoc: + super unless no_touching? + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/null_relation.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/null_relation.rb new file mode 100644 index 00000000..cf0de0fd --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/null_relation.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +module ActiveRecord + module NullRelation # :nodoc: + def pluck(*column_names) + [] + end + + def delete_all + 0 + end + + def update_all(_updates) + 0 + end + + def delete(_id_or_array) + 0 + end + + def empty? + true + end + + def none? + true + end + + def any? + false + end + + def one? + false + end + + def many? + false + end + + def to_sql + "" + end + + def calculate(operation, _column_name) + case operation + when :count, :sum + group_values.any? ? Hash.new : 0 + when :average, :minimum, :maximum + group_values.any? ? Hash.new : nil + end + end + + def exists?(_conditions = :none) + false + end + + def or(other) + other.spawn + end + + private + + def exec_queries + @records = [].freeze + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/persistence.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/persistence.rb new file mode 100644 index 00000000..175f0c08 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/persistence.rb @@ -0,0 +1,763 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record \Persistence + module Persistence + extend ActiveSupport::Concern + + module ClassMethods + # Creates an object (or multiple objects) and saves it to the database, if validations pass. + # The resulting object is returned whether the object was saved successfully to the database or not. + # + # The +attributes+ parameter can be either a Hash or an Array of Hashes. These Hashes describe the + # attributes on the objects that are to be created. + # + # ==== Examples + # # Create a single new object + # User.create(first_name: 'Jamie') + # + # # Create an Array of new objects + # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) + # + # # Create a single object and pass it into a block to set other attributes. + # User.create(first_name: 'Jamie') do |u| + # u.is_admin = false + # end + # + # # Creating an Array of new objects using a block, where the block is executed for each object: + # User.create([{ first_name: 'Jamie' }, { first_name: 'Jeremy' }]) do |u| + # u.is_admin = false + # end + def create(attributes = nil, &block) + if attributes.is_a?(Array) + attributes.collect { |attr| create(attr, &block) } + else + object = new(attributes, &block) + object.save + object + end + end + + # Creates an object (or multiple objects) and saves it to the database, + # if validations pass. Raises a RecordInvalid error if validations fail, + # unlike Base#create. + # + # The +attributes+ parameter can be either a Hash or an Array of Hashes. + # These describe which attributes to be created on the object, or + # multiple objects when given an Array of Hashes. + def create!(attributes = nil, &block) + if attributes.is_a?(Array) + attributes.collect { |attr| create!(attr, &block) } + else + object = new(attributes, &block) + object.save! + object + end + end + + # Given an attributes hash, +instantiate+ returns a new instance of + # the appropriate class. Accepts only keys as strings. + # + # For example, +Post.all+ may return Comments, Messages, and Emails + # by storing the record's subclass in a +type+ attribute. By calling + # +instantiate+ instead of +new+, finder methods ensure they get new + # instances of the appropriate class for each record. + # + # See ActiveRecord::Inheritance#discriminate_class_for_record to see + # how this "single-table" inheritance mapping is implemented. + def instantiate(attributes, column_types = {}, &block) + klass = discriminate_class_for_record(attributes) + attributes = klass.attributes_builder.build_from_database(attributes, column_types) + klass.allocate.init_with("attributes" => attributes, "new_record" => false, &block) + end + + # Updates an object (or multiple objects) and saves it to the database, if validations pass. + # The resulting object is returned whether the object was saved successfully to the database or not. + # + # ==== Parameters + # + # * +id+ - This should be the id or an array of ids to be updated. + # * +attributes+ - This should be a hash of attributes or an array of hashes. + # + # ==== Examples + # + # # Updates one record + # Person.update(15, user_name: "Samuel", group: "expert") + # + # # Updates multiple records + # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } } + # Person.update(people.keys, people.values) + # + # # Updates multiple records from the result of a relation + # people = Person.where(group: "expert") + # people.update(group: "masters") + # + # Note: Updating a large number of records will run an UPDATE + # query for each record, which may cause a performance issue. + # When running callbacks is not needed for each record update, + # it is preferred to use {update_all}[rdoc-ref:Relation#update_all] + # for updating all records in a single query. + def update(id = :all, attributes) + if id.is_a?(Array) + id.map { |one_id| find(one_id) }.each_with_index { |object, idx| + object.update(attributes[idx]) + } + elsif id == :all + all.each { |record| record.update(attributes) } + else + if ActiveRecord::Base === id + raise ArgumentError, + "You are passing an instance of ActiveRecord::Base to `update`. " \ + "Please pass the id of the object by calling `.id`." + end + object = find(id) + object.update(attributes) + object + end + end + + # Destroy an object (or multiple objects) that has the given id. The object is instantiated first, + # therefore all callbacks and filters are fired off before the object is deleted. This method is + # less efficient than #delete but allows cleanup methods and other actions to be run. + # + # This essentially finds the object (or multiple objects) with the given id, creates a new object + # from the attributes, and then calls destroy on it. + # + # ==== Parameters + # + # * +id+ - This should be the id or an array of ids to be destroyed. + # + # ==== Examples + # + # # Destroy a single object + # Todo.destroy(1) + # + # # Destroy multiple objects + # todos = [1,2,3] + # Todo.destroy(todos) + def destroy(id) + if id.is_a?(Array) + find(id).each(&:destroy) + else + find(id).destroy + end + end + + # Deletes the row with a primary key matching the +id+ argument, using a + # SQL +DELETE+ statement, and returns the number of rows deleted. Active + # Record objects are not instantiated, so the object's callbacks are not + # executed, including any :dependent association options. + # + # You can delete multiple rows at once by passing an Array of ids. + # + # Note: Although it is often much faster than the alternative, #destroy, + # skipping callbacks might bypass business logic in your application + # that ensures referential integrity or performs other essential jobs. + # + # ==== Examples + # + # # Delete a single row + # Todo.delete(1) + # + # # Delete multiple rows + # Todo.delete([2,3,4]) + def delete(id_or_array) + where(primary_key => id_or_array).delete_all + end + + def _insert_record(values) # :nodoc: + primary_key_value = nil + + if primary_key && Hash === values + primary_key_value = values[primary_key] + + if !primary_key_value && prefetch_primary_key? + primary_key_value = next_sequence_value + values[primary_key] = primary_key_value + end + end + + if values.empty? + im = arel_table.compile_insert(connection.empty_insert_statement_value) + im.into arel_table + else + im = arel_table.compile_insert(_substitute_values(values)) + end + + connection.insert(im, "#{self} Create", primary_key || false, primary_key_value) + end + + def _update_record(values, constraints) # :nodoc: + constraints = _substitute_values(constraints).map { |attr, bind| attr.eq(bind) } + + um = arel_table.where( + constraints.reduce(&:and) + ).compile_update(_substitute_values(values), primary_key) + + connection.update(um, "#{self} Update") + end + + def _delete_record(constraints) # :nodoc: + constraints = _substitute_values(constraints).map { |attr, bind| attr.eq(bind) } + + dm = Arel::DeleteManager.new + dm.from(arel_table) + dm.wheres = constraints + + connection.delete(dm, "#{self} Destroy") + end + + private + # Called by +instantiate+ to decide which class to use for a new + # record instance. + # + # See +ActiveRecord::Inheritance#discriminate_class_for_record+ for + # the single-table inheritance discriminator. + def discriminate_class_for_record(record) + self + end + + def _substitute_values(values) + values.map do |name, value| + attr = arel_attribute(name) + bind = predicate_builder.build_bind_attribute(name, value) + [attr, bind] + end + end + end + + # Returns true if this object hasn't been saved yet -- that is, a record + # for the object doesn't exist in the database yet; otherwise, returns false. + def new_record? + sync_with_transaction_state + @new_record + end + + # Returns true if this object has been destroyed, otherwise returns false. + def destroyed? + sync_with_transaction_state + @destroyed + end + + # Returns true if the record is persisted, i.e. it's not a new record and it was + # not destroyed, otherwise returns false. + def persisted? + sync_with_transaction_state + !(@new_record || @destroyed) + end + + ## + # :call-seq: + # save(*args) + # + # Saves the model. + # + # If the model is new, a record gets created in the database, otherwise + # the existing record gets updated. + # + # By default, save always runs validations. If any of them fail the action + # is cancelled and #save returns +false+, and the record won't be saved. However, if you supply + # validate: false, validations are bypassed altogether. See + # ActiveRecord::Validations for more information. + # + # By default, #save also sets the +updated_at+/+updated_on+ attributes to + # the current time. However, if you supply touch: false, these + # timestamps will not be updated. + # + # There's a series of callbacks associated with #save. If any of the + # before_* callbacks throws +:abort+ the action is cancelled and + # #save returns +false+. See ActiveRecord::Callbacks for further + # details. + # + # Attributes marked as readonly are silently ignored if the record is + # being updated. + def save(*args, &block) + create_or_update(*args, &block) + rescue ActiveRecord::RecordInvalid + false + end + + ## + # :call-seq: + # save!(*args) + # + # Saves the model. + # + # If the model is new, a record gets created in the database, otherwise + # the existing record gets updated. + # + # By default, #save! always runs validations. If any of them fail + # ActiveRecord::RecordInvalid gets raised, and the record won't be saved. However, if you supply + # validate: false, validations are bypassed altogether. See + # ActiveRecord::Validations for more information. + # + # By default, #save! also sets the +updated_at+/+updated_on+ attributes to + # the current time. However, if you supply touch: false, these + # timestamps will not be updated. + # + # There's a series of callbacks associated with #save!. If any of + # the before_* callbacks throws +:abort+ the action is cancelled + # and #save! raises ActiveRecord::RecordNotSaved. See + # ActiveRecord::Callbacks for further details. + # + # Attributes marked as readonly are silently ignored if the record is + # being updated. + # + # Unless an error is raised, returns true. + def save!(*args, &block) + create_or_update(*args, &block) || raise(RecordNotSaved.new("Failed to save the record", self)) + end + + # Deletes the record in the database and freezes this instance to + # reflect that no changes should be made (since they can't be + # persisted). Returns the frozen instance. + # + # The row is simply removed with an SQL +DELETE+ statement on the + # record's primary key, and no callbacks are executed. + # + # Note that this will also delete records marked as {#readonly?}[rdoc-ref:Core#readonly?]. + # + # To enforce the object's +before_destroy+ and +after_destroy+ + # callbacks or any :dependent association + # options, use #destroy. + def delete + _delete_row if persisted? + @destroyed = true + freeze + end + + # Deletes the record in the database and freezes this instance to reflect + # that no changes should be made (since they can't be persisted). + # + # There's a series of callbacks associated with #destroy. If the + # before_destroy callback throws +:abort+ the action is cancelled + # and #destroy returns +false+. + # See ActiveRecord::Callbacks for further details. + def destroy + _raise_readonly_record_error if readonly? + destroy_associations + self.class.connection.add_transaction_record(self) + @_trigger_destroy_callback = if persisted? + destroy_row > 0 + else + true + end + @destroyed = true + freeze + end + + # Deletes the record in the database and freezes this instance to reflect + # that no changes should be made (since they can't be persisted). + # + # There's a series of callbacks associated with #destroy!. If the + # before_destroy callback throws +:abort+ the action is cancelled + # and #destroy! raises ActiveRecord::RecordNotDestroyed. + # See ActiveRecord::Callbacks for further details. + def destroy! + destroy || _raise_record_not_destroyed + end + + # Returns an instance of the specified +klass+ with the attributes of the + # current record. This is mostly useful in relation to single-table + # inheritance structures where you want a subclass to appear as the + # superclass. This can be used along with record identification in + # Action Pack to allow, say, Client < Company to do something + # like render partial: @client.becomes(Company) to render that + # instance using the companies/company partial instead of clients/client. + # + # Note: The new instance will share a link to the same attributes as the original class. + # Therefore the sti column value will still be the same. + # Any change to the attributes on either instance will affect both instances. + # If you want to change the sti column as well, use #becomes! instead. + def becomes(klass) + became = klass.allocate + became.send(:initialize) + became.instance_variable_set("@attributes", @attributes) + became.instance_variable_set("@mutations_from_database", @mutations_from_database ||= nil) + became.instance_variable_set("@changed_attributes", attributes_changed_by_setter) + became.instance_variable_set("@new_record", new_record?) + became.instance_variable_set("@destroyed", destroyed?) + became.errors.copy!(errors) + became + end + + # Wrapper around #becomes that also changes the instance's sti column value. + # This is especially useful if you want to persist the changed class in your + # database. + # + # Note: The old instance's sti column value will be changed too, as both objects + # share the same set of attributes. + def becomes!(klass) + became = becomes(klass) + sti_type = nil + if !klass.descends_from_active_record? + sti_type = klass.sti_name + end + became.public_send("#{klass.inheritance_column}=", sti_type) + became + end + + # Updates a single attribute and saves the record. + # This is especially useful for boolean flags on existing records. Also note that + # + # * Validation is skipped. + # * \Callbacks are invoked. + # * updated_at/updated_on column is updated if that column is available. + # * Updates all the attributes that are dirty in this object. + # + # This method raises an ActiveRecord::ActiveRecordError if the + # attribute is marked as readonly. + # + # Also see #update_column. + def update_attribute(name, value) + name = name.to_s + verify_readonly_attribute(name) + public_send("#{name}=", value) + + save(validate: false) + end + + # Updates the attributes of the model from the passed-in hash and saves the + # record, all wrapped in a transaction. If the object is invalid, the saving + # will fail and false will be returned. + def update(attributes) + # The following transaction covers any possible database side-effects of the + # attributes assignment. For example, setting the IDs of a child collection. + with_transaction_returning_status do + assign_attributes(attributes) + save + end + end + + alias update_attributes update + + # Updates its receiver just like #update but calls #save! instead + # of +save+, so an exception is raised if the record is invalid and saving will fail. + def update!(attributes) + # The following transaction covers any possible database side-effects of the + # attributes assignment. For example, setting the IDs of a child collection. + with_transaction_returning_status do + assign_attributes(attributes) + save! + end + end + + alias update_attributes! update! + + # Equivalent to update_columns(name => value). + def update_column(name, value) + update_columns(name => value) + end + + # Updates the attributes directly in the database issuing an UPDATE SQL + # statement and sets them in the receiver: + # + # user.update_columns(last_request_at: Time.current) + # + # This is the fastest way to update attributes because it goes straight to + # the database, but take into account that in consequence the regular update + # procedures are totally bypassed. In particular: + # + # * \Validations are skipped. + # * \Callbacks are skipped. + # * +updated_at+/+updated_on+ are not updated. + # * However, attributes are serialized with the same rules as ActiveRecord::Relation#update_all + # + # This method raises an ActiveRecord::ActiveRecordError when called on new + # objects, or when at least one of the attributes is marked as readonly. + def update_columns(attributes) + raise ActiveRecordError, "cannot update a new record" if new_record? + raise ActiveRecordError, "cannot update a destroyed record" if destroyed? + + attributes.each_key do |key| + verify_readonly_attribute(key.to_s) + end + + id_in_database = self.id_in_database + attributes.each do |k, v| + write_attribute_without_type_cast(k, v) + end + + affected_rows = self.class._update_record( + attributes, + self.class.primary_key => id_in_database + ) + + affected_rows == 1 + end + + # Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1). + # The increment is performed directly on the underlying attribute, no setter is invoked. + # Only makes sense for number-based attributes. Returns +self+. + def increment(attribute, by = 1) + self[attribute] ||= 0 + self[attribute] += by + self + end + + # Wrapper around #increment that writes the update to the database. + # Only +attribute+ is updated; the record itself is not saved. + # This means that any other modified attributes will still be dirty. + # Validations and callbacks are skipped. Supports the +touch+ option from + # +update_counters+, see that for more. + # Returns +self+. + def increment!(attribute, by = 1, touch: nil) + increment(attribute, by) + change = public_send(attribute) - (attribute_in_database(attribute.to_s) || 0) + self.class.update_counters(id, attribute => change, touch: touch) + clear_attribute_change(attribute) # eww + self + end + + # Initializes +attribute+ to zero if +nil+ and subtracts the value passed as +by+ (default is 1). + # The decrement is performed directly on the underlying attribute, no setter is invoked. + # Only makes sense for number-based attributes. Returns +self+. + def decrement(attribute, by = 1) + increment(attribute, -by) + end + + # Wrapper around #decrement that writes the update to the database. + # Only +attribute+ is updated; the record itself is not saved. + # This means that any other modified attributes will still be dirty. + # Validations and callbacks are skipped. Supports the +touch+ option from + # +update_counters+, see that for more. + # Returns +self+. + def decrement!(attribute, by = 1, touch: nil) + increment!(attribute, -by, touch: touch) + end + + # Assigns to +attribute+ the boolean opposite of attribute?. So + # if the predicate returns +true+ the attribute will become +false+. This + # method toggles directly the underlying value without calling any setter. + # Returns +self+. + # + # Example: + # + # user = User.first + # user.banned? # => false + # user.toggle(:banned) + # user.banned? # => true + # + def toggle(attribute) + self[attribute] = !public_send("#{attribute}?") + self + end + + # Wrapper around #toggle that saves the record. This method differs from + # its non-bang version in the sense that it passes through the attribute setter. + # Saving is not subjected to validation checks. Returns +true+ if the + # record could be saved. + def toggle!(attribute) + toggle(attribute).update_attribute(attribute, self[attribute]) + end + + # Reloads the record from the database. + # + # This method finds the record by its primary key (which could be assigned + # manually) and modifies the receiver in-place: + # + # account = Account.new + # # => # + # account.id = 1 + # account.reload + # # Account Load (1.2ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT 1 [["id", 1]] + # # => # + # + # Attributes are reloaded from the database, and caches busted, in + # particular the associations cache and the QueryCache. + # + # If the record no longer exists in the database ActiveRecord::RecordNotFound + # is raised. Otherwise, in addition to the in-place modification the method + # returns +self+ for convenience. + # + # The optional :lock flag option allows you to lock the reloaded record: + # + # reload(lock: true) # reload with pessimistic locking + # + # Reloading is commonly used in test suites to test something is actually + # written to the database, or when some action modifies the corresponding + # row in the database but not the object in memory: + # + # assert account.deposit!(25) + # assert_equal 25, account.credit # check it is updated in memory + # assert_equal 25, account.reload.credit # check it is also persisted + # + # Another common use case is optimistic locking handling: + # + # def with_optimistic_retry + # begin + # yield + # rescue ActiveRecord::StaleObjectError + # begin + # # Reload lock_version in particular. + # reload + # rescue ActiveRecord::RecordNotFound + # # If the record is gone there is nothing to do. + # else + # retry + # end + # end + # end + # + def reload(options = nil) + self.class.connection.clear_query_cache + + fresh_object = + if options && options[:lock] + self.class.unscoped { self.class.lock(options[:lock]).find(id) } + else + self.class.unscoped { self.class.find(id) } + end + + @attributes = fresh_object.instance_variable_get("@attributes") + @new_record = false + self + end + + # Saves the record with the updated_at/on attributes set to the current time + # or the time specified. + # Please note that no validation is performed and only the +after_touch+, + # +after_commit+ and +after_rollback+ callbacks are executed. + # + # This method can be passed attribute names and an optional time argument. + # If attribute names are passed, they are updated along with updated_at/on + # attributes. If no time argument is passed, the current time is used as default. + # + # product.touch # updates updated_at/on with current time + # product.touch(time: Time.new(2015, 2, 16, 0, 0, 0)) # updates updated_at/on with specified time + # product.touch(:designed_at) # updates the designed_at attribute and updated_at/on + # product.touch(:started_at, :ended_at) # updates started_at, ended_at and updated_at/on attributes + # + # If used along with {belongs_to}[rdoc-ref:Associations::ClassMethods#belongs_to] + # then +touch+ will invoke +touch+ method on associated object. + # + # class Brake < ActiveRecord::Base + # belongs_to :car, touch: true + # end + # + # class Car < ActiveRecord::Base + # belongs_to :corporation, touch: true + # end + # + # # triggers @brake.car.touch and @brake.car.corporation.touch + # @brake.touch + # + # Note that +touch+ must be used on a persisted object, or else an + # ActiveRecordError will be thrown. For example: + # + # ball = Ball.new + # ball.touch(:updated_at) # => raises ActiveRecordError + # + def touch(*names, time: nil) + unless persisted? + raise ActiveRecordError, <<-MSG.squish + cannot touch on a new or destroyed record object. Consider using + persisted?, new_record?, or destroyed? before touching + MSG + end + + attribute_names = timestamp_attributes_for_update_in_model + attribute_names |= names.map(&:to_s) + + unless attribute_names.empty? + affected_rows = _touch_row(attribute_names, time) + @_trigger_update_callback = affected_rows == 1 + else + true + end + end + + private + + # A hook to be overridden by association modules. + def destroy_associations + end + + def destroy_row + _delete_row + end + + def _delete_row + self.class._delete_record(self.class.primary_key => id_in_database) + end + + def _touch_row(attribute_names, time) + time ||= current_time_from_proper_timezone + + attribute_names.each do |attr_name| + write_attribute(attr_name, time) + clear_attribute_change(attr_name) + end + + _update_row(attribute_names, "touch") + end + + def _update_row(attribute_names, attempted_action = "update") + self.class._update_record( + attributes_with_values(attribute_names), + self.class.primary_key => id_in_database + ) + end + + def create_or_update(*args, &block) + _raise_readonly_record_error if readonly? + return false if destroyed? + result = new_record? ? _create_record(&block) : _update_record(*args, &block) + result != false + end + + # Updates the associated record with values matching those of the instance attributes. + # Returns the number of affected rows. + def _update_record(attribute_names = self.attribute_names) + attribute_names &= self.class.column_names + attribute_names = attributes_for_update(attribute_names) + + if attribute_names.empty? + affected_rows = 0 + @_trigger_update_callback = true + else + affected_rows = _update_row(attribute_names) + @_trigger_update_callback = affected_rows == 1 + end + + yield(self) if block_given? + + affected_rows + end + + # Creates a record with values matching those of the instance attributes + # and returns its id. + def _create_record(attribute_names = self.attribute_names) + attribute_names &= self.class.column_names + attributes_values = attributes_with_values_for_create(attribute_names) + + new_id = self.class._insert_record(attributes_values) + self.id ||= new_id if self.class.primary_key + + @new_record = false + + yield(self) if block_given? + + id + end + + def verify_readonly_attribute(name) + raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name) + end + + def _raise_record_not_destroyed + @_association_destroy_exception ||= nil + raise @_association_destroy_exception || RecordNotDestroyed.new("Failed to destroy the record", self) + ensure + @_association_destroy_exception = nil + end + + def belongs_to_touch_method + :touch + end + + def _raise_readonly_record_error + raise ReadOnlyRecord, "#{self.class} is marked as readonly" + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/query_cache.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/query_cache.rb new file mode 100644 index 00000000..28194c7c --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/query_cache.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record Query Cache + class QueryCache + module ClassMethods + # Enable the query cache within the block if Active Record is configured. + # If it's not, it will execute the given block. + def cache(&block) + if connected? || !configurations.empty? + connection.cache(&block) + else + yield + end + end + + # Disable the query cache within the block if Active Record is configured. + # If it's not, it will execute the given block. + def uncached(&block) + if connected? || !configurations.empty? + connection.uncached(&block) + else + yield + end + end + end + + def self.run + ActiveRecord::Base.connection_handler.connection_pool_list. + reject { |p| p.query_cache_enabled }.each { |p| p.enable_query_cache! } + end + + def self.complete(pools) + pools.each { |pool| pool.disable_query_cache! } + + ActiveRecord::Base.connection_handler.connection_pool_list.each do |pool| + pool.release_connection if pool.active_connection? && !pool.connection.transaction_open? + end + end + + def self.install_executor_hooks(executor = ActiveSupport::Executor) + executor.register_hook(self) + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/querying.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/querying.rb new file mode 100644 index 00000000..be6c626f --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/querying.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module ActiveRecord + module Querying + delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, :none?, :one?, to: :all + delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, :third_to_last, :third_to_last!, :second_to_last, :second_to_last!, to: :all + delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all + delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, to: :all + delegate :find_by, :find_by!, to: :all + delegate :destroy_all, :delete_all, :update_all, to: :all + delegate :find_each, :find_in_batches, :in_batches, to: :all + delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :left_joins, :left_outer_joins, :or, + :where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly, :extending, + :having, :create_with, :distinct, :references, :none, :unscope, :merge, to: :all + delegate :count, :average, :minimum, :maximum, :sum, :calculate, to: :all + delegate :pluck, :ids, to: :all + + # Executes a custom SQL query against your database and returns all the results. The results will + # be returned as an array with columns requested encapsulated as attributes of the model you call + # this method from. If you call Product.find_by_sql then the results will be returned in + # a +Product+ object with the attributes you specified in the SQL query. + # + # If you call a complicated SQL query which spans multiple tables the columns specified by the + # SELECT will be attributes of the model, whether or not they are columns of the corresponding + # table. + # + # The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be + # no database agnostic conversions performed. This should be a last resort because using, for example, + # MySQL specific terms will lock you to using that particular database engine or require you to + # change your call if you switch engines. + # + # # A simple SQL query spanning multiple tables + # Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id" + # # => [#"Ruby Meetup", "first_name"=>"Quentin"}>, ...] + # + # You can use the same string replacement techniques as you can with ActiveRecord::QueryMethods#where: + # + # Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date] + # Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }] + def find_by_sql(sql, binds = [], preparable: nil, &block) + result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable) + column_types = result_set.column_types.dup + attribute_types.each_key { |k| column_types.delete k } + message_bus = ActiveSupport::Notifications.instrumenter + + payload = { + record_count: result_set.length, + class_name: name + } + + message_bus.instrument("instantiation.active_record", payload) do + result_set.map { |record| instantiate(record, column_types, &block) } + end + end + + # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part. + # The use of this method should be restricted to complicated SQL queries that can't be executed + # using the ActiveRecord::Calculations class methods. Look into those before using this. + # + # Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id" + # # => 12 + # + # ==== Parameters + # + # * +sql+ - An SQL statement which should return a count query from the database, see the example above. + def count_by_sql(sql) + connection.select_value(sanitize_sql(sql), "#{name} Count").to_i + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/railtie.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/railtie.rb new file mode 100644 index 00000000..ac38a882 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/railtie.rb @@ -0,0 +1,226 @@ +# frozen_string_literal: true + +require "active_record" +require "rails" +require "active_model/railtie" + +# For now, action_controller must always be present with +# Rails, so let's make sure that it gets required before +# here. This is needed for correctly setting up the middleware. +# In the future, this might become an optional require. +require "action_controller/railtie" + +module ActiveRecord + # = Active Record Railtie + class Railtie < Rails::Railtie # :nodoc: + config.active_record = ActiveSupport::OrderedOptions.new + + config.app_generators.orm :active_record, migration: true, + timestamps: true + + config.action_dispatch.rescue_responses.merge!( + "ActiveRecord::RecordNotFound" => :not_found, + "ActiveRecord::StaleObjectError" => :conflict, + "ActiveRecord::RecordInvalid" => :unprocessable_entity, + "ActiveRecord::RecordNotSaved" => :unprocessable_entity + ) + + config.active_record.use_schema_cache_dump = true + config.active_record.maintain_test_schema = true + + config.active_record.sqlite3 = ActiveSupport::OrderedOptions.new + config.active_record.sqlite3.represent_boolean_as_integer = nil + + config.eager_load_namespaces << ActiveRecord + + rake_tasks do + namespace :db do + task :load_config do + ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration + + if defined?(ENGINE_ROOT) && engine = Rails::Engine.find(ENGINE_ROOT) + if engine.paths["db/migrate"].existent + ActiveRecord::Tasks::DatabaseTasks.migrations_paths += engine.paths["db/migrate"].to_a + end + end + end + end + + load "active_record/railties/databases.rake" + end + + # When loading console, force ActiveRecord::Base to be loaded + # to avoid cross references when loading a constant for the + # first time. Also, make it output to STDERR. + console do |app| + require "active_record/railties/console_sandbox" if app.sandbox? + require "active_record/base" + unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDERR, STDOUT) + console = ActiveSupport::Logger.new(STDERR) + Rails.logger.extend ActiveSupport::Logger.broadcast console + end + ActiveRecord::Base.verbose_query_logs = false + end + + runner do + require "active_record/base" + end + + initializer "active_record.initialize_timezone" do + ActiveSupport.on_load(:active_record) do + self.time_zone_aware_attributes = true + self.default_timezone = :utc + end + end + + initializer "active_record.logger" do + ActiveSupport.on_load(:active_record) { self.logger ||= ::Rails.logger } + end + + initializer "active_record.migration_error" do + if config.active_record.delete(:migration_error) == :page_load + config.app_middleware.insert_after ::ActionDispatch::Callbacks, + ActiveRecord::Migration::CheckPending + end + end + + initializer "active_record.check_schema_cache_dump" do + if config.active_record.delete(:use_schema_cache_dump) + config.after_initialize do |app| + ActiveSupport.on_load(:active_record) do + filename = File.join(app.config.paths["db"].first, "schema_cache.yml") + + if File.file?(filename) + current_version = ActiveRecord::Migrator.current_version + + next if current_version.nil? + + cache = YAML.load(File.read(filename)) + if cache.version == current_version + connection.schema_cache = cache + connection_pool.schema_cache = cache.dup + else + warn "Ignoring db/schema_cache.yml because it has expired. The current schema version is #{current_version}, but the one in the cache is #{cache.version}." + end + end + end + end + end + end + + initializer "active_record.warn_on_records_fetched_greater_than" do + if config.active_record.warn_on_records_fetched_greater_than + ActiveSupport.on_load(:active_record) do + require "active_record/relation/record_fetch_warning" + end + end + end + + initializer "active_record.set_configs" do |app| + ActiveSupport.on_load(:active_record) do + configs = app.config.active_record.dup + configs.delete(:sqlite3) + configs.each do |k, v| + send "#{k}=", v + end + end + end + + # This sets the database configuration from Configuration#database_configuration + # and then establishes the connection. + initializer "active_record.initialize_database" do + ActiveSupport.on_load(:active_record) do + self.configurations = Rails.application.config.database_configuration + + begin + establish_connection + rescue ActiveRecord::NoDatabaseError + warn <<-end_warning +Oops - You have a database configured, but it doesn't exist yet! + +Here's how to get started: + + 1. Configure your database in config/database.yml. + 2. Run `bin/rails db:create` to create the database. + 3. Run `bin/rails db:setup` to load your database schema. +end_warning + raise + end + end + end + + # Expose database runtime to controller for logging. + initializer "active_record.log_runtime" do + require "active_record/railties/controller_runtime" + ActiveSupport.on_load(:action_controller) do + include ActiveRecord::Railties::ControllerRuntime + end + end + + initializer "active_record.set_reloader_hooks" do + ActiveSupport.on_load(:active_record) do + ActiveSupport::Reloader.before_class_unload do + if ActiveRecord::Base.connected? + ActiveRecord::Base.clear_cache! + ActiveRecord::Base.clear_reloadable_connections! + end + end + end + end + + initializer "active_record.set_executor_hooks" do + ActiveRecord::QueryCache.install_executor_hooks + end + + initializer "active_record.add_watchable_files" do |app| + path = app.paths["db"].first + config.watchable_files.concat ["#{path}/schema.rb", "#{path}/structure.sql"] + end + + initializer "active_record.clear_active_connections" do + config.after_initialize do + ActiveSupport.on_load(:active_record) do + # Ideally the application doesn't connect to the database during boot, + # but sometimes it does. In case it did, we want to empty out the + # connection pools so that a non-database-using process (e.g. a master + # process in a forking server model) doesn't retain a needless + # connection. If it was needed, the incremental cost of reestablishing + # this connection is trivial: the rest of the pool would need to be + # populated anyway. + + clear_active_connections! + flush_idle_connections! + end + end + end + + initializer "active_record.check_represent_sqlite3_boolean_as_integer" do + config.after_initialize do + ActiveSupport.on_load(:active_record_sqlite3adapter) do + represent_boolean_as_integer = Rails.application.config.active_record.sqlite3.delete(:represent_boolean_as_integer) + unless represent_boolean_as_integer.nil? + ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer = represent_boolean_as_integer + end + + unless ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer + ActiveSupport::Deprecation.warn <<-MSG +Leaving `ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer` +set to false is deprecated. SQLite databases have used 't' and 'f' to serialize +boolean values and must have old data converted to 1 and 0 (its native boolean +serialization) before setting this flag to true. Conversion can be accomplished +by setting up a rake task which runs + + ExampleModel.where("boolean_column = 't'").update_all(boolean_column: 1) + ExampleModel.where("boolean_column = 'f'").update_all(boolean_column: 0) + +for all models and all boolean columns, after which the flag must be set to +true by adding the following to your application.rb file: + + Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true +MSG + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/railties/console_sandbox.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/railties/console_sandbox.rb new file mode 100644 index 00000000..8917638a --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/railties/console_sandbox.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +ActiveRecord::Base.connection.begin_transaction(joinable: false) + +at_exit do + ActiveRecord::Base.connection.rollback_transaction +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/railties/controller_runtime.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/railties/controller_runtime.rb new file mode 100644 index 00000000..2ae733f6 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/railties/controller_runtime.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attr_internal" +require "active_record/log_subscriber" + +module ActiveRecord + module Railties # :nodoc: + module ControllerRuntime #:nodoc: + extend ActiveSupport::Concern + + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected + + attr_internal :db_runtime + + private + + def process_action(action, *args) + # We also need to reset the runtime before each action + # because of queries in middleware or in cases we are streaming + # and it won't be cleaned up by the method below. + ActiveRecord::LogSubscriber.reset_runtime + super + end + + def cleanup_view_runtime + if logger && logger.info? && ActiveRecord::Base.connected? + db_rt_before_render = ActiveRecord::LogSubscriber.reset_runtime + self.db_runtime = (db_runtime || 0) + db_rt_before_render + runtime = super + db_rt_after_render = ActiveRecord::LogSubscriber.reset_runtime + self.db_runtime += db_rt_after_render + runtime - db_rt_after_render + else + super + end + end + + def append_info_to_payload(payload) + super + if ActiveRecord::Base.connected? + payload[:db_runtime] = (db_runtime || 0) + ActiveRecord::LogSubscriber.reset_runtime + end + end + + module ClassMethods # :nodoc: + def log_process_action(payload) + messages, db_runtime = super, payload[:db_runtime] + messages << ("ActiveRecord: %.1fms" % db_runtime.to_f) if db_runtime + messages + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/railties/databases.rake b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/railties/databases.rake new file mode 100644 index 00000000..662a8bc7 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/railties/databases.rake @@ -0,0 +1,377 @@ +# frozen_string_literal: true + +require "active_record" + +db_namespace = namespace :db do + desc "Set the environment value for the database" + task "environment:set" => :load_config do + ActiveRecord::InternalMetadata.create_table + ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Base.connection.migration_context.current_environment + end + + task check_protected_environments: :load_config do + ActiveRecord::Tasks::DatabaseTasks.check_protected_environments! + end + + task load_config: :environment do + ActiveRecord::Base.configurations = ActiveRecord::Tasks::DatabaseTasks.database_configuration || {} + ActiveRecord::Migrator.migrations_paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths + end + + namespace :create do + task all: :load_config do + ActiveRecord::Tasks::DatabaseTasks.create_all + end + end + + desc "Creates the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:create:all to create all databases in the config). Without RAILS_ENV or when RAILS_ENV is development, it defaults to creating the development and test databases." + task create: [:load_config] do + ActiveRecord::Tasks::DatabaseTasks.create_current + end + + namespace :drop do + task all: [:load_config, :check_protected_environments] do + ActiveRecord::Tasks::DatabaseTasks.drop_all + end + end + + desc "Drops the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config). Without RAILS_ENV or when RAILS_ENV is development, it defaults to dropping the development and test databases." + task drop: [:load_config, :check_protected_environments] do + db_namespace["drop:_unsafe"].invoke + end + + task "drop:_unsafe" => [:load_config] do + ActiveRecord::Tasks::DatabaseTasks.drop_current + end + + namespace :purge do + task all: [:load_config, :check_protected_environments] do + ActiveRecord::Tasks::DatabaseTasks.purge_all + end + end + + # desc "Empty the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:purge:all to purge all databases in the config). Without RAILS_ENV it defaults to purging the development and test databases." + task purge: [:load_config, :check_protected_environments] do + ActiveRecord::Tasks::DatabaseTasks.purge_current + end + + desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)." + task migrate: :load_config do + ActiveRecord::Tasks::DatabaseTasks.migrate + db_namespace["_dump"].invoke + end + + # IMPORTANT: This task won't dump the schema if ActiveRecord::Base.dump_schema_after_migration is set to false + task :_dump do + if ActiveRecord::Base.dump_schema_after_migration + case ActiveRecord::Base.schema_format + when :ruby then db_namespace["schema:dump"].invoke + when :sql then db_namespace["structure:dump"].invoke + else + raise "unknown schema format #{ActiveRecord::Base.schema_format}" + end + end + # Allow this task to be called as many times as required. An example is the + # migrate:redo task, which calls other two internally that depend on this one. + db_namespace["_dump"].reenable + end + + namespace :migrate do + # desc 'Rollbacks the database one migration and re migrate up (options: STEP=x, VERSION=x).' + task redo: :load_config do + raise "Empty VERSION provided" if ENV["VERSION"] && ENV["VERSION"].empty? + + if ENV["VERSION"] + db_namespace["migrate:down"].invoke + db_namespace["migrate:up"].invoke + else + db_namespace["rollback"].invoke + db_namespace["migrate"].invoke + end + end + + # desc 'Resets your database using your migrations for the current environment' + task reset: ["db:drop", "db:create", "db:migrate"] + + # desc 'Runs the "up" for a given migration VERSION.' + task up: :load_config do + raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty? + + ActiveRecord::Tasks::DatabaseTasks.check_target_version + + ActiveRecord::Base.connection.migration_context.run( + :up, + ActiveRecord::Tasks::DatabaseTasks.target_version + ) + db_namespace["_dump"].invoke + end + + # desc 'Runs the "down" for a given migration VERSION.' + task down: :load_config do + raise "VERSION is required - To go down one migration, use db:rollback" if !ENV["VERSION"] || ENV["VERSION"].empty? + + ActiveRecord::Tasks::DatabaseTasks.check_target_version + + ActiveRecord::Base.connection.migration_context.run( + :down, + ActiveRecord::Tasks::DatabaseTasks.target_version + ) + db_namespace["_dump"].invoke + end + + desc "Display status of migrations" + task status: :load_config do + unless ActiveRecord::SchemaMigration.table_exists? + abort "Schema migrations table does not exist yet." + end + + # output + puts "\ndatabase: #{ActiveRecord::Base.connection_config[:database]}\n\n" + puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name" + puts "-" * 50 + ActiveRecord::Base.connection.migration_context.migrations_status.each do |status, version, name| + puts "#{status.center(8)} #{version.ljust(14)} #{name}" + end + puts + end + end + + desc "Rolls the schema back to the previous version (specify steps w/ STEP=n)." + task rollback: :load_config do + step = ENV["STEP"] ? ENV["STEP"].to_i : 1 + ActiveRecord::Base.connection.migration_context.rollback(step) + db_namespace["_dump"].invoke + end + + # desc 'Pushes the schema to the next version (specify steps w/ STEP=n).' + task forward: :load_config do + step = ENV["STEP"] ? ENV["STEP"].to_i : 1 + ActiveRecord::Base.connection.migration_context.forward(step) + db_namespace["_dump"].invoke + end + + # desc 'Drops and recreates the database from db/schema.rb for the current environment and loads the seeds.' + task reset: [ "db:drop", "db:setup" ] + + # desc "Retrieves the charset for the current environment's database" + task charset: :load_config do + puts ActiveRecord::Tasks::DatabaseTasks.charset_current + end + + # desc "Retrieves the collation for the current environment's database" + task collation: :load_config do + begin + puts ActiveRecord::Tasks::DatabaseTasks.collation_current + rescue NoMethodError + $stderr.puts "Sorry, your database adapter is not supported yet. Feel free to submit a patch." + end + end + + desc "Retrieves the current schema version number" + task version: :load_config do + puts "Current version: #{ActiveRecord::Base.connection.migration_context.current_version}" + end + + # desc "Raises an error if there are pending migrations" + task abort_if_pending_migrations: :load_config do + pending_migrations = ActiveRecord::Base.connection.migration_context.open.pending_migrations + + if pending_migrations.any? + puts "You have #{pending_migrations.size} pending #{pending_migrations.size > 1 ? 'migrations:' : 'migration:'}" + pending_migrations.each do |pending_migration| + puts " %4d %s" % [pending_migration.version, pending_migration.name] + end + abort %{Run `rails db:migrate` to update your database then try again.} + end + end + + desc "Creates the database, loads the schema, and initializes with the seed data (use db:reset to also drop the database first)" + task setup: ["db:schema:load_if_ruby", "db:structure:load_if_sql", :seed] + + desc "Loads the seed data from db/seeds.rb" + task :seed do + db_namespace["abort_if_pending_migrations"].invoke + ActiveRecord::Tasks::DatabaseTasks.load_seed + end + + namespace :fixtures do + desc "Loads fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures." + task load: :load_config do + require "active_record/fixtures" + + base_dir = ActiveRecord::Tasks::DatabaseTasks.fixtures_path + + fixtures_dir = if ENV["FIXTURES_DIR"] + File.join base_dir, ENV["FIXTURES_DIR"] + else + base_dir + end + + fixture_files = if ENV["FIXTURES"] + ENV["FIXTURES"].split(",") + else + # The use of String#[] here is to support namespaced fixtures. + Dir["#{fixtures_dir}/**/*.yml"].map { |f| f[(fixtures_dir.size + 1)..-5] } + end + + ActiveRecord::FixtureSet.create_fixtures(fixtures_dir, fixture_files) + end + + # desc "Search for a fixture given a LABEL or ID. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures." + task identify: :load_config do + require "active_record/fixtures" + + label, id = ENV["LABEL"], ENV["ID"] + raise "LABEL or ID required" if label.blank? && id.blank? + + puts %Q(The fixture ID for "#{label}" is #{ActiveRecord::FixtureSet.identify(label)}.) if label + + base_dir = ActiveRecord::Tasks::DatabaseTasks.fixtures_path + + Dir["#{base_dir}/**/*.yml"].each do |file| + if data = YAML.load(ERB.new(IO.read(file)).result) + data.each_key do |key| + key_id = ActiveRecord::FixtureSet.identify(key) + + if key == label || key_id == id.to_i + puts "#{file}: #{key} (#{key_id})" + end + end + end + end + end + end + + namespace :schema do + desc "Creates a db/schema.rb file that is portable against any DB supported by Active Record" + task dump: :load_config do + require "active_record/schema_dumper" + filename = ENV["SCHEMA"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema.rb") + File.open(filename, "w:utf-8") do |file| + ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file) + end + db_namespace["schema:dump"].reenable + end + + desc "Loads a schema.rb file into the database" + task load: [:load_config, :check_protected_environments] do + ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:ruby, ENV["SCHEMA"]) + end + + task load_if_ruby: ["db:create", :environment] do + db_namespace["schema:load"].invoke if ActiveRecord::Base.schema_format == :ruby + end + + namespace :cache do + desc "Creates a db/schema_cache.yml file." + task dump: :load_config do + conn = ActiveRecord::Base.connection + filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.yml") + ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(conn, filename) + end + + desc "Clears a db/schema_cache.yml file." + task clear: :load_config do + filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.yml") + rm_f filename, verbose: false + end + end + + end + + namespace :structure do + desc "Dumps the database structure to db/structure.sql. Specify another file with SCHEMA=db/my_structure.sql" + task dump: :load_config do + filename = ENV["SCHEMA"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "structure.sql") + current_config = ActiveRecord::Tasks::DatabaseTasks.current_config + ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename) + + if ActiveRecord::SchemaMigration.table_exists? + File.open(filename, "a") do |f| + f.puts ActiveRecord::Base.connection.dump_schema_information + f.print "\n" + end + end + db_namespace["structure:dump"].reenable + end + + desc "Recreates the databases from the structure.sql file" + task load: [:load_config, :check_protected_environments] do + ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:sql, ENV["SCHEMA"]) + end + + task load_if_sql: ["db:create", :environment] do + db_namespace["structure:load"].invoke if ActiveRecord::Base.schema_format == :sql + end + end + + namespace :test do + # desc "Recreate the test database from the current schema" + task load: %w(db:test:purge) do + case ActiveRecord::Base.schema_format + when :ruby + db_namespace["test:load_schema"].invoke + when :sql + db_namespace["test:load_structure"].invoke + end + end + + # desc "Recreate the test database from an existent schema.rb file" + task load_schema: %w(db:test:purge) do + begin + should_reconnect = ActiveRecord::Base.connection_pool.active_connection? + ActiveRecord::Schema.verbose = false + ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations["test"], :ruby, ENV["SCHEMA"], "test" + ensure + if should_reconnect + ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env]) + end + end + end + + # desc "Recreate the test database from an existent structure.sql file" + task load_structure: %w(db:test:purge) do + ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations["test"], :sql, ENV["SCHEMA"], "test" + end + + # desc "Empty the test database" + task purge: %w(load_config check_protected_environments) do + ActiveRecord::Tasks::DatabaseTasks.purge ActiveRecord::Base.configurations["test"] + end + + # desc 'Load the test schema' + task prepare: :load_config do + unless ActiveRecord::Base.configurations.blank? + db_namespace["test:load"].invoke + end + end + end +end + +namespace :railties do + namespace :install do + # desc "Copies missing migrations from Railties (e.g. engines). You can specify Railties to use with FROM=railtie1,railtie2" + task migrations: :'db:load_config' do + to_load = ENV["FROM"].blank? ? :all : ENV["FROM"].split(",").map(&:strip) + railties = {} + Rails.application.migration_railties.each do |railtie| + next unless to_load == :all || to_load.include?(railtie.railtie_name) + + if railtie.respond_to?(:paths) && (path = railtie.paths["db/migrate"].first) + railties[railtie.railtie_name] = path + end + end + + on_skip = Proc.new do |name, migration| + puts "NOTE: Migration #{migration.basename} from #{name} has been skipped. Migration with the same name already exists." + end + + on_copy = Proc.new do |name, migration| + puts "Copied migration #{migration.basename} from #{name}" + end + + ActiveRecord::Migration.copy(ActiveRecord::Tasks::DatabaseTasks.migrations_paths.first, railties, + on_skip: on_skip, on_copy: on_copy) + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/readonly_attributes.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/readonly_attributes.rb new file mode 100644 index 00000000..7bc26993 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/readonly_attributes.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module ActiveRecord + module ReadonlyAttributes + extend ActiveSupport::Concern + + included do + class_attribute :_attr_readonly, instance_accessor: false, default: [] + end + + module ClassMethods + # Attributes listed as readonly will be used to create a new record but update operations will + # ignore these fields. + def attr_readonly(*attributes) + self._attr_readonly = Set.new(attributes.map(&:to_s)) + (_attr_readonly || []) + end + + # Returns an array of all the attributes that have been specified as readonly. + def readonly_attributes + _attr_readonly + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/reflection.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/reflection.rb new file mode 100644 index 00000000..96144e18 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/reflection.rb @@ -0,0 +1,1044 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/filters" +require "concurrent/map" + +module ActiveRecord + # = Active Record Reflection + module Reflection # :nodoc: + extend ActiveSupport::Concern + + included do + class_attribute :_reflections, instance_writer: false, default: {} + class_attribute :aggregate_reflections, instance_writer: false, default: {} + end + + def self.create(macro, name, scope, options, ar) + klass = \ + case macro + when :composed_of + AggregateReflection + when :has_many + HasManyReflection + when :has_one + HasOneReflection + when :belongs_to + BelongsToReflection + else + raise "Unsupported Macro: #{macro}" + end + + reflection = klass.new(name, scope, options, ar) + options[:through] ? ThroughReflection.new(reflection) : reflection + end + + def self.add_reflection(ar, name, reflection) + ar.clear_reflections_cache + name = name.to_s + ar._reflections = ar._reflections.except(name).merge!(name => reflection) + end + + def self.add_aggregate_reflection(ar, name, reflection) + ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_s => reflection) + end + + # \Reflection enables the ability to examine the associations and aggregations of + # Active Record classes and objects. This information, for example, + # can be used in a form builder that takes an Active Record object + # and creates input fields for all of the attributes depending on their type + # and displays the associations to other objects. + # + # MacroReflection class has info for AggregateReflection and AssociationReflection + # classes. + module ClassMethods + # Returns an array of AggregateReflection objects for all the aggregations in the class. + def reflect_on_all_aggregations + aggregate_reflections.values + end + + # Returns the AggregateReflection object for the named +aggregation+ (use the symbol). + # + # Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection + # + def reflect_on_aggregation(aggregation) + aggregate_reflections[aggregation.to_s] + end + + # Returns a Hash of name of the reflection as the key and an AssociationReflection as the value. + # + # Account.reflections # => {"balance" => AggregateReflection} + # + def reflections + @__reflections ||= begin + ref = {} + + _reflections.each do |name, reflection| + parent_reflection = reflection.parent_reflection + + if parent_reflection + parent_name = parent_reflection.name + ref[parent_name.to_s] = parent_reflection + else + ref[name] = reflection + end + end + + ref + end + end + + # Returns an array of AssociationReflection objects for all the + # associations in the class. If you only want to reflect on a certain + # association type, pass in the symbol (:has_many, :has_one, + # :belongs_to) as the first parameter. + # + # Example: + # + # Account.reflect_on_all_associations # returns an array of all associations + # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations + # + def reflect_on_all_associations(macro = nil) + association_reflections = reflections.values + association_reflections.select! { |reflection| reflection.macro == macro } if macro + association_reflections + end + + # Returns the AssociationReflection object for the +association+ (use the symbol). + # + # Account.reflect_on_association(:owner) # returns the owner AssociationReflection + # Invoice.reflect_on_association(:line_items).macro # returns :has_many + # + def reflect_on_association(association) + reflections[association.to_s] + end + + def _reflect_on_association(association) #:nodoc: + _reflections[association.to_s] + end + + # Returns an array of AssociationReflection objects for all associations which have :autosave enabled. + def reflect_on_all_autosave_associations + reflections.values.select { |reflection| reflection.options[:autosave] } + end + + def clear_reflections_cache # :nodoc: + @__reflections = nil + end + end + + # Holds all the methods that are shared between MacroReflection and ThroughReflection. + # + # AbstractReflection + # MacroReflection + # AggregateReflection + # AssociationReflection + # HasManyReflection + # HasOneReflection + # BelongsToReflection + # HasAndBelongsToManyReflection + # ThroughReflection + # PolymorphicReflection + # RuntimeReflection + class AbstractReflection # :nodoc: + def through_reflection? + false + end + + def table_name + klass.table_name + end + + # Returns a new, unsaved instance of the associated class. +attributes+ will + # be passed to the class's constructor. + def build_association(attributes, &block) + klass.new(attributes, &block) + end + + # Returns the class name for the macro. + # + # composed_of :balance, class_name: 'Money' returns 'Money' + # has_many :clients returns 'Client' + def class_name + @class_name ||= (options[:class_name] || derive_class_name).to_s + end + + JoinKeys = Struct.new(:key, :foreign_key) # :nodoc: + + def join_keys + @join_keys ||= get_join_keys(klass) + end + + # Returns a list of scopes that should be applied for this Reflection + # object when querying the database. + def scopes + scope ? [scope] : [] + end + + def build_join_constraint(table, foreign_table) + key = join_keys.key + foreign_key = join_keys.foreign_key + + constraint = table[key].eq(foreign_table[foreign_key]) + + if klass.finder_needs_type_condition? + table.create_and([constraint, klass.send(:type_condition, table)]) + else + constraint + end + end + + def join_scope(table, foreign_klass) + predicate_builder = predicate_builder(table) + scope_chain_items = join_scopes(table, predicate_builder) + klass_scope = klass_join_scope(table, predicate_builder) + + if type + klass_scope.where!(type => foreign_klass.polymorphic_name) + end + + scope_chain_items.inject(klass_scope, &:merge!) + end + + def join_scopes(table, predicate_builder) # :nodoc: + if scope + [scope_for(build_scope(table, predicate_builder))] + else + [] + end + end + + def klass_join_scope(table, predicate_builder) # :nodoc: + relation = build_scope(table, predicate_builder) + klass.scope_for_association(relation) + end + + def constraints + chain.flat_map(&:scopes) + end + + def counter_cache_column + if belongs_to? + if options[:counter_cache] == true + "#{active_record.name.demodulize.underscore.pluralize}_count" + elsif options[:counter_cache] + options[:counter_cache].to_s + end + else + options[:counter_cache] ? options[:counter_cache].to_s : "#{name}_count" + end + end + + def inverse_of + return unless inverse_name + + @inverse_of ||= klass._reflect_on_association inverse_name + end + + def check_validity_of_inverse! + unless polymorphic? + if has_inverse? && inverse_of.nil? + raise InverseOfAssociationNotFoundError.new(self) + end + end + end + + # This shit is nasty. We need to avoid the following situation: + # + # * An associated record is deleted via record.destroy + # * Hence the callbacks run, and they find a belongs_to on the record with a + # :counter_cache options which points back at our owner. So they update the + # counter cache. + # * In which case, we must make sure to *not* update the counter cache, or else + # it will be decremented twice. + # + # Hence this method. + def inverse_which_updates_counter_cache + return @inverse_which_updates_counter_cache if defined?(@inverse_which_updates_counter_cache) + @inverse_which_updates_counter_cache = klass.reflect_on_all_associations(:belongs_to).find do |inverse| + inverse.counter_cache_column == counter_cache_column + end + end + alias inverse_updates_counter_cache? inverse_which_updates_counter_cache + + def inverse_updates_counter_in_memory? + inverse_of && inverse_which_updates_counter_cache == inverse_of + end + + # Returns whether a counter cache should be used for this association. + # + # The counter_cache option must be given on either the owner or inverse + # association, and the column must be present on the owner. + def has_cached_counter? + options[:counter_cache] || + inverse_which_updates_counter_cache && inverse_which_updates_counter_cache.options[:counter_cache] && + !!active_record.columns_hash[counter_cache_column] + end + + def counter_must_be_updated_by_has_many? + !inverse_updates_counter_in_memory? && has_cached_counter? + end + + def alias_candidate(name) + "#{plural_name}_#{name}" + end + + def chain + collect_join_chain + end + + def get_join_keys(association_klass) + JoinKeys.new(join_primary_key(association_klass), join_foreign_key) + end + + def build_scope(table, predicate_builder = predicate_builder(table)) + Relation.create( + klass, + table: table, + predicate_builder: predicate_builder + ) + end + + def join_primary_key(*) + foreign_key + end + + def join_foreign_key + active_record_primary_key + end + + protected + def actual_source_reflection # FIXME: this is a horrible name + self + end + + private + def predicate_builder(table) + PredicateBuilder.new(TableMetadata.new(klass, table)) + end + + def primary_key(klass) + klass.primary_key || raise(UnknownPrimaryKey.new(klass)) + end + end + + # Base class for AggregateReflection and AssociationReflection. Objects of + # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods. + class MacroReflection < AbstractReflection + # Returns the name of the macro. + # + # composed_of :balance, class_name: 'Money' returns :balance + # has_many :clients returns :clients + attr_reader :name + + attr_reader :scope + + # Returns the hash of options used for the macro. + # + # composed_of :balance, class_name: 'Money' returns { class_name: "Money" } + # has_many :clients returns {} + attr_reader :options + + attr_reader :active_record + + attr_reader :plural_name # :nodoc: + + def initialize(name, scope, options, active_record) + @name = name + @scope = scope + @options = options + @active_record = active_record + @klass = options[:anonymous_class] + @plural_name = active_record.pluralize_table_names ? + name.to_s.pluralize : name.to_s + end + + def autosave=(autosave) + @options[:autosave] = autosave + parent_reflection = self.parent_reflection + if parent_reflection + parent_reflection.autosave = autosave + end + end + + # Returns the class for the macro. + # + # composed_of :balance, class_name: 'Money' returns the Money class + # has_many :clients returns the Client class + # + # class Company < ActiveRecord::Base + # has_many :clients + # end + # + # Company.reflect_on_association(:clients).klass + # # => Client + # + # Note: Do not call +klass.new+ or +klass.create+ to instantiate + # a new association object. Use +build_association+ or +create_association+ + # instead. This allows plugins to hook into association object creation. + def klass + @klass ||= compute_class(class_name) + end + + def compute_class(name) + name.constantize + end + + # Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute, + # and +other_aggregation+ has an options hash assigned to it. + def ==(other_aggregation) + super || + other_aggregation.kind_of?(self.class) && + name == other_aggregation.name && + !other_aggregation.options.nil? && + active_record == other_aggregation.active_record + end + + def scope_for(relation, owner = nil) + relation.instance_exec(owner, &scope) || relation + end + + private + def derive_class_name + name.to_s.camelize + end + end + + # Holds all the metadata about an aggregation as it was specified in the + # Active Record class. + class AggregateReflection < MacroReflection #:nodoc: + def mapping + mapping = options[:mapping] || [name, name] + mapping.first.is_a?(Array) ? mapping : [mapping] + end + end + + # Holds all the metadata about an association as it was specified in the + # Active Record class. + class AssociationReflection < MacroReflection #:nodoc: + def compute_class(name) + if polymorphic? + raise ArgumentError, "Polymorphic association does not support to compute class." + end + active_record.send(:compute_type, name) + end + + attr_reader :type, :foreign_type + attr_accessor :parent_reflection # Reflection + + def initialize(name, scope, options, active_record) + super + @type = options[:as] && (options[:foreign_type] || "#{options[:as]}_type") + @foreign_type = options[:polymorphic] && (options[:foreign_type] || "#{name}_type") + @constructable = calculate_constructable(macro, options) + @association_scope_cache = Concurrent::Map.new + + if options[:class_name] && options[:class_name].class == Class + raise ArgumentError, "A class was passed to `:class_name` but we are expecting a string." + end + end + + def association_scope_cache(conn, owner, &block) + key = conn.prepared_statements + if polymorphic? + key = [key, owner._read_attribute(@foreign_type)] + end + @association_scope_cache.compute_if_absent(key) { StatementCache.create(conn, &block) } + end + + def constructable? # :nodoc: + @constructable + end + + def join_table + @join_table ||= options[:join_table] || derive_join_table + end + + def foreign_key + @foreign_key ||= options[:foreign_key] || derive_foreign_key.freeze + end + + def association_foreign_key + @association_foreign_key ||= options[:association_foreign_key] || class_name.foreign_key + end + + # klass option is necessary to support loading polymorphic associations + def association_primary_key(klass = nil) + options[:primary_key] || primary_key(klass || self.klass) + end + + def active_record_primary_key + @active_record_primary_key ||= options[:primary_key] || primary_key(active_record) + end + + def check_validity! + check_validity_of_inverse! + end + + def check_preloadable! + return unless scope + + if scope.arity > 0 + raise ArgumentError, <<-MSG.squish + The association scope '#{name}' is instance dependent (the scope + block takes an argument). Preloading instance dependent scopes is + not supported. + MSG + end + end + alias :check_eager_loadable! :check_preloadable! + + def join_id_for(owner) # :nodoc: + owner[join_foreign_key] + end + + def through_reflection + nil + end + + def source_reflection + self + end + + # A chain of reflections from this one back to the owner. For more see the explanation in + # ThroughReflection. + def collect_join_chain + [self] + end + + # This is for clearing cache on the reflection. Useful for tests that need to compare + # SQL queries on associations. + def clear_association_scope_cache # :nodoc: + @association_scope_cache.clear + end + + def nested? + false + end + + def has_scope? + scope + end + + def has_inverse? + inverse_name + end + + def polymorphic_inverse_of(associated_class) + if has_inverse? + if inverse_relationship = associated_class._reflect_on_association(options[:inverse_of]) + inverse_relationship + else + raise InverseOfAssociationNotFoundError.new(self, associated_class) + end + end + end + + # Returns the macro type. + # + # has_many :clients returns :has_many + def macro; raise NotImplementedError; end + + # Returns whether or not this association reflection is for a collection + # association. Returns +true+ if the +macro+ is either +has_many+ or + # +has_and_belongs_to_many+, +false+ otherwise. + def collection? + false + end + + # Returns whether or not the association should be validated as part of + # the parent's validation. + # + # Unless you explicitly disable validation with + # validate: false, validation will take place when: + # + # * you explicitly enable validation; validate: true + # * you use autosave; autosave: true + # * the association is a +has_many+ association + def validate? + !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || collection?) + end + + # Returns +true+ if +self+ is a +belongs_to+ reflection. + def belongs_to?; false; end + + # Returns +true+ if +self+ is a +has_one+ reflection. + def has_one?; false; end + + def association_class; raise NotImplementedError; end + + def polymorphic? + options[:polymorphic] + end + + VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to] + INVALID_AUTOMATIC_INVERSE_OPTIONS = [:through, :foreign_key] + + def add_as_source(seed) + seed + end + + def add_as_polymorphic_through(reflection, seed) + seed + [PolymorphicReflection.new(self, reflection)] + end + + def add_as_through(seed) + seed + [self] + end + + def extensions + Array(options[:extend]) + end + + private + + def calculate_constructable(macro, options) + true + end + + # Attempts to find the inverse association name automatically. + # If it cannot find a suitable inverse association name, it returns + # +nil+. + def inverse_name + unless defined?(@inverse_name) + @inverse_name = options.fetch(:inverse_of) { automatic_inverse_of } + end + + @inverse_name + end + + # returns either +nil+ or the inverse association name that it finds. + def automatic_inverse_of + if can_find_inverse_of_automatically?(self) + inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym + + begin + reflection = klass._reflect_on_association(inverse_name) + rescue NameError + # Give up: we couldn't compute the klass type so we won't be able + # to find any associations either. + reflection = false + end + + if valid_inverse_reflection?(reflection) + return inverse_name + end + end + end + + # Checks if the inverse reflection that is returned from the + # +automatic_inverse_of+ method is a valid reflection. We must + # make sure that the reflection's active_record name matches up + # with the current reflection's klass name. + def valid_inverse_reflection?(reflection) + reflection && + klass <= reflection.active_record && + can_find_inverse_of_automatically?(reflection) + end + + # Checks to see if the reflection doesn't have any options that prevent + # us from being able to guess the inverse automatically. First, the + # inverse_of option cannot be set to false. Second, we must + # have has_many, has_one, belongs_to associations. + # Third, we must not have options such as :foreign_key + # which prevent us from correctly guessing the inverse association. + # + # Anything with a scope can additionally ruin our attempt at finding an + # inverse, so we exclude reflections with scopes. + def can_find_inverse_of_automatically?(reflection) + reflection.options[:inverse_of] != false && + VALID_AUTOMATIC_INVERSE_MACROS.include?(reflection.macro) && + !INVALID_AUTOMATIC_INVERSE_OPTIONS.any? { |opt| reflection.options[opt] } && + !reflection.scope + end + + def derive_class_name + class_name = name.to_s + class_name = class_name.singularize if collection? + class_name.camelize + end + + def derive_foreign_key + if belongs_to? + "#{name}_id" + elsif options[:as] + "#{options[:as]}_id" + else + active_record.name.foreign_key + end + end + + def derive_join_table + ModelSchema.derive_join_table_name active_record.table_name, klass.table_name + end + end + + class HasManyReflection < AssociationReflection # :nodoc: + def macro; :has_many; end + + def collection?; true; end + + def association_class + if options[:through] + Associations::HasManyThroughAssociation + else + Associations::HasManyAssociation + end + end + + def association_primary_key(klass = nil) + primary_key(klass || self.klass) + end + end + + class HasOneReflection < AssociationReflection # :nodoc: + def macro; :has_one; end + + def has_one?; true; end + + def association_class + if options[:through] + Associations::HasOneThroughAssociation + else + Associations::HasOneAssociation + end + end + + private + + def calculate_constructable(macro, options) + !options[:through] + end + end + + class BelongsToReflection < AssociationReflection # :nodoc: + def macro; :belongs_to; end + + def belongs_to?; true; end + + def association_class + if polymorphic? + Associations::BelongsToPolymorphicAssociation + else + Associations::BelongsToAssociation + end + end + + def join_primary_key(klass = nil) + polymorphic? ? association_primary_key(klass) : association_primary_key + end + + def join_foreign_key + foreign_key + end + + private + def can_find_inverse_of_automatically?(_) + !polymorphic? && super + end + + def calculate_constructable(macro, options) + !polymorphic? + end + end + + class HasAndBelongsToManyReflection < AssociationReflection # :nodoc: + def macro; :has_and_belongs_to_many; end + + def collection? + true + end + end + + # Holds all the metadata about a :through association as it was specified + # in the Active Record class. + class ThroughReflection < AbstractReflection #:nodoc: + delegate :foreign_key, :foreign_type, :association_foreign_key, :join_id_for, + :active_record_primary_key, :type, :get_join_keys, to: :source_reflection + + def initialize(delegate_reflection) + @delegate_reflection = delegate_reflection + @klass = delegate_reflection.options[:anonymous_class] + @source_reflection_name = delegate_reflection.options[:source] + end + + def through_reflection? + true + end + + def klass + @klass ||= delegate_reflection.compute_class(class_name) + end + + # Returns the source of the through reflection. It checks both a singularized + # and pluralized form for :belongs_to or :has_many. + # + # class Post < ActiveRecord::Base + # has_many :taggings + # has_many :tags, through: :taggings + # end + # + # class Tagging < ActiveRecord::Base + # belongs_to :post + # belongs_to :tag + # end + # + # tags_reflection = Post.reflect_on_association(:tags) + # tags_reflection.source_reflection + # # => + # + def source_reflection + through_reflection.klass._reflect_on_association(source_reflection_name) + end + + # Returns the AssociationReflection object specified in the :through option + # of a HasManyThrough or HasOneThrough association. + # + # class Post < ActiveRecord::Base + # has_many :taggings + # has_many :tags, through: :taggings + # end + # + # tags_reflection = Post.reflect_on_association(:tags) + # tags_reflection.through_reflection + # # => + # + def through_reflection + active_record._reflect_on_association(options[:through]) + end + + # Returns an array of reflections which are involved in this association. Each item in the + # array corresponds to a table which will be part of the query for this association. + # + # The chain is built by recursively calling #chain on the source reflection and the through + # reflection. The base case for the recursion is a normal association, which just returns + # [self] as its #chain. + # + # class Post < ActiveRecord::Base + # has_many :taggings + # has_many :tags, through: :taggings + # end + # + # tags_reflection = Post.reflect_on_association(:tags) + # tags_reflection.chain + # # => [, + # ] + # + def collect_join_chain + collect_join_reflections [self] + end + + # This is for clearing cache on the reflection. Useful for tests that need to compare + # SQL queries on associations. + def clear_association_scope_cache # :nodoc: + delegate_reflection.clear_association_scope_cache + source_reflection.clear_association_scope_cache + through_reflection.clear_association_scope_cache + end + + def scopes + source_reflection.scopes + super + end + + def join_scopes(table, predicate_builder) # :nodoc: + source_reflection.join_scopes(table, predicate_builder) + super + end + + def has_scope? + scope || options[:source_type] || + source_reflection.has_scope? || + through_reflection.has_scope? + end + + # A through association is nested if there would be more than one join table + def nested? + source_reflection.through_reflection? || through_reflection.through_reflection? + end + + # We want to use the klass from this reflection, rather than just delegate straight to + # the source_reflection, because the source_reflection may be polymorphic. We still + # need to respect the source_reflection's :primary_key option, though. + def association_primary_key(klass = nil) + # Get the "actual" source reflection if the immediate source reflection has a + # source reflection itself + actual_source_reflection.options[:primary_key] || primary_key(klass || self.klass) + end + + # Gets an array of possible :through source reflection names in both singular and plural form. + # + # class Post < ActiveRecord::Base + # has_many :taggings + # has_many :tags, through: :taggings + # end + # + # tags_reflection = Post.reflect_on_association(:tags) + # tags_reflection.source_reflection_names + # # => [:tag, :tags] + # + def source_reflection_names + options[:source] ? [options[:source]] : [name.to_s.singularize, name].uniq + end + + def source_reflection_name # :nodoc: + return @source_reflection_name if @source_reflection_name + + names = [name.to_s.singularize, name].collect(&:to_sym).uniq + names = names.find_all { |n| + through_reflection.klass._reflect_on_association(n) + } + + if names.length > 1 + raise AmbiguousSourceReflectionForThroughAssociation.new( + active_record.name, + macro, + name, + options, + source_reflection_names + ) + end + + @source_reflection_name = names.first + end + + def source_options + source_reflection.options + end + + def through_options + through_reflection.options + end + + def check_validity! + if through_reflection.nil? + raise HasManyThroughAssociationNotFoundError.new(active_record.name, self) + end + + if through_reflection.polymorphic? + if has_one? + raise HasOneAssociationPolymorphicThroughError.new(active_record.name, self) + else + raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self) + end + end + + if source_reflection.nil? + raise HasManyThroughSourceAssociationNotFoundError.new(self) + end + + if options[:source_type] && !source_reflection.polymorphic? + raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection) + end + + if source_reflection.polymorphic? && options[:source_type].nil? + raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection) + end + + if has_one? && through_reflection.collection? + raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection) + end + + if parent_reflection.nil? + reflections = active_record.reflections.keys.map(&:to_sym) + + if reflections.index(through_reflection.name) > reflections.index(name) + raise HasManyThroughOrderError.new(active_record.name, self, through_reflection) + end + end + + check_validity_of_inverse! + end + + def constraints + scope_chain = source_reflection.constraints + scope_chain << scope if scope + scope_chain + end + + def add_as_source(seed) + collect_join_reflections seed + end + + def add_as_polymorphic_through(reflection, seed) + collect_join_reflections(seed + [PolymorphicReflection.new(self, reflection)]) + end + + def add_as_through(seed) + collect_join_reflections(seed + [self]) + end + + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected + attr_reader :delegate_reflection + + def actual_source_reflection # FIXME: this is a horrible name + source_reflection.actual_source_reflection + end + + private + def collect_join_reflections(seed) + a = source_reflection.add_as_source seed + if options[:source_type] + through_reflection.add_as_polymorphic_through self, a + else + through_reflection.add_as_through a + end + end + + def inverse_name; delegate_reflection.send(:inverse_name); end + + def derive_class_name + # get the class_name of the belongs_to association of the through reflection + options[:source_type] || source_reflection.class_name + end + + delegate_methods = AssociationReflection.public_instance_methods - + public_instance_methods + + delegate(*delegate_methods, to: :delegate_reflection) + end + + class PolymorphicReflection < AbstractReflection # :nodoc: + delegate :klass, :scope, :plural_name, :type, :get_join_keys, :scope_for, to: :@reflection + + def initialize(reflection, previous_reflection) + @reflection = reflection + @previous_reflection = previous_reflection + end + + def join_scopes(table, predicate_builder) # :nodoc: + scopes = @previous_reflection.join_scopes(table, predicate_builder) + super + scopes << build_scope(table, predicate_builder).instance_exec(nil, &source_type_scope) + end + + def constraints + @reflection.constraints + [source_type_scope] + end + + private + def source_type_scope + type = @previous_reflection.foreign_type + source_type = @previous_reflection.options[:source_type] + lambda { |object| where(type => source_type) } + end + end + + class RuntimeReflection < AbstractReflection # :nodoc: + delegate :scope, :type, :constraints, :get_join_keys, to: :@reflection + + def initialize(reflection, association) + @reflection = reflection + @association = association + end + + def klass + @association.klass + end + + def aliased_table + @aliased_table ||= Arel::Table.new(table_name, type_caster: klass.type_caster) + end + + def all_includes; yield; end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation.rb new file mode 100644 index 00000000..0588e7ba --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation.rb @@ -0,0 +1,629 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record \Relation + class Relation + MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group, + :order, :joins, :left_outer_joins, :references, + :extending, :unscope] + + SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering, + :reverse_order, :distinct, :create_with, :skip_query_cache] + CLAUSE_METHODS = [:where, :having, :from] + INVALID_METHODS_FOR_DELETE_ALL = [:distinct, :group, :having] + + VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS + + include Enumerable + include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation + + attr_reader :table, :klass, :loaded, :predicate_builder + alias :model :klass + alias :loaded? :loaded + alias :locked? :lock_value + + def initialize(klass, table: klass.arel_table, predicate_builder: klass.predicate_builder, values: {}) + @klass = klass + @table = table + @values = values + @offsets = {} + @loaded = false + @predicate_builder = predicate_builder + @delegate_to_klass = false + end + + def initialize_copy(other) + @values = @values.dup + reset + end + + def arel_attribute(name) # :nodoc: + klass.arel_attribute(name, table) + end + + # Initializes new record from relation while maintaining the current + # scope. + # + # Expects arguments in the same format as {ActiveRecord::Base.new}[rdoc-ref:Core.new]. + # + # users = User.where(name: 'DHH') + # user = users.new # => # + # + # You can also pass a block to new with the new record as argument: + # + # user = users.new { |user| user.name = 'Oscar' } + # user.name # => Oscar + def new(attributes = nil, &block) + scoping { klass.new(values_for_create(attributes), &block) } + end + + alias build new + + # Tries to create a new record with the same scoped attributes + # defined in the relation. Returns the initialized object if validation fails. + # + # Expects arguments in the same format as + # {ActiveRecord::Base.create}[rdoc-ref:Persistence::ClassMethods#create]. + # + # ==== Examples + # + # users = User.where(name: 'Oscar') + # users.create # => # + # + # users.create(name: 'fxn') + # users.create # => # + # + # users.create { |user| user.name = 'tenderlove' } + # # => # + # + # users.create(name: nil) # validation on name + # # => # + def create(attributes = nil, &block) + if attributes.is_a?(Array) + attributes.collect { |attr| create(attr, &block) } + else + scoping { klass.create(values_for_create(attributes), &block) } + end + end + + # Similar to #create, but calls + # {create!}[rdoc-ref:Persistence::ClassMethods#create!] + # on the base class. Raises an exception if a validation error occurs. + # + # Expects arguments in the same format as + # {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!]. + def create!(attributes = nil, &block) + if attributes.is_a?(Array) + attributes.collect { |attr| create!(attr, &block) } + else + scoping { klass.create!(values_for_create(attributes), &block) } + end + end + + def first_or_create(attributes = nil, &block) # :nodoc: + first || create(attributes, &block) + end + + def first_or_create!(attributes = nil, &block) # :nodoc: + first || create!(attributes, &block) + end + + def first_or_initialize(attributes = nil, &block) # :nodoc: + first || new(attributes, &block) + end + + # Finds the first record with the given attributes, or creates a record + # with the attributes if one is not found: + # + # # Find the first user named "Penélope" or create a new one. + # User.find_or_create_by(first_name: 'Penélope') + # # => # + # + # # Find the first user named "Penélope" or create a new one. + # # We already have one so the existing record will be returned. + # User.find_or_create_by(first_name: 'Penélope') + # # => # + # + # # Find the first user named "Scarlett" or create a new one with + # # a particular last name. + # User.create_with(last_name: 'Johansson').find_or_create_by(first_name: 'Scarlett') + # # => # + # + # This method accepts a block, which is passed down to #create. The last example + # above can be alternatively written this way: + # + # # Find the first user named "Scarlett" or create a new one with a + # # different last name. + # User.find_or_create_by(first_name: 'Scarlett') do |user| + # user.last_name = 'Johansson' + # end + # # => # + # + # This method always returns a record, but if creation was attempted and + # failed due to validation errors it won't be persisted, you get what + # #create returns in such situation. + # + # Please note *this method is not atomic*, it runs first a SELECT, and if + # there are no results an INSERT is attempted. If there are other threads + # or processes there is a race condition between both calls and it could + # be the case that you end up with two similar records. + # + # Whether that is a problem or not depends on the logic of the + # application, but in the particular case in which rows have a UNIQUE + # constraint an exception may be raised, just retry: + # + # begin + # CreditAccount.transaction(requires_new: true) do + # CreditAccount.find_or_create_by(user_id: user.id) + # end + # rescue ActiveRecord::RecordNotUnique + # retry + # end + # + def find_or_create_by(attributes, &block) + find_by(attributes) || create(attributes, &block) + end + + # Like #find_or_create_by, but calls + # {create!}[rdoc-ref:Persistence::ClassMethods#create!] so an exception + # is raised if the created record is invalid. + def find_or_create_by!(attributes, &block) + find_by(attributes) || create!(attributes, &block) + end + + # Like #find_or_create_by, but calls {new}[rdoc-ref:Core#new] + # instead of {create}[rdoc-ref:Persistence::ClassMethods#create]. + def find_or_initialize_by(attributes, &block) + find_by(attributes) || new(attributes, &block) + end + + # Runs EXPLAIN on the query or queries triggered by this relation and + # returns the result as a string. The string is formatted imitating the + # ones printed by the database shell. + # + # Note that this method actually runs the queries, since the results of some + # are needed by the next ones when eager loading is going on. + # + # Please see further details in the + # {Active Record Query Interface guide}[http://guides.rubyonrails.org/active_record_querying.html#running-explain]. + def explain + exec_explain(collecting_queries_for_explain { exec_queries }) + end + + # Converts relation objects to Array. + def to_ary + records.dup + end + alias to_a to_ary + + def records # :nodoc: + load + @records + end + + # Serializes the relation objects Array. + def encode_with(coder) + coder.represent_seq(nil, records) + end + + # Returns size of the records. + def size + loaded? ? @records.length : count(:all) + end + + # Returns true if there are no records. + def empty? + return @records.empty? if loaded? + !exists? + end + + # Returns true if there are no records. + def none? + return super if block_given? + empty? + end + + # Returns true if there are any records. + def any? + return super if block_given? + !empty? + end + + # Returns true if there is exactly one record. + def one? + return super if block_given? + limit_value ? records.one? : size == 1 + end + + # Returns true if there is more than one record. + def many? + return super if block_given? + limit_value ? records.many? : size > 1 + end + + # Returns a cache key that can be used to identify the records fetched by + # this query. The cache key is built with a fingerprint of the sql query, + # the number of records matched by the query and a timestamp of the last + # updated record. When a new record comes to match the query, or any of + # the existing records is updated or deleted, the cache key changes. + # + # Product.where("name like ?", "%Cosmic Encounter%").cache_key + # # => "products/query-1850ab3d302391b85b8693e941286659-1-20150714212553907087000" + # + # If the collection is loaded, the method will iterate through the records + # to generate the timestamp, otherwise it will trigger one SQL query like: + # + # SELECT COUNT(*), MAX("products"."updated_at") FROM "products" WHERE (name like '%Cosmic Encounter%') + # + # You can also pass a custom timestamp column to fetch the timestamp of the + # last updated record. + # + # Product.where("name like ?", "%Game%").cache_key(:last_reviewed_at) + # + # You can customize the strategy to generate the key on a per model basis + # overriding ActiveRecord::Base#collection_cache_key. + def cache_key(timestamp_column = :updated_at) + @cache_keys ||= {} + @cache_keys[timestamp_column] ||= @klass.collection_cache_key(self, timestamp_column) + end + + # Scope all queries to the current scope. + # + # Comment.where(post_id: 1).scoping do + # Comment.first + # end + # # => SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 ORDER BY "comments"."id" ASC LIMIT 1 + # + # Please check unscoped if you want to remove all previous scopes (including + # the default_scope) during the execution of a block. + def scoping + previous, klass.current_scope = klass.current_scope(true), self unless @delegate_to_klass + yield + ensure + klass.current_scope = previous unless @delegate_to_klass + end + + def _exec_scope(*args, &block) # :nodoc: + @delegate_to_klass = true + instance_exec(*args, &block) || self + ensure + @delegate_to_klass = false + end + + # Updates all records in the current relation with details given. This method constructs a single SQL UPDATE + # statement and sends it straight to the database. It does not instantiate the involved models and it does not + # trigger Active Record callbacks or validations. However, values passed to #update_all will still go through + # Active Record's normal type casting and serialization. + # + # ==== Parameters + # + # * +updates+ - A string, array, or hash representing the SET part of an SQL statement. + # + # ==== Examples + # + # # Update all customers with the given attributes + # Customer.update_all wants_email: true + # + # # Update all books with 'Rails' in their title + # Book.where('title LIKE ?', '%Rails%').update_all(author: 'David') + # + # # Update all books that match conditions, but limit it to 5 ordered by date + # Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(author: 'David') + # + # # Update all invoices and set the number column to its id value. + # Invoice.update_all('number = id') + def update_all(updates) + raise ArgumentError, "Empty list of attributes to change" if updates.blank? + + if eager_loading? + relation = apply_join_dependency + return relation.update_all(updates) + end + + stmt = Arel::UpdateManager.new + + stmt.set Arel.sql(@klass.sanitize_sql_for_assignment(updates)) + stmt.table(table) + + if has_join_values? || offset_value + @klass.connection.join_to_update(stmt, arel, arel_attribute(primary_key)) + else + stmt.key = arel_attribute(primary_key) + stmt.take(arel.limit) + stmt.order(*arel.orders) + stmt.wheres = arel.constraints + end + + @klass.connection.update stmt, "#{@klass} Update All" + end + + def update(id = :all, attributes) # :nodoc: + if id == :all + each { |record| record.update(attributes) } + else + klass.update(id, attributes) + end + end + + # Destroys the records by instantiating each + # record and calling its {#destroy}[rdoc-ref:Persistence#destroy] method. + # Each object's callbacks are executed (including :dependent association options). + # Returns the collection of objects that were destroyed; each will be frozen, to + # reflect that no changes should be made (since they can't be persisted). + # + # Note: Instantiation, callback execution, and deletion of each + # record can be time consuming when you're removing many records at + # once. It generates at least one SQL +DELETE+ query per record (or + # possibly more, to enforce your callbacks). If you want to delete many + # rows quickly, without concern for their associations or callbacks, use + # #delete_all instead. + # + # ==== Examples + # + # Person.where(age: 0..18).destroy_all + def destroy_all + records.each(&:destroy).tap { reset } + end + + # Deletes the records without instantiating the records + # first, and hence not calling the {#destroy}[rdoc-ref:Persistence#destroy] + # method nor invoking callbacks. + # This is a single SQL DELETE statement that goes straight to the database, much more + # efficient than #destroy_all. Be careful with relations though, in particular + # :dependent rules defined on associations are not honored. Returns the + # number of rows affected. + # + # Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all + # + # Both calls delete the affected posts all at once with a single DELETE statement. + # If you need to destroy dependent associations or call your before_* or + # +after_destroy+ callbacks, use the #destroy_all method instead. + # + # If an invalid method is supplied, #delete_all raises an ActiveRecordError: + # + # Post.distinct.delete_all + # # => ActiveRecord::ActiveRecordError: delete_all doesn't support distinct + def delete_all + invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method| + value = get_value(method) + SINGLE_VALUE_METHODS.include?(method) ? value : value.any? + end + if invalid_methods.any? + raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}") + end + + if eager_loading? + relation = apply_join_dependency + return relation.delete_all + end + + stmt = Arel::DeleteManager.new + stmt.from(table) + + if has_join_values? || has_limit_or_offset? + @klass.connection.join_to_delete(stmt, arel, arel_attribute(primary_key)) + else + stmt.wheres = arel.constraints + end + + affected = @klass.connection.delete(stmt, "#{@klass} Destroy") + + reset + affected + end + + # Causes the records to be loaded from the database if they have not + # been loaded already. You can use this if for some reason you need + # to explicitly load some records before actually using them. The + # return value is the relation itself, not the records. + # + # Post.where(published: true).load # => # + def load(&block) + exec_queries(&block) unless loaded? + + self + end + + # Forces reloading of relation. + def reload + reset + load + end + + def reset + @delegate_to_klass = false + @to_sql = @arel = @loaded = @should_eager_load = nil + @records = [].freeze + @offsets = {} + self + end + + # Returns sql statement for the relation. + # + # User.where(name: 'Oscar').to_sql + # # => SELECT "users".* FROM "users" WHERE "users"."name" = 'Oscar' + def to_sql + @to_sql ||= begin + if eager_loading? + apply_join_dependency do |relation, join_dependency| + relation = join_dependency.apply_column_aliases(relation) + relation.to_sql + end + else + conn = klass.connection + conn.unprepared_statement { conn.to_sql(arel) } + end + end + end + + # Returns a hash of where conditions. + # + # User.where(name: 'Oscar').where_values_hash + # # => {name: "Oscar"} + def where_values_hash(relation_table_name = klass.table_name) + where_clause.to_h(relation_table_name) + end + + def scope_for_create + where_values_hash.merge!(create_with_value.stringify_keys) + end + + # Returns true if relation needs eager loading. + def eager_loading? + @should_eager_load ||= + eager_load_values.any? || + includes_values.any? && (joined_includes_values.any? || references_eager_loaded_tables?) + end + + # Joins that are also marked for preloading. In which case we should just eager load them. + # Note that this is a naive implementation because we could have strings and symbols which + # represent the same association, but that aren't matched by this. Also, we could have + # nested hashes which partially match, e.g. { a: :b } & { a: [:b, :c] } + def joined_includes_values + includes_values & joins_values + end + + # Compares two relations for equality. + def ==(other) + case other + when Associations::CollectionProxy, AssociationRelation + self == other.records + when Relation + other.to_sql == to_sql + when Array + records == other + end + end + + def pretty_print(q) + q.pp(records) + end + + # Returns true if relation is blank. + def blank? + records.blank? + end + + def values + @values.dup + end + + def inspect + subject = loaded? ? records : self + entries = subject.take([limit_value, 11].compact.min).map!(&:inspect) + + entries[10] = "..." if entries.size == 11 + + "#<#{self.class.name} [#{entries.join(', ')}]>" + end + + def empty_scope? # :nodoc: + @values == klass.unscoped.values + end + + def has_limit_or_offset? # :nodoc: + limit_value || offset_value + end + + def alias_tracker(joins = [], aliases = nil) # :nodoc: + joins += [aliases] if aliases + ActiveRecord::Associations::AliasTracker.create(connection, table.name, joins) + end + + protected + + def load_records(records) + @records = records.freeze + @loaded = true + end + + private + + def has_join_values? + joins_values.any? || left_outer_joins_values.any? + end + + def exec_queries(&block) + skip_query_cache_if_necessary do + @records = + if eager_loading? + apply_join_dependency do |relation, join_dependency| + if ActiveRecord::NullRelation === relation + [] + else + relation = join_dependency.apply_column_aliases(relation) + rows = connection.select_all(relation.arel, "SQL") + join_dependency.instantiate(rows, &block) + end.freeze + end + else + klass.find_by_sql(arel, &block).freeze + end + + preload = preload_values + preload += includes_values unless eager_loading? + preloader = nil + preload.each do |associations| + preloader ||= build_preloader + preloader.preload @records, associations + end + + @records.each(&:readonly!) if readonly_value + + @loaded = true + @records + end + end + + def skip_query_cache_if_necessary + if skip_query_cache_value + uncached do + yield + end + else + yield + end + end + + def build_preloader + ActiveRecord::Associations::Preloader.new + end + + def references_eager_loaded_tables? + joined_tables = arel.join_sources.map do |join| + if join.is_a?(Arel::Nodes::StringJoin) + tables_in_string(join.left) + else + [join.left.table_name, join.left.table_alias] + end + end + + joined_tables += [table.name, table.table_alias] + + # always convert table names to downcase as in Oracle quoted table names are in uppercase + joined_tables = joined_tables.flatten.compact.map(&:downcase).uniq + + (references_values - joined_tables).any? + end + + def tables_in_string(string) + return [] if string.blank? + # always convert table names to downcase as in Oracle quoted table names are in uppercase + # ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries + string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map(&:downcase).uniq - ["raw_sql_"] + end + + def values_for_create(attributes = nil) + result = attributes ? where_values_hash.merge!(attributes) : where_values_hash + + # NOTE: if there are same keys in both create_with and result, create_with should be used. + # This is to make sure nested attributes don't get passed to the klass.new, + # while keeping the precedence of the duplicate keys in create_with. + create_with_value.stringify_keys.each do |k, v| + result[k] = v if result.key?(k) + end + + result + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/batches.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/batches.rb new file mode 100644 index 00000000..56186901 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/batches.rb @@ -0,0 +1,287 @@ +# frozen_string_literal: true + +require "active_record/relation/batches/batch_enumerator" + +module ActiveRecord + module Batches + ORDER_IGNORE_MESSAGE = "Scoped order is ignored, it's forced to be batch order." + + # Looping through a collection of records from the database + # (using the Scoping::Named::ClassMethods.all method, for example) + # is very inefficient since it will try to instantiate all the objects at once. + # + # In that case, batch processing methods allow you to work + # with the records in batches, thereby greatly reducing memory consumption. + # + # The #find_each method uses #find_in_batches with a batch size of 1000 (or as + # specified by the +:batch_size+ option). + # + # Person.find_each do |person| + # person.do_awesome_stuff + # end + # + # Person.where("age > 21").find_each do |person| + # person.party_all_night! + # end + # + # If you do not provide a block to #find_each, it will return an Enumerator + # for chaining with other methods: + # + # Person.find_each.with_index do |person, index| + # person.award_trophy(index + 1) + # end + # + # ==== Options + # * :batch_size - Specifies the size of the batch. Defaults to 1000. + # * :start - Specifies the primary key value to start from, inclusive of the value. + # * :finish - Specifies the primary key value to end at, inclusive of the value. + # * :error_on_ignore - Overrides the application config to specify if an error should be raised when + # an order is present in the relation. + # + # Limits are honored, and if present there is no requirement for the batch + # size: it can be less than, equal to, or greater than the limit. + # + # The options +start+ and +finish+ are especially useful if you want + # multiple workers dealing with the same processing queue. You can make + # worker 1 handle all the records between id 1 and 9999 and worker 2 + # handle from 10000 and beyond by setting the +:start+ and +:finish+ + # option on each worker. + # + # # In worker 1, let's process until 9999 records. + # Person.find_each(finish: 9_999) do |person| + # person.party_all_night! + # end + # + # # In worker 2, let's process from record 10_000 and onwards. + # Person.find_each(start: 10_000) do |person| + # person.party_all_night! + # end + # + # NOTE: It's not possible to set the order. That is automatically set to + # ascending on the primary key ("id ASC") to make the batch ordering + # work. This also means that this method only works when the primary key is + # orderable (e.g. an integer or string). + # + # NOTE: By its nature, batch processing is subject to race conditions if + # other processes are modifying the database. + def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil) + if block_given? + find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do |records| + records.each { |record| yield record } + end + else + enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do + relation = self + apply_limits(relation, start, finish).size + end + end + end + + # Yields each batch of records that was found by the find options as + # an array. + # + # Person.where("age > 21").find_in_batches do |group| + # sleep(50) # Make sure it doesn't get too crowded in there! + # group.each { |person| person.party_all_night! } + # end + # + # If you do not provide a block to #find_in_batches, it will return an Enumerator + # for chaining with other methods: + # + # Person.find_in_batches.with_index do |group, batch| + # puts "Processing group ##{batch}" + # group.each(&:recover_from_last_night!) + # end + # + # To be yielded each record one by one, use #find_each instead. + # + # ==== Options + # * :batch_size - Specifies the size of the batch. Defaults to 1000. + # * :start - Specifies the primary key value to start from, inclusive of the value. + # * :finish - Specifies the primary key value to end at, inclusive of the value. + # * :error_on_ignore - Overrides the application config to specify if an error should be raised when + # an order is present in the relation. + # + # Limits are honored, and if present there is no requirement for the batch + # size: it can be less than, equal to, or greater than the limit. + # + # The options +start+ and +finish+ are especially useful if you want + # multiple workers dealing with the same processing queue. You can make + # worker 1 handle all the records between id 1 and 9999 and worker 2 + # handle from 10000 and beyond by setting the +:start+ and +:finish+ + # option on each worker. + # + # # Let's process from record 10_000 on. + # Person.find_in_batches(start: 10_000) do |group| + # group.each { |person| person.party_all_night! } + # end + # + # NOTE: It's not possible to set the order. That is automatically set to + # ascending on the primary key ("id ASC") to make the batch ordering + # work. This also means that this method only works when the primary key is + # orderable (e.g. an integer or string). + # + # NOTE: By its nature, batch processing is subject to race conditions if + # other processes are modifying the database. + def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil) + relation = self + unless block_given? + return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do + total = apply_limits(relation, start, finish).size + (total - 1).div(batch_size) + 1 + end + end + + in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore) do |batch| + yield batch.to_a + end + end + + # Yields ActiveRecord::Relation objects to work with a batch of records. + # + # Person.where("age > 21").in_batches do |relation| + # relation.delete_all + # sleep(10) # Throttle the delete queries + # end + # + # If you do not provide a block to #in_batches, it will return a + # BatchEnumerator which is enumerable. + # + # Person.in_batches.each_with_index do |relation, batch_index| + # puts "Processing relation ##{batch_index}" + # relation.delete_all + # end + # + # Examples of calling methods on the returned BatchEnumerator object: + # + # Person.in_batches.delete_all + # Person.in_batches.update_all(awesome: true) + # Person.in_batches.each_record(&:party_all_night!) + # + # ==== Options + # * :of - Specifies the size of the batch. Defaults to 1000. + # * :load - Specifies if the relation should be loaded. Defaults to false. + # * :start - Specifies the primary key value to start from, inclusive of the value. + # * :finish - Specifies the primary key value to end at, inclusive of the value. + # * :error_on_ignore - Overrides the application config to specify if an error should be raised when + # an order is present in the relation. + # + # Limits are honored, and if present there is no requirement for the batch + # size, it can be less than, equal, or greater than the limit. + # + # The options +start+ and +finish+ are especially useful if you want + # multiple workers dealing with the same processing queue. You can make + # worker 1 handle all the records between id 1 and 9999 and worker 2 + # handle from 10000 and beyond by setting the +:start+ and +:finish+ + # option on each worker. + # + # # Let's process from record 10_000 on. + # Person.in_batches(start: 10_000).update_all(awesome: true) + # + # An example of calling where query method on the relation: + # + # Person.in_batches.each do |relation| + # relation.update_all('age = age + 1') + # relation.where('age > 21').update_all(should_party: true) + # relation.where('age <= 21').delete_all + # end + # + # NOTE: If you are going to iterate through each record, you should call + # #each_record on the yielded BatchEnumerator: + # + # Person.in_batches.each_record(&:party_all_night!) + # + # NOTE: It's not possible to set the order. That is automatically set to + # ascending on the primary key ("id ASC") to make the batch ordering + # consistent. Therefore the primary key must be orderable, e.g. an integer + # or a string. + # + # NOTE: By its nature, batch processing is subject to race conditions if + # other processes are modifying the database. + def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil) + relation = self + unless block_given? + return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self) + end + + if arel.orders.present? + act_on_ignored_order(error_on_ignore) + end + + batch_limit = of + if limit_value + remaining = limit_value + batch_limit = remaining if remaining < batch_limit + end + + relation = relation.reorder(batch_order).limit(batch_limit) + relation = apply_limits(relation, start, finish) + relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching + batch_relation = relation + + loop do + if load + records = batch_relation.records + ids = records.map(&:id) + yielded_relation = where(primary_key => ids) + yielded_relation.load_records(records) + else + ids = batch_relation.pluck(primary_key) + yielded_relation = where(primary_key => ids) + end + + break if ids.empty? + + primary_key_offset = ids.last + raise ArgumentError.new("Primary key not included in the custom select clause") unless primary_key_offset + + yield yielded_relation + + break if ids.length < batch_limit + + if limit_value + remaining -= ids.length + + if remaining == 0 + # Saves a useless iteration when the limit is a multiple of the + # batch size. + break + elsif remaining < batch_limit + relation = relation.limit(remaining) + end + end + + attr = Relation::QueryAttribute.new(primary_key, primary_key_offset, klass.type_for_attribute(primary_key)) + batch_relation = relation.where(arel_attribute(primary_key).gt(Arel::Nodes::BindParam.new(attr))) + end + end + + private + + def apply_limits(relation, start, finish) + if start + attr = Relation::QueryAttribute.new(primary_key, start, klass.type_for_attribute(primary_key)) + relation = relation.where(arel_attribute(primary_key).gteq(Arel::Nodes::BindParam.new(attr))) + end + if finish + attr = Relation::QueryAttribute.new(primary_key, finish, klass.type_for_attribute(primary_key)) + relation = relation.where(arel_attribute(primary_key).lteq(Arel::Nodes::BindParam.new(attr))) + end + relation + end + + def batch_order + arel_attribute(primary_key).asc + end + + def act_on_ignored_order(error_on_ignore) + raise_error = (error_on_ignore.nil? ? klass.error_on_ignored_order : error_on_ignore) + + if raise_error + raise ArgumentError.new(ORDER_IGNORE_MESSAGE) + elsif logger + logger.warn(ORDER_IGNORE_MESSAGE) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/batches/batch_enumerator.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/batches/batch_enumerator.rb new file mode 100644 index 00000000..49697da3 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/batches/batch_enumerator.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +module ActiveRecord + module Batches + class BatchEnumerator + include Enumerable + + def initialize(of: 1000, start: nil, finish: nil, relation:) #:nodoc: + @of = of + @relation = relation + @start = start + @finish = finish + end + + # Looping through a collection of records from the database (using the + # +all+ method, for example) is very inefficient since it will try to + # instantiate all the objects at once. + # + # In that case, batch processing methods allow you to work with the + # records in batches, thereby greatly reducing memory consumption. + # + # Person.in_batches.each_record do |person| + # person.do_awesome_stuff + # end + # + # Person.where("age > 21").in_batches(of: 10).each_record do |person| + # person.party_all_night! + # end + # + # If you do not provide a block to #each_record, it will return an Enumerator + # for chaining with other methods: + # + # Person.in_batches.each_record.with_index do |person, index| + # person.award_trophy(index + 1) + # end + def each_record + return to_enum(:each_record) unless block_given? + + @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true).each do |relation| + relation.records.each { |record| yield record } + end + end + + # Delegates #delete_all, #update_all, #destroy_all methods to each batch. + # + # People.in_batches.delete_all + # People.where('age < 10').in_batches.destroy_all + # People.in_batches.update_all('age = age + 1') + [:delete_all, :update_all, :destroy_all].each do |method| + define_method(method) do |*args, &block| + @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false).each do |relation| + relation.send(method, *args, &block) + end + end + end + + # Yields an ActiveRecord::Relation object for each batch of records. + # + # Person.in_batches.each do |relation| + # relation.update_all(awesome: true) + # end + def each + enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false) + return enum.each { |relation| yield relation } if block_given? + enum + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/calculations.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/calculations.rb new file mode 100644 index 00000000..51b11331 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/calculations.rb @@ -0,0 +1,417 @@ +# frozen_string_literal: true + +module ActiveRecord + module Calculations + # Count the records. + # + # Person.count + # # => the total count of all people + # + # Person.count(:age) + # # => returns the total count of all people whose age is present in database + # + # Person.count(:all) + # # => performs a COUNT(*) (:all is an alias for '*') + # + # Person.distinct.count(:age) + # # => counts the number of different age values + # + # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group], + # it returns a Hash whose keys represent the aggregated column, + # and the values are the respective amounts: + # + # Person.group(:city).count + # # => { 'Rome' => 5, 'Paris' => 3 } + # + # If #count is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose + # keys are an array containing the individual values of each column and the value + # of each key would be the #count. + # + # Article.group(:status, :category).count + # # => {["draft", "business"]=>10, ["draft", "technology"]=>4, + # ["published", "business"]=>0, ["published", "technology"]=>2} + # + # If #count is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns: + # + # Person.select(:age).count + # # => counts the number of different age values + # + # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ + # between databases. In invalid cases, an error from the database is thrown. + def count(column_name = nil) + if block_given? + unless column_name.nil? + ActiveSupport::Deprecation.warn \ + "When `count' is called with a block, it ignores other arguments. " \ + "This behavior is now deprecated and will result in an ArgumentError in Rails 6.0." + end + + return super() + end + + calculate(:count, column_name) + end + + # Calculates the average value on a given column. Returns +nil+ if there's + # no row. See #calculate for examples with options. + # + # Person.average(:age) # => 35.8 + def average(column_name) + calculate(:average, column_name) + end + + # Calculates the minimum value on a given column. The value is returned + # with the same data type of the column, or +nil+ if there's no row. See + # #calculate for examples with options. + # + # Person.minimum(:age) # => 7 + def minimum(column_name) + calculate(:minimum, column_name) + end + + # Calculates the maximum value on a given column. The value is returned + # with the same data type of the column, or +nil+ if there's no row. See + # #calculate for examples with options. + # + # Person.maximum(:age) # => 93 + def maximum(column_name) + calculate(:maximum, column_name) + end + + # Calculates the sum of values on a given column. The value is returned + # with the same data type of the column, +0+ if there's no row. See + # #calculate for examples with options. + # + # Person.sum(:age) # => 4562 + def sum(column_name = nil) + if block_given? + unless column_name.nil? + ActiveSupport::Deprecation.warn \ + "When `sum' is called with a block, it ignores other arguments. " \ + "This behavior is now deprecated and will result in an ArgumentError in Rails 6.0." + end + + return super() + end + + calculate(:sum, column_name) + end + + # This calculates aggregate values in the given column. Methods for #count, #sum, #average, + # #minimum, and #maximum have been added as shortcuts. + # + # Person.calculate(:count, :all) # The same as Person.count + # Person.average(:age) # SELECT AVG(age) FROM people... + # + # # Selects the minimum age for any family without any minors + # Person.group(:last_name).having("min(age) > 17").minimum(:age) + # + # Person.sum("2 * age") + # + # There are two basic forms of output: + # + # * Single aggregate value: The single value is type cast to Integer for COUNT, Float + # for AVG, and the given column's type for everything else. + # + # * Grouped values: This returns an ordered hash of the values and groups them. It + # takes either a column name, or the name of a belongs_to association. + # + # values = Person.group('last_name').maximum(:age) + # puts values["Drake"] + # # => 43 + # + # drake = Family.find_by(last_name: 'Drake') + # values = Person.group(:family).maximum(:age) # Person belongs_to :family + # puts values[drake] + # # => 43 + # + # values.each do |family, max_age| + # ... + # end + def calculate(operation, column_name) + if has_include?(column_name) + relation = apply_join_dependency + + if operation.to_s.downcase == "count" + relation.distinct! + # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT + if (column_name == :all || column_name.nil?) && select_values.empty? + relation.order_values = [] + end + end + + relation.calculate(operation, column_name) + else + perform_calculation(operation, column_name) + end + end + + # Use #pluck as a shortcut to select one or more attributes without + # loading a bunch of records just to grab the attributes you want. + # + # Person.pluck(:name) + # + # instead of + # + # Person.all.map(&:name) + # + # Pluck returns an Array of attribute values type-casted to match + # the plucked column names, if they can be deduced. Plucking an SQL fragment + # returns String values by default. + # + # Person.pluck(:name) + # # SELECT people.name FROM people + # # => ['David', 'Jeremy', 'Jose'] + # + # Person.pluck(:id, :name) + # # SELECT people.id, people.name FROM people + # # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']] + # + # Person.distinct.pluck(:role) + # # SELECT DISTINCT role FROM people + # # => ['admin', 'member', 'guest'] + # + # Person.where(age: 21).limit(5).pluck(:id) + # # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5 + # # => [2, 3] + # + # Person.pluck('DATEDIFF(updated_at, created_at)') + # # SELECT DATEDIFF(updated_at, created_at) FROM people + # # => ['0', '27761', '173'] + # + # See also #ids. + # + def pluck(*column_names) + if loaded? && (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty? + return records.pluck(*column_names) + end + + if has_include?(column_names.first) + relation = apply_join_dependency + relation.pluck(*column_names) + else + klass.enforce_raw_sql_whitelist(column_names) + relation = spawn + relation.select_values = column_names + result = skip_query_cache_if_necessary { klass.connection.select_all(relation.arel, nil) } + result.cast_values(klass.attribute_types) + end + end + + # Pluck all the ID's for the relation using the table's primary key + # + # Person.ids # SELECT people.id FROM people + # Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.person_id = people.id + def ids + pluck primary_key + end + + private + def has_include?(column_name) + eager_loading? || (includes_values.present? && column_name && column_name != :all) + end + + def perform_calculation(operation, column_name) + operation = operation.to_s.downcase + + # If #count is used with #distinct (i.e. `relation.distinct.count`) it is + # considered distinct. + distinct = distinct_value + + if operation == "count" + column_name ||= select_for_count + if column_name == :all + if !distinct + distinct = distinct_select?(select_for_count) if group_values.empty? + elsif group_values.any? || select_values.empty? && order_values.empty? + column_name = primary_key + end + elsif distinct_select?(column_name) + distinct = nil + end + end + + if group_values.any? + execute_grouped_calculation(operation, column_name, distinct) + else + execute_simple_calculation(operation, column_name, distinct) + end + end + + def distinct_select?(column_name) + column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name) + end + + def aggregate_column(column_name) + return column_name if Arel::Expressions === column_name + + if @klass.has_attribute?(column_name) || @klass.attribute_alias?(column_name) + @klass.arel_attribute(column_name) + else + Arel.sql(column_name == :all ? "*" : column_name.to_s) + end + end + + def operation_over_aggregate_column(column, operation, distinct) + operation == "count" ? column.count(distinct) : column.send(operation) + end + + def execute_simple_calculation(operation, column_name, distinct) #:nodoc: + column_alias = column_name + + if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?) + # Shortcut when limit is zero. + return 0 if limit_value == 0 + + query_builder = build_count_subquery(spawn, column_name, distinct) + else + # PostgreSQL doesn't like ORDER BY when there are no GROUP BY + relation = unscope(:order).distinct!(false) + + column = aggregate_column(column_name) + + select_value = operation_over_aggregate_column(column, operation, distinct) + if operation == "sum" && distinct + select_value.distinct = true + end + + column_alias = select_value.alias + column_alias ||= @klass.connection.column_name_for_operation(operation, select_value) + relation.select_values = [select_value] + + query_builder = relation.arel + end + + result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder, nil) } + row = result.first + value = row && row.values.first + type = result.column_types.fetch(column_alias) do + type_for(column_name) + end + + type_cast_calculated_value(value, type, operation) + end + + def execute_grouped_calculation(operation, column_name, distinct) #:nodoc: + group_attrs = group_values + + if group_attrs.first.respond_to?(:to_sym) + association = @klass._reflect_on_association(group_attrs.first) + associated = group_attrs.size == 1 && association && association.belongs_to? # only count belongs_to associations + group_fields = Array(associated ? association.foreign_key : group_attrs) + else + group_fields = group_attrs + end + group_fields = arel_columns(group_fields) + + group_aliases = group_fields.map { |field| column_alias_for(field) } + group_columns = group_aliases.zip(group_fields) + + if operation == "count" && column_name == :all + aggregate_alias = "count_all" + else + aggregate_alias = column_alias_for([operation, column_name].join(" ")) + end + + select_values = [ + operation_over_aggregate_column( + aggregate_column(column_name), + operation, + distinct).as(aggregate_alias) + ] + select_values += self.select_values unless having_clause.empty? + + select_values.concat group_columns.map { |aliaz, field| + if field.respond_to?(:as) + field.as(aliaz) + else + "#{field} AS #{aliaz}" + end + } + + relation = except(:group).distinct!(false) + relation.group_values = group_fields + relation.select_values = select_values + + calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, nil) } + + if association + key_ids = calculated_data.collect { |row| row[group_aliases.first] } + key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids) + key_records = Hash[key_records.map { |r| [r.id, r] }] + end + + Hash[calculated_data.map do |row| + key = group_columns.map { |aliaz, col_name| + type = type_for(col_name) do + calculated_data.column_types.fetch(aliaz, Type.default_value) + end + type_cast_calculated_value(row[aliaz], type) + } + key = key.first if key.size == 1 + key = key_records[key] if associated + + type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) } + [key, type_cast_calculated_value(row[aggregate_alias], type, operation)] + end] + end + + # Converts the given keys to the value that the database adapter returns as + # a usable column name: + # + # column_alias_for("users.id") # => "users_id" + # column_alias_for("sum(id)") # => "sum_id" + # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id" + # column_alias_for("count(*)") # => "count_all" + def column_alias_for(keys) + if keys.respond_to? :name + keys = "#{keys.relation.name}.#{keys.name}" + end + + table_name = keys.to_s.downcase + table_name.gsub!(/\*/, "all") + table_name.gsub!(/\W+/, " ") + table_name.strip! + table_name.gsub!(/ +/, "_") + + @klass.connection.table_alias_for(table_name) + end + + def type_for(field, &block) + field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last + @klass.type_for_attribute(field_name, &block) + end + + def type_cast_calculated_value(value, type, operation = nil) + case operation + when "count" then value.to_i + when "sum" then type.deserialize(value || 0) + when "average" then value && value.respond_to?(:to_d) ? value.to_d : value + else type.deserialize(value) + end + end + + def select_for_count + if select_values.present? + return select_values.first if select_values.one? + select_values.join(", ") + else + :all + end + end + + def build_count_subquery(relation, column_name, distinct) + if column_name == :all + relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct + else + column_alias = Arel.sql("count_column") + relation.select_values = [ aggregate_column(column_name).as(column_alias) ] + end + + subquery = relation.arel.as(Arel.sql("subquery_for_count")) + select_value = operation_over_aggregate_column(column_alias || Arel.star, "count", false) + + Arel::SelectManager.new(subquery).project(select_value) + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/delegation.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/delegation.rb new file mode 100644 index 00000000..8ae0ffe9 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/delegation.rb @@ -0,0 +1,147 @@ +# frozen_string_literal: true + +module ActiveRecord + module Delegation # :nodoc: + module DelegateCache # :nodoc: + def relation_delegate_class(klass) + @relation_delegate_cache[klass] + end + + def initialize_relation_delegate_cache + @relation_delegate_cache = cache = {} + [ + ActiveRecord::Relation, + ActiveRecord::Associations::CollectionProxy, + ActiveRecord::AssociationRelation + ].each do |klass| + delegate = Class.new(klass) { + include ClassSpecificRelation + } + include_relation_methods(delegate) + mangled_name = klass.name.gsub("::".freeze, "_".freeze) + const_set mangled_name, delegate + private_constant mangled_name + + cache[klass] = delegate + end + end + + def inherited(child_class) + child_class.initialize_relation_delegate_cache + super + end + + protected + def include_relation_methods(delegate) + superclass.include_relation_methods(delegate) unless base_class == self + delegate.include generated_relation_methods + end + + private + def generated_relation_methods + @generated_relation_methods ||= Module.new.tap do |mod| + mod_name = "GeneratedRelationMethods" + const_set mod_name, mod + private_constant mod_name + end + end + + def generate_relation_method(method) + if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) + generated_relation_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{method}(*args, &block) + scoping { klass.#{method}(*args, &block) } + end + RUBY + else + generated_relation_methods.send(:define_method, method) do |*args, &block| + scoping { klass.public_send(method, *args, &block) } + end + end + end + end + + extend ActiveSupport::Concern + + # This module creates compiled delegation methods dynamically at runtime, which makes + # subsequent calls to that method faster by avoiding method_missing. The delegations + # may vary depending on the klass of a relation, so we create a subclass of Relation + # for each different klass, and the delegations are compiled into that subclass only. + + delegate :to_xml, :encode_with, :length, :each, :uniq, :join, + :[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of, + :to_sentence, :to_formatted_s, :as_json, + :shuffle, :split, :slice, :index, :rindex, to: :records + + delegate :primary_key, :connection, to: :klass + + module ClassSpecificRelation # :nodoc: + extend ActiveSupport::Concern + + included do + @delegation_mutex = Mutex.new + end + + module ClassMethods # :nodoc: + def name + superclass.name + end + + def delegate_to_scoped_klass(method) + @delegation_mutex.synchronize do + return if method_defined?(method) + + if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method) + module_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{method}(*args, &block) + scoping { @klass.#{method}(*args, &block) } + end + RUBY + else + define_method method do |*args, &block| + scoping { @klass.public_send(method, *args, &block) } + end + end + end + end + end + + private + + def method_missing(method, *args, &block) + if @klass.respond_to?(method) + self.class.delegate_to_scoped_klass(method) + scoping { @klass.public_send(method, *args, &block) } + elsif @delegate_to_klass && @klass.respond_to?(method, true) + ActiveSupport::Deprecation.warn \ + "Delegating missing #{method} method to #{@klass}. " \ + "Accessibility of private/protected class methods in :scope is deprecated and will be removed in Rails 6.0." + @klass.send(method, *args, &block) + elsif arel.respond_to?(method) + ActiveSupport::Deprecation.warn \ + "Delegating #{method} to arel is deprecated and will be removed in Rails 6.0." + arel.public_send(method, *args, &block) + else + super + end + end + end + + module ClassMethods # :nodoc: + def create(klass, *args) + relation_class_for(klass).new(klass, *args) + end + + private + + def relation_class_for(klass) + klass.relation_delegate_class(self) + end + end + + private + def respond_to_missing?(method, _) + super || @klass.respond_to?(method) || arel.respond_to?(method) + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/finder_methods.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/finder_methods.rb new file mode 100644 index 00000000..d4fe2a7b --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/finder_methods.rb @@ -0,0 +1,565 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/filters" + +module ActiveRecord + module FinderMethods + ONE_AS_ONE = "1 AS one" + + # Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]). + # If one or more records can not be found for the requested ids, then RecordNotFound will be raised. If the primary key + # is an integer, find by id coerces its arguments using +to_i+. + # + # Person.find(1) # returns the object for ID = 1 + # Person.find("1") # returns the object for ID = 1 + # Person.find("31-sarah") # returns the object for ID = 31 + # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6) + # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17) + # Person.find([1]) # returns an array for the object with ID = 1 + # Person.where("administrator = 1").order("created_on DESC").find(1) + # + # NOTE: The returned records are in the same order as the ids you provide. + # If you want the results to be sorted by database, you can use ActiveRecord::QueryMethods#where + # method and provide an explicit ActiveRecord::QueryMethods#order option. + # But ActiveRecord::QueryMethods#where method doesn't raise ActiveRecord::RecordNotFound. + # + # ==== Find with lock + # + # Example for find with a lock: Imagine two concurrent transactions: + # each will read person.visits == 2, add 1 to it, and save, resulting + # in two saves of person.visits = 3. By locking the row, the second + # transaction has to wait until the first is finished; we get the + # expected person.visits == 4. + # + # Person.transaction do + # person = Person.lock(true).find(1) + # person.visits += 1 + # person.save! + # end + # + # ==== Variations of #find + # + # Person.where(name: 'Spartacus', rating: 4) + # # returns a chainable list (which can be empty). + # + # Person.find_by(name: 'Spartacus', rating: 4) + # # returns the first item or nil. + # + # Person.find_or_initialize_by(name: 'Spartacus', rating: 4) + # # returns the first item or returns a new instance (requires you call .save to persist against the database). + # + # Person.find_or_create_by(name: 'Spartacus', rating: 4) + # # returns the first item or creates it and returns it. + # + # ==== Alternatives for #find + # + # Person.where(name: 'Spartacus', rating: 4).exists?(conditions = :none) + # # returns a boolean indicating if any record with the given conditions exist. + # + # Person.where(name: 'Spartacus', rating: 4).select("field1, field2, field3") + # # returns a chainable list of instances with only the mentioned fields. + # + # Person.where(name: 'Spartacus', rating: 4).ids + # # returns an Array of ids. + # + # Person.where(name: 'Spartacus', rating: 4).pluck(:field1, :field2) + # # returns an Array of the required fields. + def find(*args) + return super if block_given? + find_with_ids(*args) + end + + # Finds the first record matching the specified conditions. There + # is no implied ordering so if order matters, you should specify it + # yourself. + # + # If no record is found, returns nil. + # + # Post.find_by name: 'Spartacus', rating: 4 + # Post.find_by "published_at < ?", 2.weeks.ago + def find_by(arg, *args) + where(arg, *args).take + rescue ::RangeError + nil + end + + # Like #find_by, except that if no record is found, raises + # an ActiveRecord::RecordNotFound error. + def find_by!(arg, *args) + where(arg, *args).take! + rescue ::RangeError + raise RecordNotFound.new("Couldn't find #{@klass.name} with an out of range value", + @klass.name, @klass.primary_key) + end + + # Gives a record (or N records if a parameter is supplied) without any implied + # order. The order will depend on the database implementation. + # If an order is supplied it will be respected. + # + # Person.take # returns an object fetched by SELECT * FROM people LIMIT 1 + # Person.take(5) # returns 5 objects fetched by SELECT * FROM people LIMIT 5 + # Person.where(["name LIKE '%?'", name]).take + def take(limit = nil) + limit ? find_take_with_limit(limit) : find_take + end + + # Same as #take but raises ActiveRecord::RecordNotFound if no record + # is found. Note that #take! accepts no arguments. + def take! + take || raise_record_not_found_exception! + end + + # Find the first record (or first N records if a parameter is supplied). + # If no order is defined it will order by primary key. + # + # Person.first # returns the first object fetched by SELECT * FROM people ORDER BY people.id LIMIT 1 + # Person.where(["user_name = ?", user_name]).first + # Person.where(["user_name = :u", { u: user_name }]).first + # Person.order("created_on DESC").offset(5).first + # Person.first(3) # returns the first three objects fetched by SELECT * FROM people ORDER BY people.id LIMIT 3 + # + def first(limit = nil) + if limit + find_nth_with_limit(0, limit) + else + find_nth 0 + end + end + + # Same as #first but raises ActiveRecord::RecordNotFound if no record + # is found. Note that #first! accepts no arguments. + def first! + first || raise_record_not_found_exception! + end + + # Find the last record (or last N records if a parameter is supplied). + # If no order is defined it will order by primary key. + # + # Person.last # returns the last object fetched by SELECT * FROM people + # Person.where(["user_name = ?", user_name]).last + # Person.order("created_on DESC").offset(5).last + # Person.last(3) # returns the last three objects fetched by SELECT * FROM people. + # + # Take note that in that last case, the results are sorted in ascending order: + # + # [#, #, #] + # + # and not: + # + # [#, #, #] + def last(limit = nil) + return find_last(limit) if loaded? || has_limit_or_offset? + + result = ordered_relation.limit(limit) + result = result.reverse_order! + + limit ? result.reverse : result.first + end + + # Same as #last but raises ActiveRecord::RecordNotFound if no record + # is found. Note that #last! accepts no arguments. + def last! + last || raise_record_not_found_exception! + end + + # Find the second record. + # If no order is defined it will order by primary key. + # + # Person.second # returns the second object fetched by SELECT * FROM people + # Person.offset(3).second # returns the second object from OFFSET 3 (which is OFFSET 4) + # Person.where(["user_name = :u", { u: user_name }]).second + def second + find_nth 1 + end + + # Same as #second but raises ActiveRecord::RecordNotFound if no record + # is found. + def second! + second || raise_record_not_found_exception! + end + + # Find the third record. + # If no order is defined it will order by primary key. + # + # Person.third # returns the third object fetched by SELECT * FROM people + # Person.offset(3).third # returns the third object from OFFSET 3 (which is OFFSET 5) + # Person.where(["user_name = :u", { u: user_name }]).third + def third + find_nth 2 + end + + # Same as #third but raises ActiveRecord::RecordNotFound if no record + # is found. + def third! + third || raise_record_not_found_exception! + end + + # Find the fourth record. + # If no order is defined it will order by primary key. + # + # Person.fourth # returns the fourth object fetched by SELECT * FROM people + # Person.offset(3).fourth # returns the fourth object from OFFSET 3 (which is OFFSET 6) + # Person.where(["user_name = :u", { u: user_name }]).fourth + def fourth + find_nth 3 + end + + # Same as #fourth but raises ActiveRecord::RecordNotFound if no record + # is found. + def fourth! + fourth || raise_record_not_found_exception! + end + + # Find the fifth record. + # If no order is defined it will order by primary key. + # + # Person.fifth # returns the fifth object fetched by SELECT * FROM people + # Person.offset(3).fifth # returns the fifth object from OFFSET 3 (which is OFFSET 7) + # Person.where(["user_name = :u", { u: user_name }]).fifth + def fifth + find_nth 4 + end + + # Same as #fifth but raises ActiveRecord::RecordNotFound if no record + # is found. + def fifth! + fifth || raise_record_not_found_exception! + end + + # Find the forty-second record. Also known as accessing "the reddit". + # If no order is defined it will order by primary key. + # + # Person.forty_two # returns the forty-second object fetched by SELECT * FROM people + # Person.offset(3).forty_two # returns the forty-second object from OFFSET 3 (which is OFFSET 44) + # Person.where(["user_name = :u", { u: user_name }]).forty_two + def forty_two + find_nth 41 + end + + # Same as #forty_two but raises ActiveRecord::RecordNotFound if no record + # is found. + def forty_two! + forty_two || raise_record_not_found_exception! + end + + # Find the third-to-last record. + # If no order is defined it will order by primary key. + # + # Person.third_to_last # returns the third-to-last object fetched by SELECT * FROM people + # Person.offset(3).third_to_last # returns the third-to-last object from OFFSET 3 + # Person.where(["user_name = :u", { u: user_name }]).third_to_last + def third_to_last + find_nth_from_last 3 + end + + # Same as #third_to_last but raises ActiveRecord::RecordNotFound if no record + # is found. + def third_to_last! + third_to_last || raise_record_not_found_exception! + end + + # Find the second-to-last record. + # If no order is defined it will order by primary key. + # + # Person.second_to_last # returns the second-to-last object fetched by SELECT * FROM people + # Person.offset(3).second_to_last # returns the second-to-last object from OFFSET 3 + # Person.where(["user_name = :u", { u: user_name }]).second_to_last + def second_to_last + find_nth_from_last 2 + end + + # Same as #second_to_last but raises ActiveRecord::RecordNotFound if no record + # is found. + def second_to_last! + second_to_last || raise_record_not_found_exception! + end + + # Returns true if a record exists in the table that matches the +id+ or + # conditions given, or false otherwise. The argument can take six forms: + # + # * Integer - Finds the record with this primary key. + # * String - Finds the record with a primary key corresponding to this + # string (such as '5'). + # * Array - Finds the record that matches these +find+-style conditions + # (such as ['name LIKE ?', "%#{query}%"]). + # * Hash - Finds the record that matches these +find+-style conditions + # (such as {name: 'David'}). + # * +false+ - Returns always +false+. + # * No args - Returns +false+ if the relation is empty, +true+ otherwise. + # + # For more information about specifying conditions as a hash or array, + # see the Conditions section in the introduction to ActiveRecord::Base. + # + # Note: You can't pass in a condition as a string (like name = + # 'Jamie'), since it would be sanitized and then queried against + # the primary key column, like id = 'name = \'Jamie\''. + # + # Person.exists?(5) + # Person.exists?('5') + # Person.exists?(['name LIKE ?', "%#{query}%"]) + # Person.exists?(id: [1, 4, 8]) + # Person.exists?(name: 'David') + # Person.exists?(false) + # Person.exists? + # Person.where(name: 'Spartacus', rating: 4).exists? + def exists?(conditions = :none) + if Base === conditions + raise ArgumentError, <<-MSG.squish + You are passing an instance of ActiveRecord::Base to `exists?`. + Please pass the id of the object by calling `.id`. + MSG + end + + return false if !conditions || limit_value == 0 + + if eager_loading? + relation = apply_join_dependency(eager_loading: false) + return relation.exists?(conditions) + end + + relation = construct_relation_for_exists(conditions) + + skip_query_cache_if_necessary { connection.select_one(relation.arel, "#{name} Exists") } ? true : false + rescue ::RangeError + false + end + + # This method is called whenever no records are found with either a single + # id or multiple ids and raises an ActiveRecord::RecordNotFound exception. + # + # The error message is different depending on whether a single id or + # multiple ids are provided. If multiple ids are provided, then the number + # of results obtained should be provided in the +result_size+ argument and + # the expected number of results should be provided in the +expected_size+ + # argument. + def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil, key = primary_key, not_found_ids = nil) # :nodoc: + conditions = arel.where_sql(@klass) + conditions = " [#{conditions}]" if conditions + name = @klass.name + + if ids.nil? + error = "Couldn't find #{name}".dup + error << " with#{conditions}" if conditions + raise RecordNotFound.new(error, name, key) + elsif Array(ids).size == 1 + error = "Couldn't find #{name} with '#{key}'=#{ids}#{conditions}" + raise RecordNotFound.new(error, name, key, ids) + else + error = "Couldn't find all #{name.pluralize} with '#{key}': ".dup + error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})." + error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.join(', ')}." if not_found_ids + raise RecordNotFound.new(error, name, key, ids) + end + end + + private + + def offset_index + offset_value || 0 + end + + def construct_relation_for_exists(conditions) + if distinct_value && offset_value + relation = limit(1) + else + relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1) + end + + case conditions + when Array, Hash + relation.where!(conditions) unless conditions.empty? + else + relation.where!(primary_key => conditions) unless conditions == :none + end + + relation + end + + def construct_join_dependency + including = eager_load_values + includes_values + ActiveRecord::Associations::JoinDependency.new( + klass, table, including + ) + end + + def apply_join_dependency(eager_loading: group_values.empty?) + join_dependency = construct_join_dependency + relation = except(:includes, :eager_load, :preload).joins!(join_dependency) + + if eager_loading && !using_limitable_reflections?(join_dependency.reflections) + if has_limit_or_offset? + limited_ids = limited_ids_for(relation) + limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids) + end + relation.limit_value = relation.offset_value = nil + end + + if block_given? + yield relation, join_dependency + else + relation + end + end + + def limited_ids_for(relation) + values = @klass.connection.columns_for_distinct( + connection.column_name_from_arel_node(arel_attribute(primary_key)), + relation.order_values + ) + + relation = relation.except(:select).select(values).distinct! + + id_rows = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "SQL") } + id_rows.map { |row| row[primary_key] } + end + + def using_limitable_reflections?(reflections) + reflections.none?(&:collection?) + end + + def find_with_ids(*ids) + raise UnknownPrimaryKey.new(@klass) if primary_key.nil? + + expects_array = ids.first.kind_of?(Array) + return [] if expects_array && ids.first.empty? + + ids = ids.flatten.compact.uniq + + model_name = @klass.name + + case ids.size + when 0 + error_message = "Couldn't find #{model_name} without an ID" + raise RecordNotFound.new(error_message, model_name, primary_key) + when 1 + result = find_one(ids.first) + expects_array ? [ result ] : result + else + find_some(ids) + end + rescue ::RangeError + error_message = "Couldn't find #{model_name} with an out of range ID" + raise RecordNotFound.new(error_message, model_name, primary_key, ids) + end + + def find_one(id) + if ActiveRecord::Base === id + raise ArgumentError, <<-MSG.squish + You are passing an instance of ActiveRecord::Base to `find`. + Please pass the id of the object by calling `.id`. + MSG + end + + relation = where(primary_key => id) + record = relation.take + + raise_record_not_found_exception!(id, 0, 1) unless record + + record + end + + def find_some(ids) + return find_some_ordered(ids) unless order_values.present? + + result = where(primary_key => ids).to_a + + expected_size = + if limit_value && ids.size > limit_value + limit_value + else + ids.size + end + + # 11 ids with limit 3, offset 9 should give 2 results. + if offset_value && (ids.size - offset_value < expected_size) + expected_size = ids.size - offset_value + end + + if result.size == expected_size + result + else + raise_record_not_found_exception!(ids, result.size, expected_size) + end + end + + def find_some_ordered(ids) + ids = ids.slice(offset_value || 0, limit_value || ids.size) || [] + + result = except(:limit, :offset).where(primary_key => ids).records + + if result.size == ids.size + pk_type = @klass.type_for_attribute(primary_key) + + records_by_id = result.index_by(&:id) + ids.map { |id| records_by_id.fetch(pk_type.cast(id)) } + else + raise_record_not_found_exception!(ids, result.size, ids.size) + end + end + + def find_take + if loaded? + records.first + else + @take ||= limit(1).records.first + end + end + + def find_take_with_limit(limit) + if loaded? + records.take(limit) + else + limit(limit).to_a + end + end + + def find_nth(index) + @offsets[offset_index + index] ||= find_nth_with_limit(index, 1).first + end + + def find_nth_with_limit(index, limit) + if loaded? + records[index, limit] || [] + else + relation = ordered_relation + + if limit_value + limit = [limit_value - index, limit].min + end + + if limit > 0 + relation = relation.offset(offset_index + index) unless index.zero? + relation.limit(limit).to_a + else + [] + end + end + end + + def find_nth_from_last(index) + if loaded? + records[-index] + else + relation = ordered_relation + + if equal?(relation) || has_limit_or_offset? + relation.records[-index] + else + relation.last(index)[-index] + end + end + end + + def find_last(limit) + limit ? records.last(limit) : records.last + end + + def ordered_relation + if order_values.empty? && primary_key + order(arel_attribute(primary_key).asc) + else + self + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/from_clause.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/from_clause.rb new file mode 100644 index 00000000..c53a682a --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/from_clause.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module ActiveRecord + class Relation + class FromClause # :nodoc: + attr_reader :value, :name + + def initialize(value, name) + @value = value + @name = name + end + + def merge(other) + self + end + + def empty? + value.nil? + end + + def self.empty + @empty ||= new(nil, nil) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/merger.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/merger.rb new file mode 100644 index 00000000..7fe14efa --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/merger.rb @@ -0,0 +1,193 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/keys" + +module ActiveRecord + class Relation + class HashMerger # :nodoc: + attr_reader :relation, :hash + + def initialize(relation, hash) + hash.assert_valid_keys(*Relation::VALUE_METHODS) + + @relation = relation + @hash = hash + end + + def merge #:nodoc: + Merger.new(relation, other).merge + end + + # Applying values to a relation has some side effects. E.g. + # interpolation might take place for where values. So we should + # build a relation to merge in rather than directly merging + # the values. + def other + other = Relation.create( + relation.klass, + table: relation.table, + predicate_builder: relation.predicate_builder + ) + hash.each { |k, v| + if k == :joins + if Hash === v + other.joins!(v) + else + other.joins!(*v) + end + elsif k == :select + other._select!(v) + else + other.send("#{k}!", v) + end + } + other + end + end + + class Merger # :nodoc: + attr_reader :relation, :values, :other + + def initialize(relation, other) + @relation = relation + @values = other.values + @other = other + end + + NORMAL_VALUES = Relation::VALUE_METHODS - + Relation::CLAUSE_METHODS - + [:includes, :preload, :joins, :left_outer_joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc: + + def normal_values + NORMAL_VALUES + end + + def merge + normal_values.each do |name| + value = values[name] + # The unless clause is here mostly for performance reasons (since the `send` call might be moderately + # expensive), most of the time the value is going to be `nil` or `.blank?`, the only catch is that + # `false.blank?` returns `true`, so there needs to be an extra check so that explicit `false` values + # don't fall through the cracks. + unless value.nil? || (value.blank? && false != value) + if name == :select + relation._select!(*value) + else + relation.send("#{name}!", *value) + end + end + end + + merge_multi_values + merge_single_values + merge_clauses + merge_preloads + merge_joins + merge_outer_joins + + relation + end + + private + + def merge_preloads + return if other.preload_values.empty? && other.includes_values.empty? + + if other.klass == relation.klass + relation.preload!(*other.preload_values) unless other.preload_values.empty? + relation.includes!(other.includes_values) unless other.includes_values.empty? + else + reflection = relation.klass.reflect_on_all_associations.find do |r| + r.class_name == other.klass.name + end || return + + unless other.preload_values.empty? + relation.preload! reflection.name => other.preload_values + end + + unless other.includes_values.empty? + relation.includes! reflection.name => other.includes_values + end + end + end + + def merge_joins + return if other.joins_values.blank? + + if other.klass == relation.klass + relation.joins!(*other.joins_values) + else + joins_dependency = other.joins_values.map do |join| + case join + when Hash, Symbol, Array + ActiveRecord::Associations::JoinDependency.new( + other.klass, other.table, join + ) + else + join + end + end + + relation.joins!(*joins_dependency) + end + end + + def merge_outer_joins + return if other.left_outer_joins_values.blank? + + if other.klass == relation.klass + relation.left_outer_joins!(*other.left_outer_joins_values) + else + joins_dependency = other.left_outer_joins_values.map do |join| + case join + when Hash, Symbol, Array + ActiveRecord::Associations::JoinDependency.new( + other.klass, other.table, join + ) + else + join + end + end + + relation.left_outer_joins!(*joins_dependency) + end + end + + def merge_multi_values + if other.reordering_value + # override any order specified in the original relation + relation.reorder!(*other.order_values) + elsif other.order_values.any? + # merge in order_values from relation + relation.order!(*other.order_values) + end + + extensions = other.extensions - relation.extensions + relation.extending!(*extensions) if extensions.any? + end + + def merge_single_values + relation.lock_value ||= other.lock_value if other.lock_value + + unless other.create_with_value.blank? + relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value) + end + end + + def merge_clauses + relation.from_clause = other.from_clause if replace_from_clause? + + where_clause = relation.where_clause.merge(other.where_clause) + relation.where_clause = where_clause unless where_clause.empty? + + having_clause = relation.having_clause.merge(other.having_clause) + relation.having_clause = having_clause unless having_clause.empty? + end + + def replace_from_clause? + relation.from_clause.empty? && !other.from_clause.empty? && + relation.klass.base_class == other.klass.base_class + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder.rb new file mode 100644 index 00000000..90fc9b6f --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder.rb @@ -0,0 +1,152 @@ +# frozen_string_literal: true + +module ActiveRecord + class PredicateBuilder # :nodoc: + delegate :resolve_column_aliases, to: :table + + def initialize(table) + @table = table + @handlers = [] + + register_handler(BasicObject, BasicObjectHandler.new(self)) + register_handler(Base, BaseHandler.new(self)) + register_handler(Range, RangeHandler.new(self)) + register_handler(Relation, RelationHandler.new) + register_handler(Array, ArrayHandler.new(self)) + register_handler(Set, ArrayHandler.new(self)) + end + + def build_from_hash(attributes) + attributes = convert_dot_notation_to_hash(attributes) + expand_from_hash(attributes) + end + + def self.references(attributes) + attributes.map do |key, value| + if value.is_a?(Hash) + key + else + key = key.to_s + key.split(".".freeze).first if key.include?(".".freeze) + end + end.compact + end + + # Define how a class is converted to Arel nodes when passed to +where+. + # The handler can be any object that responds to +call+, and will be used + # for any value that +===+ the class given. For example: + # + # MyCustomDateRange = Struct.new(:start, :end) + # handler = proc do |column, range| + # Arel::Nodes::Between.new(column, + # Arel::Nodes::And.new([range.start, range.end]) + # ) + # end + # ActiveRecord::PredicateBuilder.new("users").register_handler(MyCustomDateRange, handler) + def register_handler(klass, handler) + @handlers.unshift([klass, handler]) + end + + def build(attribute, value) + if table.type(attribute.name).force_equality?(value) + bind = build_bind_attribute(attribute.name, value) + attribute.eq(bind) + else + handler_for(value).call(attribute, value) + end + end + + def build_bind_attribute(column_name, value) + attr = Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name)) + Arel::Nodes::BindParam.new(attr) + end + + protected + + attr_reader :table + + def expand_from_hash(attributes) + return ["1=0"] if attributes.empty? + + attributes.flat_map do |key, value| + if value.is_a?(Hash) && !table.has_column?(key) + associated_predicate_builder(key).expand_from_hash(value) + elsif table.associated_with?(key) + # Find the foreign key when using queries such as: + # Post.where(author: author) + # + # For polymorphic relationships, find the foreign key and type: + # PriceEstimate.where(estimate_of: treasure) + associated_table = table.associated_table(key) + if associated_table.polymorphic_association? + case value.is_a?(Array) ? value.first : value + when Base, Relation + value = [value] unless value.is_a?(Array) + klass = PolymorphicArrayValue + end + end + + klass ||= AssociationQueryValue + queries = klass.new(associated_table, value).queries.map do |query| + expand_from_hash(query).reduce(&:and) + end + queries.reduce(&:or) + elsif table.aggregated_with?(key) + mapping = table.reflect_on_aggregation(key).mapping + values = value.nil? ? [nil] : Array.wrap(value) + if mapping.length == 1 || values.empty? + column_name, aggr_attr = mapping.first + values = values.map do |object| + object.respond_to?(aggr_attr) ? object.public_send(aggr_attr) : object + end + build(table.arel_attribute(column_name), values) + else + queries = values.map do |object| + mapping.map do |field_attr, aggregate_attr| + build(table.arel_attribute(field_attr), object.try!(aggregate_attr)) + end.reduce(&:and) + end + queries.reduce(&:or) + end + else + build(table.arel_attribute(key), value) + end + end + end + + private + + def associated_predicate_builder(association_name) + self.class.new(table.associated_table(association_name)) + end + + def convert_dot_notation_to_hash(attributes) + dot_notation = attributes.select do |k, v| + k.include?(".".freeze) && !v.is_a?(Hash) + end + + dot_notation.each_key do |key| + table_name, column_name = key.split(".".freeze) + value = attributes.delete(key) + attributes[table_name] ||= {} + + attributes[table_name] = attributes[table_name].merge(column_name => value) + end + + attributes + end + + def handler_for(object) + @handlers.detect { |klass, _| klass === object }.last + end + end +end + +require "active_record/relation/predicate_builder/array_handler" +require "active_record/relation/predicate_builder/base_handler" +require "active_record/relation/predicate_builder/basic_object_handler" +require "active_record/relation/predicate_builder/range_handler" +require "active_record/relation/predicate_builder/relation_handler" + +require "active_record/relation/predicate_builder/association_query_value" +require "active_record/relation/predicate_builder/polymorphic_array_value" diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/array_handler.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/array_handler.rb new file mode 100644 index 00000000..fe68a34c --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/array_handler.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module ActiveRecord + class PredicateBuilder + class ArrayHandler # :nodoc: + def initialize(predicate_builder) + @predicate_builder = predicate_builder + end + + def call(attribute, value) + return attribute.in([]) if value.empty? + + values = value.map { |x| x.is_a?(Base) ? x.id : x } + nils, values = values.partition(&:nil?) + ranges, values = values.partition { |v| v.is_a?(Range) } + + values_predicate = + case values.length + when 0 then NullPredicate + when 1 then predicate_builder.build(attribute, values.first) + else + values.map! do |v| + predicate_builder.build_bind_attribute(attribute.name, v) + end + values.empty? ? NullPredicate : attribute.in(values) + end + + unless nils.empty? + values_predicate = values_predicate.or(predicate_builder.build(attribute, nil)) + end + + array_predicates = ranges.map { |range| predicate_builder.build(attribute, range) } + array_predicates.unshift(values_predicate) + array_predicates.inject(&:or) + end + + protected + + attr_reader :predicate_builder + + module NullPredicate # :nodoc: + def self.or(other) + other + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/association_query_value.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/association_query_value.rb new file mode 100644 index 00000000..28c7483c --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/association_query_value.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module ActiveRecord + class PredicateBuilder + class AssociationQueryValue # :nodoc: + def initialize(associated_table, value) + @associated_table = associated_table + @value = value + end + + def queries + [associated_table.association_join_foreign_key.to_s => ids] + end + + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected + attr_reader :associated_table, :value + + private + def ids + case value + when Relation + value.select_values.empty? ? value.select(primary_key) : value + when Array + value.map { |v| convert_to_id(v) } + else + convert_to_id(value) + end + end + + def primary_key + associated_table.association_join_primary_key + end + + def convert_to_id(value) + case value + when Base + value._read_attribute(primary_key) + else + value + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/base_handler.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/base_handler.rb new file mode 100644 index 00000000..11282113 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/base_handler.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module ActiveRecord + class PredicateBuilder + class BaseHandler # :nodoc: + def initialize(predicate_builder) + @predicate_builder = predicate_builder + end + + def call(attribute, value) + predicate_builder.build(attribute, value.id) + end + + protected + + attr_reader :predicate_builder + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/basic_object_handler.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/basic_object_handler.rb new file mode 100644 index 00000000..34db266f --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/basic_object_handler.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module ActiveRecord + class PredicateBuilder + class BasicObjectHandler # :nodoc: + def initialize(predicate_builder) + @predicate_builder = predicate_builder + end + + def call(attribute, value) + bind = predicate_builder.build_bind_attribute(attribute.name, value) + attribute.eq(bind) + end + + protected + + attr_reader :predicate_builder + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb new file mode 100644 index 00000000..19b0958a --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module ActiveRecord + class PredicateBuilder + class PolymorphicArrayValue # :nodoc: + def initialize(associated_table, values) + @associated_table = associated_table + @values = values + end + + def queries + type_to_ids_mapping.map do |type, ids| + { + associated_table.association_foreign_type.to_s => type, + associated_table.association_foreign_key.to_s => ids + } + end + end + + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected + attr_reader :associated_table, :values + + private + def type_to_ids_mapping + default_hash = Hash.new { |hsh, key| hsh[key] = [] } + values.each_with_object(default_hash) do |value, hash| + hash[klass(value).polymorphic_name] << convert_to_id(value) + end + end + + def primary_key(value) + associated_table.association_join_primary_key(klass(value)) + end + + def klass(value) + case value + when Base + value.class + when Relation + value.klass + end + end + + def convert_to_id(value) + case value + when Base + value._read_attribute(primary_key(value)) + when Relation + value.select(primary_key(value)) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/range_handler.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/range_handler.rb new file mode 100644 index 00000000..681d01d9 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/range_handler.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module ActiveRecord + class PredicateBuilder + class RangeHandler # :nodoc: + class RangeWithBinds < Struct.new(:begin, :end) + def exclude_end? + false + end + end + + def initialize(predicate_builder) + @predicate_builder = predicate_builder + end + + def call(attribute, value) + begin_bind = predicate_builder.build_bind_attribute(attribute.name, value.begin) + end_bind = predicate_builder.build_bind_attribute(attribute.name, value.end) + + if begin_bind.value.infinity? + if end_bind.value.infinity? + attribute.not_in([]) + elsif value.exclude_end? + attribute.lt(end_bind) + else + attribute.lteq(end_bind) + end + elsif end_bind.value.infinity? + attribute.gteq(begin_bind) + elsif value.exclude_end? + attribute.gteq(begin_bind).and(attribute.lt(end_bind)) + else + attribute.between(RangeWithBinds.new(begin_bind, end_bind)) + end + end + + protected + + attr_reader :predicate_builder + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/relation_handler.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/relation_handler.rb new file mode 100644 index 00000000..c8bbfa50 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/predicate_builder/relation_handler.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module ActiveRecord + class PredicateBuilder + class RelationHandler # :nodoc: + def call(attribute, value) + if value.eager_loading? + value = value.send(:apply_join_dependency) + end + + if value.select_values.empty? + value = value.select(value.arel_attribute(value.klass.primary_key)) + end + + attribute.in(value.arel) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/query_attribute.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/query_attribute.rb new file mode 100644 index 00000000..eb4236b4 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/query_attribute.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require "active_model/attribute" + +module ActiveRecord + class Relation + class QueryAttribute < ActiveModel::Attribute # :nodoc: + def type_cast(value) + value + end + + def value_for_database + @value_for_database ||= super + end + + def with_cast_value(value) + QueryAttribute.new(name, value, type) + end + + def nil? + unless value_before_type_cast.is_a?(StatementCache::Substitute) + value_before_type_cast.nil? || + type.respond_to?(:subtype, true) && value_for_database.nil? + end + end + + def boundable? + return @_boundable if defined?(@_boundable) + value_for_database unless value_before_type_cast.is_a?(StatementCache::Substitute) + @_boundable = true + rescue ::RangeError + @_boundable = false + end + + def infinity? + _infinity?(value_before_type_cast) || boundable? && _infinity?(value_for_database) + end + + private + def _infinity?(value) + value.respond_to?(:infinite?) && value.infinite? + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/query_methods.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/query_methods.rb new file mode 100644 index 00000000..cf95e2fc --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/query_methods.rb @@ -0,0 +1,1231 @@ +# frozen_string_literal: true + +require "active_record/relation/from_clause" +require "active_record/relation/query_attribute" +require "active_record/relation/where_clause" +require "active_record/relation/where_clause_factory" +require "active_model/forbidden_attributes_protection" + +module ActiveRecord + module QueryMethods + extend ActiveSupport::Concern + + include ActiveModel::ForbiddenAttributesProtection + + # WhereChain objects act as placeholder for queries in which #where does not have any parameter. + # In this case, #where must be chained with #not to return a new relation. + class WhereChain + include ActiveModel::ForbiddenAttributesProtection + + def initialize(scope) + @scope = scope + end + + # Returns a new relation expressing WHERE + NOT condition according to + # the conditions in the arguments. + # + # #not accepts conditions as a string, array, or hash. See QueryMethods#where for + # more details on each format. + # + # User.where.not("name = 'Jon'") + # # SELECT * FROM users WHERE NOT (name = 'Jon') + # + # User.where.not(["name = ?", "Jon"]) + # # SELECT * FROM users WHERE NOT (name = 'Jon') + # + # User.where.not(name: "Jon") + # # SELECT * FROM users WHERE name != 'Jon' + # + # User.where.not(name: nil) + # # SELECT * FROM users WHERE name IS NOT NULL + # + # User.where.not(name: %w(Ko1 Nobu)) + # # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu') + # + # User.where.not(name: "Jon", role: "admin") + # # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin' + def not(opts, *rest) + opts = sanitize_forbidden_attributes(opts) + + where_clause = @scope.send(:where_clause_factory).build(opts, rest) + + @scope.references!(PredicateBuilder.references(opts)) if Hash === opts + @scope.where_clause += where_clause.invert + @scope + end + end + + FROZEN_EMPTY_ARRAY = [].freeze + FROZEN_EMPTY_HASH = {}.freeze + + Relation::VALUE_METHODS.each do |name| + method_name = \ + case name + when *Relation::MULTI_VALUE_METHODS then "#{name}_values" + when *Relation::SINGLE_VALUE_METHODS then "#{name}_value" + when *Relation::CLAUSE_METHODS then "#{name}_clause" + end + class_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{method_name} # def includes_values + get_value(#{name.inspect}) # get_value(:includes) + end # end + + def #{method_name}=(value) # def includes_values=(value) + set_value(#{name.inspect}, value) # set_value(:includes, value) + end # end + CODE + end + + alias extensions extending_values + + # Specify relationships to be included in the result set. For + # example: + # + # users = User.includes(:address) + # users.each do |user| + # user.address.city + # end + # + # allows you to access the +address+ attribute of the +User+ model without + # firing an additional query. This will often result in a + # performance improvement over a simple join. + # + # You can also specify multiple relationships, like this: + # + # users = User.includes(:address, :friends) + # + # Loading nested relationships is possible using a Hash: + # + # users = User.includes(:address, friends: [:address, :followers]) + # + # === conditions + # + # If you want to add conditions to your included models you'll have + # to explicitly reference them. For example: + # + # User.includes(:posts).where('posts.name = ?', 'example') + # + # Will throw an error, but this will work: + # + # User.includes(:posts).where('posts.name = ?', 'example').references(:posts) + # + # Note that #includes works with association names while #references needs + # the actual table name. + def includes(*args) + check_if_method_has_arguments!(:includes, args) + spawn.includes!(*args) + end + + def includes!(*args) # :nodoc: + args.reject!(&:blank?) + args.flatten! + + self.includes_values |= args + self + end + + # Forces eager loading by performing a LEFT OUTER JOIN on +args+: + # + # User.eager_load(:posts) + # # SELECT "users"."id" AS t0_r0, "users"."name" AS t0_r1, ... + # # FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = + # # "users"."id" + def eager_load(*args) + check_if_method_has_arguments!(:eager_load, args) + spawn.eager_load!(*args) + end + + def eager_load!(*args) # :nodoc: + self.eager_load_values += args + self + end + + # Allows preloading of +args+, in the same way that #includes does: + # + # User.preload(:posts) + # # SELECT "posts".* FROM "posts" WHERE "posts"."user_id" IN (1, 2, 3) + def preload(*args) + check_if_method_has_arguments!(:preload, args) + spawn.preload!(*args) + end + + def preload!(*args) # :nodoc: + self.preload_values += args + self + end + + # Use to indicate that the given +table_names+ are referenced by an SQL string, + # and should therefore be JOINed in any query rather than loaded separately. + # This method only works in conjunction with #includes. + # See #includes for more details. + # + # User.includes(:posts).where("posts.name = 'foo'") + # # Doesn't JOIN the posts table, resulting in an error. + # + # User.includes(:posts).where("posts.name = 'foo'").references(:posts) + # # Query now knows the string references posts, so adds a JOIN + def references(*table_names) + check_if_method_has_arguments!(:references, table_names) + spawn.references!(*table_names) + end + + def references!(*table_names) # :nodoc: + table_names.flatten! + table_names.map!(&:to_s) + + self.references_values |= table_names + self + end + + # Works in two unique ways. + # + # First: takes a block so it can be used just like Array#select. + # + # Model.all.select { |m| m.field == value } + # + # This will build an array of objects from the database for the scope, + # converting them into an array and iterating through them using + # Array#select. + # + # Second: Modifies the SELECT statement for the query so that only certain + # fields are retrieved: + # + # Model.select(:field) + # # => [#] + # + # Although in the above example it looks as though this method returns an + # array, it actually returns a relation object and can have other query + # methods appended to it, such as the other methods in ActiveRecord::QueryMethods. + # + # The argument to the method can also be an array of fields. + # + # Model.select(:field, :other_field, :and_one_more) + # # => [#] + # + # You can also use one or more strings, which will be used unchanged as SELECT fields. + # + # Model.select('field AS field_one', 'other_field AS field_two') + # # => [#] + # + # If an alias was specified, it will be accessible from the resulting objects: + # + # Model.select('field AS field_one').first.field_one + # # => "value" + # + # Accessing attributes of an object that do not have fields retrieved by a select + # except +id+ will throw ActiveModel::MissingAttributeError: + # + # Model.select(:field).first.other_field + # # => ActiveModel::MissingAttributeError: missing attribute: other_field + def select(*fields) + if block_given? + if fields.any? + raise ArgumentError, "`select' with block doesn't take arguments." + end + + return super() + end + + raise ArgumentError, "Call `select' with at least one field" if fields.empty? + spawn._select!(*fields) + end + + def _select!(*fields) # :nodoc: + fields.flatten! + fields.map! do |field| + klass.attribute_alias?(field) ? klass.attribute_alias(field).to_sym : field + end + self.select_values += fields + self + end + + # Allows to specify a group attribute: + # + # User.group(:name) + # # SELECT "users".* FROM "users" GROUP BY name + # + # Returns an array with distinct records based on the +group+ attribute: + # + # User.select([:id, :name]) + # # => [#, #, #] + # + # User.group(:name) + # # => [#, #] + # + # User.group('name AS grouped_name, age') + # # => [#, #, #] + # + # Passing in an array of attributes to group by is also supported. + # + # User.select([:id, :first_name]).group(:id, :first_name).first(3) + # # => [#, #, #] + def group(*args) + check_if_method_has_arguments!(:group, args) + spawn.group!(*args) + end + + def group!(*args) # :nodoc: + args.flatten! + + self.group_values += args + self + end + + # Allows to specify an order attribute: + # + # User.order(:name) + # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC + # + # User.order(email: :desc) + # # SELECT "users".* FROM "users" ORDER BY "users"."email" DESC + # + # User.order(:name, email: :desc) + # # SELECT "users".* FROM "users" ORDER BY "users"."name" ASC, "users"."email" DESC + # + # User.order('name') + # # SELECT "users".* FROM "users" ORDER BY name + # + # User.order('name DESC') + # # SELECT "users".* FROM "users" ORDER BY name DESC + # + # User.order('name DESC, email') + # # SELECT "users".* FROM "users" ORDER BY name DESC, email + def order(*args) + check_if_method_has_arguments!(:order, args) + spawn.order!(*args) + end + + # Same as #order but operates on relation in-place instead of copying. + def order!(*args) # :nodoc: + preprocess_order_args(args) + + self.order_values += args + self + end + + # Replaces any existing order defined on the relation with the specified order. + # + # User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC' + # + # Subsequent calls to order on the same relation will be appended. For example: + # + # User.order('email DESC').reorder('id ASC').order('name ASC') + # + # generates a query with 'ORDER BY id ASC, name ASC'. + def reorder(*args) + check_if_method_has_arguments!(:reorder, args) + spawn.reorder!(*args) + end + + # Same as #reorder but operates on relation in-place instead of copying. + def reorder!(*args) # :nodoc: + preprocess_order_args(args) + + self.reordering_value = true + self.order_values = args + self + end + + VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock, + :limit, :offset, :joins, :left_outer_joins, + :includes, :from, :readonly, :having]) + + # Removes an unwanted relation that is already defined on a chain of relations. + # This is useful when passing around chains of relations and would like to + # modify the relations without reconstructing the entire chain. + # + # User.order('email DESC').unscope(:order) == User.all + # + # The method arguments are symbols which correspond to the names of the methods + # which should be unscoped. The valid arguments are given in VALID_UNSCOPING_VALUES. + # The method can also be called with multiple arguments. For example: + # + # User.order('email DESC').select('id').where(name: "John") + # .unscope(:order, :select, :where) == User.all + # + # One can additionally pass a hash as an argument to unscope specific +:where+ values. + # This is done by passing a hash with a single key-value pair. The key should be + # +:where+ and the value should be the where value to unscope. For example: + # + # User.where(name: "John", active: true).unscope(where: :name) + # == User.where(active: true) + # + # This method is similar to #except, but unlike + # #except, it persists across merges: + # + # User.order('email').merge(User.except(:order)) + # == User.order('email') + # + # User.order('email').merge(User.unscope(:order)) + # == User.all + # + # This means it can be used in association definitions: + # + # has_many :comments, -> { unscope(where: :trashed) } + # + def unscope(*args) + check_if_method_has_arguments!(:unscope, args) + spawn.unscope!(*args) + end + + def unscope!(*args) # :nodoc: + args.flatten! + self.unscope_values += args + + args.each do |scope| + case scope + when Symbol + scope = :left_outer_joins if scope == :left_joins + if !VALID_UNSCOPING_VALUES.include?(scope) + raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}." + end + set_value(scope, DEFAULT_VALUES[scope]) + when Hash + scope.each do |key, target_value| + if key != :where + raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key." + end + + target_values = Array(target_value).map(&:to_s) + self.where_clause = where_clause.except(*target_values) + end + else + raise ArgumentError, "Unrecognized scoping: #{args.inspect}. Use .unscope(where: :attribute_name) or .unscope(:order), for example." + end + end + + self + end + + # Performs a joins on +args+. The given symbol(s) should match the name of + # the association(s). + # + # User.joins(:posts) + # # SELECT "users".* + # # FROM "users" + # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id" + # + # Multiple joins: + # + # User.joins(:posts, :account) + # # SELECT "users".* + # # FROM "users" + # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id" + # # INNER JOIN "accounts" ON "accounts"."id" = "users"."account_id" + # + # Nested joins: + # + # User.joins(posts: [:comments]) + # # SELECT "users".* + # # FROM "users" + # # INNER JOIN "posts" ON "posts"."user_id" = "users"."id" + # # INNER JOIN "comments" "comments_posts" + # # ON "comments_posts"."post_id" = "posts"."id" + # + # You can use strings in order to customize your joins: + # + # User.joins("LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id") + # # SELECT "users".* FROM "users" LEFT JOIN bookmarks ON bookmarks.bookmarkable_type = 'Post' AND bookmarks.user_id = users.id + def joins(*args) + check_if_method_has_arguments!(:joins, args) + spawn.joins!(*args) + end + + def joins!(*args) # :nodoc: + args.compact! + args.flatten! + self.joins_values += args + self + end + + # Performs a left outer joins on +args+: + # + # User.left_outer_joins(:posts) + # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id" + # + def left_outer_joins(*args) + check_if_method_has_arguments!(__callee__, args) + spawn.left_outer_joins!(*args) + end + alias :left_joins :left_outer_joins + + def left_outer_joins!(*args) # :nodoc: + args.compact! + args.flatten! + self.left_outer_joins_values += args + self + end + + # Returns a new relation, which is the result of filtering the current relation + # according to the conditions in the arguments. + # + # #where accepts conditions in one of several formats. In the examples below, the resulting + # SQL is given as an illustration; the actual query generated may be different depending + # on the database adapter. + # + # === string + # + # A single string, without additional arguments, is passed to the query + # constructor as an SQL fragment, and used in the where clause of the query. + # + # Client.where("orders_count = '2'") + # # SELECT * from clients where orders_count = '2'; + # + # Note that building your own string from user input may expose your application + # to injection attacks if not done properly. As an alternative, it is recommended + # to use one of the following methods. + # + # === array + # + # If an array is passed, then the first element of the array is treated as a template, and + # the remaining elements are inserted into the template to generate the condition. + # Active Record takes care of building the query to avoid injection attacks, and will + # convert from the ruby type to the database type where needed. Elements are inserted + # into the string in the order in which they appear. + # + # User.where(["name = ? and email = ?", "Joe", "joe@example.com"]) + # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'; + # + # Alternatively, you can use named placeholders in the template, and pass a hash as the + # second element of the array. The names in the template are replaced with the corresponding + # values from the hash. + # + # User.where(["name = :name and email = :email", { name: "Joe", email: "joe@example.com" }]) + # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'; + # + # This can make for more readable code in complex queries. + # + # Lastly, you can use sprintf-style % escapes in the template. This works slightly differently + # than the previous methods; you are responsible for ensuring that the values in the template + # are properly quoted. The values are passed to the connector for quoting, but the caller + # is responsible for ensuring they are enclosed in quotes in the resulting SQL. After quoting, + # the values are inserted using the same escapes as the Ruby core method +Kernel::sprintf+. + # + # User.where(["name = '%s' and email = '%s'", "Joe", "joe@example.com"]) + # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'; + # + # If #where is called with multiple arguments, these are treated as if they were passed as + # the elements of a single array. + # + # User.where("name = :name and email = :email", { name: "Joe", email: "joe@example.com" }) + # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'; + # + # When using strings to specify conditions, you can use any operator available from + # the database. While this provides the most flexibility, you can also unintentionally introduce + # dependencies on the underlying database. If your code is intended for general consumption, + # test with multiple database backends. + # + # === hash + # + # #where will also accept a hash condition, in which the keys are fields and the values + # are values to be searched for. + # + # Fields can be symbols or strings. Values can be single values, arrays, or ranges. + # + # User.where({ name: "Joe", email: "joe@example.com" }) + # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com' + # + # User.where({ name: ["Alice", "Bob"]}) + # # SELECT * FROM users WHERE name IN ('Alice', 'Bob') + # + # User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight }) + # # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000') + # + # In the case of a belongs_to relationship, an association key can be used + # to specify the model if an ActiveRecord object is used as the value. + # + # author = Author.find(1) + # + # # The following queries will be equivalent: + # Post.where(author: author) + # Post.where(author_id: author) + # + # This also works with polymorphic belongs_to relationships: + # + # treasure = Treasure.create(name: 'gold coins') + # treasure.price_estimates << PriceEstimate.create(price: 125) + # + # # The following queries will be equivalent: + # PriceEstimate.where(estimate_of: treasure) + # PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: treasure) + # + # === Joins + # + # If the relation is the result of a join, you may create a condition which uses any of the + # tables in the join. For string and array conditions, use the table name in the condition. + # + # User.joins(:posts).where("posts.created_at < ?", Time.now) + # + # For hash conditions, you can either use the table name in the key, or use a sub-hash. + # + # User.joins(:posts).where({ "posts.published" => true }) + # User.joins(:posts).where({ posts: { published: true } }) + # + # === no argument + # + # If no argument is passed, #where returns a new instance of WhereChain, that + # can be chained with #not to return a new relation that negates the where clause. + # + # User.where.not(name: "Jon") + # # SELECT * FROM users WHERE name != 'Jon' + # + # See WhereChain for more details on #not. + # + # === blank condition + # + # If the condition is any blank-ish object, then #where is a no-op and returns + # the current relation. + def where(opts = :chain, *rest) + if :chain == opts + WhereChain.new(spawn) + elsif opts.blank? + self + else + spawn.where!(opts, *rest) + end + end + + def where!(opts, *rest) # :nodoc: + opts = sanitize_forbidden_attributes(opts) + references!(PredicateBuilder.references(opts)) if Hash === opts + self.where_clause += where_clause_factory.build(opts, rest) + self + end + + # Allows you to change a previously set where condition for a given attribute, instead of appending to that condition. + # + # Post.where(trashed: true).where(trashed: false) + # # WHERE `trashed` = 1 AND `trashed` = 0 + # + # Post.where(trashed: true).rewhere(trashed: false) + # # WHERE `trashed` = 0 + # + # Post.where(active: true).where(trashed: true).rewhere(trashed: false) + # # WHERE `active` = 1 AND `trashed` = 0 + # + # This is short-hand for unscope(where: conditions.keys).where(conditions). + # Note that unlike reorder, we're only unscoping the named conditions -- not the entire where statement. + def rewhere(conditions) + unscope(where: conditions.keys).where(conditions) + end + + # Returns a new relation, which is the logical union of this relation and the one passed as an + # argument. + # + # The two relations must be structurally compatible: they must be scoping the same model, and + # they must differ only by #where (if no #group has been defined) or #having (if a #group is + # present). Neither relation may have a #limit, #offset, or #distinct set. + # + # Post.where("id = 1").or(Post.where("author_id = 3")) + # # SELECT `posts`.* FROM `posts` WHERE ((id = 1) OR (author_id = 3)) + # + def or(other) + unless other.is_a? Relation + raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead." + end + + spawn.or!(other) + end + + def or!(other) # :nodoc: + incompatible_values = structurally_incompatible_values_for_or(other) + + unless incompatible_values.empty? + raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}" + end + + self.where_clause = self.where_clause.or(other.where_clause) + self.having_clause = having_clause.or(other.having_clause) + self.references_values += other.references_values + + self + end + + # Allows to specify a HAVING clause. Note that you can't use HAVING + # without also specifying a GROUP clause. + # + # Order.having('SUM(price) > 30').group('user_id') + def having(opts, *rest) + opts.blank? ? self : spawn.having!(opts, *rest) + end + + def having!(opts, *rest) # :nodoc: + opts = sanitize_forbidden_attributes(opts) + references!(PredicateBuilder.references(opts)) if Hash === opts + + self.having_clause += having_clause_factory.build(opts, rest) + self + end + + # Specifies a limit for the number of records to retrieve. + # + # User.limit(10) # generated SQL has 'LIMIT 10' + # + # User.limit(10).limit(20) # generated SQL has 'LIMIT 20' + def limit(value) + spawn.limit!(value) + end + + def limit!(value) # :nodoc: + self.limit_value = value + self + end + + # Specifies the number of rows to skip before returning rows. + # + # User.offset(10) # generated SQL has "OFFSET 10" + # + # Should be used with order. + # + # User.offset(10).order("name ASC") + def offset(value) + spawn.offset!(value) + end + + def offset!(value) # :nodoc: + self.offset_value = value + self + end + + # Specifies locking settings (default to +true+). For more information + # on locking, please see ActiveRecord::Locking. + def lock(locks = true) + spawn.lock!(locks) + end + + def lock!(locks = true) # :nodoc: + case locks + when String, TrueClass, NilClass + self.lock_value = locks || true + else + self.lock_value = false + end + + self + end + + # Returns a chainable relation with zero records. + # + # The returned relation implements the Null Object pattern. It is an + # object with defined null behavior and always returns an empty array of + # records without querying the database. + # + # Any subsequent condition chained to the returned relation will continue + # generating an empty relation and will not fire any query to the database. + # + # Used in cases where a method or scope could return zero records but the + # result needs to be chainable. + # + # For example: + # + # @posts = current_user.visible_posts.where(name: params[:name]) + # # the visible_posts method is expected to return a chainable Relation + # + # def visible_posts + # case role + # when 'Country Manager' + # Post.where(country: country) + # when 'Reviewer' + # Post.published + # when 'Bad User' + # Post.none # It can't be chained if [] is returned. + # end + # end + # + def none + spawn.none! + end + + def none! # :nodoc: + where!("1=0").extending!(NullRelation) + end + + # Sets readonly attributes for the returned relation. If value is + # true (default), attempting to update a record will result in an error. + # + # users = User.readonly + # users.first.save + # => ActiveRecord::ReadOnlyRecord: User is marked as readonly + def readonly(value = true) + spawn.readonly!(value) + end + + def readonly!(value = true) # :nodoc: + self.readonly_value = value + self + end + + # Sets attributes to be used when creating new records from a + # relation object. + # + # users = User.where(name: 'Oscar') + # users.new.name # => 'Oscar' + # + # users = users.create_with(name: 'DHH') + # users.new.name # => 'DHH' + # + # You can pass +nil+ to #create_with to reset attributes: + # + # users = users.create_with(nil) + # users.new.name # => 'Oscar' + def create_with(value) + spawn.create_with!(value) + end + + def create_with!(value) # :nodoc: + if value + value = sanitize_forbidden_attributes(value) + self.create_with_value = create_with_value.merge(value) + else + self.create_with_value = FROZEN_EMPTY_HASH + end + + self + end + + # Specifies table from which the records will be fetched. For example: + # + # Topic.select('title').from('posts') + # # SELECT title FROM posts + # + # Can accept other relation objects. For example: + # + # Topic.select('title').from(Topic.approved) + # # SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery + # + # Topic.select('a.title').from(Topic.approved, :a) + # # SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a + # + def from(value, subquery_name = nil) + spawn.from!(value, subquery_name) + end + + def from!(value, subquery_name = nil) # :nodoc: + self.from_clause = Relation::FromClause.new(value, subquery_name) + self + end + + # Specifies whether the records should be unique or not. For example: + # + # User.select(:name) + # # Might return two records with the same name + # + # User.select(:name).distinct + # # Returns 1 record per distinct name + # + # User.select(:name).distinct.distinct(false) + # # You can also remove the uniqueness + def distinct(value = true) + spawn.distinct!(value) + end + + # Like #distinct, but modifies relation in place. + def distinct!(value = true) # :nodoc: + self.distinct_value = value + self + end + + # Used to extend a scope with additional methods, either through + # a module or through a block provided. + # + # The object returned is a relation, which can be further extended. + # + # === Using a module + # + # module Pagination + # def page(number) + # # pagination code goes here + # end + # end + # + # scope = Model.all.extending(Pagination) + # scope.page(params[:page]) + # + # You can also pass a list of modules: + # + # scope = Model.all.extending(Pagination, SomethingElse) + # + # === Using a block + # + # scope = Model.all.extending do + # def page(number) + # # pagination code goes here + # end + # end + # scope.page(params[:page]) + # + # You can also use a block and a module list: + # + # scope = Model.all.extending(Pagination) do + # def per_page(number) + # # pagination code goes here + # end + # end + def extending(*modules, &block) + if modules.any? || block + spawn.extending!(*modules, &block) + else + self + end + end + + def extending!(*modules, &block) # :nodoc: + modules << Module.new(&block) if block + modules.flatten! + + self.extending_values += modules + extend(*extending_values) if extending_values.any? + + self + end + + # Reverse the existing order clause on the relation. + # + # User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC' + def reverse_order + spawn.reverse_order! + end + + def reverse_order! # :nodoc: + orders = order_values.uniq + orders.reject!(&:blank?) + self.order_values = reverse_sql_order(orders) + self + end + + def skip_query_cache!(value = true) # :nodoc: + self.skip_query_cache_value = value + self + end + + # Returns the Arel object associated with the relation. + def arel(aliases = nil) # :nodoc: + @arel ||= build_arel(aliases) + end + + # Returns a relation value with a given name + def get_value(name) # :nodoc: + @values.fetch(name, DEFAULT_VALUES[name]) + end + + protected + + # Sets the relation value with the given name + def set_value(name, value) # :nodoc: + assert_mutability! + @values[name] = value + end + + private + + def assert_mutability! + raise ImmutableRelation if @loaded + raise ImmutableRelation if defined?(@arel) && @arel + end + + def build_arel(aliases) + arel = Arel::SelectManager.new(table) + + aliases = build_joins(arel, joins_values.flatten, aliases) unless joins_values.empty? + build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases) unless left_outer_joins_values.empty? + + arel.where(where_clause.ast) unless where_clause.empty? + arel.having(having_clause.ast) unless having_clause.empty? + if limit_value + limit_attribute = ActiveModel::Attribute.with_cast_value( + "LIMIT".freeze, + connection.sanitize_limit(limit_value), + Type.default_value, + ) + arel.take(Arel::Nodes::BindParam.new(limit_attribute)) + end + if offset_value + offset_attribute = ActiveModel::Attribute.with_cast_value( + "OFFSET".freeze, + offset_value.to_i, + Type.default_value, + ) + arel.skip(Arel::Nodes::BindParam.new(offset_attribute)) + end + arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty? + + build_order(arel) + + build_select(arel) + + arel.distinct(distinct_value) + arel.from(build_from) unless from_clause.empty? + arel.lock(lock_value) if lock_value + + arel + end + + def build_from + opts = from_clause.value + name = from_clause.name + case opts + when Relation + if opts.eager_loading? + opts = opts.send(:apply_join_dependency) + end + name ||= "subquery" + opts.arel.as(name.to_s) + else + opts + end + end + + def build_left_outer_joins(manager, outer_joins, aliases) + buckets = outer_joins.group_by do |join| + case join + when Hash, Symbol, Array + :association_join + when ActiveRecord::Associations::JoinDependency + :stashed_join + else + raise ArgumentError, "only Hash, Symbol and Array are allowed" + end + end + + build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases) + end + + def build_joins(manager, joins, aliases) + buckets = joins.group_by do |join| + case join + when String + :string_join + when Hash, Symbol, Array + :association_join + when ActiveRecord::Associations::JoinDependency + :stashed_join + when Arel::Nodes::Join + :join_node + else + raise "unknown class: %s" % join.class.name + end + end + + build_join_query(manager, buckets, Arel::Nodes::InnerJoin, aliases) + end + + def build_join_query(manager, buckets, join_type, aliases) + buckets.default = [] + + association_joins = buckets[:association_join] + stashed_joins = buckets[:stashed_join] + join_nodes = buckets[:join_node].uniq + string_joins = buckets[:string_join].map(&:strip).uniq + + join_list = join_nodes + convert_join_strings_to_ast(string_joins) + alias_tracker = alias_tracker(join_list, aliases) + + join_dependency = ActiveRecord::Associations::JoinDependency.new( + klass, table, association_joins + ) + + joins = join_dependency.join_constraints(stashed_joins, join_type, alias_tracker) + joins.each { |join| manager.from(join) } + + manager.join_sources.concat(join_list) + + alias_tracker.aliases + end + + def convert_join_strings_to_ast(joins) + joins + .flatten + .reject(&:blank?) + .map { |join| table.create_string_join(Arel.sql(join)) } + end + + def build_select(arel) + if select_values.any? + arel.project(*arel_columns(select_values.uniq)) + elsif klass.ignored_columns.any? + arel.project(*klass.column_names.map { |field| arel_attribute(field) }) + else + arel.project(table[Arel.star]) + end + end + + def arel_columns(columns) + columns.flat_map do |field| + case field + when Symbol + field = field.to_s + arel_column(field) { connection.quote_table_name(field) } + when String + arel_column(field) { field } + when Proc + field.call + else + field + end + end + end + + def arel_column(field) + field = klass.attribute_alias(field) if klass.attribute_alias?(field) + from = from_clause.name || from_clause.value + + if klass.columns_hash.key?(field) && (!from || table_name_matches?(from)) + arel_attribute(field) + else + yield + end + end + + def table_name_matches?(from) + /(?:\A|(? warn_on_records_fetched_greater_than + logger.warn "Query fetched #{@records.size} #{@klass} records: #{QueryRegistry.queries.join(";")}" + end + end + end + end + + # :stopdoc: + ActiveSupport::Notifications.subscribe("sql.active_record") do |*, payload| + QueryRegistry.queries << payload[:sql] + end + # :startdoc: + + class QueryRegistry # :nodoc: + extend ActiveSupport::PerThreadRegistry + + attr_reader :queries + + def initialize + @queries = [] + end + + def reset + @queries.clear + end + end + end + end +end + +ActiveRecord::Relation.prepend ActiveRecord::Relation::RecordFetchWarning diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/spawn_methods.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/spawn_methods.rb new file mode 100644 index 00000000..7874c4c3 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/spawn_methods.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/except" +require "active_support/core_ext/hash/slice" +require "active_record/relation/merger" + +module ActiveRecord + module SpawnMethods + # This is overridden by Associations::CollectionProxy + def spawn #:nodoc: + @delegate_to_klass ? klass.all : clone + end + + # Merges in the conditions from other, if other is an ActiveRecord::Relation. + # Returns an array representing the intersection of the resulting records with other, if other is an array. + # + # Post.where(published: true).joins(:comments).merge( Comment.where(spam: false) ) + # # Performs a single join query with both where conditions. + # + # recent_posts = Post.order('created_at DESC').first(5) + # Post.where(published: true).merge(recent_posts) + # # Returns the intersection of all published posts with the 5 most recently created posts. + # # (This is just an example. You'd probably want to do this with a single query!) + # + # Procs will be evaluated by merge: + # + # Post.where(published: true).merge(-> { joins(:comments) }) + # # => Post.where(published: true).joins(:comments) + # + # This is mainly intended for sharing common conditions between multiple associations. + def merge(other) + if other.is_a?(Array) + records & other + elsif other + spawn.merge!(other) + else + raise ArgumentError, "invalid argument: #{other.inspect}." + end + end + + def merge!(other) # :nodoc: + if other.is_a?(Hash) + Relation::HashMerger.new(self, other).merge + elsif other.is_a?(Relation) + Relation::Merger.new(self, other).merge + elsif other.respond_to?(:to_proc) + instance_exec(&other) + else + raise ArgumentError, "#{other.inspect} is not an ActiveRecord::Relation" + end + end + + # Removes from the query the condition(s) specified in +skips+. + # + # Post.order('id asc').except(:order) # discards the order condition + # Post.where('id > 10').order('id asc').except(:where) # discards the where condition but keeps the order + def except(*skips) + relation_with values.except(*skips) + end + + # Removes any condition from the query other than the one(s) specified in +onlies+. + # + # Post.order('id asc').only(:where) # discards the order condition + # Post.order('id asc').only(:where, :order) # uses the specified order + def only(*onlies) + relation_with values.slice(*onlies) + end + + private + + def relation_with(values) + result = Relation.create(klass, values: values) + result.extend(*extending_values) if extending_values.any? + result + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/where_clause.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/where_clause.rb new file mode 100644 index 00000000..a502713e --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/where_clause.rb @@ -0,0 +1,186 @@ +# frozen_string_literal: true + +module ActiveRecord + class Relation + class WhereClause # :nodoc: + delegate :any?, :empty?, to: :predicates + + def initialize(predicates) + @predicates = predicates + end + + def +(other) + WhereClause.new( + predicates + other.predicates, + ) + end + + def -(other) + WhereClause.new( + predicates - other.predicates, + ) + end + + def merge(other) + WhereClause.new( + predicates_unreferenced_by(other) + other.predicates, + ) + end + + def except(*columns) + WhereClause.new(except_predicates(columns)) + end + + def or(other) + left = self - other + common = self - left + right = other - common + + if left.empty? || right.empty? + common + else + or_clause = WhereClause.new( + [left.ast.or(right.ast)], + ) + common + or_clause + end + end + + def to_h(table_name = nil) + equalities = equalities(predicates) + if table_name + equalities = equalities.select do |node| + node.left.relation.name == table_name + end + end + + equalities.map { |node| + name = node.left.name.to_s + value = extract_node_value(node.right) + [name, value] + }.to_h + end + + def ast + Arel::Nodes::And.new(predicates_with_wrapped_sql_literals) + end + + def ==(other) + other.is_a?(WhereClause) && + predicates == other.predicates + end + + def invert + WhereClause.new(inverted_predicates) + end + + def self.empty + @empty ||= new([]) + end + + protected + + attr_reader :predicates + + def referenced_columns + @referenced_columns ||= begin + equality_nodes = predicates.select { |n| equality_node?(n) } + Set.new(equality_nodes, &:left) + end + end + + private + def equalities(predicates) + equalities = [] + + predicates.each do |node| + case node + when Arel::Nodes::Equality + equalities << node + when Arel::Nodes::And + equalities.concat equalities(node.children) + end + end + + equalities + end + + def predicates_unreferenced_by(other) + predicates.reject do |n| + equality_node?(n) && other.referenced_columns.include?(n.left) + end + end + + def equality_node?(node) + node.respond_to?(:operator) && node.operator == :== + end + + def inverted_predicates + predicates.map { |node| invert_predicate(node) } + end + + def invert_predicate(node) + case node + when NilClass + raise ArgumentError, "Invalid argument for .where.not(), got nil." + when Arel::Nodes::In + Arel::Nodes::NotIn.new(node.left, node.right) + when Arel::Nodes::Equality + Arel::Nodes::NotEqual.new(node.left, node.right) + when String + Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(node)) + else + Arel::Nodes::Not.new(node) + end + end + + def except_predicates(columns) + predicates.reject do |node| + case node + when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual + subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right) + columns.include?(subrelation.name.to_s) + end + end + end + + def predicates_with_wrapped_sql_literals + non_empty_predicates.map do |node| + case node + when Arel::Nodes::SqlLiteral, ::String + wrap_sql_literal(node) + else node + end + end + end + + ARRAY_WITH_EMPTY_STRING = [""] + def non_empty_predicates + predicates - ARRAY_WITH_EMPTY_STRING + end + + def wrap_sql_literal(node) + if ::String === node + node = Arel.sql(node) + end + Arel::Nodes::Grouping.new(node) + end + + def extract_node_value(node) + case node + when Array + node.map { |v| extract_node_value(v) } + when Arel::Nodes::Casted, Arel::Nodes::Quoted + node.val + when Arel::Nodes::BindParam + value = node.value + if value.respond_to?(:value_before_type_cast) + value.value_before_type_cast + else + value + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/where_clause_factory.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/where_clause_factory.rb new file mode 100644 index 00000000..92b72006 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/relation/where_clause_factory.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module ActiveRecord + class Relation + class WhereClauseFactory # :nodoc: + def initialize(klass, predicate_builder) + @klass = klass + @predicate_builder = predicate_builder + end + + def build(opts, other) + case opts + when String, Array + parts = [klass.sanitize_sql(other.empty? ? opts : ([opts] + other))] + when Hash + attributes = predicate_builder.resolve_column_aliases(opts) + attributes.stringify_keys! + + parts = predicate_builder.build_from_hash(attributes) + when Arel::Nodes::Node + parts = [opts] + else + raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})" + end + + WhereClause.new(parts) + end + + protected + + attr_reader :klass, :predicate_builder + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/result.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/result.rb new file mode 100644 index 00000000..e54e8086 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/result.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: true + +module ActiveRecord + ### + # This class encapsulates a result returned from calling + # {#exec_query}[rdoc-ref:ConnectionAdapters::DatabaseStatements#exec_query] + # on any database connection adapter. For example: + # + # result = ActiveRecord::Base.connection.exec_query('SELECT id, title, body FROM posts') + # result # => # + # + # # Get the column names of the result: + # result.columns + # # => ["id", "title", "body"] + # + # # Get the record values of the result: + # result.rows + # # => [[1, "title_1", "body_1"], + # [2, "title_2", "body_2"], + # ... + # ] + # + # # Get an array of hashes representing the result (column => value): + # result.to_hash + # # => [{"id" => 1, "title" => "title_1", "body" => "body_1"}, + # {"id" => 2, "title" => "title_2", "body" => "body_2"}, + # ... + # ] + # + # # ActiveRecord::Result also includes Enumerable. + # result.each do |row| + # puts row['title'] + " " + row['body'] + # end + class Result + include Enumerable + + attr_reader :columns, :rows, :column_types + + def initialize(columns, rows, column_types = {}) + @columns = columns + @rows = rows + @hash_rows = nil + @column_types = column_types + end + + # Returns the number of elements in the rows array. + def length + @rows.length + end + + # Calls the given block once for each element in row collection, passing + # row as parameter. + # + # Returns an +Enumerator+ if no block is given. + def each + if block_given? + hash_rows.each { |row| yield row } + else + hash_rows.to_enum { @rows.size } + end + end + + # Returns an array of hashes representing each row record. + def to_hash + hash_rows + end + + alias :map! :map + alias :collect! :map + + # Returns true if there are no records, otherwise false. + def empty? + rows.empty? + end + + # Returns an array of hashes representing each row record. + def to_ary + hash_rows + end + + def [](idx) + hash_rows[idx] + end + + # Returns the first record from the rows collection. + # If the rows collection is empty, returns +nil+. + def first + return nil if @rows.empty? + Hash[@columns.zip(@rows.first)] + end + + # Returns the last record from the rows collection. + # If the rows collection is empty, returns +nil+. + def last + return nil if @rows.empty? + Hash[@columns.zip(@rows.last)] + end + + def cast_values(type_overrides = {}) # :nodoc: + types = columns.map { |name| column_type(name, type_overrides) } + result = rows.map do |values| + types.zip(values).map { |type, value| type.deserialize(value) } + end + + columns.one? ? result.map!(&:first) : result + end + + def initialize_copy(other) + @columns = columns.dup + @rows = rows.dup + @column_types = column_types.dup + @hash_rows = nil + end + + private + + def column_type(name, type_overrides = {}) + type_overrides.fetch(name) do + column_types.fetch(name, Type.default_value) + end + end + + def hash_rows + @hash_rows ||= + begin + # We freeze the strings to prevent them getting duped when + # used as keys in ActiveRecord::Base's @attributes hash + columns = @columns.map { |c| c.dup.freeze } + @rows.map { |row| + # In the past we used Hash[columns.zip(row)] + # though elegant, the verbose way is much more efficient + # both time and memory wise cause it avoids a big array allocation + # this method is called a lot and needs to be micro optimised + hash = {} + + index = 0 + length = columns.length + + while index < length + hash[columns[index]] = row[index] + index += 1 + end + + hash + } + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/runtime_registry.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/runtime_registry.rb new file mode 100644 index 00000000..4975cb89 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/runtime_registry.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "active_support/per_thread_registry" + +module ActiveRecord + # This is a thread locals registry for Active Record. For example: + # + # ActiveRecord::RuntimeRegistry.connection_handler + # + # returns the connection handler local to the current thread. + # + # See the documentation of ActiveSupport::PerThreadRegistry + # for further details. + class RuntimeRegistry # :nodoc: + extend ActiveSupport::PerThreadRegistry + + attr_accessor :connection_handler, :sql_runtime + + [:connection_handler, :sql_runtime].each do |val| + class_eval %{ def self.#{val}; instance.#{val}; end }, __FILE__, __LINE__ + class_eval %{ def self.#{val}=(x); instance.#{val}=x; end }, __FILE__, __LINE__ + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/sanitization.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/sanitization.rb new file mode 100644 index 00000000..c6c26885 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/sanitization.rb @@ -0,0 +1,222 @@ +# frozen_string_literal: true + +module ActiveRecord + module Sanitization + extend ActiveSupport::Concern + + module ClassMethods + # Accepts an array or string of SQL conditions and sanitizes + # them into a valid SQL fragment for a WHERE clause. + # + # sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4]) + # # => "name='foo''bar' and group_id=4" + # + # sanitize_sql_for_conditions(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4]) + # # => "name='foo''bar' and group_id='4'" + # + # sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4]) + # # => "name='foo''bar' and group_id='4'" + # + # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'") + # # => "name='foo''bar' and group_id='4'" + def sanitize_sql_for_conditions(condition) + return nil if condition.blank? + + case condition + when Array; sanitize_sql_array(condition) + else condition + end + end + alias :sanitize_sql :sanitize_sql_for_conditions + + # Accepts an array, hash, or string of SQL conditions and sanitizes + # them into a valid SQL fragment for a SET clause. + # + # sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4]) + # # => "name=NULL and group_id=4" + # + # sanitize_sql_for_assignment(["name=:name and group_id=:group_id", name: nil, group_id: 4]) + # # => "name=NULL and group_id=4" + # + # Post.sanitize_sql_for_assignment({ name: nil, group_id: 4 }) + # # => "`posts`.`name` = NULL, `posts`.`group_id` = 4" + # + # sanitize_sql_for_assignment("name=NULL and group_id='4'") + # # => "name=NULL and group_id='4'" + def sanitize_sql_for_assignment(assignments, default_table_name = table_name) + case assignments + when Array; sanitize_sql_array(assignments) + when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name) + else assignments + end + end + + # Accepts an array, or string of SQL conditions and sanitizes + # them into a valid SQL fragment for an ORDER clause. + # + # sanitize_sql_for_order(["field(id, ?)", [1,3,2]]) + # # => "field(id, 1,3,2)" + # + # sanitize_sql_for_order("id ASC") + # # => "id ASC" + def sanitize_sql_for_order(condition) + if condition.is_a?(Array) && condition.first.to_s.include?("?") + enforce_raw_sql_whitelist([condition.first], + whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST + ) + + # Ensure we aren't dealing with a subclass of String that might + # override methods we use (eg. Arel::Nodes::SqlLiteral). + if condition.first.kind_of?(String) && !condition.first.instance_of?(String) + condition = [String.new(condition.first), *condition[1..-1]] + end + + Arel.sql(sanitize_sql_array(condition)) + else + condition + end + end + + # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause. + # + # sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts") + # # => "`posts`.`status` = NULL, `posts`.`group_id` = 1" + def sanitize_sql_hash_for_assignment(attrs, table) + c = connection + attrs.map do |attr, value| + type = type_for_attribute(attr) + value = type.serialize(type.cast(value)) + "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}" + end.join(", ") + end + + # Sanitizes a +string+ so that it is safe to use within an SQL + # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%". + # + # sanitize_sql_like("100%") + # # => "100\\%" + # + # sanitize_sql_like("snake_cased_string") + # # => "snake\\_cased\\_string" + # + # sanitize_sql_like("100%", "!") + # # => "100!%" + # + # sanitize_sql_like("snake_cased_string", "!") + # # => "snake!_cased!_string" + def sanitize_sql_like(string, escape_character = "\\") + pattern = Regexp.union(escape_character, "%", "_") + string.gsub(pattern) { |x| [escape_character, x].join } + end + + # Accepts an array of conditions. The array has each value + # sanitized and interpolated into the SQL statement. + # + # sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4]) + # # => "name='foo''bar' and group_id=4" + # + # sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4]) + # # => "name='foo''bar' and group_id=4" + # + # sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4]) + # # => "name='foo''bar' and group_id='4'" + def sanitize_sql_array(ary) + statement, *values = ary + if values.first.is_a?(Hash) && /:\w+/.match?(statement) + replace_named_bind_variables(statement, values.first) + elsif statement.include?("?") + replace_bind_variables(statement, values) + elsif statement.blank? + statement + else + statement % values.collect { |value| connection.quote_string(value.to_s) } + end + end + + private + # Accepts a hash of SQL conditions and replaces those attributes + # that correspond to a {#composed_of}[rdoc-ref:Aggregations::ClassMethods#composed_of] + # relationship with their expanded aggregate attribute values. + # + # Given: + # + # class Person < ActiveRecord::Base + # composed_of :address, class_name: "Address", + # mapping: [%w(address_street street), %w(address_city city)] + # end + # + # Then: + # + # { address: Address.new("813 abc st.", "chicago") } + # # => { address_street: "813 abc st.", address_city: "chicago" } + def expand_hash_conditions_for_aggregates(attrs) # :doc: + expanded_attrs = {} + attrs.each do |attr, value| + if aggregation = reflect_on_aggregation(attr.to_sym) + mapping = aggregation.mapping + mapping.each do |field_attr, aggregate_attr| + expanded_attrs[field_attr] = if value.is_a?(Array) + value.map { |it| it.send(aggregate_attr) } + elsif mapping.size == 1 && !value.respond_to?(aggregate_attr) + value + else + value.send(aggregate_attr) + end + end + else + expanded_attrs[attr] = value + end + end + expanded_attrs + end + deprecate :expand_hash_conditions_for_aggregates + + def replace_bind_variables(statement, values) + raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size) + bound = values.dup + c = connection + statement.gsub(/\?/) do + replace_bind_variable(bound.shift, c) + end + end + + def replace_bind_variable(value, c = connection) + if ActiveRecord::Relation === value + value.to_sql + else + quote_bound_value(value, c) + end + end + + def replace_named_bind_variables(statement, bind_vars) + statement.gsub(/(:?):([a-zA-Z]\w*)/) do |match| + if $1 == ":" # skip postgresql casts + match # return the whole match + elsif bind_vars.include?(match = $2.to_sym) + replace_bind_variable(bind_vars[match]) + else + raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}" + end + end + end + + def quote_bound_value(value, c = connection) + if value.respond_to?(:map) && !value.acts_like?(:string) + if value.respond_to?(:empty?) && value.empty? + c.quote(nil) + else + value.map { |v| c.quote(v) }.join(",") + end + else + c.quote(value) + end + end + + def raise_if_bind_arity_mismatch(statement, expected, provided) + unless expected == provided + raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}" + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/schema.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/schema.rb new file mode 100644 index 00000000..21635986 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/schema.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record \Schema + # + # Allows programmers to programmatically define a schema in a portable + # DSL. This means you can define tables, indexes, etc. without using SQL + # directly, so your applications can more easily support multiple + # databases. + # + # Usage: + # + # ActiveRecord::Schema.define do + # create_table :authors do |t| + # t.string :name, null: false + # end + # + # add_index :authors, :name, :unique + # + # create_table :posts do |t| + # t.integer :author_id, null: false + # t.string :subject + # t.text :body + # t.boolean :private, default: false + # end + # + # add_index :posts, :author_id + # end + # + # ActiveRecord::Schema is only supported by database adapters that also + # support migrations, the two features being very similar. + class Schema < Migration::Current + # Eval the given block. All methods available to the current connection + # adapter are available within the block, so you can easily use the + # database definition DSL to build up your schema ( + # {create_table}[rdoc-ref:ConnectionAdapters::SchemaStatements#create_table], + # {add_index}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_index], etc.). + # + # The +info+ hash is optional, and if given is used to define metadata + # about the current schema (currently, only the schema's version): + # + # ActiveRecord::Schema.define(version: 2038_01_19_000001) do + # ... + # end + def self.define(info = {}, &block) + new.define(info, &block) + end + + def define(info, &block) # :nodoc: + instance_eval(&block) + + if info[:version].present? + ActiveRecord::SchemaMigration.create_table + connection.assume_migrated_upto_version(info[:version], migrations_paths) + end + + ActiveRecord::InternalMetadata.create_table + ActiveRecord::InternalMetadata[:environment] = connection.migration_context.current_environment + end + + private + # Returns the migrations paths. + # + # ActiveRecord::Schema.new.migrations_paths + # # => ["db/migrate"] # Rails migration path by default. + def migrations_paths + ActiveRecord::Migrator.migrations_paths + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/schema_dumper.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/schema_dumper.rb new file mode 100644 index 00000000..b8d848b9 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/schema_dumper.rb @@ -0,0 +1,255 @@ +# frozen_string_literal: true + +require "stringio" + +module ActiveRecord + # = Active Record Schema Dumper + # + # This class is used to dump the database schema for some connection to some + # output format (i.e., ActiveRecord::Schema). + class SchemaDumper #:nodoc: + private_class_method :new + + ## + # :singleton-method: + # A list of tables which should not be dumped to the schema. + # Acceptable values are strings as well as regexp if ActiveRecord::Base.schema_format == :ruby. + # Only strings are accepted if ActiveRecord::Base.schema_format == :sql. + cattr_accessor :ignore_tables, default: [] + + class << self + def dump(connection = ActiveRecord::Base.connection, stream = STDOUT, config = ActiveRecord::Base) + connection.create_schema_dumper(generate_options(config)).dump(stream) + stream + end + + private + def generate_options(config) + { + table_name_prefix: config.table_name_prefix, + table_name_suffix: config.table_name_suffix + } + end + end + + def dump(stream) + header(stream) + extensions(stream) + tables(stream) + trailer(stream) + stream + end + + private + + def initialize(connection, options = {}) + @connection = connection + @version = connection.migration_context.current_version rescue nil + @options = options + end + + # turns 20170404131909 into "2017_04_04_131909" + def formatted_version + stringified = @version.to_s + return stringified unless stringified.length == 14 + stringified.insert(4, "_").insert(7, "_").insert(10, "_") + end + + def define_params + @version ? "version: #{formatted_version}" : "" + end + + def header(stream) + stream.puts <
    e + stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}" + stream.puts "# #{e.message}" + stream.puts + end + end + + # Keep it for indexing materialized views + def indexes(table, stream) + if (indexes = @connection.indexes(table)).any? + add_index_statements = indexes.map do |index| + table_name = remove_prefix_and_suffix(index.table).inspect + " add_index #{([table_name] + index_parts(index)).join(', ')}" + end + + stream.puts add_index_statements.sort.join("\n") + stream.puts + end + end + + def indexes_in_create(table, stream) + if (indexes = @connection.indexes(table)).any? + index_statements = indexes.map do |index| + " t.index #{index_parts(index).join(', ')}" + end + stream.puts index_statements.sort.join("\n") + end + end + + def index_parts(index) + index_parts = [ + index.columns.inspect, + "name: #{index.name.inspect}", + ] + index_parts << "unique: true" if index.unique + index_parts << "length: #{format_index_parts(index.lengths)}" if index.lengths.present? + index_parts << "order: #{format_index_parts(index.orders)}" if index.orders.present? + index_parts << "opclass: #{format_index_parts(index.opclasses)}" if index.opclasses.present? + index_parts << "where: #{index.where.inspect}" if index.where + index_parts << "using: #{index.using.inspect}" if !@connection.default_index_type?(index) + index_parts << "type: #{index.type.inspect}" if index.type + index_parts << "comment: #{index.comment.inspect}" if index.comment + index_parts + end + + def foreign_keys(table, stream) + if (foreign_keys = @connection.foreign_keys(table)).any? + add_foreign_key_statements = foreign_keys.map do |foreign_key| + parts = [ + "add_foreign_key #{remove_prefix_and_suffix(foreign_key.from_table).inspect}", + remove_prefix_and_suffix(foreign_key.to_table).inspect, + ] + + if foreign_key.column != @connection.foreign_key_column_for(foreign_key.to_table) + parts << "column: #{foreign_key.column.inspect}" + end + + if foreign_key.custom_primary_key? + parts << "primary_key: #{foreign_key.primary_key.inspect}" + end + + if foreign_key.name !~ /^fk_rails_[0-9a-f]{10}$/ + parts << "name: #{foreign_key.name.inspect}" + end + + parts << "on_update: #{foreign_key.on_update.inspect}" if foreign_key.on_update + parts << "on_delete: #{foreign_key.on_delete.inspect}" if foreign_key.on_delete + + " #{parts.join(', ')}" + end + + stream.puts add_foreign_key_statements.sort.join("\n") + end + end + + def format_colspec(colspec) + colspec.map { |key, value| "#{key}: #{value}" }.join(", ") + end + + def format_options(options) + options.map { |key, value| "#{key}: #{value.inspect}" }.join(", ") + end + + def format_index_parts(options) + if options.is_a?(Hash) + "{ #{format_options(options)} }" + else + options.inspect + end + end + + def remove_prefix_and_suffix(table) + prefix = Regexp.escape(@options[:table_name_prefix].to_s) + suffix = Regexp.escape(@options[:table_name_suffix].to_s) + table.sub(/\A#{prefix}(.+)#{suffix}\z/, "\\1") + end + + def ignored?(table_name) + [ActiveRecord::Base.schema_migrations_table_name, ActiveRecord::Base.internal_metadata_table_name, ignore_tables].flatten.any? do |ignored| + ignored === remove_prefix_and_suffix(table_name) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/schema_migration.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/schema_migration.rb new file mode 100644 index 00000000..f2d8b038 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/schema_migration.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require "active_record/scoping/default" +require "active_record/scoping/named" + +module ActiveRecord + # This class is used to create a table that keeps track of which migrations + # have been applied to a given database. When a migration is run, its schema + # number is inserted in to the `SchemaMigration.table_name` so it doesn't need + # to be executed the next time. + class SchemaMigration < ActiveRecord::Base # :nodoc: + class << self + def primary_key + "version" + end + + def table_name + "#{table_name_prefix}#{ActiveRecord::Base.schema_migrations_table_name}#{table_name_suffix}" + end + + def table_exists? + connection.table_exists?(table_name) + end + + def create_table + unless table_exists? + version_options = connection.internal_string_options_for_primary_key + + connection.create_table(table_name, id: false) do |t| + t.string :version, version_options + end + end + end + + def drop_table + connection.drop_table table_name, if_exists: true + end + + def normalize_migration_number(number) + "%.3d" % number.to_i + end + + def normalized_versions + all_versions.map { |v| normalize_migration_number v } + end + + def all_versions + order(:version).pluck(:version) + end + end + + def version + super.to_i + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/scoping.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/scoping.rb new file mode 100644 index 00000000..01ac5657 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/scoping.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +require "active_support/per_thread_registry" + +module ActiveRecord + module Scoping + extend ActiveSupport::Concern + + included do + include Default + include Named + end + + module ClassMethods # :nodoc: + def current_scope(skip_inherited_scope = false) + ScopeRegistry.value_for(:current_scope, self, skip_inherited_scope) + end + + def current_scope=(scope) + ScopeRegistry.set_value_for(:current_scope, self, scope) + end + + # Collects attributes from scopes that should be applied when creating + # an AR instance for the particular class this is called on. + def scope_attributes + all.scope_for_create + end + + # Are there attributes associated with this scope? + def scope_attributes? + current_scope + end + end + + def populate_with_current_scope_attributes # :nodoc: + return unless self.class.scope_attributes? + + attributes = self.class.scope_attributes + _assign_attributes(attributes) if attributes.any? + end + + def initialize_internals_callback # :nodoc: + super + populate_with_current_scope_attributes + end + + # This class stores the +:current_scope+ and +:ignore_default_scope+ values + # for different classes. The registry is stored as a thread local, which is + # accessed through +ScopeRegistry.current+. + # + # This class allows you to store and get the scope values on different + # classes and different types of scopes. For example, if you are attempting + # to get the current_scope for the +Board+ model, then you would use the + # following code: + # + # registry = ActiveRecord::Scoping::ScopeRegistry + # registry.set_value_for(:current_scope, Board, some_new_scope) + # + # Now when you run: + # + # registry.value_for(:current_scope, Board) + # + # You will obtain whatever was defined in +some_new_scope+. The #value_for + # and #set_value_for methods are delegated to the current ScopeRegistry + # object, so the above example code can also be called as: + # + # ActiveRecord::Scoping::ScopeRegistry.set_value_for(:current_scope, + # Board, some_new_scope) + class ScopeRegistry # :nodoc: + extend ActiveSupport::PerThreadRegistry + + VALID_SCOPE_TYPES = [:current_scope, :ignore_default_scope] + + def initialize + @registry = Hash.new { |hash, key| hash[key] = {} } + end + + # Obtains the value for a given +scope_type+ and +model+. + def value_for(scope_type, model, skip_inherited_scope = false) + raise_invalid_scope_type!(scope_type) + return @registry[scope_type][model.name] if skip_inherited_scope + klass = model + base = model.base_class + while klass <= base + value = @registry[scope_type][klass.name] + return value if value + klass = klass.superclass + end + end + + # Sets the +value+ for a given +scope_type+ and +model+. + def set_value_for(scope_type, model, value) + raise_invalid_scope_type!(scope_type) + @registry[scope_type][model.name] = value + end + + private + + def raise_invalid_scope_type!(scope_type) + if !VALID_SCOPE_TYPES.include?(scope_type) + raise ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES" + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/scoping/default.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/scoping/default.rb new file mode 100644 index 00000000..8c612df2 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/scoping/default.rb @@ -0,0 +1,152 @@ +# frozen_string_literal: true + +module ActiveRecord + module Scoping + module Default + extend ActiveSupport::Concern + + included do + # Stores the default scope for the class. + class_attribute :default_scopes, instance_writer: false, instance_predicate: false, default: [] + class_attribute :default_scope_override, instance_writer: false, instance_predicate: false, default: nil + end + + module ClassMethods + # Returns a scope for the model without the previously set scopes. + # + # class Post < ActiveRecord::Base + # def self.default_scope + # where(published: true) + # end + # end + # + # Post.all # Fires "SELECT * FROM posts WHERE published = true" + # Post.unscoped.all # Fires "SELECT * FROM posts" + # Post.where(published: false).unscoped.all # Fires "SELECT * FROM posts" + # + # This method also accepts a block. All queries inside the block will + # not use the previously set scopes. + # + # Post.unscoped { + # Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10" + # } + def unscoped + block_given? ? relation.scoping { yield } : relation + end + + # Are there attributes associated with this scope? + def scope_attributes? # :nodoc: + super || default_scopes.any? || respond_to?(:default_scope) + end + + def before_remove_const #:nodoc: + self.current_scope = nil + end + + private + + # Use this macro in your model to set a default scope for all operations on + # the model. + # + # class Article < ActiveRecord::Base + # default_scope { where(published: true) } + # end + # + # Article.all # => SELECT * FROM articles WHERE published = true + # + # The #default_scope is also applied while creating/building a record. + # It is not applied while updating a record. + # + # Article.new.published # => true + # Article.create.published # => true + # + # (You can also pass any object which responds to +call+ to the + # +default_scope+ macro, and it will be called when building the + # default scope.) + # + # If you use multiple #default_scope declarations in your model then + # they will be merged together: + # + # class Article < ActiveRecord::Base + # default_scope { where(published: true) } + # default_scope { where(rating: 'G') } + # end + # + # Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G' + # + # This is also the case with inheritance and module includes where the + # parent or module defines a #default_scope and the child or including + # class defines a second one. + # + # If you need to do more complex things with a default scope, you can + # alternatively define it as a class method: + # + # class Article < ActiveRecord::Base + # def self.default_scope + # # Should return a scope, you can call 'super' here etc. + # end + # end + def default_scope(scope = nil) # :doc: + scope = Proc.new if block_given? + + if scope.is_a?(Relation) || !scope.respond_to?(:call) + raise ArgumentError, + "Support for calling #default_scope without a block is removed. For example instead " \ + "of `default_scope where(color: 'red')`, please use " \ + "`default_scope { where(color: 'red') }`. (Alternatively you can just redefine " \ + "self.default_scope.)" + end + + self.default_scopes += [scope] + end + + def build_default_scope(base_rel = nil) + return if abstract_class? + + if default_scope_override.nil? + self.default_scope_override = !Base.is_a?(method(:default_scope).owner) + end + + if default_scope_override + # The user has defined their own default scope method, so call that + evaluate_default_scope do + if scope = default_scope + (base_rel ||= relation).merge!(scope) + end + end + elsif default_scopes.any? + base_rel ||= relation + evaluate_default_scope do + default_scopes.inject(base_rel) do |default_scope, scope| + scope = scope.respond_to?(:to_proc) ? scope : scope.method(:call) + default_scope.merge!(base_rel.instance_exec(&scope)) + end + end + end + end + + def ignore_default_scope? + ScopeRegistry.value_for(:ignore_default_scope, base_class) + end + + def ignore_default_scope=(ignore) + ScopeRegistry.set_value_for(:ignore_default_scope, base_class, ignore) + end + + # The ignore_default_scope flag is used to prevent an infinite recursion + # situation where a default scope references a scope which has a default + # scope which references a scope... + def evaluate_default_scope + return if ignore_default_scope? + + begin + self.ignore_default_scope = true + yield + ensure + self.ignore_default_scope = false + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/scoping/named.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/scoping/named.rb new file mode 100644 index 00000000..46287f12 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/scoping/named.rb @@ -0,0 +1,213 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array" +require "active_support/core_ext/hash/except" +require "active_support/core_ext/kernel/singleton_class" + +module ActiveRecord + # = Active Record \Named \Scopes + module Scoping + module Named + extend ActiveSupport::Concern + + module ClassMethods + # Returns an ActiveRecord::Relation scope object. + # + # posts = Post.all + # posts.size # Fires "select count(*) from posts" and returns the count + # posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects + # + # fruits = Fruit.all + # fruits = fruits.where(color: 'red') if options[:red_only] + # fruits = fruits.limit(10) if limited? + # + # You can define a scope that applies to all finders using + # {default_scope}[rdoc-ref:Scoping::Default::ClassMethods#default_scope]. + def all + current_scope = self.current_scope + + if current_scope + if self == current_scope.klass + current_scope.clone + else + relation.merge!(current_scope) + end + else + default_scoped + end + end + + def scope_for_association(scope = relation) # :nodoc: + current_scope = self.current_scope + + if current_scope && current_scope.empty_scope? + scope + else + default_scoped(scope) + end + end + + def default_scoped(scope = relation) # :nodoc: + build_default_scope(scope) || scope + end + + def default_extensions # :nodoc: + if scope = current_scope || build_default_scope + scope.extensions + else + [] + end + end + + # Adds a class method for retrieving and querying objects. + # The method is intended to return an ActiveRecord::Relation + # object, which is composable with other scopes. + # If it returns +nil+ or +false+, an + # {all}[rdoc-ref:Scoping::Named::ClassMethods#all] scope is returned instead. + # + # A \scope represents a narrowing of a database query, such as + # where(color: :red).select('shirts.*').includes(:washing_instructions). + # + # class Shirt < ActiveRecord::Base + # scope :red, -> { where(color: 'red') } + # scope :dry_clean_only, -> { joins(:washing_instructions).where('washing_instructions.dry_clean_only = ?', true) } + # end + # + # The above calls to #scope define class methods Shirt.red and + # Shirt.dry_clean_only. Shirt.red, in effect, + # represents the query Shirt.where(color: 'red'). + # + # You should always pass a callable object to the scopes defined + # with #scope. This ensures that the scope is re-evaluated each + # time it is called. + # + # Note that this is simply 'syntactic sugar' for defining an actual + # class method: + # + # class Shirt < ActiveRecord::Base + # def self.red + # where(color: 'red') + # end + # end + # + # Unlike Shirt.find(...), however, the object returned by + # Shirt.red is not an Array but an ActiveRecord::Relation, + # which is composable with other scopes; it resembles the association object + # constructed by a {has_many}[rdoc-ref:Associations::ClassMethods#has_many] + # declaration. For instance, you can invoke Shirt.red.first, Shirt.red.count, + # Shirt.red.where(size: 'small'). Also, just as with the + # association objects, named \scopes act like an Array, implementing + # Enumerable; Shirt.red.each(&block), Shirt.red.first, + # and Shirt.red.inject(memo, &block) all behave as if + # Shirt.red really was an array. + # + # These named \scopes are composable. For instance, + # Shirt.red.dry_clean_only will produce all shirts that are + # both red and dry clean only. Nested finds and calculations also work + # with these compositions: Shirt.red.dry_clean_only.count + # returns the number of garments for which these criteria obtain. + # Similarly with Shirt.red.dry_clean_only.average(:thread_count). + # + # All scopes are available as class methods on the ActiveRecord::Base + # descendant upon which the \scopes were defined. But they are also + # available to {has_many}[rdoc-ref:Associations::ClassMethods#has_many] + # associations. If, + # + # class Person < ActiveRecord::Base + # has_many :shirts + # end + # + # then elton.shirts.red.dry_clean_only will return all of + # Elton's red, dry clean only shirts. + # + # \Named scopes can also have extensions, just as with + # {has_many}[rdoc-ref:Associations::ClassMethods#has_many] declarations: + # + # class Shirt < ActiveRecord::Base + # scope :red, -> { where(color: 'red') } do + # def dom_id + # 'red_shirts' + # end + # end + # end + # + # Scopes can also be used while creating/building a record. + # + # class Article < ActiveRecord::Base + # scope :published, -> { where(published: true) } + # end + # + # Article.published.new.published # => true + # Article.published.create.published # => true + # + # \Class methods on your model are automatically available + # on scopes. Assuming the following setup: + # + # class Article < ActiveRecord::Base + # scope :published, -> { where(published: true) } + # scope :featured, -> { where(featured: true) } + # + # def self.latest_article + # order('published_at desc').first + # end + # + # def self.titles + # pluck(:title) + # end + # end + # + # We are able to call the methods like this: + # + # Article.published.featured.latest_article + # Article.featured.titles + def scope(name, body, &block) + unless body.respond_to?(:call) + raise ArgumentError, "The scope body needs to be callable." + end + + if dangerous_class_method?(name) + raise ArgumentError, "You tried to define a scope named \"#{name}\" " \ + "on the model \"#{self.name}\", but Active Record already defined " \ + "a class method with the same name." + end + + if method_defined_within?(name, Relation) + raise ArgumentError, "You tried to define a scope named \"#{name}\" " \ + "on the model \"#{self.name}\", but ActiveRecord::Relation already defined " \ + "an instance method with the same name." + end + + valid_scope_name?(name) + extension = Module.new(&block) if block + + if body.respond_to?(:to_proc) + singleton_class.send(:define_method, name) do |*args| + scope = all + scope = scope._exec_scope(*args, &body) + scope = scope.extending(extension) if extension + scope + end + else + singleton_class.send(:define_method, name) do |*args| + scope = all + scope = scope.scoping { body.call(*args) || scope } + scope = scope.extending(extension) if extension + scope + end + end + + generate_relation_method(name) + end + + private + + def valid_scope_name?(name) + if respond_to?(name, true) && logger + logger.warn "Creating scope :#{name}. " \ + "Overwriting existing method #{self.name}.#{name}." + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/secure_token.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/secure_token.rb new file mode 100644 index 00000000..bcdb3390 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/secure_token.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module ActiveRecord + module SecureToken + extend ActiveSupport::Concern + + module ClassMethods + # Example using #has_secure_token + # + # # Schema: User(token:string, auth_token:string) + # class User < ActiveRecord::Base + # has_secure_token + # has_secure_token :auth_token + # end + # + # user = User.new + # user.save + # user.token # => "pX27zsMN2ViQKta1bGfLmVJE" + # user.auth_token # => "77TMHrHJFvFDwodq8w7Ev2m7" + # user.regenerate_token # => true + # user.regenerate_auth_token # => true + # + # SecureRandom::base58 is used to generate the 24-character unique token, so collisions are highly unlikely. + # + # Note that it's still possible to generate a race condition in the database in the same way that + # {validates_uniqueness_of}[rdoc-ref:Validations::ClassMethods#validates_uniqueness_of] can. + # You're encouraged to add a unique index in the database to deal with this even more unlikely scenario. + def has_secure_token(attribute = :token) + # Load securerandom only when has_secure_token is used. + require "active_support/core_ext/securerandom" + define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token } + before_create { send("#{attribute}=", self.class.generate_unique_secure_token) unless send("#{attribute}?") } + end + + def generate_unique_secure_token + SecureRandom.base58(24) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/serialization.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/serialization.rb new file mode 100644 index 00000000..741fea43 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/serialization.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module ActiveRecord #:nodoc: + # = Active Record \Serialization + module Serialization + extend ActiveSupport::Concern + include ActiveModel::Serializers::JSON + + included do + self.include_root_in_json = false + end + + def serializable_hash(options = nil) + options = options.try(:dup) || {} + + options[:except] = Array(options[:except]).map(&:to_s) + options[:except] |= Array(self.class.inheritance_column) + + super(options) + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/statement_cache.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/statement_cache.rb new file mode 100644 index 00000000..59acd63a --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/statement_cache.rb @@ -0,0 +1,121 @@ +# frozen_string_literal: true + +module ActiveRecord + # Statement cache is used to cache a single statement in order to avoid creating the AST again. + # Initializing the cache is done by passing the statement in the create block: + # + # cache = StatementCache.create(Book.connection) do |params| + # Book.where(name: "my book").where("author_id > 3") + # end + # + # The cached statement is executed by using the + # {connection.execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute] method: + # + # cache.execute([], Book.connection) + # + # The relation returned by the block is cached, and for each + # {execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute] + # call the cached relation gets duped. Database is queried when +to_a+ is called on the relation. + # + # If you want to cache the statement without the values you can use the +bind+ method of the + # block parameter. + # + # cache = StatementCache.create(Book.connection) do |params| + # Book.where(name: params.bind) + # end + # + # And pass the bind values as the first argument of +execute+ call. + # + # cache.execute(["my book"], Book.connection) + class StatementCache # :nodoc: + class Substitute; end # :nodoc: + + class Query # :nodoc: + def initialize(sql) + @sql = sql + end + + def sql_for(binds, connection) + @sql + end + end + + class PartialQuery < Query # :nodoc: + def initialize(values) + @values = values + @indexes = values.each_with_index.find_all { |thing, i| + Arel::Nodes::BindParam === thing + }.map(&:last) + end + + def sql_for(binds, connection) + val = @values.dup + casted_binds = binds.map(&:value_for_database) + @indexes.each { |i| val[i] = connection.quote(casted_binds.shift) } + val.join + end + end + + def self.query(sql) + Query.new(sql) + end + + def self.partial_query(values) + PartialQuery.new(values) + end + + class Params # :nodoc: + def bind; Substitute.new; end + end + + class BindMap # :nodoc: + def initialize(bound_attributes) + @indexes = [] + @bound_attributes = bound_attributes + + bound_attributes.each_with_index do |attr, i| + if Substitute === attr.value + @indexes << i + end + end + end + + def bind(values) + bas = @bound_attributes.dup + @indexes.each_with_index { |offset, i| bas[offset] = bas[offset].with_cast_value(values[i]) } + bas + end + end + + def self.create(connection, block = Proc.new) + relation = block.call Params.new + query_builder, binds = connection.cacheable_query(self, relation.arel) + bind_map = BindMap.new(binds) + new(query_builder, bind_map, relation.klass) + end + + def initialize(query_builder, bind_map, klass) + @query_builder = query_builder + @bind_map = bind_map + @klass = klass + end + + def execute(params, connection, &block) + bind_values = bind_map.bind params + + sql = query_builder.sql_for bind_values, connection + + klass.find_by_sql(sql, bind_values, preparable: true, &block) + end + + def self.unsupported_value?(value) + case value + when NilClass, Array, Range, Hash, Relation, Base then true + end + end + + protected + + attr_reader :query_builder, :bind_map, :klass + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/store.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/store.rb new file mode 100644 index 00000000..6dbc977f --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/store.rb @@ -0,0 +1,211 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/indifferent_access" + +module ActiveRecord + # Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column. + # It's like a simple key/value store baked into your record when you don't care about being able to + # query that store outside the context of a single record. + # + # You can then declare accessors to this store that are then accessible just like any other attribute + # of the model. This is very helpful for easily exposing store keys to a form or elsewhere that's + # already built around just accessing attributes on the model. + # + # Make sure that you declare the database column used for the serialized store as a text, so there's + # plenty of room. + # + # You can set custom coder to encode/decode your serialized attributes to/from different formats. + # JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+. + # + # NOTE: If you are using PostgreSQL specific columns like +hstore+ or +json+ there is no need for + # the serialization provided by {.store}[rdoc-ref:rdoc-ref:ClassMethods#store]. + # Simply use {.store_accessor}[rdoc-ref:ClassMethods#store_accessor] instead to generate + # the accessor methods. Be aware that these columns use a string keyed hash and do not allow access + # using a symbol. + # + # NOTE: The default validations with the exception of +uniqueness+ will work. + # For example, if you want to check for +uniqueness+ with +hstore+ you will + # need to use a custom validation to handle it. + # + # Examples: + # + # class User < ActiveRecord::Base + # store :settings, accessors: [ :color, :homepage ], coder: JSON + # end + # + # u = User.new(color: 'black', homepage: '37signals.com') + # u.color # Accessor stored attribute + # u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor + # + # # There is no difference between strings and symbols for accessing custom attributes + # u.settings[:country] # => 'Denmark' + # u.settings['country'] # => 'Denmark' + # + # # Add additional accessors to an existing store through store_accessor + # class SuperUser < User + # store_accessor :settings, :privileges, :servants + # end + # + # The stored attribute names can be retrieved using {.stored_attributes}[rdoc-ref:rdoc-ref:ClassMethods#stored_attributes]. + # + # User.stored_attributes[:settings] # [:color, :homepage] + # + # == Overwriting default accessors + # + # All stored values are automatically available through accessors on the Active Record + # object, but sometimes you want to specialize this behavior. This can be done by overwriting + # the default accessors (using the same name as the attribute) and calling super + # to actually change things. + # + # class Song < ActiveRecord::Base + # # Uses a stored integer to hold the volume adjustment of the song + # store :settings, accessors: [:volume_adjustment] + # + # def volume_adjustment=(decibels) + # super(decibels.to_i) + # end + # + # def volume_adjustment + # super.to_i + # end + # end + module Store + extend ActiveSupport::Concern + + included do + class << self + attr_accessor :local_stored_attributes + end + end + + module ClassMethods + def store(store_attribute, options = {}) + serialize store_attribute, IndifferentCoder.new(store_attribute, options[:coder]) + store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors + end + + def store_accessor(store_attribute, *keys) + keys = keys.flatten + + _store_accessors_module.module_eval do + keys.each do |key| + define_method("#{key}=") do |value| + write_store_attribute(store_attribute, key, value) + end + + define_method(key) do + read_store_attribute(store_attribute, key) + end + end + end + + # assign new store attribute and create new hash to ensure that each class in the hierarchy + # has its own hash of stored attributes. + self.local_stored_attributes ||= {} + self.local_stored_attributes[store_attribute] ||= [] + self.local_stored_attributes[store_attribute] |= keys + end + + def _store_accessors_module # :nodoc: + @_store_accessors_module ||= begin + mod = Module.new + include mod + mod + end + end + + def stored_attributes + parent = superclass.respond_to?(:stored_attributes) ? superclass.stored_attributes : {} + if local_stored_attributes + parent.merge!(local_stored_attributes) { |k, a, b| a | b } + end + parent + end + end + + private + def read_store_attribute(store_attribute, key) # :doc: + accessor = store_accessor_for(store_attribute) + accessor.read(self, store_attribute, key) + end + + def write_store_attribute(store_attribute, key, value) # :doc: + accessor = store_accessor_for(store_attribute) + accessor.write(self, store_attribute, key, value) + end + + def store_accessor_for(store_attribute) + type_for_attribute(store_attribute).accessor + end + + class HashAccessor # :nodoc: + def self.read(object, attribute, key) + prepare(object, attribute) + object.public_send(attribute)[key] + end + + def self.write(object, attribute, key, value) + prepare(object, attribute) + if value != read(object, attribute, key) + object.public_send :"#{attribute}_will_change!" + object.public_send(attribute)[key] = value + end + end + + def self.prepare(object, attribute) + object.public_send :"#{attribute}=", {} unless object.send(attribute) + end + end + + class StringKeyedHashAccessor < HashAccessor # :nodoc: + def self.read(object, attribute, key) + super object, attribute, key.to_s + end + + def self.write(object, attribute, key, value) + super object, attribute, key.to_s, value + end + end + + class IndifferentHashAccessor < ActiveRecord::Store::HashAccessor # :nodoc: + def self.prepare(object, store_attribute) + attribute = object.send(store_attribute) + unless attribute.is_a?(ActiveSupport::HashWithIndifferentAccess) + attribute = IndifferentCoder.as_indifferent_hash(attribute) + object.send :"#{store_attribute}=", attribute + end + attribute + end + end + + class IndifferentCoder # :nodoc: + def initialize(attr_name, coder_or_class_name) + @coder = + if coder_or_class_name.respond_to?(:load) && coder_or_class_name.respond_to?(:dump) + coder_or_class_name + else + ActiveRecord::Coders::YAMLColumn.new(attr_name, coder_or_class_name || Object) + end + end + + def dump(obj) + @coder.dump self.class.as_indifferent_hash(obj) + end + + def load(yaml) + self.class.as_indifferent_hash(@coder.load(yaml || "")) + end + + def self.as_indifferent_hash(obj) + case obj + when ActiveSupport::HashWithIndifferentAccess + obj + when Hash + obj.with_indifferent_access + else + ActiveSupport::HashWithIndifferentAccess.new + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/suppressor.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/suppressor.rb new file mode 100644 index 00000000..8cdb8e07 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/suppressor.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module ActiveRecord + # ActiveRecord::Suppressor prevents the receiver from being saved during + # a given block. + # + # For example, here's a pattern of creating notifications when new comments + # are posted. (The notification may in turn trigger an email, a push + # notification, or just appear in the UI somewhere): + # + # class Comment < ActiveRecord::Base + # belongs_to :commentable, polymorphic: true + # after_create -> { Notification.create! comment: self, + # recipients: commentable.recipients } + # end + # + # That's what you want the bulk of the time. New comment creates a new + # Notification. But there may well be off cases, like copying a commentable + # and its comments, where you don't want that. So you'd have a concern + # something like this: + # + # module Copyable + # def copy_to(destination) + # Notification.suppress do + # # Copy logic that creates new comments that we do not want + # # triggering notifications. + # end + # end + # end + module Suppressor + extend ActiveSupport::Concern + + module ClassMethods + def suppress(&block) + previous_state = SuppressorRegistry.suppressed[name] + SuppressorRegistry.suppressed[name] = true + yield + ensure + SuppressorRegistry.suppressed[name] = previous_state + end + end + + def save(*) # :nodoc: + SuppressorRegistry.suppressed[self.class.name] ? true : super + end + + def save!(*) # :nodoc: + SuppressorRegistry.suppressed[self.class.name] ? true : super + end + end + + class SuppressorRegistry # :nodoc: + extend ActiveSupport::PerThreadRegistry + + attr_reader :suppressed + + def initialize + @suppressed = {} + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/table_metadata.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/table_metadata.rb new file mode 100644 index 00000000..d3b91711 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/table_metadata.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +module ActiveRecord + class TableMetadata # :nodoc: + delegate :foreign_type, :foreign_key, :join_primary_key, :join_foreign_key, to: :association, prefix: true + + def initialize(klass, arel_table, association = nil) + @klass = klass + @arel_table = arel_table + @association = association + end + + def resolve_column_aliases(hash) + new_hash = hash.dup + hash.each do |key, _| + if (key.is_a?(Symbol)) && klass.attribute_alias?(key) + new_hash[klass.attribute_alias(key)] = new_hash.delete(key) + end + end + new_hash + end + + def arel_attribute(column_name) + if klass + klass.arel_attribute(column_name, arel_table) + else + arel_table[column_name] + end + end + + def type(column_name) + if klass + klass.type_for_attribute(column_name) + else + Type.default_value + end + end + + def has_column?(column_name) + klass && klass.columns_hash.key?(column_name.to_s) + end + + def associated_with?(association_name) + klass && klass._reflect_on_association(association_name) + end + + def associated_table(table_name) + association = klass._reflect_on_association(table_name) || klass._reflect_on_association(table_name.to_s.singularize) + + if !association && table_name == arel_table.name + return self + elsif association && !association.polymorphic? + association_klass = association.klass + arel_table = association_klass.arel_table.alias(table_name) + else + type_caster = TypeCaster::Connection.new(klass, table_name) + association_klass = nil + arel_table = Arel::Table.new(table_name, type_caster: type_caster) + end + + TableMetadata.new(association_klass, arel_table, association) + end + + def polymorphic_association? + association && association.polymorphic? + end + + def aggregated_with?(aggregation_name) + klass && reflect_on_aggregation(aggregation_name) + end + + def reflect_on_aggregation(aggregation_name) + klass.reflect_on_aggregation(aggregation_name) + end + + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected + + attr_reader :klass, :arel_table, :association + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/tasks/database_tasks.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/tasks/database_tasks.rb new file mode 100644 index 00000000..255c82ca --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/tasks/database_tasks.rb @@ -0,0 +1,337 @@ +# frozen_string_literal: true + +module ActiveRecord + module Tasks # :nodoc: + class DatabaseAlreadyExists < StandardError; end # :nodoc: + class DatabaseNotSupported < StandardError; end # :nodoc: + + # ActiveRecord::Tasks::DatabaseTasks is a utility class, which encapsulates + # logic behind common tasks used to manage database and migrations. + # + # The tasks defined here are used with Rake tasks provided by Active Record. + # + # In order to use DatabaseTasks, a few config values need to be set. All the needed + # config values are set by Rails already, so it's necessary to do it only if you + # want to change the defaults or when you want to use Active Record outside of Rails + # (in such case after configuring the database tasks, you can also use the rake tasks + # defined in Active Record). + # + # The possible config values are: + # + # * +env+: current environment (like Rails.env). + # * +database_configuration+: configuration of your databases (as in +config/database.yml+). + # * +db_dir+: your +db+ directory. + # * +fixtures_path+: a path to fixtures directory. + # * +migrations_paths+: a list of paths to directories with migrations. + # * +seed_loader+: an object which will load seeds, it needs to respond to the +load_seed+ method. + # * +root+: a path to the root of the application. + # + # Example usage of DatabaseTasks outside Rails could look as such: + # + # include ActiveRecord::Tasks + # DatabaseTasks.database_configuration = YAML.load_file('my_database_config.yml') + # DatabaseTasks.db_dir = 'db' + # # other settings... + # + # DatabaseTasks.create_current('production') + module DatabaseTasks + ## + # :singleton-method: + # Extra flags passed to database CLI tool (mysqldump/pg_dump) when calling db:structure:dump + mattr_accessor :structure_dump_flags, instance_accessor: false + + ## + # :singleton-method: + # Extra flags passed to database CLI tool when calling db:structure:load + mattr_accessor :structure_load_flags, instance_accessor: false + + extend self + + attr_writer :current_config, :db_dir, :migrations_paths, :fixtures_path, :root, :env, :seed_loader + attr_accessor :database_configuration + + LOCAL_HOSTS = ["127.0.0.1", "localhost"] + + def check_protected_environments! + unless ENV["DISABLE_DATABASE_ENVIRONMENT_CHECK"] + current = ActiveRecord::Base.connection.migration_context.current_environment + stored = ActiveRecord::Base.connection.migration_context.last_stored_environment + + if ActiveRecord::Base.connection.migration_context.protected_environment? + raise ActiveRecord::ProtectedEnvironmentError.new(stored) + end + + if stored && stored != current + raise ActiveRecord::EnvironmentMismatchError.new(current: current, stored: stored) + end + end + rescue ActiveRecord::NoDatabaseError + end + + def register_task(pattern, task) + @tasks ||= {} + @tasks[pattern] = task + end + + register_task(/mysql/, "ActiveRecord::Tasks::MySQLDatabaseTasks") + register_task(/postgresql/, "ActiveRecord::Tasks::PostgreSQLDatabaseTasks") + register_task(/sqlite/, "ActiveRecord::Tasks::SQLiteDatabaseTasks") + + def db_dir + @db_dir ||= Rails.application.config.paths["db"].first + end + + def migrations_paths + @migrations_paths ||= Rails.application.paths["db/migrate"].to_a + end + + def fixtures_path + @fixtures_path ||= if ENV["FIXTURES_PATH"] + File.join(root, ENV["FIXTURES_PATH"]) + else + File.join(root, "test", "fixtures") + end + end + + def root + @root ||= Rails.root + end + + def env + @env ||= Rails.env + end + + def seed_loader + @seed_loader ||= Rails.application + end + + def current_config(options = {}) + options.reverse_merge! env: env + if options.has_key?(:config) + @current_config = options[:config] + else + @current_config ||= ActiveRecord::Base.configurations[options[:env]] + end + end + + def create(*arguments) + configuration = arguments.first + class_for_adapter(configuration["adapter"]).new(*arguments).create + $stdout.puts "Created database '#{configuration['database']}'" + rescue DatabaseAlreadyExists + $stderr.puts "Database '#{configuration['database']}' already exists" + rescue Exception => error + $stderr.puts error + $stderr.puts "Couldn't create '#{configuration['database']}' database. Please check your configuration." + raise + end + + def create_all + old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base.connection_specification_name) + each_local_configuration { |configuration| create configuration } + if old_pool + ActiveRecord::Base.connection_handler.establish_connection(old_pool.spec.to_hash) + end + end + + def create_current(environment = env) + each_current_configuration(environment) { |configuration| + create configuration + } + ActiveRecord::Base.establish_connection(environment.to_sym) + end + + def drop(*arguments) + configuration = arguments.first + class_for_adapter(configuration["adapter"]).new(*arguments).drop + $stdout.puts "Dropped database '#{configuration['database']}'" + rescue ActiveRecord::NoDatabaseError + $stderr.puts "Database '#{configuration['database']}' does not exist" + rescue Exception => error + $stderr.puts error + $stderr.puts "Couldn't drop database '#{configuration['database']}'" + raise + end + + def drop_all + each_local_configuration { |configuration| drop configuration } + end + + def drop_current(environment = env) + each_current_configuration(environment) { |configuration| + drop configuration + } + end + + def migrate + check_target_version + + verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] != "false" : true + scope = ENV["SCOPE"] + verbose_was, Migration.verbose = Migration.verbose, verbose + Base.connection.migration_context.migrate(target_version) do |migration| + scope.blank? || scope == migration.scope + end + ActiveRecord::Base.clear_cache! + ensure + Migration.verbose = verbose_was + end + + def check_target_version + if target_version && !(Migration::MigrationFilenameRegexp.match?(ENV["VERSION"]) || /\A\d+\z/.match?(ENV["VERSION"])) + raise "Invalid format of target version: `VERSION=#{ENV['VERSION']}`" + end + end + + def target_version + ENV["VERSION"].to_i if ENV["VERSION"] && !ENV["VERSION"].empty? + end + + def charset_current(environment = env) + charset ActiveRecord::Base.configurations[environment] + end + + def charset(*arguments) + configuration = arguments.first + class_for_adapter(configuration["adapter"]).new(*arguments).charset + end + + def collation_current(environment = env) + collation ActiveRecord::Base.configurations[environment] + end + + def collation(*arguments) + configuration = arguments.first + class_for_adapter(configuration["adapter"]).new(*arguments).collation + end + + def purge(configuration) + class_for_adapter(configuration["adapter"]).new(configuration).purge + end + + def purge_all + each_local_configuration { |configuration| + purge configuration + } + end + + def purge_current(environment = env) + each_current_configuration(environment) { |configuration| + purge configuration + } + ActiveRecord::Base.establish_connection(environment.to_sym) + end + + def structure_dump(*arguments) + configuration = arguments.first + filename = arguments.delete_at 1 + class_for_adapter(configuration["adapter"]).new(*arguments).structure_dump(filename, structure_dump_flags) + end + + def structure_load(*arguments) + configuration = arguments.first + filename = arguments.delete_at 1 + class_for_adapter(configuration["adapter"]).new(*arguments).structure_load(filename, structure_load_flags) + end + + def load_schema(configuration, format = ActiveRecord::Base.schema_format, file = nil, environment = env) # :nodoc: + file ||= schema_file(format) + + check_schema_file(file) + ActiveRecord::Base.establish_connection(configuration) + + case format + when :ruby + load(file) + when :sql + structure_load(configuration, file) + else + raise ArgumentError, "unknown format #{format.inspect}" + end + ActiveRecord::InternalMetadata.create_table + ActiveRecord::InternalMetadata[:environment] = environment + end + + def schema_file(format = ActiveRecord::Base.schema_format) + case format + when :ruby + File.join(db_dir, "schema.rb") + when :sql + File.join(db_dir, "structure.sql") + end + end + + def load_schema_current(format = ActiveRecord::Base.schema_format, file = nil, environment = env) + each_current_configuration(environment) { |configuration, configuration_environment| + load_schema configuration, format, file, configuration_environment + } + ActiveRecord::Base.establish_connection(environment.to_sym) + end + + def check_schema_file(filename) + unless File.exist?(filename) + message = %{#{filename} doesn't exist yet. Run `rails db:migrate` to create it, then try again.}.dup + message << %{ If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded.} if defined?(::Rails.root) + Kernel.abort message + end + end + + def load_seed + if seed_loader + seed_loader.load_seed + else + raise "You tried to load seed data, but no seed loader is specified. Please specify seed " \ + "loader with ActiveRecord::Tasks::DatabaseTasks.seed_loader = your_seed_loader\n" \ + "Seed loader should respond to load_seed method" + end + end + + # Dumps the schema cache in YAML format for the connection into the file + # + # ==== Examples: + # ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(ActiveRecord::Base.connection, "tmp/schema_dump.yaml") + def dump_schema_cache(conn, filename) + conn.schema_cache.clear! + conn.data_sources.each { |table| conn.schema_cache.add(table) } + open(filename, "wb") { |f| f.write(YAML.dump(conn.schema_cache)) } + end + + private + + def class_for_adapter(adapter) + _key, task = @tasks.each_pair.detect { |pattern, _task| adapter[pattern] } + unless task + raise DatabaseNotSupported, "Rake tasks not supported by '#{adapter}' adapter" + end + task.is_a?(String) ? task.constantize : task + end + + def each_current_configuration(environment) + environments = [environment] + environments << "test" if environment == "development" + + ActiveRecord::Base.configurations.slice(*environments).each do |configuration_environment, configuration| + next unless configuration["database"] + + yield configuration, configuration_environment + end + end + + def each_local_configuration + ActiveRecord::Base.configurations.each_value do |configuration| + next unless configuration["database"] + + if local_database?(configuration) + yield configuration + else + $stderr.puts "This task only modifies local databases. #{configuration['database']} is on a remote host." + end + end + end + + def local_database?(configuration) + configuration["host"].blank? || LOCAL_HOSTS.include?(configuration["host"]) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/tasks/mysql_database_tasks.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/tasks/mysql_database_tasks.rb new file mode 100644 index 00000000..e697fa6d --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/tasks/mysql_database_tasks.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +module ActiveRecord + module Tasks # :nodoc: + class MySQLDatabaseTasks # :nodoc: + delegate :connection, :establish_connection, to: ActiveRecord::Base + + def initialize(configuration) + @configuration = configuration + end + + def create + establish_connection configuration_without_database + connection.create_database configuration["database"], creation_options + establish_connection configuration + rescue ActiveRecord::StatementInvalid => error + if error.message.include?("database exists") + raise DatabaseAlreadyExists + else + raise + end + end + + def drop + establish_connection configuration + connection.drop_database configuration["database"] + end + + def purge + establish_connection configuration + connection.recreate_database configuration["database"], creation_options + end + + def charset + connection.charset + end + + def collation + connection.collation + end + + def structure_dump(filename, extra_flags) + args = prepare_command_options + args.concat(["--result-file", "#{filename}"]) + args.concat(["--no-data"]) + args.concat(["--routines"]) + args.concat(["--skip-comments"]) + + ignore_tables = ActiveRecord::SchemaDumper.ignore_tables + if ignore_tables.any? + args += ignore_tables.map { |table| "--ignore-table=#{configuration['database']}.#{table}" } + end + + args.concat(["#{configuration['database']}"]) + args.unshift(*extra_flags) if extra_flags + + run_cmd("mysqldump", args, "dumping") + end + + def structure_load(filename, extra_flags) + args = prepare_command_options + args.concat(["--execute", %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}]) + args.concat(["--database", "#{configuration['database']}"]) + args.unshift(*extra_flags) if extra_flags + + run_cmd("mysql", args, "loading") + end + + private + + def configuration + @configuration + end + + def configuration_without_database + configuration.merge("database" => nil) + end + + def creation_options + Hash.new.tap do |options| + options[:charset] = configuration["encoding"] if configuration.include? "encoding" + options[:collation] = configuration["collation"] if configuration.include? "collation" + end + end + + def prepare_command_options + args = { + "host" => "--host", + "port" => "--port", + "socket" => "--socket", + "username" => "--user", + "password" => "--password", + "encoding" => "--default-character-set", + "sslca" => "--ssl-ca", + "sslcert" => "--ssl-cert", + "sslcapath" => "--ssl-capath", + "sslcipher" => "--ssl-cipher", + "sslkey" => "--ssl-key" + }.map { |opt, arg| "#{arg}=#{configuration[opt]}" if configuration[opt] }.compact + + args + end + + def run_cmd(cmd, args, action) + fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args) + end + + def run_cmd_error(cmd, args, action) + msg = "failed to execute: `#{cmd}`\n".dup + msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n" + msg + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/tasks/postgresql_database_tasks.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/tasks/postgresql_database_tasks.rb new file mode 100644 index 00000000..647e0661 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/tasks/postgresql_database_tasks.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +require "tempfile" + +module ActiveRecord + module Tasks # :nodoc: + class PostgreSQLDatabaseTasks # :nodoc: + DEFAULT_ENCODING = ENV["CHARSET"] || "utf8" + ON_ERROR_STOP_1 = "ON_ERROR_STOP=1".freeze + SQL_COMMENT_BEGIN = "--".freeze + + delegate :connection, :establish_connection, :clear_active_connections!, + to: ActiveRecord::Base + + def initialize(configuration) + @configuration = configuration + end + + def create(master_established = false) + establish_master_connection unless master_established + connection.create_database configuration["database"], + configuration.merge("encoding" => encoding) + establish_connection configuration + rescue ActiveRecord::StatementInvalid => error + if error.cause.is_a?(PG::DuplicateDatabase) + raise DatabaseAlreadyExists + else + raise + end + end + + def drop + establish_master_connection + connection.drop_database configuration["database"] + end + + def charset + connection.encoding + end + + def collation + connection.collation + end + + def purge + clear_active_connections! + drop + create true + end + + def structure_dump(filename, extra_flags) + set_psql_env + + search_path = \ + case ActiveRecord::Base.dump_schemas + when :schema_search_path + configuration["schema_search_path"] + when :all + nil + when String + ActiveRecord::Base.dump_schemas + end + + args = ["-s", "-x", "-O", "-f", filename] + args.concat(Array(extra_flags)) if extra_flags + unless search_path.blank? + args += search_path.split(",").map do |part| + "--schema=#{part.strip}" + end + end + + ignore_tables = ActiveRecord::SchemaDumper.ignore_tables + if ignore_tables.any? + args += ignore_tables.flat_map { |table| ["-T", table] } + end + + args << configuration["database"] + run_cmd("pg_dump", args, "dumping") + remove_sql_header_comments(filename) + File.open(filename, "a") { |f| f << "SET search_path TO #{connection.schema_search_path};\n\n" } + end + + def structure_load(filename, extra_flags) + set_psql_env + args = ["-v", ON_ERROR_STOP_1, "-q", "-f", filename] + args.concat(Array(extra_flags)) if extra_flags + args << configuration["database"] + run_cmd("psql", args, "loading") + end + + private + + def configuration + @configuration + end + + def encoding + configuration["encoding"] || DEFAULT_ENCODING + end + + def establish_master_connection + establish_connection configuration.merge( + "database" => "postgres", + "schema_search_path" => "public" + ) + end + + def set_psql_env + ENV["PGHOST"] = configuration["host"] if configuration["host"] + ENV["PGPORT"] = configuration["port"].to_s if configuration["port"] + ENV["PGPASSWORD"] = configuration["password"].to_s if configuration["password"] + ENV["PGUSER"] = configuration["username"].to_s if configuration["username"] + end + + def run_cmd(cmd, args, action) + fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args) + end + + def run_cmd_error(cmd, args, action) + msg = "failed to execute:\n".dup + msg << "#{cmd} #{args.join(' ')}\n\n" + msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n" + msg + end + + def remove_sql_header_comments(filename) + removing_comments = true + tempfile = Tempfile.open("uncommented_structure.sql") + begin + File.foreach(filename) do |line| + unless removing_comments && (line.start_with?(SQL_COMMENT_BEGIN) || line.blank?) + tempfile << line + removing_comments = false + end + end + ensure + tempfile.close + end + FileUtils.cp(tempfile.path, filename) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/tasks/sqlite_database_tasks.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/tasks/sqlite_database_tasks.rb new file mode 100644 index 00000000..dfe599c4 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/tasks/sqlite_database_tasks.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +module ActiveRecord + module Tasks # :nodoc: + class SQLiteDatabaseTasks # :nodoc: + delegate :connection, :establish_connection, to: ActiveRecord::Base + + def initialize(configuration, root = ActiveRecord::Tasks::DatabaseTasks.root) + @configuration, @root = configuration, root + end + + def create + raise DatabaseAlreadyExists if File.exist?(configuration["database"]) + + establish_connection configuration + connection + end + + def drop + require "pathname" + path = Pathname.new configuration["database"] + file = path.absolute? ? path.to_s : File.join(root, path) + + FileUtils.rm(file) + rescue Errno::ENOENT => error + raise NoDatabaseError.new(error.message) + end + + def purge + drop + rescue NoDatabaseError + ensure + create + end + + def charset + connection.encoding + end + + def structure_dump(filename, extra_flags) + args = [] + args.concat(Array(extra_flags)) if extra_flags + args << configuration["database"] + + ignore_tables = ActiveRecord::SchemaDumper.ignore_tables + if ignore_tables.any? + condition = ignore_tables.map { |table| connection.quote(table) }.join(", ") + args << "SELECT sql FROM sqlite_master WHERE tbl_name NOT IN (#{condition}) ORDER BY tbl_name, type DESC, name" + else + args << ".schema" + end + run_cmd("sqlite3", args, filename) + end + + def structure_load(filename, extra_flags) + dbfile = configuration["database"] + flags = extra_flags.join(" ") if extra_flags + `sqlite3 #{flags} #{dbfile} < "#{filename}"` + end + + private + + def configuration + @configuration + end + + def root + @root + end + + def run_cmd(cmd, args, out) + fail run_cmd_error(cmd, args) unless Kernel.system(cmd, *args, out: out) + end + + def run_cmd_error(cmd, args) + msg = "failed to execute:\n".dup + msg << "#{cmd} #{args.join(' ')}\n\n" + msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n" + msg + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/timestamp.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/timestamp.rb new file mode 100644 index 00000000..96c74b16 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/timestamp.rb @@ -0,0 +1,153 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record \Timestamp + # + # Active Record automatically timestamps create and update operations if the + # table has fields named created_at/created_on or + # updated_at/updated_on. + # + # Timestamping can be turned off by setting: + # + # config.active_record.record_timestamps = false + # + # Timestamps are in UTC by default but you can use the local timezone by setting: + # + # config.active_record.default_timezone = :local + # + # == Time Zone aware attributes + # + # Active Record keeps all the datetime and time columns + # timezone aware. By default, these values are stored in the database as UTC + # and converted back to the current Time.zone when pulled from the database. + # + # This feature can be turned off completely by setting: + # + # config.active_record.time_zone_aware_attributes = false + # + # You can also specify that only datetime columns should be time-zone + # aware (while time should not) by setting: + # + # ActiveRecord::Base.time_zone_aware_types = [:datetime] + # + # You can also add database specific timezone aware types. For example, for PostgreSQL: + # + # ActiveRecord::Base.time_zone_aware_types += [:tsrange, :tstzrange] + # + # Finally, you can indicate specific attributes of a model for which time zone + # conversion should not applied, for instance by setting: + # + # class Topic < ActiveRecord::Base + # self.skip_time_zone_conversion_for_attributes = [:written_on] + # end + module Timestamp + extend ActiveSupport::Concern + + included do + class_attribute :record_timestamps, default: true + end + + def initialize_dup(other) # :nodoc: + super + clear_timestamp_attributes + end + + module ClassMethods # :nodoc: + def touch_attributes_with_time(*names, time: nil) + attribute_names = timestamp_attributes_for_update_in_model + attribute_names |= names.map(&:to_s) + time ||= current_time_from_proper_timezone + attribute_names.each_with_object({}) { |attr_name, result| result[attr_name] = time } + end + + private + def timestamp_attributes_for_create_in_model + timestamp_attributes_for_create.select { |c| column_names.include?(c) } + end + + def timestamp_attributes_for_update_in_model + timestamp_attributes_for_update.select { |c| column_names.include?(c) } + end + + def all_timestamp_attributes_in_model + timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model + end + + def timestamp_attributes_for_create + ["created_at", "created_on"] + end + + def timestamp_attributes_for_update + ["updated_at", "updated_on"] + end + + def current_time_from_proper_timezone + default_timezone == :utc ? Time.now.utc : Time.now + end + end + + private + + def _create_record + if record_timestamps + current_time = current_time_from_proper_timezone + + all_timestamp_attributes_in_model.each do |column| + if !attribute_present?(column) + _write_attribute(column, current_time) + end + end + end + + super + end + + def _update_record(*args, touch: true, **options) + if touch && should_record_timestamps? + current_time = current_time_from_proper_timezone + + timestamp_attributes_for_update_in_model.each do |column| + next if will_save_change_to_attribute?(column) + _write_attribute(column, current_time) + end + end + super(*args) + end + + def should_record_timestamps? + record_timestamps && (!partial_writes? || has_changes_to_save?) + end + + def timestamp_attributes_for_create_in_model + self.class.send(:timestamp_attributes_for_create_in_model) + end + + def timestamp_attributes_for_update_in_model + self.class.send(:timestamp_attributes_for_update_in_model) + end + + def all_timestamp_attributes_in_model + self.class.send(:all_timestamp_attributes_in_model) + end + + def current_time_from_proper_timezone + self.class.send(:current_time_from_proper_timezone) + end + + def max_updated_column_timestamp(timestamp_names = timestamp_attributes_for_update_in_model) + timestamp_names + .map { |attr| self[attr] } + .compact + .map(&:to_time) + .max + end + + # Clear attributes and changed_attributes + def clear_timestamp_attributes + all_timestamp_attributes_in_model.each do |attribute_name| + self[attribute_name] = nil + clear_attribute_changes([attribute_name]) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/touch_later.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/touch_later.rb new file mode 100644 index 00000000..f70b7c50 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/touch_later.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record Touch Later + module TouchLater + extend ActiveSupport::Concern + + included do + before_commit_without_transaction_enrollment :touch_deferred_attributes + end + + def touch_later(*names) # :nodoc: + unless persisted? + raise ActiveRecordError, <<-MSG.squish + cannot touch on a new or destroyed record object. Consider using + persisted?, new_record?, or destroyed? before touching + MSG + end + + @_defer_touch_attrs ||= timestamp_attributes_for_update_in_model + @_defer_touch_attrs |= names + @_touch_time = current_time_from_proper_timezone + + surreptitiously_touch @_defer_touch_attrs + self.class.connection.add_transaction_record self + + # touch the parents as we are not calling the after_save callbacks + self.class.reflect_on_all_associations(:belongs_to).each do |r| + if touch = r.options[:touch] + ActiveRecord::Associations::Builder::BelongsTo.touch_record(self, changes_to_save, r.foreign_key, r.name, touch, :touch_later) + end + end + end + + def touch(*names, time: nil) # :nodoc: + if has_defer_touch_attrs? + names |= @_defer_touch_attrs + end + super(*names, time: time) + end + + private + + def surreptitiously_touch(attrs) + attrs.each { |attr| write_attribute attr, @_touch_time } + clear_attribute_changes attrs + end + + def touch_deferred_attributes + if has_defer_touch_attrs? && persisted? + touch(*@_defer_touch_attrs, time: @_touch_time) + @_defer_touch_attrs, @_touch_time = nil, nil + end + end + + def has_defer_touch_attrs? + defined?(@_defer_touch_attrs) && @_defer_touch_attrs.present? + end + + def belongs_to_touch_method + :touch_later + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/transactions.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/transactions.rb new file mode 100644 index 00000000..bcb2ba04 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/transactions.rb @@ -0,0 +1,502 @@ +# frozen_string_literal: true + +module ActiveRecord + # See ActiveRecord::Transactions::ClassMethods for documentation. + module Transactions + extend ActiveSupport::Concern + #:nodoc: + ACTIONS = [:create, :destroy, :update] + + included do + define_callbacks :commit, :rollback, + :before_commit, + :before_commit_without_transaction_enrollment, + :commit_without_transaction_enrollment, + :rollback_without_transaction_enrollment, + scope: [:kind, :name] + end + + # = Active Record Transactions + # + # \Transactions are protective blocks where SQL statements are only permanent + # if they can all succeed as one atomic action. The classic example is a + # transfer between two accounts where you can only have a deposit if the + # withdrawal succeeded and vice versa. \Transactions enforce the integrity of + # the database and guard the data against program errors or database + # break-downs. So basically you should use transaction blocks whenever you + # have a number of statements that must be executed together or not at all. + # + # For example: + # + # ActiveRecord::Base.transaction do + # david.withdrawal(100) + # mary.deposit(100) + # end + # + # This example will only take money from David and give it to Mary if neither + # +withdrawal+ nor +deposit+ raise an exception. Exceptions will force a + # ROLLBACK that returns the database to the state before the transaction + # began. Be aware, though, that the objects will _not_ have their instance + # data returned to their pre-transactional state. + # + # == Different Active Record classes in a single transaction + # + # Though the #transaction class method is called on some Active Record class, + # the objects within the transaction block need not all be instances of + # that class. This is because transactions are per-database connection, not + # per-model. + # + # In this example a +balance+ record is transactionally saved even + # though #transaction is called on the +Account+ class: + # + # Account.transaction do + # balance.save! + # account.save! + # end + # + # The #transaction method is also available as a model instance method. + # For example, you can also do this: + # + # balance.transaction do + # balance.save! + # account.save! + # end + # + # == Transactions are not distributed across database connections + # + # A transaction acts on a single database connection. If you have + # multiple class-specific databases, the transaction will not protect + # interaction among them. One workaround is to begin a transaction + # on each class whose models you alter: + # + # Student.transaction do + # Course.transaction do + # course.enroll(student) + # student.units += course.units + # end + # end + # + # This is a poor solution, but fully distributed transactions are beyond + # the scope of Active Record. + # + # == +save+ and +destroy+ are automatically wrapped in a transaction + # + # Both {#save}[rdoc-ref:Persistence#save] and + # {#destroy}[rdoc-ref:Persistence#destroy] come wrapped in a transaction that ensures + # that whatever you do in validations or callbacks will happen under its + # protected cover. So you can use validations to check for values that + # the transaction depends on or you can raise exceptions in the callbacks + # to rollback, including after_* callbacks. + # + # As a consequence changes to the database are not seen outside your connection + # until the operation is complete. For example, if you try to update the index + # of a search engine in +after_save+ the indexer won't see the updated record. + # The #after_commit callback is the only one that is triggered once the update + # is committed. See below. + # + # == Exception handling and rolling back + # + # Also have in mind that exceptions thrown within a transaction block will + # be propagated (after triggering the ROLLBACK), so you should be ready to + # catch those in your application code. + # + # One exception is the ActiveRecord::Rollback exception, which will trigger + # a ROLLBACK when raised, but not be re-raised by the transaction block. + # + # *Warning*: one should not catch ActiveRecord::StatementInvalid exceptions + # inside a transaction block. ActiveRecord::StatementInvalid exceptions indicate that an + # error occurred at the database level, for example when a unique constraint + # is violated. On some database systems, such as PostgreSQL, database errors + # inside a transaction cause the entire transaction to become unusable + # until it's restarted from the beginning. Here is an example which + # demonstrates the problem: + # + # # Suppose that we have a Number model with a unique column called 'i'. + # Number.transaction do + # Number.create(i: 0) + # begin + # # This will raise a unique constraint error... + # Number.create(i: 0) + # rescue ActiveRecord::StatementInvalid + # # ...which we ignore. + # end + # + # # On PostgreSQL, the transaction is now unusable. The following + # # statement will cause a PostgreSQL error, even though the unique + # # constraint is no longer violated: + # Number.create(i: 1) + # # => "PG::Error: ERROR: current transaction is aborted, commands + # # ignored until end of transaction block" + # end + # + # One should restart the entire transaction if an + # ActiveRecord::StatementInvalid occurred. + # + # == Nested transactions + # + # #transaction calls can be nested. By default, this makes all database + # statements in the nested transaction block become part of the parent + # transaction. For example, the following behavior may be surprising: + # + # User.transaction do + # User.create(username: 'Kotori') + # User.transaction do + # User.create(username: 'Nemu') + # raise ActiveRecord::Rollback + # end + # end + # + # creates both "Kotori" and "Nemu". Reason is the ActiveRecord::Rollback + # exception in the nested block does not issue a ROLLBACK. Since these exceptions + # are captured in transaction blocks, the parent block does not see it and the + # real transaction is committed. + # + # In order to get a ROLLBACK for the nested transaction you may ask for a real + # sub-transaction by passing requires_new: true. If anything goes wrong, + # the database rolls back to the beginning of the sub-transaction without rolling + # back the parent transaction. If we add it to the previous example: + # + # User.transaction do + # User.create(username: 'Kotori') + # User.transaction(requires_new: true) do + # User.create(username: 'Nemu') + # raise ActiveRecord::Rollback + # end + # end + # + # only "Kotori" is created. This works on MySQL and PostgreSQL. SQLite3 version >= '3.6.8' also supports it. + # + # Most databases don't support true nested transactions. At the time of + # writing, the only database that we're aware of that supports true nested + # transactions, is MS-SQL. Because of this, Active Record emulates nested + # transactions by using savepoints on MySQL and PostgreSQL. See + # https://dev.mysql.com/doc/refman/5.7/en/savepoint.html + # for more information about savepoints. + # + # === \Callbacks + # + # There are two types of callbacks associated with committing and rolling back transactions: + # #after_commit and #after_rollback. + # + # #after_commit callbacks are called on every record saved or destroyed within a + # transaction immediately after the transaction is committed. #after_rollback callbacks + # are called on every record saved or destroyed within a transaction immediately after the + # transaction or savepoint is rolled back. + # + # These callbacks are useful for interacting with other systems since you will be guaranteed + # that the callback is only executed when the database is in a permanent state. For example, + # #after_commit is a good spot to put in a hook to clearing a cache since clearing it from + # within a transaction could trigger the cache to be regenerated before the database is updated. + # + # === Caveats + # + # If you're on MySQL, then do not use Data Definition Language (DDL) operations in nested + # transactions blocks that are emulated with savepoints. That is, do not execute statements + # like 'CREATE TABLE' inside such blocks. This is because MySQL automatically + # releases all savepoints upon executing a DDL operation. When +transaction+ + # is finished and tries to release the savepoint it created earlier, a + # database error will occur because the savepoint has already been + # automatically released. The following example demonstrates the problem: + # + # Model.connection.transaction do # BEGIN + # Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1 + # Model.connection.create_table(...) # active_record_1 now automatically released + # end # RELEASE SAVEPOINT active_record_1 + # # ^^^^ BOOM! database error! + # end + # + # Note that "TRUNCATE" is also a MySQL DDL statement! + module ClassMethods + # See the ConnectionAdapters::DatabaseStatements#transaction API docs. + def transaction(options = {}, &block) + connection.transaction(options, &block) + end + + def before_commit(*args, &block) # :nodoc: + set_options_for_callbacks!(args) + set_callback(:before_commit, :before, *args, &block) + end + + # This callback is called after a record has been created, updated, or destroyed. + # + # You can specify that the callback should only be fired by a certain action with + # the +:on+ option: + # + # after_commit :do_foo, on: :create + # after_commit :do_bar, on: :update + # after_commit :do_baz, on: :destroy + # + # after_commit :do_foo_bar, on: [:create, :update] + # after_commit :do_bar_baz, on: [:update, :destroy] + # + def after_commit(*args, &block) + set_options_for_callbacks!(args) + set_callback(:commit, :after, *args, &block) + end + + # Shortcut for after_commit :hook, on: :create. + def after_create_commit(*args, &block) + set_options_for_callbacks!(args, on: :create) + set_callback(:commit, :after, *args, &block) + end + + # Shortcut for after_commit :hook, on: :update. + def after_update_commit(*args, &block) + set_options_for_callbacks!(args, on: :update) + set_callback(:commit, :after, *args, &block) + end + + # Shortcut for after_commit :hook, on: :destroy. + def after_destroy_commit(*args, &block) + set_options_for_callbacks!(args, on: :destroy) + set_callback(:commit, :after, *args, &block) + end + + # This callback is called after a create, update, or destroy are rolled back. + # + # Please check the documentation of #after_commit for options. + def after_rollback(*args, &block) + set_options_for_callbacks!(args) + set_callback(:rollback, :after, *args, &block) + end + + def before_commit_without_transaction_enrollment(*args, &block) # :nodoc: + set_options_for_callbacks!(args) + set_callback(:before_commit_without_transaction_enrollment, :before, *args, &block) + end + + def after_commit_without_transaction_enrollment(*args, &block) # :nodoc: + set_options_for_callbacks!(args) + set_callback(:commit_without_transaction_enrollment, :after, *args, &block) + end + + def after_rollback_without_transaction_enrollment(*args, &block) # :nodoc: + set_options_for_callbacks!(args) + set_callback(:rollback_without_transaction_enrollment, :after, *args, &block) + end + + private + + def set_options_for_callbacks!(args, enforced_options = {}) + options = args.extract_options!.merge!(enforced_options) + args << options + + if options[:on] + fire_on = Array(options[:on]) + assert_valid_transaction_action(fire_on) + options[:if] = Array(options[:if]) + options[:if].unshift(-> { transaction_include_any_action?(fire_on) }) + end + end + + def assert_valid_transaction_action(actions) + if (actions - ACTIONS).any? + raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS}" + end + end + end + + # See ActiveRecord::Transactions::ClassMethods for detailed documentation. + def transaction(options = {}, &block) + self.class.transaction(options, &block) + end + + def destroy #:nodoc: + with_transaction_returning_status { super } + end + + def save(*) #:nodoc: + rollback_active_record_state! do + with_transaction_returning_status { super } + end + end + + def save!(*) #:nodoc: + with_transaction_returning_status { super } + end + + def touch(*) #:nodoc: + with_transaction_returning_status { super } + end + + # Reset id and @new_record if the transaction rolls back. + def rollback_active_record_state! + remember_transaction_record_state + yield + rescue Exception + restore_transaction_record_state + raise + ensure + clear_transaction_record_state + end + + def before_committed! # :nodoc: + _run_before_commit_without_transaction_enrollment_callbacks + _run_before_commit_callbacks + end + + # Call the #after_commit callbacks. + # + # Ensure that it is not called if the object was never persisted (failed create), + # but call it after the commit of a destroyed object. + def committed!(should_run_callbacks: true) #:nodoc: + if should_run_callbacks && (destroyed? || persisted?) + @_committed_already_called = true + _run_commit_without_transaction_enrollment_callbacks + _run_commit_callbacks + end + ensure + @_committed_already_called = false + force_clear_transaction_record_state + end + + # Call the #after_rollback callbacks. The +force_restore_state+ argument indicates if the record + # state should be rolled back to the beginning or just to the last savepoint. + def rolledback!(force_restore_state: false, should_run_callbacks: true) #:nodoc: + if should_run_callbacks + _run_rollback_callbacks + _run_rollback_without_transaction_enrollment_callbacks + end + ensure + restore_transaction_record_state(force_restore_state) + clear_transaction_record_state + end + + # Add the record to the current transaction so that the #after_rollback and #after_commit callbacks + # can be called. + def add_to_transaction + if has_transactional_callbacks? + self.class.connection.add_transaction_record(self) + else + sync_with_transaction_state + set_transaction_state(self.class.connection.transaction_state) + end + remember_transaction_record_state + end + + # Executes +method+ within a transaction and captures its return value as a + # status flag. If the status is true the transaction is committed, otherwise + # a ROLLBACK is issued. In any case the status flag is returned. + # + # This method is available within the context of an ActiveRecord::Base + # instance. + def with_transaction_returning_status + status = nil + self.class.transaction do + add_to_transaction + status = yield + raise ActiveRecord::Rollback unless status + end + status + ensure + if @transaction_state && @transaction_state.committed? + clear_transaction_record_state + end + end + + protected + attr_reader :_committed_already_called, :_trigger_update_callback, :_trigger_destroy_callback + + private + + # Save the new record state and id of a record so it can be restored later if a transaction fails. + def remember_transaction_record_state + @_start_transaction_state.reverse_merge!( + id: id, + new_record: @new_record, + destroyed: @destroyed, + frozen?: frozen?, + ) + @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1 + remember_new_record_before_last_commit + end + + def remember_new_record_before_last_commit + if _committed_already_called + @_new_record_before_last_commit = false + else + @_new_record_before_last_commit = @_start_transaction_state[:new_record] + end + end + + # Clear the new record state and id of a record. + def clear_transaction_record_state + @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1 + force_clear_transaction_record_state if @_start_transaction_state[:level] < 1 + end + + # Force to clear the transaction record state. + def force_clear_transaction_record_state + @_start_transaction_state.clear + end + + # Restore the new record state and id of a record that was previously saved by a call to save_record_state. + def restore_transaction_record_state(force = false) + unless @_start_transaction_state.empty? + transaction_level = (@_start_transaction_state[:level] || 0) - 1 + if transaction_level < 1 || force + restore_state = @_start_transaction_state + thaw + @new_record = restore_state[:new_record] + @destroyed = restore_state[:destroyed] + pk = self.class.primary_key + if pk && _read_attribute(pk) != restore_state[:id] + _write_attribute(pk, restore_state[:id]) + end + freeze if restore_state[:frozen?] + end + end + end + + # Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks. + def transaction_include_any_action?(actions) + actions.any? do |action| + case action + when :create + persisted? && @_new_record_before_last_commit + when :update + !(@_new_record_before_last_commit || destroyed?) && _trigger_update_callback + when :destroy + _trigger_destroy_callback + end + end + end + + def set_transaction_state(state) + @transaction_state = state + end + + def has_transactional_callbacks? + !_rollback_callbacks.empty? || !_commit_callbacks.empty? || !_before_commit_callbacks.empty? + end + + # Updates the attributes on this particular Active Record object so that + # if it's associated with a transaction, then the state of the Active Record + # object will be updated to reflect the current state of the transaction. + # + # The @transaction_state variable stores the states of the associated + # transaction. This relies on the fact that a transaction can only be in + # one rollback or commit (otherwise a list of states would be required). + # Each Active Record object inside of a transaction carries that transaction's + # TransactionState. + # + # This method checks to see if the ActiveRecord object's state reflects + # the TransactionState, and rolls back or commits the Active Record object + # as appropriate. + # + # Since Active Record objects can be inside multiple transactions, this + # method recursively goes through the parent of the TransactionState and + # checks if the Active Record object reflects the state of the object. + def sync_with_transaction_state + update_attributes_from_transaction_state(@transaction_state) + end + + def update_attributes_from_transaction_state(transaction_state) + if transaction_state && transaction_state.finalized? + restore_transaction_record_state(transaction_state.fully_rolledback?) if transaction_state.rolledback? + force_clear_transaction_record_state if transaction_state.fully_committed? + clear_transaction_record_state if transaction_state.fully_completed? + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/translation.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/translation.rb new file mode 100644 index 00000000..3cf70eaf --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/translation.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module ActiveRecord + module Translation + include ActiveModel::Translation + + # Set the lookup ancestors for ActiveModel. + def lookup_ancestors #:nodoc: + klass = self + classes = [klass] + return classes if klass == ActiveRecord::Base + + while klass != klass.base_class + classes << klass = klass.superclass + end + classes + end + + # Set the i18n scope to overwrite ActiveModel. + def i18n_scope #:nodoc: + :activerecord + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type.rb new file mode 100644 index 00000000..c303186e --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require "active_model/type" + +require "active_record/type/internal/timezone" + +require "active_record/type/date" +require "active_record/type/date_time" +require "active_record/type/decimal_without_scale" +require "active_record/type/json" +require "active_record/type/time" +require "active_record/type/text" +require "active_record/type/unsigned_integer" + +require "active_record/type/serialized" +require "active_record/type/adapter_specific_registry" + +require "active_record/type/type_map" +require "active_record/type/hash_lookup_type_map" + +module ActiveRecord + module Type + @registry = AdapterSpecificRegistry.new + + class << self + attr_accessor :registry # :nodoc: + delegate :add_modifier, to: :registry + + # Add a new type to the registry, allowing it to be referenced as a + # symbol by {ActiveRecord::Base.attribute}[rdoc-ref:Attributes::ClassMethods#attribute]. + # If your type is only meant to be used with a specific database adapter, you can + # do so by passing adapter: :postgresql. If your type has the same + # name as a native type for the current adapter, an exception will be + # raised unless you specify an +:override+ option. override: true will + # cause your type to be used instead of the native type. override: + # false will cause the native type to be used over yours if one exists. + def register(type_name, klass = nil, **options, &block) + registry.register(type_name, klass, **options, &block) + end + + def lookup(*args, adapter: current_adapter_name, **kwargs) # :nodoc: + registry.lookup(*args, adapter: adapter, **kwargs) + end + + def default_value # :nodoc: + @default_value ||= Value.new + end + + private + + def current_adapter_name + ActiveRecord::Base.connection.adapter_name.downcase.to_sym + end + end + + Helpers = ActiveModel::Type::Helpers + BigInteger = ActiveModel::Type::BigInteger + Binary = ActiveModel::Type::Binary + Boolean = ActiveModel::Type::Boolean + Decimal = ActiveModel::Type::Decimal + Float = ActiveModel::Type::Float + Integer = ActiveModel::Type::Integer + String = ActiveModel::Type::String + Value = ActiveModel::Type::Value + + register(:big_integer, Type::BigInteger, override: false) + register(:binary, Type::Binary, override: false) + register(:boolean, Type::Boolean, override: false) + register(:date, Type::Date, override: false) + register(:datetime, Type::DateTime, override: false) + register(:decimal, Type::Decimal, override: false) + register(:float, Type::Float, override: false) + register(:integer, Type::Integer, override: false) + register(:json, Type::Json, override: false) + register(:string, Type::String, override: false) + register(:text, Type::Text, override: false) + register(:time, Type::Time, override: false) + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/adapter_specific_registry.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/adapter_specific_registry.rb new file mode 100644 index 00000000..e7468aa5 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/adapter_specific_registry.rb @@ -0,0 +1,136 @@ +# frozen_string_literal: true + +require "active_model/type/registry" + +module ActiveRecord + # :stopdoc: + module Type + class AdapterSpecificRegistry < ActiveModel::Type::Registry + def add_modifier(options, klass, **args) + registrations << DecorationRegistration.new(options, klass, **args) + end + + private + + def registration_klass + Registration + end + + def find_registration(symbol, *args) + registrations + .select { |registration| registration.matches?(symbol, *args) } + .max + end + end + + class Registration + def initialize(name, block, adapter: nil, override: nil) + @name = name + @block = block + @adapter = adapter + @override = override + end + + def call(_registry, *args, adapter: nil, **kwargs) + if kwargs.any? # https://bugs.ruby-lang.org/issues/10856 + block.call(*args, **kwargs) + else + block.call(*args) + end + end + + def matches?(type_name, *args, **kwargs) + type_name == name && matches_adapter?(**kwargs) + end + + def <=>(other) + if conflicts_with?(other) + raise TypeConflictError.new("Type #{name} was registered for all + adapters, but shadows a native type with + the same name for #{other.adapter}".squish) + end + priority <=> other.priority + end + + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected + + attr_reader :name, :block, :adapter, :override + + def priority + result = 0 + if adapter + result |= 1 + end + if override + result |= 2 + end + result + end + + def priority_except_adapter + priority & 0b111111100 + end + + private + + def matches_adapter?(adapter: nil, **) + (self.adapter.nil? || adapter == self.adapter) + end + + def conflicts_with?(other) + same_priority_except_adapter?(other) && + has_adapter_conflict?(other) + end + + def same_priority_except_adapter?(other) + priority_except_adapter == other.priority_except_adapter + end + + def has_adapter_conflict?(other) + (override.nil? && other.adapter) || + (adapter && other.override.nil?) + end + end + + class DecorationRegistration < Registration + def initialize(options, klass, adapter: nil) + @options = options + @klass = klass + @adapter = adapter + end + + def call(registry, *args, **kwargs) + subtype = registry.lookup(*args, **kwargs.except(*options.keys)) + klass.new(subtype) + end + + def matches?(*args, **kwargs) + matches_adapter?(**kwargs) && matches_options?(**kwargs) + end + + def priority + super | 4 + end + + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected + + attr_reader :options, :klass + + private + + def matches_options?(**kwargs) + options.all? do |key, value| + kwargs[key] == value + end + end + end + end + + class TypeConflictError < StandardError + end + # :startdoc: +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/date.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/date.rb new file mode 100644 index 00000000..8177074a --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/date.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module ActiveRecord + module Type + class Date < ActiveModel::Type::Date + include Internal::Timezone + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/date_time.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/date_time.rb new file mode 100644 index 00000000..4acde6b9 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/date_time.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module ActiveRecord + module Type + class DateTime < ActiveModel::Type::DateTime + include Internal::Timezone + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/decimal_without_scale.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/decimal_without_scale.rb new file mode 100644 index 00000000..a207940d --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/decimal_without_scale.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActiveRecord + module Type + class DecimalWithoutScale < ActiveModel::Type::BigInteger # :nodoc: + def type + :decimal + end + + def type_cast_for_schema(value) + value.to_s.inspect + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/hash_lookup_type_map.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/hash_lookup_type_map.rb new file mode 100644 index 00000000..db9853fb --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/hash_lookup_type_map.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module ActiveRecord + module Type + class HashLookupTypeMap < TypeMap # :nodoc: + def alias_type(type, alias_type) + register_type(type) { |_, *args| lookup(alias_type, *args) } + end + + def key?(key) + @mapping.key?(key) + end + + def keys + @mapping.keys + end + + private + + def perform_fetch(type, *args, &block) + @mapping.fetch(type, block).call(type, *args) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/internal/timezone.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/internal/timezone.rb new file mode 100644 index 00000000..30597557 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/internal/timezone.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActiveRecord + module Type + module Internal + module Timezone + def is_utc? + ActiveRecord::Base.default_timezone == :utc + end + + def default_timezone + ActiveRecord::Base.default_timezone + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/json.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/json.rb new file mode 100644 index 00000000..3f9ff227 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/json.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module ActiveRecord + module Type + class Json < ActiveModel::Type::Value + include ActiveModel::Type::Helpers::Mutable + + def type + :json + end + + def deserialize(value) + return value unless value.is_a?(::String) + ActiveSupport::JSON.decode(value) rescue nil + end + + def serialize(value) + ActiveSupport::JSON.encode(value) unless value.nil? + end + + def changed_in_place?(raw_old_value, new_value) + deserialize(raw_old_value) != new_value + end + + def accessor + ActiveRecord::Store::StringKeyedHashAccessor + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/serialized.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/serialized.rb new file mode 100644 index 00000000..0a2f6cb9 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/serialized.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +module ActiveRecord + module Type + class Serialized < DelegateClass(ActiveModel::Type::Value) # :nodoc: + undef to_yaml if method_defined?(:to_yaml) + + include ActiveModel::Type::Helpers::Mutable + + attr_reader :subtype, :coder + + def initialize(subtype, coder) + @subtype = subtype + @coder = coder + super(subtype) + end + + def deserialize(value) + if default_value?(value) + value + else + coder.load(super) + end + end + + def serialize(value) + return if value.nil? + unless default_value?(value) + super coder.dump(value) + end + end + + def inspect + Kernel.instance_method(:inspect).bind(self).call + end + + def changed_in_place?(raw_old_value, value) + return false if value.nil? + raw_new_value = encoded(value) + raw_old_value.nil? != raw_new_value.nil? || + subtype.changed_in_place?(raw_old_value, raw_new_value) + end + + def accessor + ActiveRecord::Store::IndifferentHashAccessor + end + + def assert_valid_value(value) + if coder.respond_to?(:assert_valid_value) + coder.assert_valid_value(value, action: "serialize") + end + end + + def force_equality?(value) + coder.respond_to?(:object_class) && value.is_a?(coder.object_class) + end + + private + + def default_value?(value) + value == coder.load(nil) + end + + def encoded(value) + unless default_value?(value) + coder.dump(value) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/text.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/text.rb new file mode 100644 index 00000000..6d196966 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/text.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module ActiveRecord + module Type + class Text < ActiveModel::Type::String # :nodoc: + def type + :text + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/time.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/time.rb new file mode 100644 index 00000000..f4da1ecf --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/time.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module ActiveRecord + module Type + class Time < ActiveModel::Type::Time + include Internal::Timezone + + class Value < DelegateClass(::Time) # :nodoc: + end + + def serialize(value) + case value = super + when ::Time + Value.new(value) + else + value + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/type_map.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/type_map.rb new file mode 100644 index 00000000..fc40b460 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/type_map.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require "concurrent/map" + +module ActiveRecord + module Type + class TypeMap # :nodoc: + def initialize + @mapping = {} + @cache = Concurrent::Map.new do |h, key| + h.fetch_or_store(key, Concurrent::Map.new) + end + end + + def lookup(lookup_key, *args) + fetch(lookup_key, *args) { Type.default_value } + end + + def fetch(lookup_key, *args, &block) + @cache[lookup_key].fetch_or_store(args) do + perform_fetch(lookup_key, *args, &block) + end + end + + def register_type(key, value = nil, &block) + raise ::ArgumentError unless value || block + @cache.clear + + if block + @mapping[key] = block + else + @mapping[key] = proc { value } + end + end + + def alias_type(key, target_key) + register_type(key) do |sql_type, *args| + metadata = sql_type[/\(.*\)/, 0] + lookup("#{target_key}#{metadata}", *args) + end + end + + def clear + @mapping.clear + end + + private + + def perform_fetch(lookup_key, *args) + matching_pair = @mapping.reverse_each.detect do |key, _| + key === lookup_key + end + + if matching_pair + matching_pair.last.call(lookup_key, *args) + else + yield lookup_key, *args + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/unsigned_integer.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/unsigned_integer.rb new file mode 100644 index 00000000..4619528f --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type/unsigned_integer.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActiveRecord + module Type + class UnsignedInteger < ActiveModel::Type::Integer # :nodoc: + private + + def max_value + super * 2 + end + + def min_value + 0 + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type_caster.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type_caster.rb new file mode 100644 index 00000000..2e5f45fa --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type_caster.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require "active_record/type_caster/map" +require "active_record/type_caster/connection" + +module ActiveRecord + module TypeCaster # :nodoc: + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type_caster/connection.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type_caster/connection.rb new file mode 100644 index 00000000..af4e4e37 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type_caster/connection.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module ActiveRecord + module TypeCaster + class Connection # :nodoc: + def initialize(klass, table_name) + @klass = klass + @table_name = table_name + end + + def type_cast_for_database(attribute_name, value) + return value if value.is_a?(Arel::Nodes::BindParam) + column = column_for(attribute_name) + connection.type_cast_from_column(column, value) + end + + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected + + attr_reader :table_name + delegate :connection, to: :@klass + + private + + def column_for(attribute_name) + if connection.schema_cache.data_source_exists?(table_name) + connection.schema_cache.columns_hash(table_name)[attribute_name.to_s] + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type_caster/map.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type_caster/map.rb new file mode 100644 index 00000000..c5cfdba8 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/type_caster/map.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module ActiveRecord + module TypeCaster + class Map # :nodoc: + def initialize(types) + @types = types + end + + def type_cast_for_database(attr_name, value) + return value if value.is_a?(Arel::Nodes::BindParam) + type = types.type_for_attribute(attr_name) + type.serialize(value) + end + + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected + + attr_reader :types + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/validations.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/validations.rb new file mode 100644 index 00000000..ca27a3f0 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/validations.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +module ActiveRecord + # = Active Record \RecordInvalid + # + # Raised by {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] and + # {ActiveRecord::Base#create!}[rdoc-ref:Persistence::ClassMethods#create!] when the record is invalid. + # Use the #record method to retrieve the record which did not validate. + # + # begin + # complex_operation_that_internally_calls_save! + # rescue ActiveRecord::RecordInvalid => invalid + # puts invalid.record.errors + # end + class RecordInvalid < ActiveRecordError + attr_reader :record + + def initialize(record = nil) + if record + @record = record + errors = @record.errors.full_messages.join(", ") + message = I18n.t(:"#{@record.class.i18n_scope}.errors.messages.record_invalid", errors: errors, default: :"errors.messages.record_invalid") + else + message = "Record invalid" + end + + super(message) + end + end + + # = Active Record \Validations + # + # Active Record includes the majority of its validations from ActiveModel::Validations + # all of which accept the :on argument to define the context where the + # validations are active. Active Record will always supply either the context of + # :create or :update dependent on whether the model is a + # {new_record?}[rdoc-ref:Persistence#new_record?]. + module Validations + extend ActiveSupport::Concern + include ActiveModel::Validations + + # The validation process on save can be skipped by passing validate: false. + # The regular {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] method is replaced + # with this when the validations module is mixed in, which it is by default. + def save(options = {}) + perform_validations(options) ? super : false + end + + # Attempts to save the record just like {ActiveRecord::Base#save}[rdoc-ref:Base#save] but + # will raise an ActiveRecord::RecordInvalid exception instead of returning +false+ if the record is not valid. + def save!(options = {}) + perform_validations(options) ? super : raise_validation_error + end + + # Runs all the validations within the specified context. Returns +true+ if + # no errors are found, +false+ otherwise. + # + # Aliased as #validate. + # + # If the argument is +false+ (default is +nil+), the context is set to :create if + # {new_record?}[rdoc-ref:Persistence#new_record?] is +true+, and to :update if it is not. + # + # \Validations with no :on option will run no matter the context. \Validations with + # some :on option will only run in the specified context. + def valid?(context = nil) + context ||= default_validation_context + output = super(context) + errors.empty? && output + end + + alias_method :validate, :valid? + + private + + def default_validation_context + new_record? ? :create : :update + end + + def raise_validation_error + raise(RecordInvalid.new(self)) + end + + def perform_validations(options = {}) + options[:validate] == false || valid?(options[:context]) + end + end +end + +require "active_record/validations/associated" +require "active_record/validations/uniqueness" +require "active_record/validations/presence" +require "active_record/validations/absence" +require "active_record/validations/length" diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/validations/absence.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/validations/absence.rb new file mode 100644 index 00000000..6afb9eab --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/validations/absence.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module ActiveRecord + module Validations + class AbsenceValidator < ActiveModel::Validations::AbsenceValidator # :nodoc: + def validate_each(record, attribute, association_or_value) + if record.class._reflect_on_association(attribute) + association_or_value = Array.wrap(association_or_value).reject(&:marked_for_destruction?) + end + super + end + end + + module ClassMethods + # Validates that the specified attributes are not present (as defined by + # Object#present?). If the attribute is an association, the associated object + # is considered absent if it was marked for destruction. + # + # See ActiveModel::Validations::HelperMethods.validates_absence_of for more information. + def validates_absence_of(*attr_names) + validates_with AbsenceValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/validations/associated.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/validations/associated.rb new file mode 100644 index 00000000..3538aeec --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/validations/associated.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module ActiveRecord + module Validations + class AssociatedValidator < ActiveModel::EachValidator #:nodoc: + def validate_each(record, attribute, value) + if Array(value).reject { |r| valid_object?(r) }.any? + record.errors.add(attribute, :invalid, options.merge(value: value)) + end + end + + private + + def valid_object?(record) + (record.respond_to?(:marked_for_destruction?) && record.marked_for_destruction?) || record.valid? + end + end + + module ClassMethods + # Validates whether the associated object or objects are all valid. + # Works with any kind of association. + # + # class Book < ActiveRecord::Base + # has_many :pages + # belongs_to :library + # + # validates_associated :pages, :library + # end + # + # WARNING: This validation must not be used on both ends of an association. + # Doing so will lead to a circular dependency and cause infinite recursion. + # + # NOTE: This validation will not fail if the association hasn't been + # assigned. If you want to ensure that the association is both present and + # guaranteed to be valid, you also need to use + # {validates_presence_of}[rdoc-ref:Validations::ClassMethods#validates_presence_of]. + # + # Configuration options: + # + # * :message - A custom error message (default is: "is invalid"). + # * :on - Specifies the contexts where this validation is active. + # Runs in all validation contexts by default +nil+. You can pass a symbol + # or an array of symbols. (e.g. on: :create or + # on: :custom_validation_context or + # on: [:create, :custom_validation_context]) + # * :if - Specifies a method, proc or string to call to determine + # if the validation should occur (e.g. if: :allow_validation, + # or if: Proc.new { |user| user.signup_step > 2 }). The method, + # proc or string should return or evaluate to a +true+ or +false+ value. + # * :unless - Specifies a method, proc or string to call to + # determine if the validation should not occur (e.g. unless: :skip_validation, + # or unless: Proc.new { |user| user.signup_step <= 2 }). The + # method, proc or string should return or evaluate to a +true+ or +false+ + # value. + def validates_associated(*attr_names) + validates_with AssociatedValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/validations/length.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/validations/length.rb new file mode 100644 index 00000000..f47b14ae --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/validations/length.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module ActiveRecord + module Validations + class LengthValidator < ActiveModel::Validations::LengthValidator # :nodoc: + def validate_each(record, attribute, association_or_value) + if association_or_value.respond_to?(:loaded?) && association_or_value.loaded? + association_or_value = association_or_value.target.reject(&:marked_for_destruction?) + end + super + end + end + + module ClassMethods + # Validates that the specified attributes match the length restrictions supplied. + # If the attribute is an association, records that are marked for destruction are not counted. + # + # See ActiveModel::Validations::HelperMethods.validates_length_of for more information. + def validates_length_of(*attr_names) + validates_with LengthValidator, _merge_attributes(attr_names) + end + + alias_method :validates_size_of, :validates_length_of + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/validations/presence.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/validations/presence.rb new file mode 100644 index 00000000..75e97e19 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/validations/presence.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +module ActiveRecord + module Validations + class PresenceValidator < ActiveModel::Validations::PresenceValidator # :nodoc: + def validate_each(record, attribute, association_or_value) + if record.class._reflect_on_association(attribute) + association_or_value = Array.wrap(association_or_value).reject(&:marked_for_destruction?) + end + super + end + end + + module ClassMethods + # Validates that the specified attributes are not blank (as defined by + # Object#blank?), and, if the attribute is an association, that the + # associated object is not marked for destruction. Happens by default + # on save. + # + # class Person < ActiveRecord::Base + # has_one :face + # validates_presence_of :face + # end + # + # The face attribute must be in the object and it cannot be blank or marked + # for destruction. + # + # If you want to validate the presence of a boolean field (where the real values + # are true and false), you will want to use + # validates_inclusion_of :field_name, in: [true, false]. + # + # This is due to the way Object#blank? handles boolean values: + # false.blank? # => true. + # + # This validator defers to the Active Model validation for presence, adding the + # check to see that an associated object is not marked for destruction. This + # prevents the parent object from validating successfully and saving, which then + # deletes the associated object, thus putting the parent object into an invalid + # state. + # + # NOTE: This validation will not fail while using it with an association + # if the latter was assigned but not valid. If you want to ensure that + # it is both present and valid, you also need to use + # {validates_associated}[rdoc-ref:Validations::ClassMethods#validates_associated]. + # + # Configuration options: + # * :message - A custom error message (default is: "can't be blank"). + # * :on - Specifies the contexts where this validation is active. + # Runs in all validation contexts by default +nil+. You can pass a symbol + # or an array of symbols. (e.g. on: :create or + # on: :custom_validation_context or + # on: [:create, :custom_validation_context]) + # * :if - Specifies a method, proc or string to call to determine if + # the validation should occur (e.g. if: :allow_validation, or + # if: Proc.new { |user| user.signup_step > 2 }). The method, proc + # or string should return or evaluate to a +true+ or +false+ value. + # * :unless - Specifies a method, proc or string to call to determine + # if the validation should not occur (e.g. unless: :skip_validation, + # or unless: Proc.new { |user| user.signup_step <= 2 }). The method, + # proc or string should return or evaluate to a +true+ or +false+ value. + # * :strict - Specifies whether validation should be strict. + # See ActiveModel::Validations#validates! for more information. + def validates_presence_of(*attr_names) + validates_with PresenceValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/validations/uniqueness.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/validations/uniqueness.rb new file mode 100644 index 00000000..5a1dbc8e --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/validations/uniqueness.rb @@ -0,0 +1,238 @@ +# frozen_string_literal: true + +module ActiveRecord + module Validations + class UniquenessValidator < ActiveModel::EachValidator # :nodoc: + def initialize(options) + if options[:conditions] && !options[:conditions].respond_to?(:call) + raise ArgumentError, "#{options[:conditions]} was passed as :conditions but is not callable. " \ + "Pass a callable instead: `conditions: -> { where(approved: true) }`" + end + unless Array(options[:scope]).all? { |scope| scope.respond_to?(:to_sym) } + raise ArgumentError, "#{options[:scope]} is not supported format for :scope option. " \ + "Pass a symbol or an array of symbols instead: `scope: :user_id`" + end + super({ case_sensitive: true }.merge!(options)) + @klass = options[:class] + end + + def validate_each(record, attribute, value) + finder_class = find_finder_class_for(record) + value = map_enum_attribute(finder_class, attribute, value) + + relation = build_relation(finder_class, attribute, value) + if record.persisted? + if finder_class.primary_key + relation = relation.where.not(finder_class.primary_key => record.id_in_database) + else + raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.") + end + end + relation = scope_relation(record, relation) + relation = relation.merge(options[:conditions]) if options[:conditions] + + if relation.exists? + error_options = options.except(:case_sensitive, :scope, :conditions) + error_options[:value] = value + + record.errors.add(attribute, :taken, error_options) + end + end + + private + # The check for an existing value should be run from a class that + # isn't abstract. This means working down from the current class + # (self), to the first non-abstract class. Since classes don't know + # their subclasses, we have to build the hierarchy between self and + # the record's class. + def find_finder_class_for(record) + class_hierarchy = [record.class] + + while class_hierarchy.first != @klass + class_hierarchy.unshift(class_hierarchy.first.superclass) + end + + class_hierarchy.detect { |klass| !klass.abstract_class? } + end + + def build_relation(klass, attribute, value) + if reflection = klass._reflect_on_association(attribute) + attribute = reflection.foreign_key + value = value.attributes[reflection.klass.primary_key] unless value.nil? + end + + if value.nil? + return klass.unscoped.where!(attribute => value) + end + + # the attribute may be an aliased attribute + if klass.attribute_alias?(attribute) + attribute = klass.attribute_alias(attribute) + end + + attribute_name = attribute.to_s + value = klass.predicate_builder.build_bind_attribute(attribute_name, value) + + table = klass.arel_table + column = klass.columns_hash[attribute_name] + + comparison = if !options[:case_sensitive] + # will use SQL LOWER function before comparison, unless it detects a case insensitive collation + klass.connection.case_insensitive_comparison(table, attribute, column, value) + else + klass.connection.case_sensitive_comparison(table, attribute, column, value) + end + klass.unscoped.where!(comparison) + end + + def scope_relation(record, relation) + Array(options[:scope]).each do |scope_item| + scope_value = if record.class._reflect_on_association(scope_item) + record.association(scope_item).reader + else + record._read_attribute(scope_item) + end + relation = relation.where(scope_item => scope_value) + end + + relation + end + + def map_enum_attribute(klass, attribute, value) + mapping = klass.defined_enums[attribute.to_s] + value = mapping[value] if value && mapping + value + end + end + + module ClassMethods + # Validates whether the value of the specified attributes are unique + # across the system. Useful for making sure that only one user + # can be named "davidhh". + # + # class Person < ActiveRecord::Base + # validates_uniqueness_of :user_name + # end + # + # It can also validate whether the value of the specified attributes are + # unique based on a :scope parameter: + # + # class Person < ActiveRecord::Base + # validates_uniqueness_of :user_name, scope: :account_id + # end + # + # Or even multiple scope parameters. For example, making sure that a + # teacher can only be on the schedule once per semester for a particular + # class. + # + # class TeacherSchedule < ActiveRecord::Base + # validates_uniqueness_of :teacher_id, scope: [:semester_id, :class_id] + # end + # + # It is also possible to limit the uniqueness constraint to a set of + # records matching certain conditions. In this example archived articles + # are not being taken into consideration when validating uniqueness + # of the title attribute: + # + # class Article < ActiveRecord::Base + # validates_uniqueness_of :title, conditions: -> { where.not(status: 'archived') } + # end + # + # When the record is created, a check is performed to make sure that no + # record exists in the database with the given value for the specified + # attribute (that maps to a column). When the record is updated, + # the same check is made but disregarding the record itself. + # + # Configuration options: + # + # * :message - Specifies a custom error message (default is: + # "has already been taken"). + # * :scope - One or more columns by which to limit the scope of + # the uniqueness constraint. + # * :conditions - Specify the conditions to be included as a + # WHERE SQL fragment to limit the uniqueness constraint lookup + # (e.g. conditions: -> { where(status: 'active') }). + # * :case_sensitive - Looks for an exact match. Ignored by + # non-text columns (+true+ by default). + # * :allow_nil - If set to +true+, skips this validation if the + # attribute is +nil+ (default is +false+). + # * :allow_blank - If set to +true+, skips this validation if the + # attribute is blank (default is +false+). + # * :if - Specifies a method, proc or string to call to determine + # if the validation should occur (e.g. if: :allow_validation, + # or if: Proc.new { |user| user.signup_step > 2 }). The method, + # proc or string should return or evaluate to a +true+ or +false+ value. + # * :unless - Specifies a method, proc or string to call to + # determine if the validation should not occur (e.g. unless: :skip_validation, + # or unless: Proc.new { |user| user.signup_step <= 2 }). The + # method, proc or string should return or evaluate to a +true+ or +false+ + # value. + # + # === Concurrency and integrity + # + # Using this validation method in conjunction with + # {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] + # does not guarantee the absence of duplicate record insertions, because + # uniqueness checks on the application level are inherently prone to race + # conditions. For example, suppose that two users try to post a Comment at + # the same time, and a Comment's title must be unique. At the database-level, + # the actions performed by these users could be interleaved in the following manner: + # + # User 1 | User 2 + # ------------------------------------+-------------------------------------- + # # User 1 checks whether there's | + # # already a comment with the title | + # # 'My Post'. This is not the case. | + # SELECT * FROM comments | + # WHERE title = 'My Post' | + # | + # | # User 2 does the same thing and also + # | # infers that their title is unique. + # | SELECT * FROM comments + # | WHERE title = 'My Post' + # | + # # User 1 inserts their comment. | + # INSERT INTO comments | + # (title, content) VALUES | + # ('My Post', 'hi!') | + # | + # | # User 2 does the same thing. + # | INSERT INTO comments + # | (title, content) VALUES + # | ('My Post', 'hello!') + # | + # | # ^^^^^^ + # | # Boom! We now have a duplicate + # | # title! + # + # The best way to work around this problem is to add a unique index to the database table using + # {connection.add_index}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_index]. + # In the rare case that a race condition occurs, the database will guarantee + # the field's uniqueness. + # + # When the database catches such a duplicate insertion, + # {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] will raise an ActiveRecord::StatementInvalid + # exception. You can either choose to let this error propagate (which + # will result in the default Rails exception page being shown), or you + # can catch it and restart the transaction (e.g. by telling the user + # that the title already exists, and asking them to re-enter the title). + # This technique is also known as + # {optimistic concurrency control}[https://en.wikipedia.org/wiki/Optimistic_concurrency_control]. + # + # The bundled ActiveRecord::ConnectionAdapters distinguish unique index + # constraint errors from other types of database errors by throwing an + # ActiveRecord::RecordNotUnique exception. For other adapters you will + # have to parse the (database-specific) exception message to detect such + # a case. + # + # The following bundled adapters throw the ActiveRecord::RecordNotUnique exception: + # + # * ActiveRecord::ConnectionAdapters::Mysql2Adapter. + # * ActiveRecord::ConnectionAdapters::SQLite3Adapter. + # * ActiveRecord::ConnectionAdapters::PostgreSQLAdapter. + def validates_uniqueness_of(*attr_names) + validates_with UniquenessValidator, _merge_attributes(attr_names) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/version.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/version.rb new file mode 100644 index 00000000..6b0d82d8 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/version.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require_relative "gem_version" + +module ActiveRecord + # Returns the version of the currently loaded ActiveRecord as a Gem::Version + def self.version + gem_version + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record.rb new file mode 100644 index 00000000..a7e5e373 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require "rails/generators/named_base" +require "rails/generators/active_model" +require "rails/generators/active_record/migration" +require "active_record" + +module ActiveRecord + module Generators # :nodoc: + class Base < Rails::Generators::NamedBase # :nodoc: + include ActiveRecord::Generators::Migration + + # Set the current directory as base for the inherited generators. + def self.base_root + __dir__ + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record/application_record/application_record_generator.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record/application_record/application_record_generator.rb new file mode 100644 index 00000000..35d56644 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record/application_record/application_record_generator.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require "rails/generators/active_record" + +module ActiveRecord + module Generators # :nodoc: + class ApplicationRecordGenerator < ::Rails::Generators::Base # :nodoc: + source_root File.expand_path("templates", __dir__) + + # FIXME: Change this file to a symlink once RubyGems 2.5.0 is required. + def create_application_record + template "application_record.rb", application_record_file_name + end + + private + + def application_record_file_name + @application_record_file_name ||= + if namespaced? + "app/models/#{namespaced_path}/application_record.rb" + else + "app/models/application_record.rb" + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt new file mode 100644 index 00000000..60050e0b --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt @@ -0,0 +1,5 @@ +<% module_namespacing do -%> +class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true +end +<% end -%> diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record/migration.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record/migration.rb new file mode 100644 index 00000000..4ceb502c --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record/migration.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "rails/generators/migration" + +module ActiveRecord + module Generators # :nodoc: + module Migration + extend ActiveSupport::Concern + include Rails::Generators::Migration + + module ClassMethods + # Implement the required interface for Rails::Generators::Migration. + def next_migration_number(dirname) + next_migration_number = current_migration_number(dirname) + 1 + ActiveRecord::Migration.next_migration_number(next_migration_number) + end + end + + private + + def primary_key_type + key_type = options[:primary_key_type] + ", id: :#{key_type}" if key_type + end + + def db_migrate_path + if defined?(Rails.application) && Rails.application + Rails.application.config.paths["db/migrate"].to_ary.first + else + "db/migrate" + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record/migration/migration_generator.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record/migration/migration_generator.rb new file mode 100644 index 00000000..856fcc58 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record/migration/migration_generator.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require "rails/generators/active_record" + +module ActiveRecord + module Generators # :nodoc: + class MigrationGenerator < Base # :nodoc: + argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]" + + class_option :primary_key_type, type: :string, desc: "The type for primary key" + + def create_migration_file + set_local_assigns! + validate_file_name! + migration_template @migration_template, File.join(db_migrate_path, "#{file_name}.rb") + end + + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected + attr_reader :migration_action, :join_tables + + private + + # Sets the default migration template that is being used for the generation of the migration. + # Depending on command line arguments, the migration template and the table name instance + # variables are set up. + def set_local_assigns! + @migration_template = "migration.rb" + case file_name + when /^(add)_.*_to_(.*)/, /^(remove)_.*?_from_(.*)/ + @migration_action = $1 + @table_name = normalize_table_name($2) + when /join_table/ + if attributes.length == 2 + @migration_action = "join" + @join_tables = pluralize_table_names? ? attributes.map(&:plural_name) : attributes.map(&:singular_name) + + set_index_names + end + when /^create_(.+)/ + @table_name = normalize_table_name($1) + @migration_template = "create_table_migration.rb" + end + end + + def set_index_names + attributes.each_with_index do |attr, i| + attr.index_name = [attr, attributes[i - 1]].map { |a| index_name_for(a) } + end + end + + def index_name_for(attribute) + if attribute.foreign_key? + attribute.name + else + attribute.name.singularize.foreign_key + end.to_sym + end + + def attributes_with_index + attributes.select { |a| !a.reference? && a.has_index? } + end + + # A migration file name can only contain underscores (_), lowercase characters, + # and numbers 0-9. Any other file name will raise an IllegalMigrationNameError. + def validate_file_name! + unless /^[_a-z0-9]+$/.match?(file_name) + raise IllegalMigrationNameError.new(file_name) + end + end + + def normalize_table_name(_table_name) + pluralize_table_names? ? _table_name.pluralize : _table_name.singularize + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt new file mode 100644 index 00000000..5f7201cf --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt @@ -0,0 +1,24 @@ +class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>] + def change + create_table :<%= table_name %><%= primary_key_type %> do |t| +<% attributes.each do |attribute| -%> +<% if attribute.password_digest? -%> + t.string :password_digest<%= attribute.inject_options %> +<% elsif attribute.token? -%> + t.string :<%= attribute.name %><%= attribute.inject_options %> +<% else -%> + t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %> +<% end -%> +<% end -%> +<% if options[:timestamps] %> + t.timestamps +<% end -%> + end +<% attributes.select(&:token?).each do |attribute| -%> + add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>, unique: true +<% end -%> +<% attributes_with_index.each do |attribute| -%> + add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> +<% end -%> + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record/migration/templates/migration.rb.tt b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record/migration/templates/migration.rb.tt new file mode 100644 index 00000000..481c7020 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record/migration/templates/migration.rb.tt @@ -0,0 +1,46 @@ +class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>] +<%- if migration_action == 'add' -%> + def change +<% attributes.each do |attribute| -%> + <%- if attribute.reference? -%> + add_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %> + <%- elsif attribute.token? -%> + add_column :<%= table_name %>, :<%= attribute.name %>, :string<%= attribute.inject_options %> + add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>, unique: true + <%- else -%> + add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %> + <%- if attribute.has_index? -%> + add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> + <%- end -%> + <%- end -%> +<%- end -%> + end +<%- elsif migration_action == 'join' -%> + def change + create_join_table :<%= join_tables.first %>, :<%= join_tables.second %> do |t| + <%- attributes.each do |attribute| -%> + <%- if attribute.reference? -%> + t.references :<%= attribute.name %><%= attribute.inject_options %> + <%- else -%> + <%= '# ' unless attribute.has_index? -%>t.index <%= attribute.index_name %><%= attribute.inject_index_options %> + <%- end -%> + <%- end -%> + end + end +<%- else -%> + def change +<% attributes.each do |attribute| -%> +<%- if migration_action -%> + <%- if attribute.reference? -%> + remove_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %> + <%- else -%> + <%- if attribute.has_index? -%> + remove_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %> + <%- end -%> + remove_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %> + <%- end -%> +<%- end -%> +<%- end -%> + end +<%- end -%> +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record/model/model_generator.rb b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record/model/model_generator.rb new file mode 100644 index 00000000..25e54f3a --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record/model/model_generator.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require "rails/generators/active_record" + +module ActiveRecord + module Generators # :nodoc: + class ModelGenerator < Base # :nodoc: + argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]" + + check_class_collision + + class_option :migration, type: :boolean + class_option :timestamps, type: :boolean + class_option :parent, type: :string, desc: "The parent class for the generated model" + class_option :indexes, type: :boolean, default: true, desc: "Add indexes for references and belongs_to columns" + class_option :primary_key_type, type: :string, desc: "The type for primary key" + + # creates the migration file for the model. + def create_migration_file + return unless options[:migration] && options[:parent].nil? + attributes.each { |a| a.attr_options.delete(:index) if a.reference? && !a.has_index? } if options[:indexes] == false + migration_template "../../migration/templates/create_table_migration.rb", File.join(db_migrate_path, "create_#{table_name}.rb") + end + + def create_model_file + template "model.rb", File.join("app/models", class_path, "#{file_name}.rb") + end + + def create_module_file + return if regular_class_path.empty? + template "module.rb", File.join("app/models", "#{class_path.join('/')}.rb") if behavior == :invoke + end + + hook_for :test_framework + + private + + def attributes_with_index + attributes.select { |a| !a.reference? && a.has_index? } + end + + # Used by the migration template to determine the parent name of the model + def parent_class_name + options[:parent] || "ApplicationRecord" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record/model/templates/model.rb.tt b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record/model/templates/model.rb.tt new file mode 100644 index 00000000..55dc65c8 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record/model/templates/model.rb.tt @@ -0,0 +1,13 @@ +<% module_namespacing do -%> +class <%= class_name %> < <%= parent_class_name.classify %> +<% attributes.select(&:reference?).each do |attribute| -%> + belongs_to :<%= attribute.name %><%= ', polymorphic: true' if attribute.polymorphic? %><%= ', required: true' if attribute.required? %> +<% end -%> +<% attributes.select(&:token?).each do |attribute| -%> + has_secure_token<% if attribute.name != "token" %> :<%= attribute.name %><% end %> +<% end -%> +<% if attributes.any?(&:password_digest?) -%> + has_secure_password +<% end -%> +end +<% end -%> diff --git a/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record/model/templates/module.rb.tt b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record/model/templates/module.rb.tt new file mode 100644 index 00000000..a3bf1c37 --- /dev/null +++ b/path/ruby/2.6.0/gems/activerecord-5.2.3/lib/rails/generators/active_record/model/templates/module.rb.tt @@ -0,0 +1,7 @@ +<% module_namespacing do -%> +module <%= class_path.map(&:camelize).join('::') %> + def self.table_name_prefix + '<%= namespaced? ? namespaced_class_path.join('_') : class_path.join('_') %>_' + end +end +<% end -%> diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/CHANGELOG.md b/path/ruby/2.6.0/gems/activestorage-5.2.3/CHANGELOG.md new file mode 100644 index 00000000..3ba118f7 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/CHANGELOG.md @@ -0,0 +1,117 @@ +## Rails 5.2.3 (March 27, 2019) ## + +* No changes. + + +## Rails 5.2.2.1 (March 11, 2019) ## + +* No changes. + + +## Rails 5.2.2 (December 04, 2018) ## + +* Support multiple submit buttons in Active Storage forms. + + *Chrıs Seelus* + +* Fix `ArgumentError` when uploading to amazon s3 + + *Hiroki Sanpei* + +* Add a foreign-key constraint to the `active_storage_attachments` table for blobs. + + *George Claghorn* + +* Discard `ActiveStorage::PurgeJobs` for missing blobs. + + *George Claghorn* + +* Fix uploading Tempfiles to Azure Storage. + + *George Claghorn* + + +## Rails 5.2.1.1 (November 27, 2018) ## + +* Prevent content type and disposition bypass in storage service URLs. + + Fix CVE-2018-16477. + + *Rosa Gutierrez* + + +## Rails 5.2.1 (August 07, 2018) ## + +* Fix direct upload with zero-byte files. + + *George Claghorn* + +* Exclude JSON root from `active_storage/direct_uploads#create` response. + + *Javan Makhmali* + + +## Rails 5.2.0 (April 09, 2018) ## + +* Allow full use of the AWS S3 SDK options for authentication. If an + explicit AWS key pair and/or region is not provided in `storage.yml`, + attempt to use environment variables, shared credentials, or IAM + (instance or task) role credentials. Order of precedence is determined + by the [AWS SDK](https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/setup-config.html). + + *Brian Knight* + +* Remove path config option from Azure service. + + The Active Storage service for Azure Storage has an option called `path` + that is ambiguous in meaning. It needs to be set to the primary blob + storage endpoint but that can be determined from the blobs client anyway. + + To simplify the configuration, we've removed the `path` option and + now get the endpoint from the blobs client instead. + + Closes #32225. + + *Andrew White* + +* Generate root-relative paths in disk service URL methods. + + Obviate the disk service's `:host` configuration option. + + *George Claghorn* + +* Add source code to published npm package. + + This allows activestorage users to depend on the javascript source code + rather than the compiled code, which can produce smaller javascript bundles. + + *Richard Macklin* + +* Preserve display aspect ratio when extracting width and height from videos + with rectangular samples in `ActiveStorage::Analyzer::VideoAnalyzer`. + + When a video contains a display aspect ratio, emit it in metadata as + `:display_aspect_ratio` rather than the ambiguous `:aspect_ratio`. Compute + its height by scaling its encoded frame width according to the DAR. + + *George Claghorn* + +* Use `after_destroy_commit` instead of `before_destroy` for purging + attachments when a record is destroyed. + + *Hiroki Zenigami* + +* Force `:attachment` disposition for specific, configurable content types. + This mitigates possible security issues such as XSS or phishing when + serving them inline. A list of such content types is included by default, + and can be configured via `content_types_to_serve_as_binary`. + + *Rosa Gutierrez* + +* Fix the gem adding the migrations files to the package. + + *Yuji Yaginuma* + +* Added to Rails. + + *DHH* diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/MIT-LICENSE b/path/ruby/2.6.0/gems/activestorage-5.2.3/MIT-LICENSE new file mode 100644 index 00000000..eed89ac3 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2017-2018 David Heinemeier Hansson, Basecamp + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/README.md b/path/ruby/2.6.0/gems/activestorage-5.2.3/README.md new file mode 100644 index 00000000..bd76c959 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/README.md @@ -0,0 +1,159 @@ +# Active Storage + +Active Storage makes it simple to upload and reference files in cloud services like [Amazon S3](https://aws.amazon.com/s3/), [Google Cloud Storage](https://cloud.google.com/storage/docs/), or [Microsoft Azure Storage](https://azure.microsoft.com/en-us/services/storage/), and attach those files to Active Records. Supports having one main service and mirrors in other services for redundancy. It also provides a disk service for testing or local deployments, but the focus is on cloud storage. + +Files can be uploaded from the server to the cloud or directly from the client to the cloud. + +Image files can furthermore be transformed using on-demand variants for quality, aspect ratio, size, or any other [MiniMagick](https://github.com/minimagick/minimagick) supported transformation. + +## Compared to other storage solutions + +A key difference to how Active Storage works compared to other attachment solutions in Rails is through the use of built-in [Blob](https://github.com/rails/rails/blob/5-2-stable/activestorage/app/models/active_storage/blob.rb) and [Attachment](https://github.com/rails/rails/blob/5-2-stable/activestorage/app/models/active_storage/attachment.rb) models (backed by Active Record). This means existing application models do not need to be modified with additional columns to associate with files. Active Storage uses polymorphic associations via the `Attachment` join model, which then connects to the actual `Blob`. + +`Blob` models store attachment metadata (filename, content-type, etc.), and their identifier key in the storage service. Blob models do not store the actual binary data. They are intended to be immutable in spirit. One file, one blob. You can associate the same blob with multiple application models as well. And if you want to do transformations of a given `Blob`, the idea is that you'll simply create a new one, rather than attempt to mutate the existing one (though of course you can delete the previous version later if you don't need it). + +## Installation + +Run `rails active_storage:install` to copy over active_storage migrations. + +## Examples + +One attachment: + +```ruby +class User < ApplicationRecord + # Associates an attachment and a blob. When the user is destroyed they are + # purged by default (models destroyed, and resource files deleted). + has_one_attached :avatar +end + +# Attach an avatar to the user. +user.avatar.attach(io: File.open("/path/to/face.jpg"), filename: "face.jpg", content_type: "image/jpg") + +# Does the user have an avatar? +user.avatar.attached? # => true + +# Synchronously destroy the avatar and actual resource files. +user.avatar.purge + +# Destroy the associated models and actual resource files async, via Active Job. +user.avatar.purge_later + +# Does the user have an avatar? +user.avatar.attached? # => false + +# Generate a permanent URL for the blob that points to the application. +# Upon access, a redirect to the actual service endpoint is returned. +# This indirection decouples the public URL from the actual one, and +# allows for example mirroring attachments in different services for +# high-availability. The redirection has an HTTP expiration of 5 min. +url_for(user.avatar) + +class AvatarsController < ApplicationController + def update + # params[:avatar] contains a ActionDispatch::Http::UploadedFile object + Current.user.avatar.attach(params.require(:avatar)) + redirect_to Current.user + end +end +``` + +Many attachments: + +```ruby +class Message < ApplicationRecord + has_many_attached :images +end +``` + +```erb +<%= form_with model: @message, local: true do |form| %> + <%= form.text_field :title, placeholder: "Title" %>
    + <%= form.text_area :content %>

    + + <%= form.file_field :images, multiple: true %>
    + <%= form.submit %> +<% end %> +``` + +```ruby +class MessagesController < ApplicationController + def index + # Use the built-in with_attached_images scope to avoid N+1 + @messages = Message.all.with_attached_images + end + + def create + message = Message.create! params.require(:message).permit(:title, :content) + message.images.attach(params[:message][:images]) + redirect_to message + end + + def show + @message = Message.find(params[:id]) + end +end +``` + +Variation of image attachment: + +```erb +<%# Hitting the variant URL will lazy transform the original blob and then redirect to its new service location %> +<%= image_tag user.avatar.variant(resize: "100x100") %> +``` + +## Direct uploads + +Active Storage, with its included JavaScript library, supports uploading directly from the client to the cloud. + +### Direct upload installation + +1. Include `activestorage.js` in your application's JavaScript bundle. + + Using the asset pipeline: + ```js + //= require activestorage + ``` + Using the npm package: + ```js + import * as ActiveStorage from "activestorage" + ActiveStorage.start() + ``` +2. Annotate file inputs with the direct upload URL. + + ```ruby + <%= form.file_field :attachments, multiple: true, direct_upload: true %> + ``` +3. That's it! Uploads begin upon form submission. + +### Direct upload JavaScript events + +| Event name | Event target | Event data (`event.detail`) | Description | +| --- | --- | --- | --- | +| `direct-uploads:start` | `
    ` | None | A form containing files for direct upload fields was submitted. | +| `direct-upload:initialize` | `` | `{id, file}` | Dispatched for every file after form submission. | +| `direct-upload:start` | `` | `{id, file}` | A direct upload is starting. | +| `direct-upload:before-blob-request` | `` | `{id, file, xhr}` | Before making a request to your application for direct upload metadata. | +| `direct-upload:before-storage-request` | `` | `{id, file, xhr}` | Before making a request to store a file. | +| `direct-upload:progress` | `` | `{id, file, progress}` | As requests to store files progress. | +| `direct-upload:error` | `` | `{id, file, error}` | An error occurred. An `alert` will display unless this event is canceled. | +| `direct-upload:end` | `` | `{id, file}` | A direct upload has ended. | +| `direct-uploads:end` | `` | None | All direct uploads have ended. | + +## License + +Active Storage is released under the [MIT License](https://opensource.org/licenses/MIT). + +## Support + +API documentation is at: + +* http://api.rubyonrails.org + +Bug reports for the Ruby on Rails project can be filed here: + +* https://github.com/rails/rails/issues + +Feature requests should be discussed on the rails-core mailing list here: + +* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/assets/javascripts/activestorage.js b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/assets/javascripts/activestorage.js new file mode 100644 index 00000000..b71e251a --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/assets/javascripts/activestorage.js @@ -0,0 +1,939 @@ +(function(global, factory) { + typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define([ "exports" ], factory) : factory(global.ActiveStorage = {}); +})(this, function(exports) { + "use strict"; + function createCommonjsModule(fn, module) { + return module = { + exports: {} + }, fn(module, module.exports), module.exports; + } + var sparkMd5 = createCommonjsModule(function(module, exports) { + (function(factory) { + { + module.exports = factory(); + } + })(function(undefined) { + var hex_chr = [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" ]; + function md5cycle(x, k) { + var a = x[0], b = x[1], c = x[2], d = x[3]; + a += (b & c | ~b & d) + k[0] - 680876936 | 0; + a = (a << 7 | a >>> 25) + b | 0; + d += (a & b | ~a & c) + k[1] - 389564586 | 0; + d = (d << 12 | d >>> 20) + a | 0; + c += (d & a | ~d & b) + k[2] + 606105819 | 0; + c = (c << 17 | c >>> 15) + d | 0; + b += (c & d | ~c & a) + k[3] - 1044525330 | 0; + b = (b << 22 | b >>> 10) + c | 0; + a += (b & c | ~b & d) + k[4] - 176418897 | 0; + a = (a << 7 | a >>> 25) + b | 0; + d += (a & b | ~a & c) + k[5] + 1200080426 | 0; + d = (d << 12 | d >>> 20) + a | 0; + c += (d & a | ~d & b) + k[6] - 1473231341 | 0; + c = (c << 17 | c >>> 15) + d | 0; + b += (c & d | ~c & a) + k[7] - 45705983 | 0; + b = (b << 22 | b >>> 10) + c | 0; + a += (b & c | ~b & d) + k[8] + 1770035416 | 0; + a = (a << 7 | a >>> 25) + b | 0; + d += (a & b | ~a & c) + k[9] - 1958414417 | 0; + d = (d << 12 | d >>> 20) + a | 0; + c += (d & a | ~d & b) + k[10] - 42063 | 0; + c = (c << 17 | c >>> 15) + d | 0; + b += (c & d | ~c & a) + k[11] - 1990404162 | 0; + b = (b << 22 | b >>> 10) + c | 0; + a += (b & c | ~b & d) + k[12] + 1804603682 | 0; + a = (a << 7 | a >>> 25) + b | 0; + d += (a & b | ~a & c) + k[13] - 40341101 | 0; + d = (d << 12 | d >>> 20) + a | 0; + c += (d & a | ~d & b) + k[14] - 1502002290 | 0; + c = (c << 17 | c >>> 15) + d | 0; + b += (c & d | ~c & a) + k[15] + 1236535329 | 0; + b = (b << 22 | b >>> 10) + c | 0; + a += (b & d | c & ~d) + k[1] - 165796510 | 0; + a = (a << 5 | a >>> 27) + b | 0; + d += (a & c | b & ~c) + k[6] - 1069501632 | 0; + d = (d << 9 | d >>> 23) + a | 0; + c += (d & b | a & ~b) + k[11] + 643717713 | 0; + c = (c << 14 | c >>> 18) + d | 0; + b += (c & a | d & ~a) + k[0] - 373897302 | 0; + b = (b << 20 | b >>> 12) + c | 0; + a += (b & d | c & ~d) + k[5] - 701558691 | 0; + a = (a << 5 | a >>> 27) + b | 0; + d += (a & c | b & ~c) + k[10] + 38016083 | 0; + d = (d << 9 | d >>> 23) + a | 0; + c += (d & b | a & ~b) + k[15] - 660478335 | 0; + c = (c << 14 | c >>> 18) + d | 0; + b += (c & a | d & ~a) + k[4] - 405537848 | 0; + b = (b << 20 | b >>> 12) + c | 0; + a += (b & d | c & ~d) + k[9] + 568446438 | 0; + a = (a << 5 | a >>> 27) + b | 0; + d += (a & c | b & ~c) + k[14] - 1019803690 | 0; + d = (d << 9 | d >>> 23) + a | 0; + c += (d & b | a & ~b) + k[3] - 187363961 | 0; + c = (c << 14 | c >>> 18) + d | 0; + b += (c & a | d & ~a) + k[8] + 1163531501 | 0; + b = (b << 20 | b >>> 12) + c | 0; + a += (b & d | c & ~d) + k[13] - 1444681467 | 0; + a = (a << 5 | a >>> 27) + b | 0; + d += (a & c | b & ~c) + k[2] - 51403784 | 0; + d = (d << 9 | d >>> 23) + a | 0; + c += (d & b | a & ~b) + k[7] + 1735328473 | 0; + c = (c << 14 | c >>> 18) + d | 0; + b += (c & a | d & ~a) + k[12] - 1926607734 | 0; + b = (b << 20 | b >>> 12) + c | 0; + a += (b ^ c ^ d) + k[5] - 378558 | 0; + a = (a << 4 | a >>> 28) + b | 0; + d += (a ^ b ^ c) + k[8] - 2022574463 | 0; + d = (d << 11 | d >>> 21) + a | 0; + c += (d ^ a ^ b) + k[11] + 1839030562 | 0; + c = (c << 16 | c >>> 16) + d | 0; + b += (c ^ d ^ a) + k[14] - 35309556 | 0; + b = (b << 23 | b >>> 9) + c | 0; + a += (b ^ c ^ d) + k[1] - 1530992060 | 0; + a = (a << 4 | a >>> 28) + b | 0; + d += (a ^ b ^ c) + k[4] + 1272893353 | 0; + d = (d << 11 | d >>> 21) + a | 0; + c += (d ^ a ^ b) + k[7] - 155497632 | 0; + c = (c << 16 | c >>> 16) + d | 0; + b += (c ^ d ^ a) + k[10] - 1094730640 | 0; + b = (b << 23 | b >>> 9) + c | 0; + a += (b ^ c ^ d) + k[13] + 681279174 | 0; + a = (a << 4 | a >>> 28) + b | 0; + d += (a ^ b ^ c) + k[0] - 358537222 | 0; + d = (d << 11 | d >>> 21) + a | 0; + c += (d ^ a ^ b) + k[3] - 722521979 | 0; + c = (c << 16 | c >>> 16) + d | 0; + b += (c ^ d ^ a) + k[6] + 76029189 | 0; + b = (b << 23 | b >>> 9) + c | 0; + a += (b ^ c ^ d) + k[9] - 640364487 | 0; + a = (a << 4 | a >>> 28) + b | 0; + d += (a ^ b ^ c) + k[12] - 421815835 | 0; + d = (d << 11 | d >>> 21) + a | 0; + c += (d ^ a ^ b) + k[15] + 530742520 | 0; + c = (c << 16 | c >>> 16) + d | 0; + b += (c ^ d ^ a) + k[2] - 995338651 | 0; + b = (b << 23 | b >>> 9) + c | 0; + a += (c ^ (b | ~d)) + k[0] - 198630844 | 0; + a = (a << 6 | a >>> 26) + b | 0; + d += (b ^ (a | ~c)) + k[7] + 1126891415 | 0; + d = (d << 10 | d >>> 22) + a | 0; + c += (a ^ (d | ~b)) + k[14] - 1416354905 | 0; + c = (c << 15 | c >>> 17) + d | 0; + b += (d ^ (c | ~a)) + k[5] - 57434055 | 0; + b = (b << 21 | b >>> 11) + c | 0; + a += (c ^ (b | ~d)) + k[12] + 1700485571 | 0; + a = (a << 6 | a >>> 26) + b | 0; + d += (b ^ (a | ~c)) + k[3] - 1894986606 | 0; + d = (d << 10 | d >>> 22) + a | 0; + c += (a ^ (d | ~b)) + k[10] - 1051523 | 0; + c = (c << 15 | c >>> 17) + d | 0; + b += (d ^ (c | ~a)) + k[1] - 2054922799 | 0; + b = (b << 21 | b >>> 11) + c | 0; + a += (c ^ (b | ~d)) + k[8] + 1873313359 | 0; + a = (a << 6 | a >>> 26) + b | 0; + d += (b ^ (a | ~c)) + k[15] - 30611744 | 0; + d = (d << 10 | d >>> 22) + a | 0; + c += (a ^ (d | ~b)) + k[6] - 1560198380 | 0; + c = (c << 15 | c >>> 17) + d | 0; + b += (d ^ (c | ~a)) + k[13] + 1309151649 | 0; + b = (b << 21 | b >>> 11) + c | 0; + a += (c ^ (b | ~d)) + k[4] - 145523070 | 0; + a = (a << 6 | a >>> 26) + b | 0; + d += (b ^ (a | ~c)) + k[11] - 1120210379 | 0; + d = (d << 10 | d >>> 22) + a | 0; + c += (a ^ (d | ~b)) + k[2] + 718787259 | 0; + c = (c << 15 | c >>> 17) + d | 0; + b += (d ^ (c | ~a)) + k[9] - 343485551 | 0; + b = (b << 21 | b >>> 11) + c | 0; + x[0] = a + x[0] | 0; + x[1] = b + x[1] | 0; + x[2] = c + x[2] | 0; + x[3] = d + x[3] | 0; + } + function md5blk(s) { + var md5blks = [], i; + for (i = 0; i < 64; i += 4) { + md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24); + } + return md5blks; + } + function md5blk_array(a) { + var md5blks = [], i; + for (i = 0; i < 64; i += 4) { + md5blks[i >> 2] = a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24); + } + return md5blks; + } + function md51(s) { + var n = s.length, state = [ 1732584193, -271733879, -1732584194, 271733878 ], i, length, tail, tmp, lo, hi; + for (i = 64; i <= n; i += 64) { + md5cycle(state, md5blk(s.substring(i - 64, i))); + } + s = s.substring(i - 64); + length = s.length; + tail = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= s.charCodeAt(i) << (i % 4 << 3); + } + tail[i >> 2] |= 128 << (i % 4 << 3); + if (i > 55) { + md5cycle(state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + tmp = n * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + tail[14] = lo; + tail[15] = hi; + md5cycle(state, tail); + return state; + } + function md51_array(a) { + var n = a.length, state = [ 1732584193, -271733879, -1732584194, 271733878 ], i, length, tail, tmp, lo, hi; + for (i = 64; i <= n; i += 64) { + md5cycle(state, md5blk_array(a.subarray(i - 64, i))); + } + a = i - 64 < n ? a.subarray(i - 64) : new Uint8Array(0); + length = a.length; + tail = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= a[i] << (i % 4 << 3); + } + tail[i >> 2] |= 128 << (i % 4 << 3); + if (i > 55) { + md5cycle(state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + tmp = n * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + tail[14] = lo; + tail[15] = hi; + md5cycle(state, tail); + return state; + } + function rhex(n) { + var s = "", j; + for (j = 0; j < 4; j += 1) { + s += hex_chr[n >> j * 8 + 4 & 15] + hex_chr[n >> j * 8 & 15]; + } + return s; + } + function hex(x) { + var i; + for (i = 0; i < x.length; i += 1) { + x[i] = rhex(x[i]); + } + return x.join(""); + } + if (hex(md51("hello")) !== "5d41402abc4b2a76b9719d911017c592") ; + if (typeof ArrayBuffer !== "undefined" && !ArrayBuffer.prototype.slice) { + (function() { + function clamp(val, length) { + val = val | 0 || 0; + if (val < 0) { + return Math.max(val + length, 0); + } + return Math.min(val, length); + } + ArrayBuffer.prototype.slice = function(from, to) { + var length = this.byteLength, begin = clamp(from, length), end = length, num, target, targetArray, sourceArray; + if (to !== undefined) { + end = clamp(to, length); + } + if (begin > end) { + return new ArrayBuffer(0); + } + num = end - begin; + target = new ArrayBuffer(num); + targetArray = new Uint8Array(target); + sourceArray = new Uint8Array(this, begin, num); + targetArray.set(sourceArray); + return target; + }; + })(); + } + function toUtf8(str) { + if (/[\u0080-\uFFFF]/.test(str)) { + str = unescape(encodeURIComponent(str)); + } + return str; + } + function utf8Str2ArrayBuffer(str, returnUInt8Array) { + var length = str.length, buff = new ArrayBuffer(length), arr = new Uint8Array(buff), i; + for (i = 0; i < length; i += 1) { + arr[i] = str.charCodeAt(i); + } + return returnUInt8Array ? arr : buff; + } + function arrayBuffer2Utf8Str(buff) { + return String.fromCharCode.apply(null, new Uint8Array(buff)); + } + function concatenateArrayBuffers(first, second, returnUInt8Array) { + var result = new Uint8Array(first.byteLength + second.byteLength); + result.set(new Uint8Array(first)); + result.set(new Uint8Array(second), first.byteLength); + return returnUInt8Array ? result : result.buffer; + } + function hexToBinaryString(hex) { + var bytes = [], length = hex.length, x; + for (x = 0; x < length - 1; x += 2) { + bytes.push(parseInt(hex.substr(x, 2), 16)); + } + return String.fromCharCode.apply(String, bytes); + } + function SparkMD5() { + this.reset(); + } + SparkMD5.prototype.append = function(str) { + this.appendBinary(toUtf8(str)); + return this; + }; + SparkMD5.prototype.appendBinary = function(contents) { + this._buff += contents; + this._length += contents.length; + var length = this._buff.length, i; + for (i = 64; i <= length; i += 64) { + md5cycle(this._hash, md5blk(this._buff.substring(i - 64, i))); + } + this._buff = this._buff.substring(i - 64); + return this; + }; + SparkMD5.prototype.end = function(raw) { + var buff = this._buff, length = buff.length, i, tail = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], ret; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= buff.charCodeAt(i) << (i % 4 << 3); + } + this._finish(tail, length); + ret = hex(this._hash); + if (raw) { + ret = hexToBinaryString(ret); + } + this.reset(); + return ret; + }; + SparkMD5.prototype.reset = function() { + this._buff = ""; + this._length = 0; + this._hash = [ 1732584193, -271733879, -1732584194, 271733878 ]; + return this; + }; + SparkMD5.prototype.getState = function() { + return { + buff: this._buff, + length: this._length, + hash: this._hash + }; + }; + SparkMD5.prototype.setState = function(state) { + this._buff = state.buff; + this._length = state.length; + this._hash = state.hash; + return this; + }; + SparkMD5.prototype.destroy = function() { + delete this._hash; + delete this._buff; + delete this._length; + }; + SparkMD5.prototype._finish = function(tail, length) { + var i = length, tmp, lo, hi; + tail[i >> 2] |= 128 << (i % 4 << 3); + if (i > 55) { + md5cycle(this._hash, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + tmp = this._length * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + tail[14] = lo; + tail[15] = hi; + md5cycle(this._hash, tail); + }; + SparkMD5.hash = function(str, raw) { + return SparkMD5.hashBinary(toUtf8(str), raw); + }; + SparkMD5.hashBinary = function(content, raw) { + var hash = md51(content), ret = hex(hash); + return raw ? hexToBinaryString(ret) : ret; + }; + SparkMD5.ArrayBuffer = function() { + this.reset(); + }; + SparkMD5.ArrayBuffer.prototype.append = function(arr) { + var buff = concatenateArrayBuffers(this._buff.buffer, arr, true), length = buff.length, i; + this._length += arr.byteLength; + for (i = 64; i <= length; i += 64) { + md5cycle(this._hash, md5blk_array(buff.subarray(i - 64, i))); + } + this._buff = i - 64 < length ? new Uint8Array(buff.buffer.slice(i - 64)) : new Uint8Array(0); + return this; + }; + SparkMD5.ArrayBuffer.prototype.end = function(raw) { + var buff = this._buff, length = buff.length, tail = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], i, ret; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= buff[i] << (i % 4 << 3); + } + this._finish(tail, length); + ret = hex(this._hash); + if (raw) { + ret = hexToBinaryString(ret); + } + this.reset(); + return ret; + }; + SparkMD5.ArrayBuffer.prototype.reset = function() { + this._buff = new Uint8Array(0); + this._length = 0; + this._hash = [ 1732584193, -271733879, -1732584194, 271733878 ]; + return this; + }; + SparkMD5.ArrayBuffer.prototype.getState = function() { + var state = SparkMD5.prototype.getState.call(this); + state.buff = arrayBuffer2Utf8Str(state.buff); + return state; + }; + SparkMD5.ArrayBuffer.prototype.setState = function(state) { + state.buff = utf8Str2ArrayBuffer(state.buff, true); + return SparkMD5.prototype.setState.call(this, state); + }; + SparkMD5.ArrayBuffer.prototype.destroy = SparkMD5.prototype.destroy; + SparkMD5.ArrayBuffer.prototype._finish = SparkMD5.prototype._finish; + SparkMD5.ArrayBuffer.hash = function(arr, raw) { + var hash = md51_array(new Uint8Array(arr)), ret = hex(hash); + return raw ? hexToBinaryString(ret) : ret; + }; + return SparkMD5; + }); + }); + var classCallCheck = function(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + }; + var createClass = function() { + function defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + return function(Constructor, protoProps, staticProps) { + if (protoProps) defineProperties(Constructor.prototype, protoProps); + if (staticProps) defineProperties(Constructor, staticProps); + return Constructor; + }; + }(); + var fileSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice; + var FileChecksum = function() { + createClass(FileChecksum, null, [ { + key: "create", + value: function create(file, callback) { + var instance = new FileChecksum(file); + instance.create(callback); + } + } ]); + function FileChecksum(file) { + classCallCheck(this, FileChecksum); + this.file = file; + this.chunkSize = 2097152; + this.chunkCount = Math.ceil(this.file.size / this.chunkSize); + this.chunkIndex = 0; + } + createClass(FileChecksum, [ { + key: "create", + value: function create(callback) { + var _this = this; + this.callback = callback; + this.md5Buffer = new sparkMd5.ArrayBuffer(); + this.fileReader = new FileReader(); + this.fileReader.addEventListener("load", function(event) { + return _this.fileReaderDidLoad(event); + }); + this.fileReader.addEventListener("error", function(event) { + return _this.fileReaderDidError(event); + }); + this.readNextChunk(); + } + }, { + key: "fileReaderDidLoad", + value: function fileReaderDidLoad(event) { + this.md5Buffer.append(event.target.result); + if (!this.readNextChunk()) { + var binaryDigest = this.md5Buffer.end(true); + var base64digest = btoa(binaryDigest); + this.callback(null, base64digest); + } + } + }, { + key: "fileReaderDidError", + value: function fileReaderDidError(event) { + this.callback("Error reading " + this.file.name); + } + }, { + key: "readNextChunk", + value: function readNextChunk() { + if (this.chunkIndex < this.chunkCount || this.chunkIndex == 0 && this.chunkCount == 0) { + var start = this.chunkIndex * this.chunkSize; + var end = Math.min(start + this.chunkSize, this.file.size); + var bytes = fileSlice.call(this.file, start, end); + this.fileReader.readAsArrayBuffer(bytes); + this.chunkIndex++; + return true; + } else { + return false; + } + } + } ]); + return FileChecksum; + }(); + function getMetaValue(name) { + var element = findElement(document.head, 'meta[name="' + name + '"]'); + if (element) { + return element.getAttribute("content"); + } + } + function findElements(root, selector) { + if (typeof root == "string") { + selector = root; + root = document; + } + var elements = root.querySelectorAll(selector); + return toArray$1(elements); + } + function findElement(root, selector) { + if (typeof root == "string") { + selector = root; + root = document; + } + return root.querySelector(selector); + } + function dispatchEvent(element, type) { + var eventInit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; + var disabled = element.disabled; + var bubbles = eventInit.bubbles, cancelable = eventInit.cancelable, detail = eventInit.detail; + var event = document.createEvent("Event"); + event.initEvent(type, bubbles || true, cancelable || true); + event.detail = detail || {}; + try { + element.disabled = false; + element.dispatchEvent(event); + } finally { + element.disabled = disabled; + } + return event; + } + function toArray$1(value) { + if (Array.isArray(value)) { + return value; + } else if (Array.from) { + return Array.from(value); + } else { + return [].slice.call(value); + } + } + var BlobRecord = function() { + function BlobRecord(file, checksum, url) { + var _this = this; + classCallCheck(this, BlobRecord); + this.file = file; + this.attributes = { + filename: file.name, + content_type: file.type, + byte_size: file.size, + checksum: checksum + }; + this.xhr = new XMLHttpRequest(); + this.xhr.open("POST", url, true); + this.xhr.responseType = "json"; + this.xhr.setRequestHeader("Content-Type", "application/json"); + this.xhr.setRequestHeader("Accept", "application/json"); + this.xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); + this.xhr.setRequestHeader("X-CSRF-Token", getMetaValue("csrf-token")); + this.xhr.addEventListener("load", function(event) { + return _this.requestDidLoad(event); + }); + this.xhr.addEventListener("error", function(event) { + return _this.requestDidError(event); + }); + } + createClass(BlobRecord, [ { + key: "create", + value: function create(callback) { + this.callback = callback; + this.xhr.send(JSON.stringify({ + blob: this.attributes + })); + } + }, { + key: "requestDidLoad", + value: function requestDidLoad(event) { + if (this.status >= 200 && this.status < 300) { + var response = this.response; + var direct_upload = response.direct_upload; + delete response.direct_upload; + this.attributes = response; + this.directUploadData = direct_upload; + this.callback(null, this.toJSON()); + } else { + this.requestDidError(event); + } + } + }, { + key: "requestDidError", + value: function requestDidError(event) { + this.callback('Error creating Blob for "' + this.file.name + '". Status: ' + this.status); + } + }, { + key: "toJSON", + value: function toJSON() { + var result = {}; + for (var key in this.attributes) { + result[key] = this.attributes[key]; + } + return result; + } + }, { + key: "status", + get: function get$$1() { + return this.xhr.status; + } + }, { + key: "response", + get: function get$$1() { + var _xhr = this.xhr, responseType = _xhr.responseType, response = _xhr.response; + if (responseType == "json") { + return response; + } else { + return JSON.parse(response); + } + } + } ]); + return BlobRecord; + }(); + var BlobUpload = function() { + function BlobUpload(blob) { + var _this = this; + classCallCheck(this, BlobUpload); + this.blob = blob; + this.file = blob.file; + var _blob$directUploadDat = blob.directUploadData, url = _blob$directUploadDat.url, headers = _blob$directUploadDat.headers; + this.xhr = new XMLHttpRequest(); + this.xhr.open("PUT", url, true); + this.xhr.responseType = "text"; + for (var key in headers) { + this.xhr.setRequestHeader(key, headers[key]); + } + this.xhr.addEventListener("load", function(event) { + return _this.requestDidLoad(event); + }); + this.xhr.addEventListener("error", function(event) { + return _this.requestDidError(event); + }); + } + createClass(BlobUpload, [ { + key: "create", + value: function create(callback) { + this.callback = callback; + this.xhr.send(this.file.slice()); + } + }, { + key: "requestDidLoad", + value: function requestDidLoad(event) { + var _xhr = this.xhr, status = _xhr.status, response = _xhr.response; + if (status >= 200 && status < 300) { + this.callback(null, response); + } else { + this.requestDidError(event); + } + } + }, { + key: "requestDidError", + value: function requestDidError(event) { + this.callback('Error storing "' + this.file.name + '". Status: ' + this.xhr.status); + } + } ]); + return BlobUpload; + }(); + var id = 0; + var DirectUpload = function() { + function DirectUpload(file, url, delegate) { + classCallCheck(this, DirectUpload); + this.id = ++id; + this.file = file; + this.url = url; + this.delegate = delegate; + } + createClass(DirectUpload, [ { + key: "create", + value: function create(callback) { + var _this = this; + FileChecksum.create(this.file, function(error, checksum) { + if (error) { + callback(error); + return; + } + var blob = new BlobRecord(_this.file, checksum, _this.url); + notify(_this.delegate, "directUploadWillCreateBlobWithXHR", blob.xhr); + blob.create(function(error) { + if (error) { + callback(error); + } else { + var upload = new BlobUpload(blob); + notify(_this.delegate, "directUploadWillStoreFileWithXHR", upload.xhr); + upload.create(function(error) { + if (error) { + callback(error); + } else { + callback(null, blob.toJSON()); + } + }); + } + }); + }); + } + } ]); + return DirectUpload; + }(); + function notify(object, methodName) { + if (object && typeof object[methodName] == "function") { + for (var _len = arguments.length, messages = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { + messages[_key - 2] = arguments[_key]; + } + return object[methodName].apply(object, messages); + } + } + var DirectUploadController = function() { + function DirectUploadController(input, file) { + classCallCheck(this, DirectUploadController); + this.input = input; + this.file = file; + this.directUpload = new DirectUpload(this.file, this.url, this); + this.dispatch("initialize"); + } + createClass(DirectUploadController, [ { + key: "start", + value: function start(callback) { + var _this = this; + var hiddenInput = document.createElement("input"); + hiddenInput.type = "hidden"; + hiddenInput.name = this.input.name; + this.input.insertAdjacentElement("beforebegin", hiddenInput); + this.dispatch("start"); + this.directUpload.create(function(error, attributes) { + if (error) { + hiddenInput.parentNode.removeChild(hiddenInput); + _this.dispatchError(error); + } else { + hiddenInput.value = attributes.signed_id; + } + _this.dispatch("end"); + callback(error); + }); + } + }, { + key: "uploadRequestDidProgress", + value: function uploadRequestDidProgress(event) { + var progress = event.loaded / event.total * 100; + if (progress) { + this.dispatch("progress", { + progress: progress + }); + } + } + }, { + key: "dispatch", + value: function dispatch(name) { + var detail = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + detail.file = this.file; + detail.id = this.directUpload.id; + return dispatchEvent(this.input, "direct-upload:" + name, { + detail: detail + }); + } + }, { + key: "dispatchError", + value: function dispatchError(error) { + var event = this.dispatch("error", { + error: error + }); + if (!event.defaultPrevented) { + alert(error); + } + } + }, { + key: "directUploadWillCreateBlobWithXHR", + value: function directUploadWillCreateBlobWithXHR(xhr) { + this.dispatch("before-blob-request", { + xhr: xhr + }); + } + }, { + key: "directUploadWillStoreFileWithXHR", + value: function directUploadWillStoreFileWithXHR(xhr) { + var _this2 = this; + this.dispatch("before-storage-request", { + xhr: xhr + }); + xhr.upload.addEventListener("progress", function(event) { + return _this2.uploadRequestDidProgress(event); + }); + } + }, { + key: "url", + get: function get$$1() { + return this.input.getAttribute("data-direct-upload-url"); + } + } ]); + return DirectUploadController; + }(); + var inputSelector = "input[type=file][data-direct-upload-url]:not([disabled])"; + var DirectUploadsController = function() { + function DirectUploadsController(form) { + classCallCheck(this, DirectUploadsController); + this.form = form; + this.inputs = findElements(form, inputSelector).filter(function(input) { + return input.files.length; + }); + } + createClass(DirectUploadsController, [ { + key: "start", + value: function start(callback) { + var _this = this; + var controllers = this.createDirectUploadControllers(); + var startNextController = function startNextController() { + var controller = controllers.shift(); + if (controller) { + controller.start(function(error) { + if (error) { + callback(error); + _this.dispatch("end"); + } else { + startNextController(); + } + }); + } else { + callback(); + _this.dispatch("end"); + } + }; + this.dispatch("start"); + startNextController(); + } + }, { + key: "createDirectUploadControllers", + value: function createDirectUploadControllers() { + var controllers = []; + this.inputs.forEach(function(input) { + toArray$1(input.files).forEach(function(file) { + var controller = new DirectUploadController(input, file); + controllers.push(controller); + }); + }); + return controllers; + } + }, { + key: "dispatch", + value: function dispatch(name) { + var detail = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + return dispatchEvent(this.form, "direct-uploads:" + name, { + detail: detail + }); + } + } ]); + return DirectUploadsController; + }(); + var processingAttribute = "data-direct-uploads-processing"; + var submitButtonsByForm = new WeakMap(); + var started = false; + function start() { + if (!started) { + started = true; + document.addEventListener("click", didClick, true); + document.addEventListener("submit", didSubmitForm); + document.addEventListener("ajax:before", didSubmitRemoteElement); + } + } + function didClick(event) { + var target = event.target; + if ((target.tagName == "INPUT" || target.tagName == "BUTTON") && target.type == "submit" && target.form) { + submitButtonsByForm.set(target.form, target); + } + } + function didSubmitForm(event) { + handleFormSubmissionEvent(event); + } + function didSubmitRemoteElement(event) { + if (event.target.tagName == "FORM") { + handleFormSubmissionEvent(event); + } + } + function handleFormSubmissionEvent(event) { + var form = event.target; + if (form.hasAttribute(processingAttribute)) { + event.preventDefault(); + return; + } + var controller = new DirectUploadsController(form); + var inputs = controller.inputs; + if (inputs.length) { + event.preventDefault(); + form.setAttribute(processingAttribute, ""); + inputs.forEach(disable); + controller.start(function(error) { + form.removeAttribute(processingAttribute); + if (error) { + inputs.forEach(enable); + } else { + submitForm(form); + } + }); + } + } + function submitForm(form) { + var button = submitButtonsByForm.get(form) || findElement(form, "input[type=submit], button[type=submit]"); + if (button) { + var _button = button, disabled = _button.disabled; + button.disabled = false; + button.focus(); + button.click(); + button.disabled = disabled; + } else { + button = document.createElement("input"); + button.type = "submit"; + button.style.display = "none"; + form.appendChild(button); + button.click(); + form.removeChild(button); + } + submitButtonsByForm.delete(form); + } + function disable(input) { + input.disabled = true; + } + function enable(input) { + input.disabled = false; + } + function autostart() { + if (window.ActiveStorage) { + start(); + } + } + setTimeout(autostart, 1); + exports.start = start; + exports.DirectUpload = DirectUpload; + Object.defineProperty(exports, "__esModule", { + value: true + }); +}); diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/controllers/active_storage/base_controller.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/controllers/active_storage/base_controller.rb new file mode 100644 index 00000000..59312ac8 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/controllers/active_storage/base_controller.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# The base controller for all ActiveStorage controllers. +class ActiveStorage::BaseController < ActionController::Base + protect_from_forgery with: :exception + + before_action do + ActiveStorage::Current.host = request.base_url + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/controllers/active_storage/blobs_controller.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/controllers/active_storage/blobs_controller.rb new file mode 100644 index 00000000..92e54c38 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/controllers/active_storage/blobs_controller.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# Take a signed permanent reference for a blob and turn it into an expiring service URL for download. +# Note: These URLs are publicly accessible. If you need to enforce access protection beyond the +# security-through-obscurity factor of the signed blob references, you'll need to implement your own +# authenticated redirection controller. +class ActiveStorage::BlobsController < ActiveStorage::BaseController + include ActiveStorage::SetBlob + + def show + expires_in ActiveStorage::Blob.service.url_expires_in + redirect_to @blob.service_url(disposition: params[:disposition]) + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/controllers/active_storage/direct_uploads_controller.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/controllers/active_storage/direct_uploads_controller.rb new file mode 100644 index 00000000..78b43fc9 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/controllers/active_storage/direct_uploads_controller.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# Creates a new blob on the server side in anticipation of a direct-to-service upload from the client side. +# When the client-side upload is completed, the signed_blob_id can be submitted as part of the form to reference +# the blob that was created up front. +class ActiveStorage::DirectUploadsController < ActiveStorage::BaseController + def create + blob = ActiveStorage::Blob.create_before_direct_upload!(blob_args) + render json: direct_upload_json(blob) + end + + private + def blob_args + params.require(:blob).permit(:filename, :byte_size, :checksum, :content_type, :metadata).to_h.symbolize_keys + end + + def direct_upload_json(blob) + blob.as_json(root: false, methods: :signed_id).merge(direct_upload: { + url: blob.service_url_for_direct_upload, + headers: blob.service_headers_for_direct_upload + }) + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/controllers/active_storage/disk_controller.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/controllers/active_storage/disk_controller.rb new file mode 100644 index 00000000..a6e80f20 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/controllers/active_storage/disk_controller.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +# Serves files stored with the disk service in the same way that the cloud services do. +# This means using expiring, signed URLs that are meant for immediate access, not permanent linking. +# Always go through the BlobsController, or your own authenticated controller, rather than directly +# to the service url. +class ActiveStorage::DiskController < ActiveStorage::BaseController + skip_forgery_protection + + def show + if key = decode_verified_key + serve_file disk_service.path_for(key[:key]), content_type: key[:content_type], disposition: key[:disposition] + else + head :not_found + end + end + + def update + if token = decode_verified_token + if acceptable_content?(token) + disk_service.upload token[:key], request.body, checksum: token[:checksum] + head :no_content + else + head :unprocessable_entity + end + end + rescue ActiveStorage::IntegrityError + head :unprocessable_entity + end + + private + def disk_service + ActiveStorage::Blob.service + end + + + def decode_verified_key + ActiveStorage.verifier.verified(params[:encoded_key], purpose: :blob_key) + end + + def serve_file(path, content_type:, disposition:) + Rack::File.new(nil).serving(request, path).tap do |(status, headers, body)| + self.status = status + self.response_body = body + + headers.each do |name, value| + response.headers[name] = value + end + + response.headers["Content-Type"] = content_type || DEFAULT_SEND_FILE_TYPE + response.headers["Content-Disposition"] = disposition || DEFAULT_SEND_FILE_DISPOSITION + end + end + + + def decode_verified_token + ActiveStorage.verifier.verified(params[:encoded_token], purpose: :blob_token) + end + + def acceptable_content?(token) + token[:content_type] == request.content_mime_type && token[:content_length] == request.content_length + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/controllers/active_storage/representations_controller.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/controllers/active_storage/representations_controller.rb new file mode 100644 index 00000000..ce9286db --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/controllers/active_storage/representations_controller.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# Take a signed permanent reference for a blob representation and turn it into an expiring service URL for download. +# Note: These URLs are publicly accessible. If you need to enforce access protection beyond the +# security-through-obscurity factor of the signed blob and variation reference, you'll need to implement your own +# authenticated redirection controller. +class ActiveStorage::RepresentationsController < ActiveStorage::BaseController + include ActiveStorage::SetBlob + + def show + expires_in ActiveStorage::Blob.service.url_expires_in + redirect_to @blob.representation(params[:variation_key]).processed.service_url(disposition: params[:disposition]) + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/controllers/concerns/active_storage/set_blob.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/controllers/concerns/active_storage/set_blob.rb new file mode 100644 index 00000000..f072954d --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/controllers/concerns/active_storage/set_blob.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module ActiveStorage::SetBlob #:nodoc: + extend ActiveSupport::Concern + + included do + before_action :set_blob + end + + private + def set_blob + @blob = ActiveStorage::Blob.find_signed(params[:signed_blob_id] || params[:signed_id]) + rescue ActiveSupport::MessageVerifier::InvalidSignature + head :not_found + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/javascript/activestorage/blob_record.js b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/javascript/activestorage/blob_record.js new file mode 100644 index 00000000..ff847892 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/javascript/activestorage/blob_record.js @@ -0,0 +1,68 @@ +import { getMetaValue } from "./helpers" + +export class BlobRecord { + constructor(file, checksum, url) { + this.file = file + + this.attributes = { + filename: file.name, + content_type: file.type, + byte_size: file.size, + checksum: checksum + } + + this.xhr = new XMLHttpRequest + this.xhr.open("POST", url, true) + this.xhr.responseType = "json" + this.xhr.setRequestHeader("Content-Type", "application/json") + this.xhr.setRequestHeader("Accept", "application/json") + this.xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest") + this.xhr.setRequestHeader("X-CSRF-Token", getMetaValue("csrf-token")) + this.xhr.addEventListener("load", event => this.requestDidLoad(event)) + this.xhr.addEventListener("error", event => this.requestDidError(event)) + } + + get status() { + return this.xhr.status + } + + get response() { + const { responseType, response } = this.xhr + if (responseType == "json") { + return response + } else { + // Shim for IE 11: https://connect.microsoft.com/IE/feedback/details/794808 + return JSON.parse(response) + } + } + + create(callback) { + this.callback = callback + this.xhr.send(JSON.stringify({ blob: this.attributes })) + } + + requestDidLoad(event) { + if (this.status >= 200 && this.status < 300) { + const { response } = this + const { direct_upload } = response + delete response.direct_upload + this.attributes = response + this.directUploadData = direct_upload + this.callback(null, this.toJSON()) + } else { + this.requestDidError(event) + } + } + + requestDidError(event) { + this.callback(`Error creating Blob for "${this.file.name}". Status: ${this.status}`) + } + + toJSON() { + const result = {} + for (const key in this.attributes) { + result[key] = this.attributes[key] + } + return result + } +} diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/javascript/activestorage/blob_upload.js b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/javascript/activestorage/blob_upload.js new file mode 100644 index 00000000..277cc8ff --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/javascript/activestorage/blob_upload.js @@ -0,0 +1,35 @@ +export class BlobUpload { + constructor(blob) { + this.blob = blob + this.file = blob.file + + const { url, headers } = blob.directUploadData + + this.xhr = new XMLHttpRequest + this.xhr.open("PUT", url, true) + this.xhr.responseType = "text" + for (const key in headers) { + this.xhr.setRequestHeader(key, headers[key]) + } + this.xhr.addEventListener("load", event => this.requestDidLoad(event)) + this.xhr.addEventListener("error", event => this.requestDidError(event)) + } + + create(callback) { + this.callback = callback + this.xhr.send(this.file.slice()) + } + + requestDidLoad(event) { + const { status, response } = this.xhr + if (status >= 200 && status < 300) { + this.callback(null, response) + } else { + this.requestDidError(event) + } + } + + requestDidError(event) { + this.callback(`Error storing "${this.file.name}". Status: ${this.xhr.status}`) + } +} diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/javascript/activestorage/direct_upload.js b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/javascript/activestorage/direct_upload.js new file mode 100644 index 00000000..c2eedf28 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/javascript/activestorage/direct_upload.js @@ -0,0 +1,48 @@ +import { FileChecksum } from "./file_checksum" +import { BlobRecord } from "./blob_record" +import { BlobUpload } from "./blob_upload" + +let id = 0 + +export class DirectUpload { + constructor(file, url, delegate) { + this.id = ++id + this.file = file + this.url = url + this.delegate = delegate + } + + create(callback) { + FileChecksum.create(this.file, (error, checksum) => { + if (error) { + callback(error) + return + } + + const blob = new BlobRecord(this.file, checksum, this.url) + notify(this.delegate, "directUploadWillCreateBlobWithXHR", blob.xhr) + + blob.create(error => { + if (error) { + callback(error) + } else { + const upload = new BlobUpload(blob) + notify(this.delegate, "directUploadWillStoreFileWithXHR", upload.xhr) + upload.create(error => { + if (error) { + callback(error) + } else { + callback(null, blob.toJSON()) + } + }) + } + }) + }) + } +} + +function notify(object, methodName, ...messages) { + if (object && typeof object[methodName] == "function") { + return object[methodName](...messages) + } +} diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/javascript/activestorage/direct_upload_controller.js b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/javascript/activestorage/direct_upload_controller.js new file mode 100644 index 00000000..98705088 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/javascript/activestorage/direct_upload_controller.js @@ -0,0 +1,67 @@ +import { DirectUpload } from "./direct_upload" +import { dispatchEvent } from "./helpers" + +export class DirectUploadController { + constructor(input, file) { + this.input = input + this.file = file + this.directUpload = new DirectUpload(this.file, this.url, this) + this.dispatch("initialize") + } + + start(callback) { + const hiddenInput = document.createElement("input") + hiddenInput.type = "hidden" + hiddenInput.name = this.input.name + this.input.insertAdjacentElement("beforebegin", hiddenInput) + + this.dispatch("start") + + this.directUpload.create((error, attributes) => { + if (error) { + hiddenInput.parentNode.removeChild(hiddenInput) + this.dispatchError(error) + } else { + hiddenInput.value = attributes.signed_id + } + + this.dispatch("end") + callback(error) + }) + } + + uploadRequestDidProgress(event) { + const progress = event.loaded / event.total * 100 + if (progress) { + this.dispatch("progress", { progress }) + } + } + + get url() { + return this.input.getAttribute("data-direct-upload-url") + } + + dispatch(name, detail = {}) { + detail.file = this.file + detail.id = this.directUpload.id + return dispatchEvent(this.input, `direct-upload:${name}`, { detail }) + } + + dispatchError(error) { + const event = this.dispatch("error", { error }) + if (!event.defaultPrevented) { + alert(error) + } + } + + // DirectUpload delegate + + directUploadWillCreateBlobWithXHR(xhr) { + this.dispatch("before-blob-request", { xhr }) + } + + directUploadWillStoreFileWithXHR(xhr) { + this.dispatch("before-storage-request", { xhr }) + xhr.upload.addEventListener("progress", event => this.uploadRequestDidProgress(event)) + } +} diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/javascript/activestorage/direct_uploads_controller.js b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/javascript/activestorage/direct_uploads_controller.js new file mode 100644 index 00000000..94b89c91 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/javascript/activestorage/direct_uploads_controller.js @@ -0,0 +1,50 @@ +import { DirectUploadController } from "./direct_upload_controller" +import { findElements, dispatchEvent, toArray } from "./helpers" + +const inputSelector = "input[type=file][data-direct-upload-url]:not([disabled])" + +export class DirectUploadsController { + constructor(form) { + this.form = form + this.inputs = findElements(form, inputSelector).filter(input => input.files.length) + } + + start(callback) { + const controllers = this.createDirectUploadControllers() + + const startNextController = () => { + const controller = controllers.shift() + if (controller) { + controller.start(error => { + if (error) { + callback(error) + this.dispatch("end") + } else { + startNextController() + } + }) + } else { + callback() + this.dispatch("end") + } + } + + this.dispatch("start") + startNextController() + } + + createDirectUploadControllers() { + const controllers = [] + this.inputs.forEach(input => { + toArray(input.files).forEach(file => { + const controller = new DirectUploadController(input, file) + controllers.push(controller) + }) + }) + return controllers + } + + dispatch(name, detail = {}) { + return dispatchEvent(this.form, `direct-uploads:${name}`, { detail }) + } +} diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/javascript/activestorage/file_checksum.js b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/javascript/activestorage/file_checksum.js new file mode 100644 index 00000000..a9dbef69 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/javascript/activestorage/file_checksum.js @@ -0,0 +1,53 @@ +import SparkMD5 from "spark-md5" + +const fileSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice + +export class FileChecksum { + static create(file, callback) { + const instance = new FileChecksum(file) + instance.create(callback) + } + + constructor(file) { + this.file = file + this.chunkSize = 2097152 // 2MB + this.chunkCount = Math.ceil(this.file.size / this.chunkSize) + this.chunkIndex = 0 + } + + create(callback) { + this.callback = callback + this.md5Buffer = new SparkMD5.ArrayBuffer + this.fileReader = new FileReader + this.fileReader.addEventListener("load", event => this.fileReaderDidLoad(event)) + this.fileReader.addEventListener("error", event => this.fileReaderDidError(event)) + this.readNextChunk() + } + + fileReaderDidLoad(event) { + this.md5Buffer.append(event.target.result) + + if (!this.readNextChunk()) { + const binaryDigest = this.md5Buffer.end(true) + const base64digest = btoa(binaryDigest) + this.callback(null, base64digest) + } + } + + fileReaderDidError(event) { + this.callback(`Error reading ${this.file.name}`) + } + + readNextChunk() { + if (this.chunkIndex < this.chunkCount || (this.chunkIndex == 0 && this.chunkCount == 0)) { + const start = this.chunkIndex * this.chunkSize + const end = Math.min(start + this.chunkSize, this.file.size) + const bytes = fileSlice.call(this.file, start, end) + this.fileReader.readAsArrayBuffer(bytes) + this.chunkIndex++ + return true + } else { + return false + } + } +} diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/javascript/activestorage/helpers.js b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/javascript/activestorage/helpers.js new file mode 100644 index 00000000..7e83c447 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/javascript/activestorage/helpers.js @@ -0,0 +1,51 @@ +export function getMetaValue(name) { + const element = findElement(document.head, `meta[name="${name}"]`) + if (element) { + return element.getAttribute("content") + } +} + +export function findElements(root, selector) { + if (typeof root == "string") { + selector = root + root = document + } + const elements = root.querySelectorAll(selector) + return toArray(elements) +} + +export function findElement(root, selector) { + if (typeof root == "string") { + selector = root + root = document + } + return root.querySelector(selector) +} + +export function dispatchEvent(element, type, eventInit = {}) { + const { disabled } = element + const { bubbles, cancelable, detail } = eventInit + const event = document.createEvent("Event") + + event.initEvent(type, bubbles || true, cancelable || true) + event.detail = detail || {} + + try { + element.disabled = false + element.dispatchEvent(event) + } finally { + element.disabled = disabled + } + + return event +} + +export function toArray(value) { + if (Array.isArray(value)) { + return value + } else if (Array.from) { + return Array.from(value) + } else { + return [].slice.call(value) + } +} diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/javascript/activestorage/index.js b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/javascript/activestorage/index.js new file mode 100644 index 00000000..a340008f --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/javascript/activestorage/index.js @@ -0,0 +1,11 @@ +import { start } from "./ujs" +import { DirectUpload } from "./direct_upload" +export { start, DirectUpload } + +function autostart() { + if (window.ActiveStorage) { + start() + } +} + +setTimeout(autostart, 1) diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/javascript/activestorage/ujs.js b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/javascript/activestorage/ujs.js new file mode 100644 index 00000000..98fcba60 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/javascript/activestorage/ujs.js @@ -0,0 +1,86 @@ +import { DirectUploadsController } from "./direct_uploads_controller" +import { findElement } from "./helpers" + +const processingAttribute = "data-direct-uploads-processing" +const submitButtonsByForm = new WeakMap +let started = false + +export function start() { + if (!started) { + started = true + document.addEventListener("click", didClick, true) + document.addEventListener("submit", didSubmitForm) + document.addEventListener("ajax:before", didSubmitRemoteElement) + } +} + +function didClick(event) { + const { target } = event + if ((target.tagName == "INPUT" || target.tagName == "BUTTON") && target.type == "submit" && target.form) { + submitButtonsByForm.set(target.form, target) + } +} + +function didSubmitForm(event) { + handleFormSubmissionEvent(event) +} + +function didSubmitRemoteElement(event) { + if (event.target.tagName == "FORM") { + handleFormSubmissionEvent(event) + } +} + +function handleFormSubmissionEvent(event) { + const form = event.target + + if (form.hasAttribute(processingAttribute)) { + event.preventDefault() + return + } + + const controller = new DirectUploadsController(form) + const { inputs } = controller + + if (inputs.length) { + event.preventDefault() + form.setAttribute(processingAttribute, "") + inputs.forEach(disable) + controller.start(error => { + form.removeAttribute(processingAttribute) + if (error) { + inputs.forEach(enable) + } else { + submitForm(form) + } + }) + } +} + +function submitForm(form) { + let button = submitButtonsByForm.get(form) || findElement(form, "input[type=submit], button[type=submit]") + + if (button) { + const { disabled } = button + button.disabled = false + button.focus() + button.click() + button.disabled = disabled + } else { + button = document.createElement("input") + button.type = "submit" + button.style.display = "none" + form.appendChild(button) + button.click() + form.removeChild(button) + } + submitButtonsByForm.delete(form) +} + +function disable(input) { + input.disabled = true +} + +function enable(input) { + input.disabled = false +} diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/jobs/active_storage/analyze_job.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/jobs/active_storage/analyze_job.rb new file mode 100644 index 00000000..2a952f9f --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/jobs/active_storage/analyze_job.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# Provides asynchronous analysis of ActiveStorage::Blob records via ActiveStorage::Blob#analyze_later. +class ActiveStorage::AnalyzeJob < ActiveStorage::BaseJob + def perform(blob) + blob.analyze + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/jobs/active_storage/base_job.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/jobs/active_storage/base_job.rb new file mode 100644 index 00000000..6caab42a --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/jobs/active_storage/base_job.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class ActiveStorage::BaseJob < ActiveJob::Base + queue_as { ActiveStorage.queue } +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/jobs/active_storage/purge_job.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/jobs/active_storage/purge_job.rb new file mode 100644 index 00000000..fa15e045 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/jobs/active_storage/purge_job.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# Provides asynchronous purging of ActiveStorage::Blob records via ActiveStorage::Blob#purge_later. +class ActiveStorage::PurgeJob < ActiveStorage::BaseJob + discard_on ActiveRecord::RecordNotFound + + def perform(blob) + blob.purge + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/attachment.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/attachment.rb new file mode 100644 index 00000000..4d112652 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/attachment.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/delegation" + +# Attachments associate records with blobs. Usually that's a one record-many blobs relationship, +# but it is possible to associate many different records with the same blob. If you're doing that, +# you'll want to declare with has_one/many_attached :thingy, dependent: false, so that destroying +# any one record won't destroy the blob as well. (Then you'll need to do your own garbage collecting, though). +class ActiveStorage::Attachment < ActiveRecord::Base + self.table_name = "active_storage_attachments" + + belongs_to :record, polymorphic: true, touch: true + belongs_to :blob, class_name: "ActiveStorage::Blob" + + delegate_missing_to :blob + + after_create_commit :analyze_blob_later, :identify_blob + + # Synchronously purges the blob (deletes it from the configured service) and destroys the attachment. + def purge + destroy + blob.purge + end + + # Destroys the attachment and asynchronously purges the blob (deletes it from the configured service). + def purge_later + destroy + blob.purge_later + end + + private + def identify_blob + blob.identify + end + + def analyze_blob_later + blob.analyze_later unless blob.analyzed? + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/blob.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/blob.rb new file mode 100644 index 00000000..b176ed24 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/blob.rb @@ -0,0 +1,236 @@ +# frozen_string_literal: true + +# A blob is a record that contains the metadata about a file and a key for where that file resides on the service. +# Blobs can be created in two ways: +# +# 1. Subsequent to the file being uploaded server-side to the service via create_after_upload!. +# 2. Ahead of the file being directly uploaded client-side to the service via create_before_direct_upload!. +# +# The first option doesn't require any client-side JavaScript integration, and can be used by any other back-end +# service that deals with files. The second option is faster, since you're not using your own server as a staging +# point for uploads, and can work with deployments like Heroku that do not provide large amounts of disk space. +# +# Blobs are intended to be immutable in as-so-far as their reference to a specific file goes. You're allowed to +# update a blob's metadata on a subsequent pass, but you should not update the key or change the uploaded file. +# If you need to create a derivative or otherwise change the blob, simply create a new blob and purge the old one. +class ActiveStorage::Blob < ActiveRecord::Base + require_dependency "active_storage/blob/analyzable" + require_dependency "active_storage/blob/identifiable" + require_dependency "active_storage/blob/representable" + + include Analyzable + include Identifiable + include Representable + + self.table_name = "active_storage_blobs" + + has_secure_token :key + store :metadata, accessors: [ :analyzed, :identified ], coder: ActiveRecord::Coders::JSON + + class_attribute :service + + has_many :attachments + + scope :unattached, -> { left_joins(:attachments).where(ActiveStorage::Attachment.table_name => { blob_id: nil }) } + + before_destroy(prepend: true) do + raise ActiveRecord::InvalidForeignKey if attachments.exists? + end + + class << self + # You can used the signed ID of a blob to refer to it on the client side without fear of tampering. + # This is particularly helpful for direct uploads where the client-side needs to refer to the blob + # that was created ahead of the upload itself on form submission. + # + # The signed ID is also used to create stable URLs for the blob through the BlobsController. + def find_signed(id) + find ActiveStorage.verifier.verify(id, purpose: :blob_id) + end + + # Returns a new, unsaved blob instance after the +io+ has been uploaded to the service. + def build_after_upload(io:, filename:, content_type: nil, metadata: nil) + new.tap do |blob| + blob.filename = filename + blob.content_type = content_type + blob.metadata = metadata + + blob.upload io + end + end + + # Returns a saved blob instance after the +io+ has been uploaded to the service. Note, the blob is first built, + # then the +io+ is uploaded, then the blob is saved. This is done this way to avoid uploading (which may take + # time), while having an open database transaction. + def create_after_upload!(io:, filename:, content_type: nil, metadata: nil) + build_after_upload(io: io, filename: filename, content_type: content_type, metadata: metadata).tap(&:save!) + end + + # Returns a saved blob _without_ uploading a file to the service. This blob will point to a key where there is + # no file yet. It's intended to be used together with a client-side upload, which will first create the blob + # in order to produce the signed URL for uploading. This signed URL points to the key generated by the blob. + # Once the form using the direct upload is submitted, the blob can be associated with the right record using + # the signed ID. + def create_before_direct_upload!(filename:, byte_size:, checksum:, content_type: nil, metadata: nil) + create! filename: filename, byte_size: byte_size, checksum: checksum, content_type: content_type, metadata: metadata + end + end + + # Returns a signed ID for this blob that's suitable for reference on the client-side without fear of tampering. + # It uses the framework-wide verifier on ActiveStorage.verifier, but with a dedicated purpose. + def signed_id + ActiveStorage.verifier.generate(id, purpose: :blob_id) + end + + # Returns the key pointing to the file on the service that's associated with this blob. The key is in the + # standard secure-token format from Rails. So it'll look like: XTAPjJCJiuDrLk3TmwyJGpUo. This key is not intended + # to be revealed directly to the user. Always refer to blobs using the signed_id or a verified form of the key. + def key + # We can't wait until the record is first saved to have a key for it + self[:key] ||= self.class.generate_unique_secure_token + end + + # Returns an ActiveStorage::Filename instance of the filename that can be + # queried for basename, extension, and a sanitized version of the filename + # that's safe to use in URLs. + def filename + ActiveStorage::Filename.new(self[:filename]) + end + + # Returns true if the content_type of this blob is in the image range, like image/png. + def image? + content_type.start_with?("image") + end + + # Returns true if the content_type of this blob is in the audio range, like audio/mpeg. + def audio? + content_type.start_with?("audio") + end + + # Returns true if the content_type of this blob is in the video range, like video/mp4. + def video? + content_type.start_with?("video") + end + + # Returns true if the content_type of this blob is in the text range, like text/plain. + def text? + content_type.start_with?("text") + end + + + # Returns the URL of the blob on the service. This URL is intended to be short-lived for security and not used directly + # with users. Instead, the +service_url+ should only be exposed as a redirect from a stable, possibly authenticated URL. + # Hiding the +service_url+ behind a redirect also gives you the power to change services without updating all URLs. And + # it allows permanent URLs that redirect to the +service_url+ to be cached in the view. + def service_url(expires_in: service.url_expires_in, disposition: :inline, filename: nil, **options) + filename = ActiveStorage::Filename.wrap(filename || self.filename) + + service.url key, expires_in: expires_in, filename: filename, content_type: content_type_for_service_url, + disposition: forced_disposition_for_service_url || disposition, **options + end + + # Returns a URL that can be used to directly upload a file for this blob on the service. This URL is intended to be + # short-lived for security and only generated on-demand by the client-side JavaScript responsible for doing the uploading. + def service_url_for_direct_upload(expires_in: service.url_expires_in) + service.url_for_direct_upload key, expires_in: expires_in, content_type: content_type, content_length: byte_size, checksum: checksum + end + + # Returns a Hash of headers for +service_url_for_direct_upload+ requests. + def service_headers_for_direct_upload + service.headers_for_direct_upload key, filename: filename, content_type: content_type, content_length: byte_size, checksum: checksum + end + + + # Uploads the +io+ to the service on the +key+ for this blob. Blobs are intended to be immutable, so you shouldn't be + # using this method after a file has already been uploaded to fit with a blob. If you want to create a derivative blob, + # you should instead simply create a new blob based on the old one. + # + # Prior to uploading, we compute the checksum, which is sent to the service for transit integrity validation. If the + # checksum does not match what the service receives, an exception will be raised. We also measure the size of the +io+ + # and store that in +byte_size+ on the blob record. + # + # Normally, you do not have to call this method directly at all. Use the factory class methods of +build_after_upload+ + # and +create_after_upload!+. + def upload(io) + self.checksum = compute_checksum_in_chunks(io) + self.content_type = extract_content_type(io) + self.byte_size = io.size + self.identified = true + + service.upload key, io, checksum: checksum, **service_metadata + end + + # Downloads the file associated with this blob. If no block is given, the entire file is read into memory and returned. + # That'll use a lot of RAM for very large files. If a block is given, then the download is streamed and yielded in chunks. + def download(&block) + service.download key, &block + end + + + # Deletes the file on the service that's associated with this blob. This should only be done if the blob is going to be + # deleted as well or you will essentially have a dead reference. It's recommended to use the +#purge+ and +#purge_later+ + # methods in most circumstances. + def delete + service.delete(key) + service.delete_prefixed("variants/#{key}/") if image? + end + + # Deletes the file on the service and then destroys the blob record. This is the recommended way to dispose of unwanted + # blobs. Note, though, that deleting the file off the service will initiate a HTTP connection to the service, which may + # be slow or prevented, so you should not use this method inside a transaction or in callbacks. Use +#purge_later+ instead. + def purge + destroy + delete + rescue ActiveRecord::InvalidForeignKey + end + + # Enqueues an ActiveStorage::PurgeJob job that'll call +purge+. This is the recommended way to purge blobs when the call + # needs to be made from a transaction, a callback, or any other real-time scenario. + def purge_later + ActiveStorage::PurgeJob.perform_later(self) + end + + private + def compute_checksum_in_chunks(io) + Digest::MD5.new.tap do |checksum| + while chunk = io.read(5.megabytes) + checksum << chunk + end + + io.rewind + end.base64digest + end + + def extract_content_type(io) + Marcel::MimeType.for io, name: filename.to_s, declared_type: content_type + end + + def forcibly_serve_as_binary? + ActiveStorage.content_types_to_serve_as_binary.include?(content_type) + end + + def allowed_inline? + ActiveStorage.content_types_allowed_inline.include?(content_type) + end + + def content_type_for_service_url + forcibly_serve_as_binary? ? ActiveStorage.binary_content_type : content_type + end + + def forced_disposition_for_service_url + if forcibly_serve_as_binary? || !allowed_inline? + :attachment + end + end + + def service_metadata + if forcibly_serve_as_binary? + { content_type: ActiveStorage.binary_content_type, disposition: :attachment, filename: filename } + elsif !allowed_inline? + { content_type: content_type, disposition: :attachment, filename: filename } + else + { content_type: content_type } + end + end + + ActiveSupport.run_load_hooks(:active_storage_blob, self) +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/blob/analyzable.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/blob/analyzable.rb new file mode 100644 index 00000000..5bda6e6d --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/blob/analyzable.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require "active_storage/analyzer/null_analyzer" + +module ActiveStorage::Blob::Analyzable + # Extracts and stores metadata from the file associated with this blob using a relevant analyzer. Active Storage comes + # with built-in analyzers for images and videos. See ActiveStorage::Analyzer::ImageAnalyzer and + # ActiveStorage::Analyzer::VideoAnalyzer for information about the specific attributes they extract and the third-party + # libraries they require. + # + # To choose the analyzer for a blob, Active Storage calls +accept?+ on each registered analyzer in order. It uses the + # first analyzer for which +accept?+ returns true when given the blob. If no registered analyzer accepts the blob, no + # metadata is extracted from it. + # + # In a Rails application, add or remove analyzers by manipulating +Rails.application.config.active_storage.analyzers+ + # in an initializer: + # + # # Add a custom analyzer for Microsoft Office documents: + # Rails.application.config.active_storage.analyzers.append DOCXAnalyzer + # + # # Remove the built-in video analyzer: + # Rails.application.config.active_storage.analyzers.delete ActiveStorage::Analyzer::VideoAnalyzer + # + # Outside of a Rails application, manipulate +ActiveStorage.analyzers+ instead. + # + # You won't ordinarily need to call this method from a Rails application. New blobs are automatically and asynchronously + # analyzed via #analyze_later when they're attached for the first time. + def analyze + update! metadata: metadata.merge(extract_metadata_via_analyzer) + end + + # Enqueues an ActiveStorage::AnalyzeJob which calls #analyze. + # + # This method is automatically called for a blob when it's attached for the first time. You can call it to analyze a blob + # again (e.g. if you add a new analyzer or modify an existing one). + def analyze_later + ActiveStorage::AnalyzeJob.perform_later(self) + end + + # Returns true if the blob has been analyzed. + def analyzed? + analyzed + end + + private + def extract_metadata_via_analyzer + analyzer.metadata.merge(analyzed: true) + end + + def analyzer + analyzer_class.new(self) + end + + def analyzer_class + ActiveStorage.analyzers.detect { |klass| klass.accept?(self) } || ActiveStorage::Analyzer::NullAnalyzer + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/blob/identifiable.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/blob/identifiable.rb new file mode 100644 index 00000000..924bd061 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/blob/identifiable.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module ActiveStorage::Blob::Identifiable + def identify + unless identified? + update! content_type: identify_content_type, identified: true + update_service_metadata + end + end + + def identified? + identified + end + + private + def identify_content_type + Marcel::MimeType.for download_identifiable_chunk, name: filename.to_s, declared_type: content_type + end + + def download_identifiable_chunk + if byte_size.positive? + service.download_chunk key, 0...4.kilobytes + else + "" + end + end + + def update_service_metadata + service.update_metadata key, service_metadata if service_metadata.any? + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/blob/representable.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/blob/representable.rb new file mode 100644 index 00000000..fea62e62 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/blob/representable.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +module ActiveStorage::Blob::Representable + extend ActiveSupport::Concern + + included do + has_one_attached :preview_image + end + + # Returns an ActiveStorage::Variant instance with the set of +transformations+ provided. This is only relevant for image + # files, and it allows any image to be transformed for size, colors, and the like. Example: + # + # avatar.variant(resize: "100x100").processed.service_url + # + # This will create and process a variant of the avatar blob that's constrained to a height and width of 100px. + # Then it'll upload said variant to the service according to a derivative key of the blob and the transformations. + # + # Frequently, though, you don't actually want to transform the variant right away. But rather simply refer to a + # specific variant that can be created by a controller on-demand. Like so: + # + # <%= image_tag Current.user.avatar.variant(resize: "100x100") %> + # + # This will create a URL for that specific blob with that specific variant, which the ActiveStorage::RepresentationsController + # can then produce on-demand. + # + # Raises ActiveStorage::InvariableError if ImageMagick cannot transform the blob. To determine whether a blob is + # variable, call ActiveStorage::Blob#variable?. + def variant(transformations) + if variable? + ActiveStorage::Variant.new(self, transformations) + else + raise ActiveStorage::InvariableError + end + end + + # Returns true if ImageMagick can transform the blob (its content type is in +ActiveStorage.variable_content_types+). + def variable? + ActiveStorage.variable_content_types.include?(content_type) + end + + + # Returns an ActiveStorage::Preview instance with the set of +transformations+ provided. A preview is an image generated + # from a non-image blob. Active Storage comes with built-in previewers for videos and PDF documents. The video previewer + # extracts the first frame from a video and the PDF previewer extracts the first page from a PDF document. + # + # blob.preview(resize: "100x100").processed.service_url + # + # Avoid processing previews synchronously in views. Instead, link to a controller action that processes them on demand. + # Active Storage provides one, but you may want to create your own (for example, if you need authentication). Here’s + # how to use the built-in version: + # + # <%= image_tag video.preview(resize: "100x100") %> + # + # This method raises ActiveStorage::UnpreviewableError if no previewer accepts the receiving blob. To determine + # whether a blob is accepted by any previewer, call ActiveStorage::Blob#previewable?. + def preview(transformations) + if previewable? + ActiveStorage::Preview.new(self, transformations) + else + raise ActiveStorage::UnpreviewableError + end + end + + # Returns true if any registered previewer accepts the blob. By default, this will return true for videos and PDF documents. + def previewable? + ActiveStorage.previewers.any? { |klass| klass.accept?(self) } + end + + + # Returns an ActiveStorage::Preview for a previewable blob or an ActiveStorage::Variant for a variable image blob. + # + # blob.representation(resize: "100x100").processed.service_url + # + # Raises ActiveStorage::UnrepresentableError if the receiving blob is neither variable nor previewable. Call + # ActiveStorage::Blob#representable? to determine whether a blob is representable. + # + # See ActiveStorage::Blob#preview and ActiveStorage::Blob#variant for more information. + def representation(transformations) + case + when previewable? + preview transformations + when variable? + variant transformations + else + raise ActiveStorage::UnrepresentableError + end + end + + # Returns true if the blob is variable or previewable. + def representable? + variable? || previewable? + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/current.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/current.rb new file mode 100644 index 00000000..7e431d84 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/current.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class ActiveStorage::Current < ActiveSupport::CurrentAttributes #:nodoc: + attribute :host +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/filename.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/filename.rb new file mode 100644 index 00000000..bebb5e61 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/filename.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +# Encapsulates a string representing a filename to provide convenient access to parts of it and sanitization. +# A Filename instance is returned by ActiveStorage::Blob#filename, and is comparable so it can be used for sorting. +class ActiveStorage::Filename + require_dependency "active_storage/filename/parameters" + + include Comparable + + class << self + # Returns a Filename instance based on the given filename. If the filename is a Filename, it is + # returned unmodified. If it is a String, it is passed to ActiveStorage::Filename.new. + def wrap(filename) + filename.kind_of?(self) ? filename : new(filename) + end + end + + def initialize(filename) + @filename = filename + end + + # Returns the part of the filename preceding any extension. + # + # ActiveStorage::Filename.new("racecar.jpg").base # => "racecar" + # ActiveStorage::Filename.new("racecar").base # => "racecar" + # ActiveStorage::Filename.new(".gitignore").base # => ".gitignore" + def base + File.basename @filename, extension_with_delimiter + end + + # Returns the extension of the filename (i.e. the substring following the last dot, excluding a dot at the + # beginning) with the dot that precedes it. If the filename has no extension, an empty string is returned. + # + # ActiveStorage::Filename.new("racecar.jpg").extension_with_delimiter # => ".jpg" + # ActiveStorage::Filename.new("racecar").extension_with_delimiter # => "" + # ActiveStorage::Filename.new(".gitignore").extension_with_delimiter # => "" + def extension_with_delimiter + File.extname @filename + end + + # Returns the extension of the filename (i.e. the substring following the last dot, excluding a dot at + # the beginning). If the filename has no extension, an empty string is returned. + # + # ActiveStorage::Filename.new("racecar.jpg").extension_without_delimiter # => "jpg" + # ActiveStorage::Filename.new("racecar").extension_without_delimiter # => "" + # ActiveStorage::Filename.new(".gitignore").extension_without_delimiter # => "" + def extension_without_delimiter + extension_with_delimiter.from(1).to_s + end + + alias_method :extension, :extension_without_delimiter + + # Returns the sanitized filename. + # + # ActiveStorage::Filename.new("foo:bar.jpg").sanitized # => "foo-bar.jpg" + # ActiveStorage::Filename.new("foo/bar.jpg").sanitized # => "foo-bar.jpg" + # + # Characters considered unsafe for storage (e.g. \, $, and the RTL override character) are replaced with a dash. + def sanitized + @filename.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;/\t\r\n\\", "-") + end + + def parameters #:nodoc: + Parameters.new self + end + + # Returns the sanitized version of the filename. + def to_s + sanitized.to_s + end + + def as_json(*) + to_s + end + + def to_json + to_s + end + + def <=>(other) + to_s.downcase <=> other.to_s.downcase + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/filename/parameters.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/filename/parameters.rb new file mode 100644 index 00000000..fb9ea10e --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/filename/parameters.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +class ActiveStorage::Filename::Parameters #:nodoc: + attr_reader :filename + + def initialize(filename) + @filename = filename + end + + def combined + "#{ascii}; #{utf8}" + end + + TRADITIONAL_ESCAPED_CHAR = /[^ A-Za-z0-9!#$+.^_`|~-]/ + + def ascii + 'filename="' + percent_escape(I18n.transliterate(filename.sanitized), TRADITIONAL_ESCAPED_CHAR) + '"' + end + + RFC_5987_ESCAPED_CHAR = /[^A-Za-z0-9!#$&+.^_`|~-]/ + + def utf8 + "filename*=UTF-8''" + percent_escape(filename.sanitized, RFC_5987_ESCAPED_CHAR) + end + + def to_s + combined + end + + private + def percent_escape(string, pattern) + string.gsub(pattern) do |char| + char.bytes.map { |byte| "%%%02X" % byte }.join + end + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/preview.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/preview.rb new file mode 100644 index 00000000..2b878971 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/preview.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +# Some non-image blobs can be previewed: that is, they can be presented as images. A video blob can be previewed by +# extracting its first frame, and a PDF blob can be previewed by extracting its first page. +# +# A previewer extracts a preview image from a blob. Active Storage provides previewers for videos and PDFs: +# ActiveStorage::Previewer::VideoPreviewer and ActiveStorage::Previewer::PDFPreviewer. Build custom previewers by +# subclassing ActiveStorage::Previewer and implementing the requisite methods. Consult the ActiveStorage::Previewer +# documentation for more details on what's required of previewers. +# +# To choose the previewer for a blob, Active Storage calls +accept?+ on each registered previewer in order. It uses the +# first previewer for which +accept?+ returns true when given the blob. In a Rails application, add or remove previewers +# by manipulating +Rails.application.config.active_storage.previewers+ in an initializer: +# +# Rails.application.config.active_storage.previewers +# # => [ ActiveStorage::Previewer::PDFPreviewer, ActiveStorage::Previewer::VideoPreviewer ] +# +# # Add a custom previewer for Microsoft Office documents: +# Rails.application.config.active_storage.previewers << DOCXPreviewer +# # => [ ActiveStorage::Previewer::PDFPreviewer, ActiveStorage::Previewer::VideoPreviewer, DOCXPreviewer ] +# +# Outside of a Rails application, modify +ActiveStorage.previewers+ instead. +# +# The built-in previewers rely on third-party system libraries. Specifically, the built-in video previewer requires +# {ffmpeg}[https://www.ffmpeg.org]. Two PDF previewers are provided: one requires {Poppler}[https://poppler.freedesktop.org], +# and the other requires {mupdf}[https://mupdf.com] (version 1.8 or newer). To preview PDFs, install either Poppler or mupdf. +# +# These libraries are not provided by Rails. You must install them yourself to use the built-in previewers. Before you +# install and use third-party software, make sure you understand the licensing implications of doing so. +class ActiveStorage::Preview + class UnprocessedError < StandardError; end + + attr_reader :blob, :variation + + def initialize(blob, variation_or_variation_key) + @blob, @variation = blob, ActiveStorage::Variation.wrap(variation_or_variation_key) + end + + # Processes the preview if it has not been processed yet. Returns the receiving Preview instance for convenience: + # + # blob.preview(resize: "100x100").processed.service_url + # + # Processing a preview generates an image from its blob and attaches the preview image to the blob. Because the preview + # image is stored with the blob, it is only generated once. + def processed + process unless processed? + self + end + + # Returns the blob's attached preview image. + def image + blob.preview_image + end + + # Returns the URL of the preview's variant on the service. Raises ActiveStorage::Preview::UnprocessedError if the + # preview has not been processed yet. + # + # This method synchronously processes a variant of the preview image, so do not call it in views. Instead, generate + # a stable URL that redirects to the short-lived URL returned by this method. + def service_url(**options) + if processed? + variant.service_url(options) + else + raise UnprocessedError + end + end + + private + def processed? + image.attached? + end + + def process + previewer.preview { |attachable| image.attach(attachable) } + end + + def variant + ActiveStorage::Variant.new(image, variation).processed + end + + + def previewer + previewer_class.new(blob) + end + + def previewer_class + ActiveStorage.previewers.detect { |klass| klass.accept?(blob) } + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/variant.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/variant.rb new file mode 100644 index 00000000..3e059cd6 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/variant.rb @@ -0,0 +1,132 @@ +# frozen_string_literal: true + +require "active_storage/downloading" + +# Image blobs can have variants that are the result of a set of transformations applied to the original. +# These variants are used to create thumbnails, fixed-size avatars, or any other derivative image from the +# original. +# +# Variants rely on {MiniMagick}[https://github.com/minimagick/minimagick] for the actual transformations +# of the file, so you must add gem "mini_magick" to your Gemfile if you wish to use variants. +# +# Note that to create a variant it's necessary to download the entire blob file from the service and load it +# into memory. The larger the image, the more memory is used. Because of this process, you also want to be +# considerate about when the variant is actually processed. You shouldn't be processing variants inline in a +# template, for example. Delay the processing to an on-demand controller, like the one provided in +# ActiveStorage::RepresentationsController. +# +# To refer to such a delayed on-demand variant, simply link to the variant through the resolved route provided +# by Active Storage like so: +# +# <%= image_tag Current.user.avatar.variant(resize: "100x100") %> +# +# This will create a URL for that specific blob with that specific variant, which the ActiveStorage::RepresentationsController +# can then produce on-demand. +# +# When you do want to actually produce the variant needed, call +processed+. This will check that the variant +# has already been processed and uploaded to the service, and, if so, just return that. Otherwise it will perform +# the transformations, upload the variant to the service, and return itself again. Example: +# +# avatar.variant(resize: "100x100").processed.service_url +# +# This will create and process a variant of the avatar blob that's constrained to a height and width of 100. +# Then it'll upload said variant to the service according to a derivative key of the blob and the transformations. +# +# A list of all possible transformations is available at https://www.imagemagick.org/script/mogrify.php. You can +# combine as many as you like freely: +# +# avatar.variant(resize: "100x100", monochrome: true, rotate: "-90") +class ActiveStorage::Variant + include ActiveStorage::Downloading + + WEB_IMAGE_CONTENT_TYPES = %w( image/png image/jpeg image/jpg image/gif ) + + attr_reader :blob, :variation + delegate :service, to: :blob + + def initialize(blob, variation_or_variation_key) + @blob, @variation = blob, ActiveStorage::Variation.wrap(variation_or_variation_key) + end + + # Returns the variant instance itself after it's been processed or an existing processing has been found on the service. + def processed + process unless processed? + self + end + + # Returns a combination key of the blob and the variation that together identifies a specific variant. + def key + "variants/#{blob.key}/#{Digest::SHA256.hexdigest(variation.key)}" + end + + # Returns the URL of the variant on the service. This URL is intended to be short-lived for security and not used directly + # with users. Instead, the +service_url+ should only be exposed as a redirect from a stable, possibly authenticated URL. + # Hiding the +service_url+ behind a redirect also gives you the power to change services without updating all URLs. And + # it allows permanent URLs that redirect to the +service_url+ to be cached in the view. + # + # Use url_for(variant) (or the implied form, like +link_to variant+ or +redirect_to variant+) to get the stable URL + # for a variant that points to the ActiveStorage::RepresentationsController, which in turn will use this +service_call+ method + # for its redirection. + def service_url(expires_in: service.url_expires_in, disposition: :inline) + service.url key, expires_in: expires_in, disposition: disposition, filename: filename, content_type: content_type + end + + # Returns the receiving variant. Allows ActiveStorage::Variant and ActiveStorage::Preview instances to be used interchangeably. + def image + self + end + + private + def processed? + service.exist?(key) + end + + def process + open_image do |image| + transform image + format image + upload image + end + end + + + def filename + if WEB_IMAGE_CONTENT_TYPES.include?(blob.content_type) + blob.filename + else + ActiveStorage::Filename.new("#{blob.filename.base}.png") + end + end + + def content_type + blob.content_type.presence_in(WEB_IMAGE_CONTENT_TYPES) || "image/png" + end + + + def open_image(&block) + image = download_image + + begin + yield image + ensure + image.destroy! + end + end + + def download_image + require "mini_magick" + MiniMagick::Image.create(blob.filename.extension_with_delimiter) { |file| download_blob_to(file) } + end + + def transform(image) + variation.transform(image) + end + + def format(image) + image.format("PNG") unless WEB_IMAGE_CONTENT_TYPES.include?(blob.content_type) + end + + def upload(image) + File.open(image.path, "r") { |file| service.upload(key, file) } + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/variation.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/variation.rb new file mode 100644 index 00000000..12e7f9f0 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/app/models/active_storage/variation.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +# A set of transformations that can be applied to a blob to create a variant. This class is exposed via +# the ActiveStorage::Blob#variant method and should rarely be used directly. +# +# In case you do need to use this directly, it's instantiated using a hash of transformations where +# the key is the command and the value is the arguments. Example: +# +# ActiveStorage::Variation.new(resize: "100x100", monochrome: true, trim: true, rotate: "-90") +# +# You can also combine multiple transformations in one step, e.g. for center-weighted cropping: +# +# ActiveStorage::Variation.new(combine_options: { +# resize: "100x100^", +# gravity: "center", +# crop: "100x100+0+0", +# }) +# +# A list of all possible transformations is available at https://www.imagemagick.org/script/mogrify.php. +class ActiveStorage::Variation + attr_reader :transformations + + class << self + # Returns a Variation instance based on the given variator. If the variator is a Variation, it is + # returned unmodified. If it is a String, it is passed to ActiveStorage::Variation.decode. Otherwise, + # it is assumed to be a transformations Hash and is passed directly to the constructor. + def wrap(variator) + case variator + when self + variator + when String + decode variator + else + new variator + end + end + + # Returns a Variation instance with the transformations that were encoded by +encode+. + def decode(key) + new ActiveStorage.verifier.verify(key, purpose: :variation) + end + + # Returns a signed key for the +transformations+, which can be used to refer to a specific + # variation in a URL or combined key (like ActiveStorage::Variant#key). + def encode(transformations) + ActiveStorage.verifier.generate(transformations, purpose: :variation) + end + end + + def initialize(transformations) + @transformations = transformations + end + + # Accepts an open MiniMagick image instance, like what's returned by MiniMagick::Image.read(io), + # and performs the +transformations+ against it. The transformed image instance is then returned. + def transform(image) + ActiveSupport::Notifications.instrument("transform.active_storage") do + transformations.each do |name, argument_or_subtransformations| + image.mogrify do |command| + if name.to_s == "combine_options" + argument_or_subtransformations.each do |subtransformation_name, subtransformation_argument| + pass_transform_argument(command, subtransformation_name, subtransformation_argument) + end + else + pass_transform_argument(command, name, argument_or_subtransformations) + end + end + end + end + end + + # Returns a signed key for all the +transformations+ that this variation was instantiated with. + def key + self.class.encode(transformations) + end + + private + def pass_transform_argument(command, method, argument) + if eligible_argument?(argument) + command.public_send(method, argument) + else + command.public_send(method) + end + end + + def eligible_argument?(argument) + argument.present? && argument != true + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/config/routes.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/config/routes.rb new file mode 100644 index 00000000..20d19f33 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/config/routes.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +Rails.application.routes.draw do + get "/rails/active_storage/blobs/:signed_id/*filename" => "active_storage/blobs#show", as: :rails_service_blob + + direct :rails_blob do |blob, options| + route_for(:rails_service_blob, blob.signed_id, blob.filename, options) + end + + resolve("ActiveStorage::Blob") { |blob, options| route_for(:rails_blob, blob, options) } + resolve("ActiveStorage::Attachment") { |attachment, options| route_for(:rails_blob, attachment.blob, options) } + + + get "/rails/active_storage/representations/:signed_blob_id/:variation_key/*filename" => "active_storage/representations#show", as: :rails_blob_representation + + direct :rails_representation do |representation, options| + signed_blob_id = representation.blob.signed_id + variation_key = representation.variation.key + filename = representation.blob.filename + + route_for(:rails_blob_representation, signed_blob_id, variation_key, filename, options) + end + + resolve("ActiveStorage::Variant") { |variant, options| route_for(:rails_representation, variant, options) } + resolve("ActiveStorage::Preview") { |preview, options| route_for(:rails_representation, preview, options) } + + + get "/rails/active_storage/disk/:encoded_key/*filename" => "active_storage/disk#show", as: :rails_disk_service + put "/rails/active_storage/disk/:encoded_token" => "active_storage/disk#update", as: :update_rails_disk_service + post "/rails/active_storage/direct_uploads" => "active_storage/direct_uploads#create", as: :rails_direct_uploads +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/db/migrate/20170806125915_create_active_storage_tables.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/db/migrate/20170806125915_create_active_storage_tables.rb new file mode 100644 index 00000000..cfaf01cd --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/db/migrate/20170806125915_create_active_storage_tables.rb @@ -0,0 +1,26 @@ +class CreateActiveStorageTables < ActiveRecord::Migration[5.2] + def change + create_table :active_storage_blobs do |t| + t.string :key, null: false + t.string :filename, null: false + t.string :content_type + t.text :metadata + t.bigint :byte_size, null: false + t.string :checksum, null: false + t.datetime :created_at, null: false + + t.index [ :key ], unique: true + end + + create_table :active_storage_attachments do |t| + t.string :name, null: false + t.references :record, null: false, polymorphic: true, index: false + t.references :blob, null: false + + t.datetime :created_at, null: false + + t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true + t.foreign_key :active_storage_blobs, column: :blob_id + end + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage.rb new file mode 100644 index 00000000..81c8c771 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +#-- +# Copyright (c) 2017-2018 David Heinemeier Hansson, Basecamp +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ + +require "active_record" +require "active_support" +require "active_support/rails" + +require "active_storage/version" +require "active_storage/errors" + +require "marcel" + +module ActiveStorage + extend ActiveSupport::Autoload + + autoload :Attached + autoload :Service + autoload :Previewer + autoload :Analyzer + + mattr_accessor :logger + mattr_accessor :verifier + mattr_accessor :queue + mattr_accessor :previewers, default: [] + mattr_accessor :analyzers, default: [] + mattr_accessor :paths, default: {} + mattr_accessor :variable_content_types, default: [] + mattr_accessor :content_types_to_serve_as_binary, default: [] + mattr_accessor :content_types_allowed_inline, default: [] + mattr_accessor :binary_content_type, default: "application/octet-stream" +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/analyzer.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/analyzer.rb new file mode 100644 index 00000000..7c4168c1 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/analyzer.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require "active_storage/downloading" + +module ActiveStorage + # This is an abstract base class for analyzers, which extract metadata from blobs. See + # ActiveStorage::Analyzer::ImageAnalyzer for an example of a concrete subclass. + class Analyzer + include Downloading + + attr_reader :blob + + # Implement this method in a concrete subclass. Have it return true when given a blob from which + # the analyzer can extract metadata. + def self.accept?(blob) + false + end + + def initialize(blob) + @blob = blob + end + + # Override this method in a concrete subclass. Have it return a Hash of metadata. + def metadata + raise NotImplementedError + end + + private + def logger #:doc: + ActiveStorage.logger + end + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/analyzer/image_analyzer.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/analyzer/image_analyzer.rb new file mode 100644 index 00000000..3b39de91 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/analyzer/image_analyzer.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module ActiveStorage + # Extracts width and height in pixels from an image blob. + # + # If the image contains EXIF data indicating its angle is 90 or 270 degrees, its width and height are swapped for convenience. + # + # Example: + # + # ActiveStorage::Analyzer::ImageAnalyzer.new(blob).metadata + # # => { width: 4104, height: 2736 } + # + # This analyzer relies on the third-party {MiniMagick}[https://github.com/minimagick/minimagick] gem. MiniMagick requires + # the {ImageMagick}[http://www.imagemagick.org] system library. + class Analyzer::ImageAnalyzer < Analyzer + def self.accept?(blob) + blob.image? + end + + def metadata + read_image do |image| + if rotated_image?(image) + { width: image.height, height: image.width } + else + { width: image.width, height: image.height } + end + end + rescue LoadError + logger.info "Skipping image analysis because the mini_magick gem isn't installed" + {} + end + + private + def read_image + download_blob_to_tempfile do |file| + require "mini_magick" + yield MiniMagick::Image.new(file.path) + end + end + + def rotated_image?(image) + %w[ RightTop LeftBottom ].include?(image["%[orientation]"]) + end + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/analyzer/null_analyzer.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/analyzer/null_analyzer.rb new file mode 100644 index 00000000..8ff7ce48 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/analyzer/null_analyzer.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module ActiveStorage + class Analyzer::NullAnalyzer < Analyzer # :nodoc: + def self.accept?(blob) + true + end + + def metadata + {} + end + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/analyzer/video_analyzer.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/analyzer/video_analyzer.rb new file mode 100644 index 00000000..656e3621 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/analyzer/video_analyzer.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/compact" + +module ActiveStorage + # Extracts the following from a video blob: + # + # * Width (pixels) + # * Height (pixels) + # * Duration (seconds) + # * Angle (degrees) + # * Display aspect ratio + # + # Example: + # + # ActiveStorage::VideoAnalyzer.new(blob).metadata + # # => { width: 640.0, height: 480.0, duration: 5.0, angle: 0, display_aspect_ratio: [4, 3] } + # + # When a video's angle is 90 or 270 degrees, its width and height are automatically swapped for convenience. + # + # This analyzer requires the {ffmpeg}[https://www.ffmpeg.org] system library, which is not provided by Rails. + class Analyzer::VideoAnalyzer < Analyzer + def self.accept?(blob) + blob.video? + end + + def metadata + { width: width, height: height, duration: duration, angle: angle, display_aspect_ratio: display_aspect_ratio }.compact + end + + private + def width + if rotated? + computed_height || encoded_height + else + encoded_width + end + end + + def height + if rotated? + encoded_width + else + computed_height || encoded_height + end + end + + def duration + Float(video_stream["duration"]) if video_stream["duration"] + end + + def angle + Integer(tags["rotate"]) if tags["rotate"] + end + + def display_aspect_ratio + if descriptor = video_stream["display_aspect_ratio"] + if terms = descriptor.split(":", 2) + numerator = Integer(terms[0]) + denominator = Integer(terms[1]) + + [numerator, denominator] unless numerator == 0 + end + end + end + + + def rotated? + angle == 90 || angle == 270 + end + + def computed_height + if encoded_width && display_height_scale + encoded_width * display_height_scale + end + end + + def encoded_width + @encoded_width ||= Float(video_stream["width"]) if video_stream["width"] + end + + def encoded_height + @encoded_height ||= Float(video_stream["height"]) if video_stream["height"] + end + + def display_height_scale + @display_height_scale ||= Float(display_aspect_ratio.last) / display_aspect_ratio.first if display_aspect_ratio + end + + + def tags + @tags ||= video_stream["tags"] || {} + end + + def video_stream + @video_stream ||= streams.detect { |stream| stream["codec_type"] == "video" } || {} + end + + def streams + probe["streams"] || [] + end + + def probe + download_blob_to_tempfile { |file| probe_from(file) } + end + + def probe_from(file) + IO.popen([ ffprobe_path, "-print_format", "json", "-show_streams", "-v", "error", file.path ]) do |output| + JSON.parse(output.read) + end + rescue Errno::ENOENT + logger.info "Skipping video analysis because ffmpeg isn't installed" + {} + end + + def ffprobe_path + ActiveStorage.paths[:ffprobe] || "ffprobe" + end + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/attached.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/attached.rb new file mode 100644 index 00000000..c08fd566 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/attached.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "action_dispatch" +require "action_dispatch/http/upload" +require "active_support/core_ext/module/delegation" + +module ActiveStorage + # Abstract base class for the concrete ActiveStorage::Attached::One and ActiveStorage::Attached::Many + # classes that both provide proxy access to the blob association for a record. + class Attached + attr_reader :name, :record, :dependent + + def initialize(name, record, dependent:) + @name, @record, @dependent = name, record, dependent + end + + private + def create_blob_from(attachable) + case attachable + when ActiveStorage::Blob + attachable + when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile + ActiveStorage::Blob.create_after_upload! \ + io: attachable.open, + filename: attachable.original_filename, + content_type: attachable.content_type + when Hash + ActiveStorage::Blob.create_after_upload!(attachable) + when String + ActiveStorage::Blob.find_signed(attachable) + else + nil + end + end + end +end + +require "active_storage/attached/one" +require "active_storage/attached/many" +require "active_storage/attached/macros" diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/attached/macros.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/attached/macros.rb new file mode 100644 index 00000000..819f00cc --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/attached/macros.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +module ActiveStorage + # Provides the class-level DSL for declaring that an Active Record model has attached blobs. + module Attached::Macros + # Specifies the relation between a single attachment and the model. + # + # class User < ActiveRecord::Base + # has_one_attached :avatar + # end + # + # There is no column defined on the model side, Active Storage takes + # care of the mapping between your records and the attachment. + # + # To avoid N+1 queries, you can include the attached blobs in your query like so: + # + # User.with_attached_avatar + # + # Under the covers, this relationship is implemented as a +has_one+ association to a + # ActiveStorage::Attachment record and a +has_one-through+ association to a + # ActiveStorage::Blob record. These associations are available as +avatar_attachment+ + # and +avatar_blob+. But you shouldn't need to work with these associations directly in + # most circumstances. + # + # The system has been designed to having you go through the ActiveStorage::Attached::One + # proxy that provides the dynamic proxy to the associations and factory methods, like +attach+. + # + # If the +:dependent+ option isn't set, the attachment will be purged + # (i.e. destroyed) whenever the record is destroyed. + def has_one_attached(name, dependent: :purge_later) + class_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{name} + @active_storage_attached_#{name} ||= ActiveStorage::Attached::One.new("#{name}", self, dependent: #{dependent == :purge_later ? ":purge_later" : "false"}) + end + + def #{name}=(attachable) + #{name}.attach(attachable) + end + CODE + + has_one :"#{name}_attachment", -> { where(name: name) }, class_name: "ActiveStorage::Attachment", as: :record, inverse_of: :record, dependent: false + has_one :"#{name}_blob", through: :"#{name}_attachment", class_name: "ActiveStorage::Blob", source: :blob + + scope :"with_attached_#{name}", -> { includes("#{name}_attachment": :blob) } + + if dependent == :purge_later + after_destroy_commit { public_send(name).purge_later } + else + before_destroy { public_send(name).detach } + end + end + + # Specifies the relation between multiple attachments and the model. + # + # class Gallery < ActiveRecord::Base + # has_many_attached :photos + # end + # + # There are no columns defined on the model side, Active Storage takes + # care of the mapping between your records and the attachments. + # + # To avoid N+1 queries, you can include the attached blobs in your query like so: + # + # Gallery.where(user: Current.user).with_attached_photos + # + # Under the covers, this relationship is implemented as a +has_many+ association to a + # ActiveStorage::Attachment record and a +has_many-through+ association to a + # ActiveStorage::Blob record. These associations are available as +photos_attachments+ + # and +photos_blobs+. But you shouldn't need to work with these associations directly in + # most circumstances. + # + # The system has been designed to having you go through the ActiveStorage::Attached::Many + # proxy that provides the dynamic proxy to the associations and factory methods, like +#attach+. + # + # If the +:dependent+ option isn't set, all the attachments will be purged + # (i.e. destroyed) whenever the record is destroyed. + def has_many_attached(name, dependent: :purge_later) + class_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{name} + @active_storage_attached_#{name} ||= ActiveStorage::Attached::Many.new("#{name}", self, dependent: #{dependent == :purge_later ? ":purge_later" : "false"}) + end + + def #{name}=(attachables) + #{name}.attach(attachables) + end + CODE + + has_many :"#{name}_attachments", -> { where(name: name) }, as: :record, class_name: "ActiveStorage::Attachment", inverse_of: :record, dependent: false do + def purge + each(&:purge) + reset + end + + def purge_later + each(&:purge_later) + reset + end + end + has_many :"#{name}_blobs", through: :"#{name}_attachments", class_name: "ActiveStorage::Blob", source: :blob + + scope :"with_attached_#{name}", -> { includes("#{name}_attachments": :blob) } + + if dependent == :purge_later + after_destroy_commit { public_send(name).purge_later } + else + before_destroy { public_send(name).detach } + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/attached/many.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/attached/many.rb new file mode 100644 index 00000000..d61acb6f --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/attached/many.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module ActiveStorage + # Decorated proxy object representing of multiple attachments to a model. + class Attached::Many < Attached + delegate_missing_to :attachments + + # Returns all the associated attachment records. + # + # All methods called on this proxy object that aren't listed here will automatically be delegated to +attachments+. + def attachments + record.public_send("#{name}_attachments") + end + + # Associates one or several attachments with the current record, saving them to the database. + # + # document.images.attach(params[:images]) # Array of ActionDispatch::Http::UploadedFile objects + # document.images.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload + # document.images.attach(io: File.open("/path/to/racecar.jpg"), filename: "racecar.jpg", content_type: "image/jpg") + # document.images.attach([ first_blob, second_blob ]) + def attach(*attachables) + attachables.flatten.collect do |attachable| + if record.new_record? + attachments.build(record: record, blob: create_blob_from(attachable)) + else + attachments.create!(record: record, blob: create_blob_from(attachable)) + end + end + end + + # Returns true if any attachments has been made. + # + # class Gallery < ActiveRecord::Base + # has_many_attached :photos + # end + # + # Gallery.new.photos.attached? # => false + def attached? + attachments.any? + end + + # Deletes associated attachments without purging them, leaving their respective blobs in place. + def detach + attachments.destroy_all if attached? + end + + ## + # :method: purge + # + # Directly purges each associated attachment (i.e. destroys the blobs and + # attachments and deletes the files on the service). + + + ## + # :method: purge_later + # + # Purges each associated attachment through the queuing system. + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/attached/one.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/attached/one.rb new file mode 100644 index 00000000..a582ed01 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/attached/one.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +module ActiveStorage + # Representation of a single attachment to a model. + class Attached::One < Attached + delegate_missing_to :attachment + + # Returns the associated attachment record. + # + # You don't have to call this method to access the attachment's methods as + # they are all available at the model level. + def attachment + record.public_send("#{name}_attachment") + end + + # Associates a given attachment with the current record, saving it to the database. + # + # person.avatar.attach(params[:avatar]) # ActionDispatch::Http::UploadedFile object + # person.avatar.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload + # person.avatar.attach(io: File.open("/path/to/face.jpg"), filename: "face.jpg", content_type: "image/jpg") + # person.avatar.attach(avatar_blob) # ActiveStorage::Blob object + def attach(attachable) + blob_was = blob if attached? + blob = create_blob_from(attachable) + + unless blob == blob_was + transaction do + detach + write_attachment build_attachment(blob: blob) + end + + blob_was.purge_later if blob_was && dependent == :purge_later + end + end + + # Returns +true+ if an attachment has been made. + # + # class User < ActiveRecord::Base + # has_one_attached :avatar + # end + # + # User.new.avatar.attached? # => false + def attached? + attachment.present? + end + + # Deletes the attachment without purging it, leaving its blob in place. + def detach + if attached? + attachment.destroy + write_attachment nil + end + end + + # Directly purges the attachment (i.e. destroys the blob and + # attachment and deletes the file on the service). + def purge + if attached? + attachment.purge + write_attachment nil + end + end + + # Purges the attachment through the queuing system. + def purge_later + if attached? + attachment.purge_later + end + end + + private + delegate :transaction, to: :record + + def build_attachment(blob:) + ActiveStorage::Attachment.new(record: record, name: name, blob: blob) + end + + def write_attachment(attachment) + record.public_send("#{name}_attachment=", attachment) + end + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/downloading.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/downloading.rb new file mode 100644 index 00000000..7c3d20ad --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/downloading.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require "tmpdir" + +module ActiveStorage + module Downloading + private + # Opens a new tempfile in #tempdir and copies blob data into it. Yields the tempfile. + def download_blob_to_tempfile #:doc: + open_tempfile_for_blob do |file| + download_blob_to file + yield file + end + end + + def open_tempfile_for_blob + tempfile = Tempfile.open([ "ActiveStorage", blob.filename.extension_with_delimiter ], tempdir) + + begin + yield tempfile + ensure + tempfile.close! + end + end + + # Efficiently downloads blob data into the given file. + def download_blob_to(file) #:doc: + file.binmode + blob.download { |chunk| file.write(chunk) } + file.flush + file.rewind + end + + # Returns the directory in which tempfiles should be opened. Defaults to +Dir.tmpdir+. + def tempdir #:doc: + Dir.tmpdir + end + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/engine.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/engine.rb new file mode 100644 index 00000000..a0a2982c --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/engine.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +require "rails" +require "active_storage" + +require "active_storage/previewer/poppler_pdf_previewer" +require "active_storage/previewer/mupdf_previewer" +require "active_storage/previewer/video_previewer" + +require "active_storage/analyzer/image_analyzer" +require "active_storage/analyzer/video_analyzer" + +module ActiveStorage + class Engine < Rails::Engine # :nodoc: + isolate_namespace ActiveStorage + + config.active_storage = ActiveSupport::OrderedOptions.new + config.active_storage.previewers = [ ActiveStorage::Previewer::PopplerPDFPreviewer, ActiveStorage::Previewer::MuPDFPreviewer, ActiveStorage::Previewer::VideoPreviewer ] + config.active_storage.analyzers = [ ActiveStorage::Analyzer::ImageAnalyzer, ActiveStorage::Analyzer::VideoAnalyzer ] + config.active_storage.paths = ActiveSupport::OrderedOptions.new + + config.active_storage.variable_content_types = %w( + image/png + image/gif + image/jpg + image/jpeg + image/vnd.adobe.photoshop + image/vnd.microsoft.icon + ) + + config.active_storage.content_types_to_serve_as_binary = %w( + text/html + text/javascript + image/svg+xml + application/postscript + application/x-shockwave-flash + text/xml + application/xml + application/xhtml+xml + application/mathml+xml + text/cache-manifest + ) + + config.active_storage.content_types_allowed_inline = %w( + image/png + image/gif + image/jpg + image/jpeg + image/vnd.adobe.photoshop + image/vnd.microsoft.icon + application/pdf + ) + + config.eager_load_namespaces << ActiveStorage + + initializer "active_storage.configs" do + config.after_initialize do |app| + ActiveStorage.logger = app.config.active_storage.logger || Rails.logger + ActiveStorage.queue = app.config.active_storage.queue + ActiveStorage.previewers = app.config.active_storage.previewers || [] + ActiveStorage.analyzers = app.config.active_storage.analyzers || [] + ActiveStorage.paths = app.config.active_storage.paths || {} + + ActiveStorage.variable_content_types = app.config.active_storage.variable_content_types || [] + ActiveStorage.content_types_to_serve_as_binary = app.config.active_storage.content_types_to_serve_as_binary || [] + ActiveStorage.content_types_allowed_inline = app.config.active_storage.content_types_allowed_inline || [] + ActiveStorage.binary_content_type = app.config.active_storage.binary_content_type || "application/octet-stream" + end + end + + initializer "active_storage.attached" do + require "active_storage/attached" + + ActiveSupport.on_load(:active_record) do + extend ActiveStorage::Attached::Macros + end + end + + initializer "active_storage.verifier" do + config.after_initialize do |app| + ActiveStorage.verifier = app.message_verifier("ActiveStorage") + end + end + + initializer "active_storage.services" do + ActiveSupport.on_load(:active_storage_blob) do + if config_choice = Rails.configuration.active_storage.service + configs = Rails.configuration.active_storage.service_configurations ||= begin + config_file = Pathname.new(Rails.root.join("config/storage.yml")) + raise("Couldn't find Active Storage configuration in #{config_file}") unless config_file.exist? + + require "yaml" + require "erb" + + YAML.load(ERB.new(config_file.read).result) || {} + rescue Psych::SyntaxError => e + raise "YAML syntax error occurred while parsing #{config_file}. " \ + "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \ + "Error: #{e.message}" + end + + ActiveStorage::Blob.service = + begin + ActiveStorage::Service.configure config_choice, configs + rescue => e + raise e, "Cannot load `Rails.config.active_storage.service`:\n#{e.message}", e.backtrace + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/errors.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/errors.rb new file mode 100644 index 00000000..f099b13f --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/errors.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module ActiveStorage + class InvariableError < StandardError; end + class UnpreviewableError < StandardError; end + class UnrepresentableError < StandardError; end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/gem_version.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/gem_version.rb new file mode 100644 index 00000000..b3930a36 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/gem_version.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActiveStorage + # Returns the version of the currently loaded Active Storage as a Gem::Version. + def self.gem_version + Gem::Version.new VERSION::STRING + end + + module VERSION + MAJOR = 5 + MINOR = 2 + TINY = 3 + PRE = nil + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/log_subscriber.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/log_subscriber.rb new file mode 100644 index 00000000..6c0b4c30 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/log_subscriber.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require "active_support/log_subscriber" + +module ActiveStorage + class LogSubscriber < ActiveSupport::LogSubscriber + def service_upload(event) + message = "Uploaded file to key: #{key_in(event)}" + message += " (checksum: #{event.payload[:checksum]})" if event.payload[:checksum] + info event, color(message, GREEN) + end + + def service_download(event) + info event, color("Downloaded file from key: #{key_in(event)}", BLUE) + end + + alias_method :service_streaming_download, :service_download + + def service_delete(event) + info event, color("Deleted file from key: #{key_in(event)}", RED) + end + + def service_delete_prefixed(event) + info event, color("Deleted files by key prefix: #{event.payload[:prefix]}", RED) + end + + def service_exist(event) + debug event, color("Checked if file exists at key: #{key_in(event)} (#{event.payload[:exist] ? "yes" : "no"})", BLUE) + end + + def service_url(event) + debug event, color("Generated URL for file at key: #{key_in(event)} (#{event.payload[:url]})", BLUE) + end + + def logger + ActiveStorage.logger + end + + private + def info(event, colored_message) + super log_prefix_for_service(event) + colored_message + end + + def debug(event, colored_message) + super log_prefix_for_service(event) + colored_message + end + + def log_prefix_for_service(event) + color " #{event.payload[:service]} Storage (#{event.duration.round(1)}ms) ", CYAN + end + + def key_in(event) + event.payload[:key] + end + end +end + +ActiveStorage::LogSubscriber.attach_to :active_storage diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/previewer.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/previewer.rb new file mode 100644 index 00000000..0d6e5ca3 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/previewer.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require "active_storage/downloading" + +module ActiveStorage + # This is an abstract base class for previewers, which generate images from blobs. See + # ActiveStorage::Previewer::MuPDFPreviewer and ActiveStorage::Previewer::VideoPreviewer for + # examples of concrete subclasses. + class Previewer + include Downloading + + attr_reader :blob + + # Implement this method in a concrete subclass. Have it return true when given a blob from which + # the previewer can generate an image. + def self.accept?(blob) + false + end + + def initialize(blob) + @blob = blob + end + + # Override this method in a concrete subclass. Have it yield an attachable preview image (i.e. + # anything accepted by ActiveStorage::Attached::One#attach). + def preview + raise NotImplementedError + end + + private + # Executes a system command, capturing its binary output in a tempfile. Yields the tempfile. + # + # Use this method to shell out to a system library (e.g. mupdf or ffmpeg) for preview image + # generation. The resulting tempfile can be used as the +:io+ value in an attachable Hash: + # + # def preview + # download_blob_to_tempfile do |input| + # draw "my-drawing-command", input.path, "--format", "png", "-" do |output| + # yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png" + # end + # end + # end + # + # The output tempfile is opened in the directory returned by ActiveStorage::Downloading#tempdir. + def draw(*argv) #:doc: + ActiveSupport::Notifications.instrument("preview.active_storage") do + open_tempfile_for_drawing do |file| + capture(*argv, to: file) + yield file + end + end + end + + def open_tempfile_for_drawing + tempfile = Tempfile.open("ActiveStorage", tempdir) + + begin + yield tempfile + ensure + tempfile.close! + end + end + + def capture(*argv, to:) + to.binmode + IO.popen(argv, err: File::NULL) { |out| IO.copy_stream(out, to) } + to.rewind + end + + def logger #:doc: + ActiveStorage.logger + end + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/previewer/mupdf_previewer.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/previewer/mupdf_previewer.rb new file mode 100644 index 00000000..ae02a488 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/previewer/mupdf_previewer.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module ActiveStorage + class Previewer::MuPDFPreviewer < Previewer + class << self + def accept?(blob) + blob.content_type == "application/pdf" && mutool_exists? + end + + def mutool_path + ActiveStorage.paths[:mutool] || "mutool" + end + + def mutool_exists? + return @mutool_exists unless @mutool_exists.nil? + + system mutool_path, out: File::NULL, err: File::NULL + + @mutool_exists = $?.exitstatus == 1 + end + end + + def preview + download_blob_to_tempfile do |input| + draw_first_page_from input do |output| + yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png" + end + end + end + + private + def draw_first_page_from(file, &block) + draw self.class.mutool_path, "draw", "-F", "png", "-o", "-", file.path, "1", &block + end + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/previewer/poppler_pdf_previewer.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/previewer/poppler_pdf_previewer.rb new file mode 100644 index 00000000..2a787362 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/previewer/poppler_pdf_previewer.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module ActiveStorage + class Previewer::PopplerPDFPreviewer < Previewer + class << self + def accept?(blob) + blob.content_type == "application/pdf" && pdftoppm_exists? + end + + def pdftoppm_path + ActiveStorage.paths[:pdftoppm] || "pdftoppm" + end + + def pdftoppm_exists? + return @pdftoppm_exists unless @pdftoppm_exists.nil? + + @pdftoppm_exists = system(pdftoppm_path, "-v", out: File::NULL, err: File::NULL) + end + end + + def preview + download_blob_to_tempfile do |input| + draw_first_page_from input do |output| + yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png" + end + end + end + + private + def draw_first_page_from(file, &block) + # use 72 dpi to match thumbnail dimesions of the PDF + draw self.class.pdftoppm_path, "-singlefile", "-r", "72", "-png", file.path, &block + end + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/previewer/video_previewer.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/previewer/video_previewer.rb new file mode 100644 index 00000000..2f28a3d3 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/previewer/video_previewer.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module ActiveStorage + class Previewer::VideoPreviewer < Previewer + def self.accept?(blob) + blob.video? + end + + def preview + download_blob_to_tempfile do |input| + draw_relevant_frame_from input do |output| + yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png" + end + end + end + + private + def draw_relevant_frame_from(file, &block) + draw ffmpeg_path, "-i", file.path, "-y", "-vcodec", "png", + "-vf", "thumbnail", "-vframes", "1", "-f", "image2", "-", &block + end + + def ffmpeg_path + ActiveStorage.paths[:ffmpeg] || "ffmpeg" + end + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/service.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/service.rb new file mode 100644 index 00000000..399893c8 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/service.rb @@ -0,0 +1,138 @@ +# frozen_string_literal: true + +require "active_storage/log_subscriber" + +module ActiveStorage + class IntegrityError < StandardError; end + + # Abstract class serving as an interface for concrete services. + # + # The available services are: + # + # * +Disk+, to manage attachments saved directly on the hard drive. + # * +GCS+, to manage attachments through Google Cloud Storage. + # * +S3+, to manage attachments through Amazon S3. + # * +AzureStorage+, to manage attachments through Microsoft Azure Storage. + # * +Mirror+, to be able to use several services to manage attachments. + # + # Inside a Rails application, you can set-up your services through the + # generated config/storage.yml file and reference one + # of the aforementioned constant under the +service+ key. For example: + # + # local: + # service: Disk + # root: <%= Rails.root.join("storage") %> + # + # You can checkout the service's constructor to know which keys are required. + # + # Then, in your application's configuration, you can specify the service to + # use like this: + # + # config.active_storage.service = :local + # + # If you are using Active Storage outside of a Ruby on Rails application, you + # can configure the service to use like this: + # + # ActiveStorage::Blob.service = ActiveStorage::Service.configure( + # :Disk, + # root: Pathname("/foo/bar/storage") + # ) + class Service + extend ActiveSupport::Autoload + autoload :Configurator + + class_attribute :url_expires_in, default: 5.minutes + + class << self + # Configure an Active Storage service by name from a set of configurations, + # typically loaded from a YAML file. The Active Storage engine uses this + # to set the global Active Storage service when the app boots. + def configure(service_name, configurations) + Configurator.build(service_name, configurations) + end + + # Override in subclasses that stitch together multiple services and hence + # need to build additional services using the configurator. + # + # Passes the configurator and all of the service's config as keyword args. + # + # See MirrorService for an example. + def build(configurator:, service: nil, **service_config) #:nodoc: + new(**service_config) + end + end + + # Upload the +io+ to the +key+ specified. If a +checksum+ is provided, the service will + # ensure a match when the upload has completed or raise an ActiveStorage::IntegrityError. + def upload(key, io, checksum: nil, **options) + raise NotImplementedError + end + + # Update metadata for the file identified by +key+ in the service. + # Override in subclasses only if the service needs to store specific + # metadata that has to be updated upon identification. + def update_metadata(key, **metadata) + end + + # Return the content of the file at the +key+. + def download(key) + raise NotImplementedError + end + + # Return the partial content in the byte +range+ of the file at the +key+. + def download_chunk(key, range) + raise NotImplementedError + end + + # Delete the file at the +key+. + def delete(key) + raise NotImplementedError + end + + # Delete files at keys starting with the +prefix+. + def delete_prefixed(prefix) + raise NotImplementedError + end + + # Return +true+ if a file exists at the +key+. + def exist?(key) + raise NotImplementedError + end + + # Returns a signed, temporary URL for the file at the +key+. The URL will be valid for the amount + # of seconds specified in +expires_in+. You most also provide the +disposition+ (+:inline+ or +:attachment+), + # +filename+, and +content_type+ that you wish the file to be served with on request. + def url(key, expires_in:, disposition:, filename:, content_type:) + raise NotImplementedError + end + + # Returns a signed, temporary URL that a direct upload file can be PUT to on the +key+. + # The URL will be valid for the amount of seconds specified in +expires_in+. + # You must also provide the +content_type+, +content_length+, and +checksum+ of the file + # that will be uploaded. All these attributes will be validated by the service upon upload. + def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:) + raise NotImplementedError + end + + # Returns a Hash of headers for +url_for_direct_upload+ requests. + def headers_for_direct_upload(key, filename:, content_type:, content_length:, checksum:) + {} + end + + private + def instrument(operation, payload = {}, &block) + ActiveSupport::Notifications.instrument( + "service_#{operation}.active_storage", + payload.merge(service: service_name), &block) + end + + def service_name + # ActiveStorage::Service::DiskService => Disk + self.class.name.split("::").third.remove("Service") + end + + def content_disposition_with(type: "inline", filename:) + (type.to_s.presence_in(%w( attachment inline )) || "inline") + "; #{filename.parameters}" + end + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/service/azure_storage_service.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/service/azure_storage_service.rb new file mode 100644 index 00000000..1df416cf --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/service/azure_storage_service.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: true + +require "active_support/core_ext/numeric/bytes" +require "azure/storage" +require "azure/storage/core/auth/shared_access_signature" + +module ActiveStorage + # Wraps the Microsoft Azure Storage Blob Service as an Active Storage service. + # See ActiveStorage::Service for the generic API documentation that applies to all services. + class Service::AzureStorageService < Service + attr_reader :client, :blobs, :container, :signer + + def initialize(storage_account_name:, storage_access_key:, container:) + @client = Azure::Storage::Client.create(storage_account_name: storage_account_name, storage_access_key: storage_access_key) + @signer = Azure::Storage::Core::Auth::SharedAccessSignature.new(storage_account_name, storage_access_key) + @blobs = client.blob_client + @container = container + end + + def upload(key, io, checksum: nil, **) + instrument :upload, key: key, checksum: checksum do + begin + blobs.create_block_blob(container, key, IO.try_convert(io) || io, content_md5: checksum) + rescue Azure::Core::Http::HTTPError + raise ActiveStorage::IntegrityError + end + end + end + + def download(key, &block) + if block_given? + instrument :streaming_download, key: key do + stream(key, &block) + end + else + instrument :download, key: key do + _, io = blobs.get_blob(container, key) + io.force_encoding(Encoding::BINARY) + end + end + end + + def download_chunk(key, range) + instrument :download_chunk, key: key, range: range do + _, io = blobs.get_blob(container, key, start_range: range.begin, end_range: range.exclude_end? ? range.end - 1 : range.end) + io.force_encoding(Encoding::BINARY) + end + end + + def delete(key) + instrument :delete, key: key do + begin + blobs.delete_blob(container, key) + rescue Azure::Core::Http::HTTPError + # Ignore files already deleted + end + end + end + + def delete_prefixed(prefix) + instrument :delete_prefixed, prefix: prefix do + marker = nil + + loop do + results = blobs.list_blobs(container, prefix: prefix, marker: marker) + + results.each do |blob| + blobs.delete_blob(container, blob.name) + end + + break unless marker = results.continuation_token.presence + end + end + end + + def exist?(key) + instrument :exist, key: key do |payload| + answer = blob_for(key).present? + payload[:exist] = answer + answer + end + end + + def url(key, expires_in:, filename:, disposition:, content_type:) + instrument :url, key: key do |payload| + generated_url = signer.signed_uri( + uri_for(key), false, + service: "b", + permissions: "r", + expiry: format_expiry(expires_in), + content_disposition: content_disposition_with(type: disposition, filename: filename), + content_type: content_type + ).to_s + + payload[:url] = generated_url + + generated_url + end + end + + def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:) + instrument :url, key: key do |payload| + generated_url = signer.signed_uri( + uri_for(key), false, + service: "b", + permissions: "rw", + expiry: format_expiry(expires_in) + ).to_s + + payload[:url] = generated_url + + generated_url + end + end + + def headers_for_direct_upload(key, content_type:, checksum:, **) + { "Content-Type" => content_type, "Content-MD5" => checksum, "x-ms-blob-type" => "BlockBlob" } + end + + private + def uri_for(key) + blobs.generate_uri("#{container}/#{key}") + end + + def blob_for(key) + blobs.get_blob_properties(container, key) + rescue Azure::Core::Http::HTTPError + false + end + + def format_expiry(expires_in) + expires_in ? Time.now.utc.advance(seconds: expires_in).iso8601 : nil + end + + # Reads the object for the given key in chunks, yielding each to the block. + def stream(key) + blob = blob_for(key) + + chunk_size = 5.megabytes + offset = 0 + + while offset < blob.properties[:content_length] + _, chunk = blobs.get_blob(container, key, start_range: offset, end_range: offset + chunk_size - 1) + yield chunk.force_encoding(Encoding::BINARY) + offset += chunk_size + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/service/configurator.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/service/configurator.rb new file mode 100644 index 00000000..39951fd0 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/service/configurator.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module ActiveStorage + class Service::Configurator #:nodoc: + attr_reader :configurations + + def self.build(service_name, configurations) + new(configurations).build(service_name) + end + + def initialize(configurations) + @configurations = configurations.deep_symbolize_keys + end + + def build(service_name) + config = config_for(service_name.to_sym) + resolve(config.fetch(:service)).build(**config, configurator: self) + end + + private + def config_for(name) + configurations.fetch name do + raise "Missing configuration for the #{name.inspect} Active Storage service. Configurations available for #{configurations.keys.inspect}" + end + end + + def resolve(class_name) + require "active_storage/service/#{class_name.to_s.underscore}_service" + ActiveStorage::Service.const_get(:"#{class_name}Service") + end + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/service/disk_service.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/service/disk_service.rb new file mode 100644 index 00000000..e67f7633 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/service/disk_service.rb @@ -0,0 +1,154 @@ +# frozen_string_literal: true + +require "fileutils" +require "pathname" +require "digest/md5" +require "active_support/core_ext/numeric/bytes" + +module ActiveStorage + # Wraps a local disk path as an Active Storage service. See ActiveStorage::Service for the generic API + # documentation that applies to all services. + class Service::DiskService < Service + attr_reader :root + + def initialize(root:) + @root = root + end + + def upload(key, io, checksum: nil, **) + instrument :upload, key: key, checksum: checksum do + IO.copy_stream(io, make_path_for(key)) + ensure_integrity_of(key, checksum) if checksum + end + end + + def download(key) + if block_given? + instrument :streaming_download, key: key do + File.open(path_for(key), "rb") do |file| + while data = file.read(5.megabytes) + yield data + end + end + end + else + instrument :download, key: key do + File.binread path_for(key) + end + end + end + + def download_chunk(key, range) + instrument :download_chunk, key: key, range: range do + File.open(path_for(key), "rb") do |file| + file.seek range.begin + file.read range.size + end + end + end + + def delete(key) + instrument :delete, key: key do + begin + File.delete path_for(key) + rescue Errno::ENOENT + # Ignore files already deleted + end + end + end + + def delete_prefixed(prefix) + instrument :delete_prefixed, prefix: prefix do + Dir.glob(path_for("#{prefix}*")).each do |path| + FileUtils.rm_rf(path) + end + end + end + + def exist?(key) + instrument :exist, key: key do |payload| + answer = File.exist? path_for(key) + payload[:exist] = answer + answer + end + end + + def url(key, expires_in:, filename:, disposition:, content_type:) + instrument :url, key: key do |payload| + content_disposition = content_disposition_with(type: disposition, filename: filename) + verified_key_with_expiration = ActiveStorage.verifier.generate( + { + key: key, + disposition: content_disposition, + content_type: content_type + }, + { expires_in: expires_in, + purpose: :blob_key } + ) + + generated_url = url_helpers.rails_disk_service_url(verified_key_with_expiration, + host: current_host, + disposition: content_disposition, + content_type: content_type, + filename: filename + ) + payload[:url] = generated_url + + generated_url + end + end + + def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:) + instrument :url, key: key do |payload| + verified_token_with_expiration = ActiveStorage.verifier.generate( + { + key: key, + content_type: content_type, + content_length: content_length, + checksum: checksum + }, + { expires_in: expires_in, + purpose: :blob_token } + ) + + generated_url = url_helpers.update_rails_disk_service_url(verified_token_with_expiration, host: current_host) + + payload[:url] = generated_url + + generated_url + end + end + + def headers_for_direct_upload(key, content_type:, **) + { "Content-Type" => content_type } + end + + def path_for(key) #:nodoc: + File.join root, folder_for(key), key + end + + private + def folder_for(key) + [ key[0..1], key[2..3] ].join("/") + end + + def make_path_for(key) + path_for(key).tap { |path| FileUtils.mkdir_p File.dirname(path) } + end + + def ensure_integrity_of(key, checksum) + unless Digest::MD5.file(path_for(key)).base64digest == checksum + delete key + raise ActiveStorage::IntegrityError + end + end + + def url_helpers + @url_helpers ||= Rails.application.routes.url_helpers + end + + def current_host + ActiveStorage::Current.host + end + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/service/gcs_service.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/service/gcs_service.rb new file mode 100644 index 00000000..2ad8b9cc --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/service/gcs_service.rb @@ -0,0 +1,139 @@ +# frozen_string_literal: true + +gem "google-cloud-storage", "~> 1.8" + +require "google/cloud/storage" +require "net/http" + +require "active_support/core_ext/object/to_query" + +module ActiveStorage + # Wraps the Google Cloud Storage as an Active Storage service. See ActiveStorage::Service for the generic API + # documentation that applies to all services. + class Service::GCSService < Service + def initialize(**config) + @config = config + end + + def upload(key, io, checksum: nil, content_type: nil, disposition: nil, filename: nil) + instrument :upload, key: key, checksum: checksum do + begin + # GCS's signed URLs don't include params such as response-content-type response-content_disposition + # in the signature, which means an attacker can modify them and bypass our effort to force these to + # binary and attachment when the file's content type requires it. The only way to force them is to + # store them as object's metadata. + content_disposition = content_disposition_with(type: disposition, filename: filename) if disposition && filename + bucket.create_file(io, key, md5: checksum, content_type: content_type, content_disposition: content_disposition) + rescue Google::Cloud::InvalidArgumentError + raise ActiveStorage::IntegrityError + end + end + end + + def update_metadata(key, content_type:, disposition: nil, filename: nil) + instrument :update_metadata, key: key, content_type: content_type, disposition: disposition do + file_for(key).update do |file| + file.content_type = content_type + file.content_disposition = content_disposition_with(type: disposition, filename: filename) if disposition && filename + end + end + end + + # FIXME: Download in chunks when given a block. + def download(key) + instrument :download, key: key do + io = file_for(key).download + io.rewind + + if block_given? + yield io.string + else + io.string + end + end + end + + def download_chunk(key, range) + instrument :download_chunk, key: key, range: range do + file = file_for(key) + uri = URI(file.signed_url(expires: 30.seconds)) + + Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |client| + client.get(uri, "Range" => "bytes=#{range.begin}-#{range.exclude_end? ? range.end - 1 : range.end}").body + end + end + end + + def delete(key) + instrument :delete, key: key do + begin + file_for(key).delete + rescue Google::Cloud::NotFoundError + # Ignore files already deleted + end + end + end + + def delete_prefixed(prefix) + instrument :delete_prefixed, prefix: prefix do + bucket.files(prefix: prefix).all do |file| + begin + file.delete + rescue Google::Cloud::NotFoundError + # Ignore concurrently-deleted files + end + end + end + end + + def exist?(key) + instrument :exist, key: key do |payload| + answer = file_for(key).exists? + payload[:exist] = answer + answer + end + end + + def url(key, expires_in:, filename:, content_type:, disposition:) + instrument :url, key: key do |payload| + generated_url = file_for(key).signed_url expires: expires_in, query: { + "response-content-disposition" => content_disposition_with(type: disposition, filename: filename), + "response-content-type" => content_type + } + + payload[:url] = generated_url + + generated_url + end + end + + def url_for_direct_upload(key, expires_in:, checksum:, **) + instrument :url, key: key do |payload| + generated_url = bucket.signed_url key, method: "PUT", expires: expires_in, content_md5: checksum + + payload[:url] = generated_url + + generated_url + end + end + + def headers_for_direct_upload(key, checksum:, **) + { "Content-MD5" => checksum } + end + + private + attr_reader :config + + def file_for(key) + bucket.file(key, skip_lookup: true) + end + + def bucket + @bucket ||= client.bucket(config.fetch(:bucket)) + end + + def client + @client ||= Google::Cloud::Storage.new(config.except(:bucket)) + end + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/service/mirror_service.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/service/mirror_service.rb new file mode 100644 index 00000000..aa41df30 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/service/mirror_service.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/delegation" + +module ActiveStorage + # Wraps a set of mirror services and provides a single ActiveStorage::Service object that will all + # have the files uploaded to them. A +primary+ service is designated to answer calls to +download+, +exists?+, + # and +url+. + class Service::MirrorService < Service + attr_reader :primary, :mirrors + + delegate :download, :download_chunk, :exist?, :url, :path_for, to: :primary + + # Stitch together from named services. + def self.build(primary:, mirrors:, configurator:, **options) #:nodoc: + new \ + primary: configurator.build(primary), + mirrors: mirrors.collect { |name| configurator.build name } + end + + def initialize(primary:, mirrors:) + @primary, @mirrors = primary, mirrors + end + + # Upload the +io+ to the +key+ specified to all services. If a +checksum+ is provided, all services will + # ensure a match when the upload has completed or raise an ActiveStorage::IntegrityError. + def upload(key, io, checksum: nil, **options) + each_service.collect do |service| + service.upload key, io.tap(&:rewind), checksum: checksum, **options + end + end + + # Delete the file at the +key+ on all services. + def delete(key) + perform_across_services :delete, key + end + + # Delete files at keys starting with the +prefix+ on all services. + def delete_prefixed(prefix) + perform_across_services :delete_prefixed, prefix + end + + private + def each_service(&block) + [ primary, *mirrors ].each(&block) + end + + def perform_across_services(method, *args) + # FIXME: Convert to be threaded + each_service.collect do |service| + service.public_send method, *args + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/service/s3_service.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/service/s3_service.rb new file mode 100644 index 00000000..cc63d9cd --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/service/s3_service.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +require "aws-sdk-s3" +require "active_support/core_ext/numeric/bytes" + +module ActiveStorage + # Wraps the Amazon Simple Storage Service (S3) as an Active Storage service. + # See ActiveStorage::Service for the generic API documentation that applies to all services. + class Service::S3Service < Service + attr_reader :client, :bucket, :upload_options + + def initialize(bucket:, upload: {}, **options) + @client = Aws::S3::Resource.new(**options) + @bucket = @client.bucket(bucket) + + @upload_options = upload + end + + def upload(key, io, checksum: nil, **) + instrument :upload, key: key, checksum: checksum do + begin + object_for(key).put(upload_options.merge(body: io, content_md5: checksum)) + rescue Aws::S3::Errors::BadDigest + raise ActiveStorage::IntegrityError + end + end + end + + def download(key, &block) + if block_given? + instrument :streaming_download, key: key do + stream(key, &block) + end + else + instrument :download, key: key do + object_for(key).get.body.string.force_encoding(Encoding::BINARY) + end + end + end + + def download_chunk(key, range) + instrument :download_chunk, key: key, range: range do + object_for(key).get(range: "bytes=#{range.begin}-#{range.exclude_end? ? range.end - 1 : range.end}").body.read.force_encoding(Encoding::BINARY) + end + end + + def delete(key) + instrument :delete, key: key do + object_for(key).delete + end + end + + def delete_prefixed(prefix) + instrument :delete_prefixed, prefix: prefix do + bucket.objects(prefix: prefix).batch_delete! + end + end + + def exist?(key) + instrument :exist, key: key do |payload| + answer = object_for(key).exists? + payload[:exist] = answer + answer + end + end + + def url(key, expires_in:, filename:, disposition:, content_type:) + instrument :url, key: key do |payload| + generated_url = object_for(key).presigned_url :get, expires_in: expires_in.to_i, + response_content_disposition: content_disposition_with(type: disposition, filename: filename), + response_content_type: content_type + + payload[:url] = generated_url + + generated_url + end + end + + def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:) + instrument :url, key: key do |payload| + generated_url = object_for(key).presigned_url :put, expires_in: expires_in.to_i, + content_type: content_type, content_length: content_length, content_md5: checksum + + payload[:url] = generated_url + + generated_url + end + end + + def headers_for_direct_upload(key, content_type:, checksum:, **) + { "Content-Type" => content_type, "Content-MD5" => checksum } + end + + private + def object_for(key) + bucket.object(key) + end + + # Reads the object for the given key in chunks, yielding each to the block. + def stream(key) + object = object_for(key) + + chunk_size = 5.megabytes + offset = 0 + + while offset < object.content_length + yield object.get(range: "bytes=#{offset}-#{offset + chunk_size - 1}").body.read.force_encoding(Encoding::BINARY) + offset += chunk_size + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/version.rb b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/version.rb new file mode 100644 index 00000000..4b663183 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/active_storage/version.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require_relative "gem_version" + +module ActiveStorage + # Returns the version of the currently loaded ActiveStorage as a Gem::Version + def self.version + gem_version + end +end diff --git a/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/tasks/activestorage.rake b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/tasks/activestorage.rake new file mode 100644 index 00000000..ac254d71 --- /dev/null +++ b/path/ruby/2.6.0/gems/activestorage-5.2.3/lib/tasks/activestorage.rake @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +namespace :active_storage do + # Prevent migration installation task from showing up twice. + Rake::Task["install:migrations"].clear_comments + + desc "Copy over the migration needed to the application" + task install: :environment do + if Rake::Task.task_defined?("active_storage:install:migrations") + Rake::Task["active_storage:install:migrations"].invoke + else + Rake::Task["app:active_storage:install:migrations"].invoke + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/CHANGELOG.md b/path/ruby/2.6.0/gems/activesupport-5.2.3/CHANGELOG.md new file mode 100644 index 00000000..7f26ebf9 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/CHANGELOG.md @@ -0,0 +1,602 @@ +## Rails 5.2.3 (March 27, 2019) ## + +* Add `ActiveSupport::HashWithIndifferentAccess#assoc`. + + `assoc` can now be called with either a string or a symbol. + + *Stefan Schüßler* + +* Fix `String#safe_constantize` throwing a `LoadError` for incorrectly cased constant references. + + *Keenan Brock* + +* Allow Range#=== and Range#cover? on Range + + `Range#cover?` can now accept a range argument like `Range#include?` and + `Range#===`. `Range#===` works correctly on Ruby 2.6. `Range#include?` is moved + into a new file, with these two methods. + + *utilum* + +* If the same block is `included` multiple times for a Concern, an exception is no longer raised. + + *Mark J. Titorenko*, *Vlad Bokov* + + +## Rails 5.2.2.1 (March 11, 2019) ## + +* No changes. + + +## Rails 5.2.2 (December 04, 2018) ## + +* Fix bug where `#to_options` for `ActiveSupport::HashWithIndifferentAccess` + would not act as alias for `#symbolize_keys`. + + *Nick Weiland* + +* Improve the logic that detects non-autoloaded constants. + + *Jan Habermann*, *Xavier Noria* + +* Fix bug where `URI.unescape` would fail with mixed Unicode/escaped character input: + + URI.unescape("\xe3\x83\x90") # => "バ" + URI.unescape("%E3%83%90") # => "バ" + URI.unescape("\xe3\x83\x90%E3%83%90") # => Encoding::CompatibilityError + + *Ashe Connor*, *Aaron Patterson* + + +## Rails 5.2.1.1 (November 27, 2018) ## + +* No changes. + + +## Rails 5.2.1 (August 07, 2018) ## + +* Redis cache store: `delete_matched` no longer blocks the Redis server. + (Switches from evaled Lua to a batched SCAN + DEL loop.) + + *Gleb Mazovetskiy* + +* Fix bug where `ActiveSupport::Timezone.all` would fail when tzinfo data for + any timezone defined in `ActiveSupport::TimeZone::MAPPING` is missing. + + *Dominik Sander* + +* Fix bug where `ActiveSupport::Cache` will massively inflate the storage + size when compression is enabled (which is true by default). This patch + does not attempt to repair existing data: please manually flush the cache + to clear out the problematic entries. + + *Godfrey Chan* + +* Fix `ActiveSupport::Cache#read_multi` bug with local cache enabled that was + returning instances of `ActiveSupport::Cache::Entry` instead of the raw values. + + *Jason Lee* + + +## Rails 5.2.0 (April 09, 2018) ## + +* Caching: MemCache and Redis `read_multi` and `fetch_multi` speedup. + Read from the local in-memory cache before consulting the backend. + + *Gabriel Sobrinho* + +* Return all mappings for a timezone identifier in `country_zones`. + + Some timezones like `Europe/London` have multiple mappings in + `ActiveSupport::TimeZone::MAPPING` so return all of them instead + of the first one found by using `Hash#value`. e.g: + + # Before + ActiveSupport::TimeZone.country_zones("GB") # => ["Edinburgh"] + + # After + ActiveSupport::TimeZone.country_zones("GB") # => ["Edinburgh", "London"] + + Fixes #31668. + + *Andrew White* + +* Add support for connection pooling on RedisCacheStore. + + *fatkodima* + +* Support hash as first argument in `assert_difference`. This allows to specify multiple + numeric differences in the same assertion. + + assert_difference ->{ Article.count } => 1, ->{ Post.count } => 2 + + *Julien Meichelbeck* + +* Add missing instrumentation for `read_multi` in `ActiveSupport::Cache::Store`. + + *Ignatius Reza Lesmana* + +* `assert_changes` will always assert that the expression changes, + regardless of `from:` and `to:` argument combinations. + + *Daniel Ma* + +* Use SHA-1 to generate non-sensitive digests, such as the ETag header. + + Enabled by default for new apps; upgrading apps can opt in by setting + `config.active_support.use_sha1_digests = true`. + + *Dmitri Dolguikh*, *Eugene Kenny* + +* Changed default behaviour of `ActiveSupport::SecurityUtils.secure_compare`, + to make it not leak length information even for variable length string. + + Renamed old `ActiveSupport::SecurityUtils.secure_compare` to `fixed_length_secure_compare`, + and started raising `ArgumentError` in case of length mismatch of passed strings. + + *Vipul A M* + +* Make `ActiveSupport::TimeZone.all` return only time zones that are in + `ActiveSupport::TimeZone::MAPPING`. + + Fixes #7245. + + *Chris LaRose* + +* MemCacheStore: Support expiring counters. + + Pass `expires_in: [seconds]` to `#increment` and `#decrement` options + to set the Memcached TTL (time-to-live) if the counter doesn't exist. + If the counter exists, Memcached doesn't extend its expiry when it's + incremented or decremented. + + ``` + Rails.cache.increment("my_counter", 1, expires_in: 2.minutes) + ``` + + *Takumasa Ochi* + +* Handle `TZInfo::AmbiguousTime` errors. + + Make `ActiveSupport::TimeWithZone` match Ruby's handling of ambiguous + times by choosing the later period, e.g. + + Ruby: + ``` + ENV["TZ"] = "Europe/Moscow" + Time.local(2014, 10, 26, 1, 0, 0) # => 2014-10-26 01:00:00 +0300 + ``` + + Before: + ``` + >> "2014-10-26 01:00:00".in_time_zone("Moscow") + TZInfo::AmbiguousTime: 26/10/2014 01:00 is an ambiguous local time. + ``` + + After: + ``` + >> "2014-10-26 01:00:00".in_time_zone("Moscow") + => Sun, 26 Oct 2014 01:00:00 MSK +03:00 + ``` + + Fixes #17395. + + *Andrew White* + +* Redis cache store. + + ``` + # Defaults to `redis://localhost:6379/0`. Only use for dev/test. + config.cache_store = :redis_cache_store + + # Supports all common cache store options (:namespace, :compress, + # :compress_threshold, :expires_in, :race_condition_ttl) and all + # Redis options. + cache_password = Rails.application.secrets.redis_cache_password + config.cache_store = :redis_cache_store, driver: :hiredis, + namespace: 'myapp-cache', compress: true, timeout: 1, + url: "redis://:#{cache_password}@myapp-cache-1:6379/0" + + # Supports Redis::Distributed with multiple hosts + config.cache_store = :redis_cache_store, driver: :hiredis + namespace: 'myapp-cache', compress: true, + url: %w[ + redis://myapp-cache-1:6379/0 + redis://myapp-cache-1:6380/0 + redis://myapp-cache-2:6379/0 + redis://myapp-cache-2:6380/0 + redis://myapp-cache-3:6379/0 + redis://myapp-cache-3:6380/0 + ] + + # Or pass a builder block + config.cache_store = :redis_cache_store, + namespace: 'myapp-cache', compress: true, + redis: -> { Redis.new … } + ``` + + Deployment note: Take care to use a *dedicated Redis cache* rather + than pointing this at your existing Redis server. It won't cope well + with mixed usage patterns and it won't expire cache entries by default. + + Redis cache server setup guide: https://redis.io/topics/lru-cache + + *Jeremy Daer* + +* Cache: Enable compression by default for values > 1kB. + + Compression has long been available, but opt-in and at a 16kB threshold. + It wasn't enabled by default due to CPU cost. Today it's cheap and typical + cache data is eminently compressible, such as HTML or JSON fragments. + Compression dramatically reduces Memcached/Redis mem usage, which means + the same cache servers can store more data, which means higher hit rates. + + To disable compression, pass `compress: false` to the initializer. + + *Jeremy Daer* + +* Allow `Range#include?` on TWZ ranges. + + In #11474 we prevented TWZ ranges being iterated over which matched + Ruby's handling of Time ranges and as a consequence `include?` + stopped working with both Time ranges and TWZ ranges. However in + ruby/ruby@b061634 support was added for `include?` to use `cover?` + for 'linear' objects. Since we have no way of making Ruby consider + TWZ instances as 'linear' we have to override `Range#include?`. + + Fixes #30799. + + *Andrew White* + +* Fix acronym support in `humanize`. + + Acronym inflections are stored with lowercase keys in the hash but + the match wasn't being lowercased before being looked up in the hash. + This shouldn't have any performance impact because before it would + fail to find the acronym and perform the `downcase` operation anyway. + + Fixes #31052. + + *Andrew White* + +* Add same method signature for `Time#prev_year` and `Time#next_year` + in accordance with `Date#prev_year`, `Date#next_year`. + + Allows pass argument for `Time#prev_year` and `Time#next_year`. + + Before: + ``` + Time.new(2017, 9, 16, 17, 0).prev_year # => 2016-09-16 17:00:00 +0300 + Time.new(2017, 9, 16, 17, 0).prev_year(1) + # => ArgumentError: wrong number of arguments (given 1, expected 0) + + Time.new(2017, 9, 16, 17, 0).next_year # => 2018-09-16 17:00:00 +0300 + Time.new(2017, 9, 16, 17, 0).next_year(1) + # => ArgumentError: wrong number of arguments (given 1, expected 0) + ``` + + After: + ``` + Time.new(2017, 9, 16, 17, 0).prev_year # => 2016-09-16 17:00:00 +0300 + Time.new(2017, 9, 16, 17, 0).prev_year(1) # => 2016-09-16 17:00:00 +0300 + + Time.new(2017, 9, 16, 17, 0).next_year # => 2018-09-16 17:00:00 +0300 + Time.new(2017, 9, 16, 17, 0).next_year(1) # => 2018-09-16 17:00:00 +0300 + ``` + + *bogdanvlviv* + +* Add same method signature for `Time#prev_month` and `Time#next_month` + in accordance with `Date#prev_month`, `Date#next_month`. + + Allows pass argument for `Time#prev_month` and `Time#next_month`. + + Before: + ``` + Time.new(2017, 9, 16, 17, 0).prev_month # => 2017-08-16 17:00:00 +0300 + Time.new(2017, 9, 16, 17, 0).prev_month(1) + # => ArgumentError: wrong number of arguments (given 1, expected 0) + + Time.new(2017, 9, 16, 17, 0).next_month # => 2017-10-16 17:00:00 +0300 + Time.new(2017, 9, 16, 17, 0).next_month(1) + # => ArgumentError: wrong number of arguments (given 1, expected 0) + ``` + + After: + ``` + Time.new(2017, 9, 16, 17, 0).prev_month # => 2017-08-16 17:00:00 +0300 + Time.new(2017, 9, 16, 17, 0).prev_month(1) # => 2017-08-16 17:00:00 +0300 + + Time.new(2017, 9, 16, 17, 0).next_month # => 2017-10-16 17:00:00 +0300 + Time.new(2017, 9, 16, 17, 0).next_month(1) # => 2017-10-16 17:00:00 +0300 + ``` + + *bogdanvlviv* + +* Add same method signature for `Time#prev_day` and `Time#next_day` + in accordance with `Date#prev_day`, `Date#next_day`. + + Allows pass argument for `Time#prev_day` and `Time#next_day`. + + Before: + ``` + Time.new(2017, 9, 16, 17, 0).prev_day # => 2017-09-15 17:00:00 +0300 + Time.new(2017, 9, 16, 17, 0).prev_day(1) + # => ArgumentError: wrong number of arguments (given 1, expected 0) + + Time.new(2017, 9, 16, 17, 0).next_day # => 2017-09-17 17:00:00 +0300 + Time.new(2017, 9, 16, 17, 0).next_day(1) + # => ArgumentError: wrong number of arguments (given 1, expected 0) + ``` + + After: + ``` + Time.new(2017, 9, 16, 17, 0).prev_day # => 2017-09-15 17:00:00 +0300 + Time.new(2017, 9, 16, 17, 0).prev_day(1) # => 2017-09-15 17:00:00 +0300 + + Time.new(2017, 9, 16, 17, 0).next_day # => 2017-09-17 17:00:00 +0300 + Time.new(2017, 9, 16, 17, 0).next_day(1) # => 2017-09-17 17:00:00 +0300 + ``` + + *bogdanvlviv* + +* `IO#to_json` now returns the `to_s` representation, rather than + attempting to convert to an array. This fixes a bug where `IO#to_json` + would raise an `IOError` when called on an unreadable object. + + Fixes #26132. + + *Paul Kuruvilla* + +* Remove deprecated `halt_callback_chains_on_return_false` option. + + *Rafael Mendonça França* + +* Remove deprecated `:if` and `:unless` string filter for callbacks. + + *Rafael Mendonça França* + +* `Hash#slice` now falls back to Ruby 2.5+'s built-in definition if defined. + + *Akira Matsuda* + +* Deprecate `secrets.secret_token`. + + The architecture for secrets had a big upgrade between Rails 3 and Rails 4, + when the default changed from using `secret_token` to `secret_key_base`. + + `secret_token` has been soft deprecated in documentation for four years + but is still in place to support apps created before Rails 4. + Deprecation warnings have been added to help developers upgrade their + applications to `secret_key_base`. + + *claudiob*, *Kasper Timm Hansen* + +* Return an instance of `HashWithIndifferentAccess` from `HashWithIndifferentAccess#transform_keys`. + + *Yuji Yaginuma* + +* Add key rotation support to `MessageEncryptor` and `MessageVerifier`. + + This change introduces a `rotate` method to both the `MessageEncryptor` and + `MessageVerifier` classes. This method accepts the same arguments and + options as the given classes' constructor. The `encrypt_and_verify` method + for `MessageEncryptor` and the `verified` method for `MessageVerifier` also + accept an optional keyword argument `:on_rotation` block which is called + when a rotated instance is used to decrypt or verify the message. + + *Michael J Coyne* + +* Deprecate `Module#reachable?` method. + + *bogdanvlviv* + +* Add `config/credentials.yml.enc` to store production app secrets. + + Allows saving any authentication credentials for third party services + directly in repo encrypted with `config/master.key` or `ENV["RAILS_MASTER_KEY"]`. + + This will eventually replace `Rails.application.secrets` and the encrypted + secrets introduced in Rails 5.1. + + *DHH*, *Kasper Timm Hansen* + +* Add `ActiveSupport::EncryptedFile` and `ActiveSupport::EncryptedConfiguration`. + + Allows for stashing encrypted files or configuration directly in repo by + encrypting it with a key. + + Backs the new credentials setup above, but can also be used independently. + + *DHH*, *Kasper Timm Hansen* + +* `Module#delegate_missing_to` now raises `DelegationError` if target is nil, + similar to `Module#delegate`. + + *Anton Khamets* + +* Update `String#camelize` to provide feedback when wrong option is passed. + + `String#camelize` was returning nil without any feedback when an + invalid option was passed as a parameter. + + Previously: + + 'one_two'.camelize(true) + # => nil + + Now: + + 'one_two'.camelize(true) + # => ArgumentError: Invalid option, use either :upper or :lower. + + *Ricardo Díaz* + +* Fix modulo operations involving durations. + + Rails 5.1 introduced `ActiveSupport::Duration::Scalar` as a wrapper + around numeric values as a way of ensuring a duration was the outcome of + an expression. However, the implementation was missing support for modulo + operations. This support has now been added and should result in a duration + being returned from expressions involving modulo operations. + + Prior to Rails 5.1: + + 5.minutes % 2.minutes + # => 60 + + Now: + + 5.minutes % 2.minutes + # => 1 minute + + Fixes #29603 and #29743. + + *Sayan Chakraborty*, *Andrew White* + +* Fix division where a duration is the denominator. + + PR #29163 introduced a change in behavior when a duration was the denominator + in a calculation - this was incorrect as dividing by a duration should always + return a `Numeric`. The behavior of previous versions of Rails has been restored. + + Fixes #29592. + + *Andrew White* + +* Add purpose and expiry support to `ActiveSupport::MessageVerifier` and + `ActiveSupport::MessageEncryptor`. + + For instance, to ensure a message is only usable for one intended purpose: + + token = @verifier.generate("x", purpose: :shipping) + + @verifier.verified(token, purpose: :shipping) # => "x" + @verifier.verified(token) # => nil + + Or make it expire after a set time: + + @verifier.generate("x", expires_in: 1.month) + @verifier.generate("y", expires_at: Time.now.end_of_year) + + Showcased with `ActiveSupport::MessageVerifier`, but works the same for + `ActiveSupport::MessageEncryptor`'s `encrypt_and_sign` and `decrypt_and_verify`. + + Pull requests: #29599, #29854 + + *Assain Jaleel* + +* Make the order of `Hash#reverse_merge!` consistent with `HashWithIndifferentAccess`. + + *Erol Fornoles* + +* Add `freeze_time` helper which freezes time to `Time.now` in tests. + + *Prathamesh Sonpatki* + +* Default `ActiveSupport::MessageEncryptor` to use AES 256 GCM encryption. + + On for new Rails 5.2 apps. Upgrading apps can find the config as a new + framework default. + + *Assain Jaleel* + +* Cache: `write_multi`. + + Rails.cache.write_multi foo: 'bar', baz: 'qux' + + Plus faster fetch_multi with stores that implement `write_multi_entries`. + Keys that aren't found may be written to the cache store in one shot + instead of separate writes. + + The default implementation simply calls `write_entry` for each entry. + Stores may override if they're capable of one-shot bulk writes, like + Redis `MSET`. + + *Jeremy Daer* + +* Add default option to module and class attribute accessors. + + mattr_accessor :settings, default: {} + + Works for `mattr_reader`, `mattr_writer`, `cattr_accessor`, `cattr_reader`, + and `cattr_writer` as well. + + *Genadi Samokovarov* + +* Add `Date#prev_occurring` and `Date#next_occurring` to return specified next/previous occurring day of week. + + *Shota Iguchi* + +* Add default option to `class_attribute`. + + Before: + + class_attribute :settings + self.settings = {} + + Now: + + class_attribute :settings, default: {} + + *DHH* + +* `#singularize` and `#pluralize` now respect uncountables for the specified locale. + + *Eilis Hamilton* + +* Add `ActiveSupport::CurrentAttributes` to provide a thread-isolated attributes singleton. + Primary use case is keeping all the per-request attributes easily available to the whole system. + + *DHH* + +* Fix implicit coercion calculations with scalars and durations. + + Previously, calculations where the scalar is first would be converted to a duration + of seconds, but this causes issues with dates being converted to times, e.g: + + Time.zone = "Beijing" # => Asia/Shanghai + date = Date.civil(2017, 5, 20) # => Mon, 20 May 2017 + 2 * 1.day # => 172800 seconds + date + 2 * 1.day # => Mon, 22 May 2017 00:00:00 CST +08:00 + + Now, the `ActiveSupport::Duration::Scalar` calculation methods will try to maintain + the part structure of the duration where possible, e.g: + + Time.zone = "Beijing" # => Asia/Shanghai + date = Date.civil(2017, 5, 20) # => Mon, 20 May 2017 + 2 * 1.day # => 2 days + date + 2 * 1.day # => Mon, 22 May 2017 + + Fixes #29160, #28970. + + *Andrew White* + +* Add support for versioned cache entries. This enables the cache stores to recycle cache keys, greatly saving + on storage in cases with frequent churn. Works together with the separation of `#cache_key` and `#cache_version` + in Active Record and its use in Action Pack's fragment caching. + + *DHH* + +* Pass gem name and deprecation horizon to deprecation notifications. + + *Willem van Bergen* + +* Add support for `:offset` and `:zone` to `ActiveSupport::TimeWithZone#change`. + + *Andrew White* + +* Add support for `:offset` to `Time#change`. + + Fixes #28723. + + *Andrew White* + +* Add `fetch_values` for `HashWithIndifferentAccess`. + + The method was originally added to `Hash` in Ruby 2.3.0. + + *Josh Pencheon* + + +Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/activesupport/CHANGELOG.md) for previous changes. diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/MIT-LICENSE b/path/ruby/2.6.0/gems/activesupport-5.2.3/MIT-LICENSE new file mode 100644 index 00000000..8f769c07 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2005-2018 David Heinemeier Hansson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/README.rdoc b/path/ruby/2.6.0/gems/activesupport-5.2.3/README.rdoc new file mode 100644 index 00000000..bbe25ace --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/README.rdoc @@ -0,0 +1,39 @@ += Active Support -- Utility classes and Ruby extensions from Rails + +Active Support is a collection of utility classes and standard library +extensions that were found useful for the Rails framework. These additions +reside in this package so they can be loaded as needed in Ruby projects +outside of Rails. + + +== Download and installation + +The latest version of Active Support can be installed with RubyGems: + + $ gem install activesupport + +Source code can be downloaded as part of the Rails project on GitHub: + +* https://github.com/rails/rails/tree/5-2-stable/activesupport + + +== License + +Active Support is released under the MIT license: + +* https://opensource.org/licenses/MIT + + +== Support + +API documentation is at: + +* http://api.rubyonrails.org + +Bug reports for the Ruby on Rails project can be filed here: + +* https://github.com/rails/rails/issues + +Feature requests should be discussed on the rails-core mailing list here: + +* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support.rb new file mode 100644 index 00000000..a4fb6976 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +#-- +# Copyright (c) 2005-2018 David Heinemeier Hansson +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +#++ + +require "securerandom" +require "active_support/dependencies/autoload" +require "active_support/version" +require "active_support/logger" +require "active_support/lazy_load_hooks" +require "active_support/core_ext/date_and_time/compatibility" + +module ActiveSupport + extend ActiveSupport::Autoload + + autoload :Concern + autoload :CurrentAttributes + autoload :Dependencies + autoload :DescendantsTracker + autoload :ExecutionWrapper + autoload :Executor + autoload :FileUpdateChecker + autoload :EventedFileUpdateChecker + autoload :LogSubscriber + autoload :Notifications + autoload :Reloader + + eager_autoload do + autoload :BacktraceCleaner + autoload :ProxyObject + autoload :Benchmarkable + autoload :Cache + autoload :Callbacks + autoload :Configurable + autoload :Deprecation + autoload :Digest + autoload :Gzip + autoload :Inflector + autoload :JSON + autoload :KeyGenerator + autoload :MessageEncryptor + autoload :MessageVerifier + autoload :Multibyte + autoload :NumberHelper + autoload :OptionMerger + autoload :OrderedHash + autoload :OrderedOptions + autoload :StringInquirer + autoload :TaggedLogging + autoload :XmlMini + autoload :ArrayInquirer + end + + autoload :Rescuable + autoload :SafeBuffer, "active_support/core_ext/string/output_safety" + autoload :TestCase + + def self.eager_load! + super + + NumberHelper.eager_load! + end + + cattr_accessor :test_order # :nodoc: + + def self.to_time_preserves_timezone + DateAndTime::Compatibility.preserve_timezone + end + + def self.to_time_preserves_timezone=(value) + DateAndTime::Compatibility.preserve_timezone = value + end +end + +autoload :I18n, "active_support/i18n" diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/all.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/all.rb new file mode 100644 index 00000000..4adf446a --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/all.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require "active_support" +require "active_support/time" +require "active_support/core_ext" diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/array_inquirer.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/array_inquirer.rb new file mode 100644 index 00000000..b2b9e9c0 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/array_inquirer.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module ActiveSupport + # Wrapping an array in an +ArrayInquirer+ gives a friendlier way to check + # its string-like contents: + # + # variants = ActiveSupport::ArrayInquirer.new([:phone, :tablet]) + # + # variants.phone? # => true + # variants.tablet? # => true + # variants.desktop? # => false + class ArrayInquirer < Array + # Passes each element of +candidates+ collection to ArrayInquirer collection. + # The method returns true if any element from the ArrayInquirer collection + # is equal to the stringified or symbolized form of any element in the +candidates+ collection. + # + # If +candidates+ collection is not given, method returns true. + # + # variants = ActiveSupport::ArrayInquirer.new([:phone, :tablet]) + # + # variants.any? # => true + # variants.any?(:phone, :tablet) # => true + # variants.any?('phone', 'desktop') # => true + # variants.any?(:desktop, :watch) # => false + def any?(*candidates) + if candidates.none? + super + else + candidates.any? do |candidate| + include?(candidate.to_sym) || include?(candidate.to_s) + end + end + end + + private + def respond_to_missing?(name, include_private = false) + (name[-1] == "?") || super + end + + def method_missing(name, *args) + if name[-1] == "?" + any?(name[0..-2]) + else + super + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/backtrace_cleaner.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/backtrace_cleaner.rb new file mode 100644 index 00000000..16dd733d --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/backtrace_cleaner.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +module ActiveSupport + # Backtraces often include many lines that are not relevant for the context + # under review. This makes it hard to find the signal amongst the backtrace + # noise, and adds debugging time. With a BacktraceCleaner, filters and + # silencers are used to remove the noisy lines, so that only the most relevant + # lines remain. + # + # Filters are used to modify lines of data, while silencers are used to remove + # lines entirely. The typical filter use case is to remove lengthy path + # information from the start of each line, and view file paths relevant to the + # app directory instead of the file system root. The typical silencer use case + # is to exclude the output of a noisy library from the backtrace, so that you + # can focus on the rest. + # + # bc = ActiveSupport::BacktraceCleaner.new + # bc.add_filter { |line| line.gsub(Rails.root.to_s, '') } # strip the Rails.root prefix + # bc.add_silencer { |line| line =~ /puma|rubygems/ } # skip any lines from puma or rubygems + # bc.clean(exception.backtrace) # perform the cleanup + # + # To reconfigure an existing BacktraceCleaner (like the default one in Rails) + # and show as much data as possible, you can always call + # BacktraceCleaner#remove_silencers!, which will restore the + # backtrace to a pristine state. If you need to reconfigure an existing + # BacktraceCleaner so that it does not filter or modify the paths of any lines + # of the backtrace, you can call BacktraceCleaner#remove_filters! + # These two methods will give you a completely untouched backtrace. + # + # Inspired by the Quiet Backtrace gem by thoughtbot. + class BacktraceCleaner + def initialize + @filters, @silencers = [], [] + end + + # Returns the backtrace after all filters and silencers have been run + # against it. Filters run first, then silencers. + def clean(backtrace, kind = :silent) + filtered = filter_backtrace(backtrace) + + case kind + when :silent + silence(filtered) + when :noise + noise(filtered) + else + filtered + end + end + alias :filter :clean + + # Adds a filter from the block provided. Each line in the backtrace will be + # mapped against this filter. + # + # # Will turn "/my/rails/root/app/models/person.rb" into "/app/models/person.rb" + # backtrace_cleaner.add_filter { |line| line.gsub(Rails.root, '') } + def add_filter(&block) + @filters << block + end + + # Adds a silencer from the block provided. If the silencer returns +true+ + # for a given line, it will be excluded from the clean backtrace. + # + # # Will reject all lines that include the word "puma", like "/gems/puma/server.rb" or "/app/my_puma_server/rb" + # backtrace_cleaner.add_silencer { |line| line =~ /puma/ } + def add_silencer(&block) + @silencers << block + end + + # Removes all silencers, but leaves in the filters. Useful if your + # context of debugging suddenly expands as you suspect a bug in one of + # the libraries you use. + def remove_silencers! + @silencers = [] + end + + # Removes all filters, but leaves in the silencers. Useful if you suddenly + # need to see entire filepaths in the backtrace that you had already + # filtered out. + def remove_filters! + @filters = [] + end + + private + def filter_backtrace(backtrace) + @filters.each do |f| + backtrace = backtrace.map { |line| f.call(line) } + end + + backtrace + end + + def silence(backtrace) + @silencers.each do |s| + backtrace = backtrace.reject { |line| s.call(line) } + end + + backtrace + end + + def noise(backtrace) + backtrace - silence(backtrace) + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/benchmarkable.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/benchmarkable.rb new file mode 100644 index 00000000..f481d681 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/benchmarkable.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require "active_support/core_ext/benchmark" +require "active_support/core_ext/hash/keys" + +module ActiveSupport + module Benchmarkable + # Allows you to measure the execution time of a block in a template and + # records the result to the log. Wrap this block around expensive operations + # or possible bottlenecks to get a time reading for the operation. For + # example, let's say you thought your file processing method was taking too + # long; you could wrap it in a benchmark block. + # + # <% benchmark 'Process data files' do %> + # <%= expensive_files_operation %> + # <% end %> + # + # That would add something like "Process data files (345.2ms)" to the log, + # which you can then use to compare timings when optimizing your code. + # + # You may give an optional logger level (:debug, :info, + # :warn, :error) as the :level option. The + # default logger level value is :info. + # + # <% benchmark 'Low-level files', level: :debug do %> + # <%= lowlevel_files_operation %> + # <% end %> + # + # Finally, you can pass true as the third argument to silence all log + # activity (other than the timing information) from inside the block. This + # is great for boiling down a noisy block to just a single statement that + # produces one log line: + # + # <% benchmark 'Process data files', level: :info, silence: true do %> + # <%= expensive_and_chatty_files_operation %> + # <% end %> + def benchmark(message = "Benchmarking", options = {}) + if logger + options.assert_valid_keys(:level, :silence) + options[:level] ||= :info + + result = nil + ms = Benchmark.ms { result = options[:silence] ? logger.silence { yield } : yield } + logger.send(options[:level], "%s (%.1fms)" % [ message, ms ]) + result + else + yield + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/builder.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/builder.rb new file mode 100644 index 00000000..3fa7e6b2 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/builder.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +begin + require "builder" +rescue LoadError => e + $stderr.puts "You don't have builder installed in your application. Please add it to your Gemfile and run bundle install" + raise e +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/cache.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/cache.rb new file mode 100644 index 00000000..d06a4971 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/cache.rb @@ -0,0 +1,808 @@ +# frozen_string_literal: true + +require "zlib" +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/array/wrap" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/numeric/bytes" +require "active_support/core_ext/numeric/time" +require "active_support/core_ext/object/to_param" +require "active_support/core_ext/string/inflections" + +module ActiveSupport + # See ActiveSupport::Cache::Store for documentation. + module Cache + autoload :FileStore, "active_support/cache/file_store" + autoload :MemoryStore, "active_support/cache/memory_store" + autoload :MemCacheStore, "active_support/cache/mem_cache_store" + autoload :NullStore, "active_support/cache/null_store" + autoload :RedisCacheStore, "active_support/cache/redis_cache_store" + + # These options mean something to all cache implementations. Individual cache + # implementations may support additional options. + UNIVERSAL_OPTIONS = [:namespace, :compress, :compress_threshold, :expires_in, :race_condition_ttl] + + module Strategy + autoload :LocalCache, "active_support/cache/strategy/local_cache" + end + + class << self + # Creates a new Store object according to the given options. + # + # If no arguments are passed to this method, then a new + # ActiveSupport::Cache::MemoryStore object will be returned. + # + # If you pass a Symbol as the first argument, then a corresponding cache + # store class under the ActiveSupport::Cache namespace will be created. + # For example: + # + # ActiveSupport::Cache.lookup_store(:memory_store) + # # => returns a new ActiveSupport::Cache::MemoryStore object + # + # ActiveSupport::Cache.lookup_store(:mem_cache_store) + # # => returns a new ActiveSupport::Cache::MemCacheStore object + # + # Any additional arguments will be passed to the corresponding cache store + # class's constructor: + # + # ActiveSupport::Cache.lookup_store(:file_store, '/tmp/cache') + # # => same as: ActiveSupport::Cache::FileStore.new('/tmp/cache') + # + # If the first argument is not a Symbol, then it will simply be returned: + # + # ActiveSupport::Cache.lookup_store(MyOwnCacheStore.new) + # # => returns MyOwnCacheStore.new + def lookup_store(*store_option) + store, *parameters = *Array.wrap(store_option).flatten + + case store + when Symbol + retrieve_store_class(store).new(*parameters) + when nil + ActiveSupport::Cache::MemoryStore.new + else + store + end + end + + # Expands out the +key+ argument into a key that can be used for the + # cache store. Optionally accepts a namespace, and all keys will be + # scoped within that namespace. + # + # If the +key+ argument provided is an array, or responds to +to_a+, then + # each of elements in the array will be turned into parameters/keys and + # concatenated into a single key. For example: + # + # ActiveSupport::Cache.expand_cache_key([:foo, :bar]) # => "foo/bar" + # ActiveSupport::Cache.expand_cache_key([:foo, :bar], "namespace") # => "namespace/foo/bar" + # + # The +key+ argument can also respond to +cache_key+ or +to_param+. + def expand_cache_key(key, namespace = nil) + expanded_cache_key = (namespace ? "#{namespace}/" : "").dup + + if prefix = ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"] + expanded_cache_key << "#{prefix}/" + end + + expanded_cache_key << retrieve_cache_key(key) + expanded_cache_key + end + + private + def retrieve_cache_key(key) + case + when key.respond_to?(:cache_key_with_version) then key.cache_key_with_version + when key.respond_to?(:cache_key) then key.cache_key + when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param + when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a) + else key.to_param + end.to_s + end + + # Obtains the specified cache store class, given the name of the +store+. + # Raises an error when the store class cannot be found. + def retrieve_store_class(store) + # require_relative cannot be used here because the class might be + # provided by another gem, like redis-activesupport for example. + require "active_support/cache/#{store}" + rescue LoadError => e + raise "Could not find cache store adapter for #{store} (#{e})" + else + ActiveSupport::Cache.const_get(store.to_s.camelize) + end + end + + # An abstract cache store class. There are multiple cache store + # implementations, each having its own additional features. See the classes + # under the ActiveSupport::Cache module, e.g. + # ActiveSupport::Cache::MemCacheStore. MemCacheStore is currently the most + # popular cache store for large production websites. + # + # Some implementations may not support all methods beyond the basic cache + # methods of +fetch+, +write+, +read+, +exist?+, and +delete+. + # + # ActiveSupport::Cache::Store can store any serializable Ruby object. + # + # cache = ActiveSupport::Cache::MemoryStore.new + # + # cache.read('city') # => nil + # cache.write('city', "Duckburgh") + # cache.read('city') # => "Duckburgh" + # + # Keys are always translated into Strings and are case sensitive. When an + # object is specified as a key and has a +cache_key+ method defined, this + # method will be called to define the key. Otherwise, the +to_param+ + # method will be called. Hashes and Arrays can also be used as keys. The + # elements will be delimited by slashes, and the elements within a Hash + # will be sorted by key so they are consistent. + # + # cache.read('city') == cache.read(:city) # => true + # + # Nil values can be cached. + # + # If your cache is on a shared infrastructure, you can define a namespace + # for your cache entries. If a namespace is defined, it will be prefixed on + # to every key. The namespace can be either a static value or a Proc. If it + # is a Proc, it will be invoked when each key is evaluated so that you can + # use application logic to invalidate keys. + # + # cache.namespace = -> { @last_mod_time } # Set the namespace to a variable + # @last_mod_time = Time.now # Invalidate the entire cache by changing namespace + # + # Cached data larger than 1kB are compressed by default. To turn off + # compression, pass compress: false to the initializer or to + # individual +fetch+ or +write+ method calls. The 1kB compression + # threshold is configurable with the :compress_threshold option, + # specified in bytes. + class Store + cattr_accessor :logger, instance_writer: true + + attr_reader :silence, :options + alias :silence? :silence + + class << self + private + def retrieve_pool_options(options) + {}.tap do |pool_options| + pool_options[:size] = options.delete(:pool_size) if options[:pool_size] + pool_options[:timeout] = options.delete(:pool_timeout) if options[:pool_timeout] + end + end + + def ensure_connection_pool_added! + require "connection_pool" + rescue LoadError => e + $stderr.puts "You don't have connection_pool installed in your application. Please add it to your Gemfile and run bundle install" + raise e + end + end + + # Creates a new cache. The options will be passed to any write method calls + # except for :namespace which can be used to set the global + # namespace for the cache. + def initialize(options = nil) + @options = options ? options.dup : {} + end + + # Silences the logger. + def silence! + @silence = true + self + end + + # Silences the logger within a block. + def mute + previous_silence, @silence = defined?(@silence) && @silence, true + yield + ensure + @silence = previous_silence + end + + # Fetches data from the cache, using the given key. If there is data in + # the cache with the given key, then that data is returned. + # + # If there is no such data in the cache (a cache miss), then +nil+ will be + # returned. However, if a block has been passed, that block will be passed + # the key and executed in the event of a cache miss. The return value of the + # block will be written to the cache under the given cache key, and that + # return value will be returned. + # + # cache.write('today', 'Monday') + # cache.fetch('today') # => "Monday" + # + # cache.fetch('city') # => nil + # cache.fetch('city') do + # 'Duckburgh' + # end + # cache.fetch('city') # => "Duckburgh" + # + # You may also specify additional options via the +options+ argument. + # Setting force: true forces a cache "miss," meaning we treat + # the cache value as missing even if it's present. Passing a block is + # required when +force+ is true so this always results in a cache write. + # + # cache.write('today', 'Monday') + # cache.fetch('today', force: true) { 'Tuesday' } # => 'Tuesday' + # cache.fetch('today', force: true) # => ArgumentError + # + # The +:force+ option is useful when you're calling some other method to + # ask whether you should force a cache write. Otherwise, it's clearer to + # just call Cache#write. + # + # Setting compress: false disables compression of the cache entry. + # + # Setting :expires_in will set an expiration time on the cache. + # All caches support auto-expiring content after a specified number of + # seconds. This value can be specified as an option to the constructor + # (in which case all entries will be affected), or it can be supplied to + # the +fetch+ or +write+ method to effect just one entry. + # + # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 5.minutes) + # cache.write(key, value, expires_in: 1.minute) # Set a lower value for one entry + # + # Setting :version verifies the cache stored under name + # is of the same version. nil is returned on mismatches despite contents. + # This feature is used to support recyclable cache keys. + # + # Setting :race_condition_ttl is very useful in situations where + # a cache entry is used very frequently and is under heavy load. If a + # cache expires and due to heavy load several different processes will try + # to read data natively and then they all will try to write to cache. To + # avoid that case the first process to find an expired cache entry will + # bump the cache expiration time by the value set in :race_condition_ttl. + # Yes, this process is extending the time for a stale value by another few + # seconds. Because of extended life of the previous cache, other processes + # will continue to use slightly stale data for a just a bit longer. In the + # meantime that first process will go ahead and will write into cache the + # new value. After that all the processes will start getting the new value. + # The key is to keep :race_condition_ttl small. + # + # If the process regenerating the entry errors out, the entry will be + # regenerated after the specified number of seconds. Also note that the + # life of stale cache is extended only if it expired recently. Otherwise + # a new value is generated and :race_condition_ttl does not play + # any role. + # + # # Set all values to expire after one minute. + # cache = ActiveSupport::Cache::MemoryStore.new(expires_in: 1.minute) + # + # cache.write('foo', 'original value') + # val_1 = nil + # val_2 = nil + # sleep 60 + # + # Thread.new do + # val_1 = cache.fetch('foo', race_condition_ttl: 10.seconds) do + # sleep 1 + # 'new value 1' + # end + # end + # + # Thread.new do + # val_2 = cache.fetch('foo', race_condition_ttl: 10.seconds) do + # 'new value 2' + # end + # end + # + # cache.fetch('foo') # => "original value" + # sleep 10 # First thread extended the life of cache by another 10 seconds + # cache.fetch('foo') # => "new value 1" + # val_1 # => "new value 1" + # val_2 # => "original value" + # + # Other options will be handled by the specific cache store implementation. + # Internally, #fetch calls #read_entry, and calls #write_entry on a cache + # miss. +options+ will be passed to the #read and #write calls. + # + # For example, MemCacheStore's #write method supports the +:raw+ + # option, which tells the memcached server to store all values as strings. + # We can use this option with #fetch too: + # + # cache = ActiveSupport::Cache::MemCacheStore.new + # cache.fetch("foo", force: true, raw: true) do + # :bar + # end + # cache.fetch('foo') # => "bar" + def fetch(name, options = nil) + if block_given? + options = merged_options(options) + key = normalize_key(name, options) + + entry = nil + instrument(:read, name, options) do |payload| + cached_entry = read_entry(key, options) unless options[:force] + entry = handle_expired_entry(cached_entry, key, options) + entry = nil if entry && entry.mismatched?(normalize_version(name, options)) + payload[:super_operation] = :fetch if payload + payload[:hit] = !!entry if payload + end + + if entry + get_entry_value(entry, name, options) + else + save_block_result_to_cache(name, options) { |_name| yield _name } + end + elsif options && options[:force] + raise ArgumentError, "Missing block: Calling `Cache#fetch` with `force: true` requires a block." + else + read(name, options) + end + end + + # Reads data from the cache, using the given key. If there is data in + # the cache with the given key, then that data is returned. Otherwise, + # +nil+ is returned. + # + # Note, if data was written with the :expires_in or :version options, + # both of these conditions are applied before the data is returned. + # + # Options are passed to the underlying cache implementation. + def read(name, options = nil) + options = merged_options(options) + key = normalize_key(name, options) + version = normalize_version(name, options) + + instrument(:read, name, options) do |payload| + entry = read_entry(key, options) + + if entry + if entry.expired? + delete_entry(key, options) + payload[:hit] = false if payload + nil + elsif entry.mismatched?(version) + payload[:hit] = false if payload + nil + else + payload[:hit] = true if payload + entry.value + end + else + payload[:hit] = false if payload + nil + end + end + end + + # Reads multiple values at once from the cache. Options can be passed + # in the last argument. + # + # Some cache implementation may optimize this method. + # + # Returns a hash mapping the names provided to the values found. + def read_multi(*names) + options = names.extract_options! + options = merged_options(options) + + instrument :read_multi, names, options do |payload| + read_multi_entries(names, options).tap do |results| + payload[:hits] = results.keys + end + end + end + + # Cache Storage API to write multiple values at once. + def write_multi(hash, options = nil) + options = merged_options(options) + + instrument :write_multi, hash, options do |payload| + entries = hash.each_with_object({}) do |(name, value), memo| + memo[normalize_key(name, options)] = Entry.new(value, options.merge(version: normalize_version(name, options))) + end + + write_multi_entries entries, options + end + end + + # Fetches data from the cache, using the given keys. If there is data in + # the cache with the given keys, then that data is returned. Otherwise, + # the supplied block is called for each key for which there was no data, + # and the result will be written to the cache and returned. + # Therefore, you need to pass a block that returns the data to be written + # to the cache. If you do not want to write the cache when the cache is + # not found, use #read_multi. + # + # Options are passed to the underlying cache implementation. + # + # Returns a hash with the data for each of the names. For example: + # + # cache.write("bim", "bam") + # cache.fetch_multi("bim", "unknown_key") do |key| + # "Fallback value for key: #{key}" + # end + # # => { "bim" => "bam", + # # "unknown_key" => "Fallback value for key: unknown_key" } + # + def fetch_multi(*names) + raise ArgumentError, "Missing block: `Cache#fetch_multi` requires a block." unless block_given? + + options = names.extract_options! + options = merged_options(options) + + instrument :read_multi, names, options do |payload| + read_multi_entries(names, options).tap do |results| + payload[:hits] = results.keys + payload[:super_operation] = :fetch_multi + + writes = {} + + (names - results.keys).each do |name| + results[name] = writes[name] = yield(name) + end + + write_multi writes, options + end + end + end + + # Writes the value to the cache, with the key. + # + # Options are passed to the underlying cache implementation. + def write(name, value, options = nil) + options = merged_options(options) + + instrument(:write, name, options) do + entry = Entry.new(value, options.merge(version: normalize_version(name, options))) + write_entry(normalize_key(name, options), entry, options) + end + end + + # Deletes an entry in the cache. Returns +true+ if an entry is deleted. + # + # Options are passed to the underlying cache implementation. + def delete(name, options = nil) + options = merged_options(options) + + instrument(:delete, name) do + delete_entry(normalize_key(name, options), options) + end + end + + # Returns +true+ if the cache contains an entry for the given key. + # + # Options are passed to the underlying cache implementation. + def exist?(name, options = nil) + options = merged_options(options) + + instrument(:exist?, name) do + entry = read_entry(normalize_key(name, options), options) + (entry && !entry.expired? && !entry.mismatched?(normalize_version(name, options))) || false + end + end + + # Deletes all entries with keys matching the pattern. + # + # Options are passed to the underlying cache implementation. + # + # All implementations may not support this method. + def delete_matched(matcher, options = nil) + raise NotImplementedError.new("#{self.class.name} does not support delete_matched") + end + + # Increments an integer value in the cache. + # + # Options are passed to the underlying cache implementation. + # + # All implementations may not support this method. + def increment(name, amount = 1, options = nil) + raise NotImplementedError.new("#{self.class.name} does not support increment") + end + + # Decrements an integer value in the cache. + # + # Options are passed to the underlying cache implementation. + # + # All implementations may not support this method. + def decrement(name, amount = 1, options = nil) + raise NotImplementedError.new("#{self.class.name} does not support decrement") + end + + # Cleanups the cache by removing expired entries. + # + # Options are passed to the underlying cache implementation. + # + # All implementations may not support this method. + def cleanup(options = nil) + raise NotImplementedError.new("#{self.class.name} does not support cleanup") + end + + # Clears the entire cache. Be careful with this method since it could + # affect other processes if shared cache is being used. + # + # The options hash is passed to the underlying cache implementation. + # + # All implementations may not support this method. + def clear(options = nil) + raise NotImplementedError.new("#{self.class.name} does not support clear") + end + + private + # Adds the namespace defined in the options to a pattern designed to + # match keys. Implementations that support delete_matched should call + # this method to translate a pattern that matches names into one that + # matches namespaced keys. + def key_matcher(pattern, options) # :doc: + prefix = options[:namespace].is_a?(Proc) ? options[:namespace].call : options[:namespace] + if prefix + source = pattern.source + if source.start_with?("^") + source = source[1, source.length] + else + source = ".*#{source[0, source.length]}" + end + Regexp.new("^#{Regexp.escape(prefix)}:#{source}", pattern.options) + else + pattern + end + end + + # Reads an entry from the cache implementation. Subclasses must implement + # this method. + def read_entry(key, options) + raise NotImplementedError.new + end + + # Writes an entry to the cache implementation. Subclasses must implement + # this method. + def write_entry(key, entry, options) + raise NotImplementedError.new + end + + # Reads multiple entries from the cache implementation. Subclasses MAY + # implement this method. + def read_multi_entries(names, options) + results = {} + names.each do |name| + key = normalize_key(name, options) + version = normalize_version(name, options) + entry = read_entry(key, options) + + if entry + if entry.expired? + delete_entry(key, options) + elsif entry.mismatched?(version) + # Skip mismatched versions + else + results[name] = entry.value + end + end + end + results + end + + # Writes multiple entries to the cache implementation. Subclasses MAY + # implement this method. + def write_multi_entries(hash, options) + hash.each do |key, entry| + write_entry key, entry, options + end + end + + # Deletes an entry from the cache implementation. Subclasses must + # implement this method. + def delete_entry(key, options) + raise NotImplementedError.new + end + + # Merges the default options with ones specific to a method call. + def merged_options(call_options) + if call_options + options.merge(call_options) + else + options.dup + end + end + + # Expands and namespaces the cache key. May be overridden by + # cache stores to do additional normalization. + def normalize_key(key, options = nil) + namespace_key expanded_key(key), options + end + + # Prefix the key with a namespace string: + # + # namespace_key 'foo', namespace: 'cache' + # # => 'cache:foo' + # + # With a namespace block: + # + # namespace_key 'foo', namespace: -> { 'cache' } + # # => 'cache:foo' + def namespace_key(key, options = nil) + options = merged_options(options) + namespace = options[:namespace] + + if namespace.respond_to?(:call) + namespace = namespace.call + end + + if namespace + "#{namespace}:#{key}" + else + key + end + end + + # Expands key to be a consistent string value. Invokes +cache_key+ if + # object responds to +cache_key+. Otherwise, +to_param+ method will be + # called. If the key is a Hash, then keys will be sorted alphabetically. + def expanded_key(key) + return key.cache_key.to_s if key.respond_to?(:cache_key) + + case key + when Array + if key.size > 1 + key = key.collect { |element| expanded_key(element) } + else + key = key.first + end + when Hash + key = key.sort_by { |k, _| k.to_s }.collect { |k, v| "#{k}=#{v}" } + end + + key.to_param + end + + def normalize_version(key, options = nil) + (options && options[:version].try(:to_param)) || expanded_version(key) + end + + def expanded_version(key) + case + when key.respond_to?(:cache_version) then key.cache_version.to_param + when key.is_a?(Array) then key.map { |element| expanded_version(element) }.compact.to_param + when key.respond_to?(:to_a) then expanded_version(key.to_a) + end + end + + def instrument(operation, key, options = nil) + log { "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" } + + payload = { key: key } + payload.merge!(options) if options.is_a?(Hash) + ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) { yield(payload) } + end + + def log + return unless logger && logger.debug? && !silence? + logger.debug(yield) + end + + def handle_expired_entry(entry, key, options) + if entry && entry.expired? + race_ttl = options[:race_condition_ttl].to_i + if (race_ttl > 0) && (Time.now.to_f - entry.expires_at <= race_ttl) + # When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache + # for a brief period while the entry is being recalculated. + entry.expires_at = Time.now + race_ttl + write_entry(key, entry, expires_in: race_ttl * 2) + else + delete_entry(key, options) + end + entry = nil + end + entry + end + + def get_entry_value(entry, name, options) + instrument(:fetch_hit, name, options) {} + entry.value + end + + def save_block_result_to_cache(name, options) + result = instrument(:generate, name, options) do + yield(name) + end + + write(name, result, options) + result + end + end + + # This class is used to represent cache entries. Cache entries have a value, an optional + # expiration time, and an optional version. The expiration time is used to support the :race_condition_ttl option + # on the cache. The version is used to support the :version option on the cache for rejecting + # mismatches. + # + # Since cache entries in most instances will be serialized, the internals of this class are highly optimized + # using short instance variable names that are lazily defined. + class Entry # :nodoc: + attr_reader :version + + DEFAULT_COMPRESS_LIMIT = 1.kilobyte + + # Creates a new cache entry for the specified value. Options supported are + # +:compress+, +:compress_threshold+, +:version+ and +:expires_in+. + def initialize(value, compress: true, compress_threshold: DEFAULT_COMPRESS_LIMIT, version: nil, expires_in: nil, **) + @value = value + @version = version + @created_at = Time.now.to_f + @expires_in = expires_in && expires_in.to_f + + compress!(compress_threshold) if compress + end + + def value + compressed? ? uncompress(@value) : @value + end + + def mismatched?(version) + @version && version && @version != version + end + + # Checks if the entry is expired. The +expires_in+ parameter can override + # the value set when the entry was created. + def expired? + @expires_in && @created_at + @expires_in <= Time.now.to_f + end + + def expires_at + @expires_in ? @created_at + @expires_in : nil + end + + def expires_at=(value) + if value + @expires_in = value.to_f - @created_at + else + @expires_in = nil + end + end + + # Returns the size of the cached value. This could be less than + # value.size if the data is compressed. + def size + case value + when NilClass + 0 + when String + @value.bytesize + else + @s ||= Marshal.dump(@value).bytesize + end + end + + # Duplicates the value in a class. This is used by cache implementations that don't natively + # serialize entries to protect against accidental cache modifications. + def dup_value! + if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false) + if @value.is_a?(String) + @value = @value.dup + else + @value = Marshal.load(Marshal.dump(@value)) + end + end + end + + private + def compress!(compress_threshold) + case @value + when nil, true, false, Numeric + uncompressed_size = 0 + when String + uncompressed_size = @value.bytesize + else + serialized = Marshal.dump(@value) + uncompressed_size = serialized.bytesize + end + + if uncompressed_size >= compress_threshold + serialized ||= Marshal.dump(@value) + compressed = Zlib::Deflate.deflate(serialized) + + if compressed.bytesize < uncompressed_size + @value = compressed + @compressed = true + end + end + end + + def compressed? + defined?(@compressed) + end + + def uncompress(value) + Marshal.load(Zlib::Inflate.inflate(value)) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/cache/file_store.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/cache/file_store.rb new file mode 100644 index 00000000..a0f44aac --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/cache/file_store.rb @@ -0,0 +1,196 @@ +# frozen_string_literal: true + +require "active_support/core_ext/marshal" +require "active_support/core_ext/file/atomic" +require "active_support/core_ext/string/conversions" +require "uri/common" + +module ActiveSupport + module Cache + # A cache store implementation which stores everything on the filesystem. + # + # FileStore implements the Strategy::LocalCache strategy which implements + # an in-memory cache inside of a block. + class FileStore < Store + prepend Strategy::LocalCache + attr_reader :cache_path + + DIR_FORMATTER = "%03X" + FILENAME_MAX_SIZE = 228 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write) + FILEPATH_MAX_SIZE = 900 # max is 1024, plus some room + EXCLUDED_DIRS = [".", ".."].freeze + GITKEEP_FILES = [".gitkeep", ".keep"].freeze + + def initialize(cache_path, options = nil) + super(options) + @cache_path = cache_path.to_s + end + + # Deletes all items from the cache. In this case it deletes all the entries in the specified + # file store directory except for .keep or .gitkeep. Be careful which directory is specified in your + # config file when using +FileStore+ because everything in that directory will be deleted. + def clear(options = nil) + root_dirs = exclude_from(cache_path, EXCLUDED_DIRS + GITKEEP_FILES) + FileUtils.rm_r(root_dirs.collect { |f| File.join(cache_path, f) }) + rescue Errno::ENOENT + end + + # Preemptively iterates through all stored keys and removes the ones which have expired. + def cleanup(options = nil) + options = merged_options(options) + search_dir(cache_path) do |fname| + entry = read_entry(fname, options) + delete_entry(fname, options) if entry && entry.expired? + end + end + + # Increments an already existing integer value that is stored in the cache. + # If the key is not found nothing is done. + def increment(name, amount = 1, options = nil) + modify_value(name, amount, options) + end + + # Decrements an already existing integer value that is stored in the cache. + # If the key is not found nothing is done. + def decrement(name, amount = 1, options = nil) + modify_value(name, -amount, options) + end + + def delete_matched(matcher, options = nil) + options = merged_options(options) + instrument(:delete_matched, matcher.inspect) do + matcher = key_matcher(matcher, options) + search_dir(cache_path) do |path| + key = file_path_key(path) + delete_entry(path, options) if key.match(matcher) + end + end + end + + private + + def read_entry(key, options) + if File.exist?(key) + File.open(key) { |f| Marshal.load(f) } + end + rescue => e + logger.error("FileStoreError (#{e}): #{e.message}") if logger + nil + end + + def write_entry(key, entry, options) + return false if options[:unless_exist] && File.exist?(key) + ensure_cache_path(File.dirname(key)) + File.atomic_write(key, cache_path) { |f| Marshal.dump(entry, f) } + true + end + + def delete_entry(key, options) + if File.exist?(key) + begin + File.delete(key) + delete_empty_directories(File.dirname(key)) + true + rescue => e + # Just in case the error was caused by another process deleting the file first. + raise e if File.exist?(key) + false + end + end + end + + # Lock a file for a block so only one process can modify it at a time. + def lock_file(file_name, &block) + if File.exist?(file_name) + File.open(file_name, "r+") do |f| + begin + f.flock File::LOCK_EX + yield + ensure + f.flock File::LOCK_UN + end + end + else + yield + end + end + + # Translate a key into a file path. + def normalize_key(key, options) + key = super + fname = URI.encode_www_form_component(key) + + if fname.size > FILEPATH_MAX_SIZE + fname = ActiveSupport::Digest.hexdigest(key) + end + + hash = Zlib.adler32(fname) + hash, dir_1 = hash.divmod(0x1000) + dir_2 = hash.modulo(0x1000) + fname_paths = [] + + # Make sure file name doesn't exceed file system limits. + begin + fname_paths << fname[0, FILENAME_MAX_SIZE] + fname = fname[FILENAME_MAX_SIZE..-1] + end until fname.blank? + + File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, *fname_paths) + end + + # Translate a file path into a key. + def file_path_key(path) + fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last + URI.decode_www_form_component(fname, Encoding::UTF_8) + end + + # Delete empty directories in the cache. + def delete_empty_directories(dir) + return if File.realpath(dir) == File.realpath(cache_path) + if exclude_from(dir, EXCLUDED_DIRS).empty? + Dir.delete(dir) rescue nil + delete_empty_directories(File.dirname(dir)) + end + end + + # Make sure a file path's directories exist. + def ensure_cache_path(path) + FileUtils.makedirs(path) unless File.exist?(path) + end + + def search_dir(dir, &callback) + return if !File.exist?(dir) + Dir.foreach(dir) do |d| + next if EXCLUDED_DIRS.include?(d) + name = File.join(dir, d) + if File.directory?(name) + search_dir(name, &callback) + else + callback.call name + end + end + end + + # Modifies the amount of an already existing integer value that is stored in the cache. + # If the key is not found nothing is done. + def modify_value(name, amount, options) + file_name = normalize_key(name, options) + + lock_file(file_name) do + options = merged_options(options) + + if num = read(name, options) + num = num.to_i + amount + write(name, num, options) + num + end + end + end + + # Exclude entries from source directory + def exclude_from(source, excludes) + Dir.entries(source).reject { |f| excludes.include?(f) } + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/cache/mem_cache_store.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/cache/mem_cache_store.rb new file mode 100644 index 00000000..2840781d --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/cache/mem_cache_store.rb @@ -0,0 +1,207 @@ +# frozen_string_literal: true + +begin + require "dalli" +rescue LoadError => e + $stderr.puts "You don't have dalli installed in your application. Please add it to your Gemfile and run bundle install" + raise e +end + +require "active_support/core_ext/marshal" +require "active_support/core_ext/array/extract_options" + +module ActiveSupport + module Cache + # A cache store implementation which stores data in Memcached: + # https://memcached.org + # + # This is currently the most popular cache store for production websites. + # + # Special features: + # - Clustering and load balancing. One can specify multiple memcached servers, + # and MemCacheStore will load balance between all available servers. If a + # server goes down, then MemCacheStore will ignore it until it comes back up. + # + # MemCacheStore implements the Strategy::LocalCache strategy which implements + # an in-memory cache inside of a block. + class MemCacheStore < Store + # Provide support for raw values in the local cache strategy. + module LocalCacheWithRaw # :nodoc: + private + def read_entry(key, options) + entry = super + if options[:raw] && local_cache && entry + entry = deserialize_entry(entry.value) + end + entry + end + + def write_entry(key, entry, options) + if options[:raw] && local_cache + raw_entry = Entry.new(entry.value.to_s) + raw_entry.expires_at = entry.expires_at + super(key, raw_entry, options) + else + super + end + end + end + + prepend Strategy::LocalCache + prepend LocalCacheWithRaw + + ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n + + # Creates a new Dalli::Client instance with specified addresses and options. + # By default address is equal localhost:11211. + # + # ActiveSupport::Cache::MemCacheStore.build_mem_cache + # # => # + # ActiveSupport::Cache::MemCacheStore.build_mem_cache('localhost:10290') + # # => # + def self.build_mem_cache(*addresses) # :nodoc: + addresses = addresses.flatten + options = addresses.extract_options! + addresses = ["localhost:11211"] if addresses.empty? + pool_options = retrieve_pool_options(options) + + if pool_options.empty? + Dalli::Client.new(addresses, options) + else + ensure_connection_pool_added! + ConnectionPool.new(pool_options) { Dalli::Client.new(addresses, options.merge(threadsafe: false)) } + end + end + + # Creates a new MemCacheStore object, with the given memcached server + # addresses. Each address is either a host name, or a host-with-port string + # in the form of "host_name:port". For example: + # + # ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229") + # + # If no addresses are specified, then MemCacheStore will connect to + # localhost port 11211 (the default memcached port). + def initialize(*addresses) + addresses = addresses.flatten + options = addresses.extract_options! + super(options) + + unless [String, Dalli::Client, NilClass].include?(addresses.first.class) + raise ArgumentError, "First argument must be an empty array, an array of hosts or a Dalli::Client instance." + end + if addresses.first.is_a?(Dalli::Client) + @data = addresses.first + else + mem_cache_options = options.dup + UNIVERSAL_OPTIONS.each { |name| mem_cache_options.delete(name) } + @data = self.class.build_mem_cache(*(addresses + [mem_cache_options])) + end + end + + # Increment a cached value. This method uses the memcached incr atomic + # operator and can only be used on values written with the :raw option. + # Calling it on a value not stored with :raw will initialize that value + # to zero. + def increment(name, amount = 1, options = nil) + options = merged_options(options) + instrument(:increment, name, amount: amount) do + rescue_error_with nil do + @data.with { |c| c.incr(normalize_key(name, options), amount, options[:expires_in]) } + end + end + end + + # Decrement a cached value. This method uses the memcached decr atomic + # operator and can only be used on values written with the :raw option. + # Calling it on a value not stored with :raw will initialize that value + # to zero. + def decrement(name, amount = 1, options = nil) + options = merged_options(options) + instrument(:decrement, name, amount: amount) do + rescue_error_with nil do + @data.with { |c| c.decr(normalize_key(name, options), amount, options[:expires_in]) } + end + end + end + + # Clear the entire cache on all memcached servers. This method should + # be used with care when shared cache is being used. + def clear(options = nil) + rescue_error_with(nil) { @data.with { |c| c.flush_all } } + end + + # Get the statistics from the memcached servers. + def stats + @data.with { |c| c.stats } + end + + private + # Read an entry from the cache. + def read_entry(key, options) + rescue_error_with(nil) { deserialize_entry(@data.with { |c| c.get(key, options) }) } + end + + # Write an entry to the cache. + def write_entry(key, entry, options) + method = options && options[:unless_exist] ? :add : :set + value = options[:raw] ? entry.value.to_s : entry + expires_in = options[:expires_in].to_i + if expires_in > 0 && !options[:raw] + # Set the memcache expire a few minutes in the future to support race condition ttls on read + expires_in += 5.minutes + end + rescue_error_with false do + @data.with { |c| c.send(method, key, value, expires_in, options) } + end + end + + # Reads multiple entries from the cache implementation. + def read_multi_entries(names, options) + keys_to_names = Hash[names.map { |name| [normalize_key(name, options), name] }] + + raw_values = @data.with { |c| c.get_multi(keys_to_names.keys) } + values = {} + + raw_values.each do |key, value| + entry = deserialize_entry(value) + + unless entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options)) + values[keys_to_names[key]] = entry.value + end + end + + values + end + + # Delete an entry from the cache. + def delete_entry(key, options) + rescue_error_with(false) { @data.with { |c| c.delete(key) } } + end + + # Memcache keys are binaries. So we need to force their encoding to binary + # before applying the regular expression to ensure we are escaping all + # characters properly. + def normalize_key(key, options) + key = super.dup + key = key.force_encoding(Encoding::ASCII_8BIT) + key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" } + key = "#{key[0, 213]}:md5:#{ActiveSupport::Digest.hexdigest(key)}" if key.size > 250 + key + end + + def deserialize_entry(raw_value) + if raw_value + entry = Marshal.load(raw_value) rescue raw_value + entry.is_a?(Entry) ? entry : Entry.new(entry) + end + end + + def rescue_error_with(fallback) + yield + rescue Dalli::DalliError => e + logger.error("DalliError (#{e}): #{e.message}") if logger + fallback + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/cache/memory_store.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/cache/memory_store.rb new file mode 100644 index 00000000..564ac172 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/cache/memory_store.rb @@ -0,0 +1,169 @@ +# frozen_string_literal: true + +require "monitor" + +module ActiveSupport + module Cache + # A cache store implementation which stores everything into memory in the + # same process. If you're running multiple Ruby on Rails server processes + # (which is the case if you're using Phusion Passenger or puma clustered mode), + # then this means that Rails server process instances won't be able + # to share cache data with each other and this may not be the most + # appropriate cache in that scenario. + # + # This cache has a bounded size specified by the :size options to the + # initializer (default is 32Mb). When the cache exceeds the allotted size, + # a cleanup will occur which tries to prune the cache down to three quarters + # of the maximum size by removing the least recently used entries. + # + # MemoryStore is thread-safe. + class MemoryStore < Store + def initialize(options = nil) + options ||= {} + super(options) + @data = {} + @key_access = {} + @max_size = options[:size] || 32.megabytes + @max_prune_time = options[:max_prune_time] || 2 + @cache_size = 0 + @monitor = Monitor.new + @pruning = false + end + + # Delete all data stored in a given cache store. + def clear(options = nil) + synchronize do + @data.clear + @key_access.clear + @cache_size = 0 + end + end + + # Preemptively iterates through all stored keys and removes the ones which have expired. + def cleanup(options = nil) + options = merged_options(options) + instrument(:cleanup, size: @data.size) do + keys = synchronize { @data.keys } + keys.each do |key| + entry = @data[key] + delete_entry(key, options) if entry && entry.expired? + end + end + end + + # To ensure entries fit within the specified memory prune the cache by removing the least + # recently accessed entries. + def prune(target_size, max_time = nil) + return if pruning? + @pruning = true + begin + start_time = Time.now + cleanup + instrument(:prune, target_size, from: @cache_size) do + keys = synchronize { @key_access.keys.sort { |a, b| @key_access[a].to_f <=> @key_access[b].to_f } } + keys.each do |key| + delete_entry(key, options) + return if @cache_size <= target_size || (max_time && Time.now - start_time > max_time) + end + end + ensure + @pruning = false + end + end + + # Returns true if the cache is currently being pruned. + def pruning? + @pruning + end + + # Increment an integer value in the cache. + def increment(name, amount = 1, options = nil) + modify_value(name, amount, options) + end + + # Decrement an integer value in the cache. + def decrement(name, amount = 1, options = nil) + modify_value(name, -amount, options) + end + + # Deletes cache entries if the cache key matches a given pattern. + def delete_matched(matcher, options = nil) + options = merged_options(options) + instrument(:delete_matched, matcher.inspect) do + matcher = key_matcher(matcher, options) + keys = synchronize { @data.keys } + keys.each do |key| + delete_entry(key, options) if key.match(matcher) + end + end + end + + def inspect # :nodoc: + "<##{self.class.name} entries=#{@data.size}, size=#{@cache_size}, options=#{@options.inspect}>" + end + + # Synchronize calls to the cache. This should be called wherever the underlying cache implementation + # is not thread safe. + def synchronize(&block) # :nodoc: + @monitor.synchronize(&block) + end + + private + + PER_ENTRY_OVERHEAD = 240 + + def cached_size(key, entry) + key.to_s.bytesize + entry.size + PER_ENTRY_OVERHEAD + end + + def read_entry(key, options) + entry = @data[key] + synchronize do + if entry + @key_access[key] = Time.now.to_f + else + @key_access.delete(key) + end + end + entry + end + + def write_entry(key, entry, options) + entry.dup_value! + synchronize do + old_entry = @data[key] + return false if @data.key?(key) && options[:unless_exist] + if old_entry + @cache_size -= (old_entry.size - entry.size) + else + @cache_size += cached_size(key, entry) + end + @key_access[key] = Time.now.to_f + @data[key] = entry + prune(@max_size * 0.75, @max_prune_time) if @cache_size > @max_size + true + end + end + + def delete_entry(key, options) + synchronize do + @key_access.delete(key) + entry = @data.delete(key) + @cache_size -= cached_size(key, entry) if entry + !!entry + end + end + + def modify_value(name, amount, options) + synchronize do + options = merged_options(options) + if num = read(name, options) + num = num.to_i + amount + write(name, num, options) + num + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/cache/null_store.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/cache/null_store.rb new file mode 100644 index 00000000..1a5983db --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/cache/null_store.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module ActiveSupport + module Cache + # A cache store implementation which doesn't actually store anything. Useful in + # development and test environments where you don't want caching turned on but + # need to go through the caching interface. + # + # This cache does implement the local cache strategy, so values will actually + # be cached inside blocks that utilize this strategy. See + # ActiveSupport::Cache::Strategy::LocalCache for more details. + class NullStore < Store + prepend Strategy::LocalCache + + def clear(options = nil) + end + + def cleanup(options = nil) + end + + def increment(name, amount = 1, options = nil) + end + + def decrement(name, amount = 1, options = nil) + end + + def delete_matched(matcher, options = nil) + end + + private + def read_entry(key, options) + end + + def write_entry(key, entry, options) + true + end + + def delete_entry(key, options) + false + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/cache/redis_cache_store.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/cache/redis_cache_store.rb new file mode 100644 index 00000000..3fe143a5 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/cache/redis_cache_store.rb @@ -0,0 +1,461 @@ +# frozen_string_literal: true + +begin + gem "redis", ">= 4.0.1" + require "redis" + require "redis/distributed" +rescue LoadError + warn "The Redis cache store requires the redis gem, version 4.0.1 or later. Please add it to your Gemfile: `gem \"redis\", \"~> 4.0\"`" + raise +end + +# Prefer the hiredis driver but don't require it. +begin + require "redis/connection/hiredis" +rescue LoadError +end + +require "digest/sha2" +require "active_support/core_ext/marshal" +require "active_support/core_ext/hash/transform_values" + +module ActiveSupport + module Cache + module ConnectionPoolLike + def with + yield self + end + end + + ::Redis.include(ConnectionPoolLike) + ::Redis::Distributed.include(ConnectionPoolLike) + + # Redis cache store. + # + # Deployment note: Take care to use a *dedicated Redis cache* rather + # than pointing this at your existing Redis server. It won't cope well + # with mixed usage patterns and it won't expire cache entries by default. + # + # Redis cache server setup guide: https://redis.io/topics/lru-cache + # + # * Supports vanilla Redis, hiredis, and Redis::Distributed. + # * Supports Memcached-like sharding across Redises with Redis::Distributed. + # * Fault tolerant. If the Redis server is unavailable, no exceptions are + # raised. Cache fetches are all misses and writes are dropped. + # * Local cache. Hot in-memory primary cache within block/middleware scope. + # * +read_multi+ and +write_multi+ support for Redis mget/mset. Use Redis::Distributed + # 4.0.1+ for distributed mget support. + # * +delete_matched+ support for Redis KEYS globs. + class RedisCacheStore < Store + # Keys are truncated with their own SHA2 digest if they exceed 1kB + MAX_KEY_BYTESIZE = 1024 + + DEFAULT_REDIS_OPTIONS = { + connect_timeout: 20, + read_timeout: 1, + write_timeout: 1, + reconnect_attempts: 0, + } + + DEFAULT_ERROR_HANDLER = -> (method:, returning:, exception:) do + if logger + logger.error { "RedisCacheStore: #{method} failed, returned #{returning.inspect}: #{exception.class}: #{exception.message}" } + end + end + + # The maximum number of entries to receive per SCAN call. + SCAN_BATCH_SIZE = 1000 + private_constant :SCAN_BATCH_SIZE + + # Support raw values in the local cache strategy. + module LocalCacheWithRaw # :nodoc: + private + def read_entry(key, options) + entry = super + if options[:raw] && local_cache && entry + entry = deserialize_entry(entry.value) + end + entry + end + + def write_entry(key, entry, options) + if options[:raw] && local_cache + raw_entry = Entry.new(serialize_entry(entry, raw: true)) + raw_entry.expires_at = entry.expires_at + super(key, raw_entry, options) + else + super + end + end + + def write_multi_entries(entries, options) + if options[:raw] && local_cache + raw_entries = entries.map do |key, entry| + raw_entry = Entry.new(serialize_entry(entry, raw: true)) + raw_entry.expires_at = entry.expires_at + end.to_h + + super(raw_entries, options) + else + super + end + end + end + + prepend Strategy::LocalCache + prepend LocalCacheWithRaw + + class << self + # Factory method to create a new Redis instance. + # + # Handles four options: :redis block, :redis instance, single :url + # string, and multiple :url strings. + # + # Option Class Result + # :redis Proc -> options[:redis].call + # :redis Object -> options[:redis] + # :url String -> Redis.new(url: …) + # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …]) + # + def build_redis(redis: nil, url: nil, **redis_options) #:nodoc: + urls = Array(url) + + if redis.is_a?(Proc) + redis.call + elsif redis + redis + elsif urls.size > 1 + build_redis_distributed_client urls: urls, **redis_options + else + build_redis_client url: urls.first, **redis_options + end + end + + private + def build_redis_distributed_client(urls:, **redis_options) + ::Redis::Distributed.new([], DEFAULT_REDIS_OPTIONS.merge(redis_options)).tap do |dist| + urls.each { |u| dist.add_node url: u } + end + end + + def build_redis_client(url:, **redis_options) + ::Redis.new DEFAULT_REDIS_OPTIONS.merge(redis_options.merge(url: url)) + end + end + + attr_reader :redis_options + attr_reader :max_key_bytesize + + # Creates a new Redis cache store. + # + # Handles three options: block provided to instantiate, single URL + # provided, and multiple URLs provided. + # + # :redis Proc -> options[:redis].call + # :url String -> Redis.new(url: …) + # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …]) + # + # No namespace is set by default. Provide one if the Redis cache + # server is shared with other apps: namespace: 'myapp-cache'. + # + # Compression is enabled by default with a 1kB threshold, so cached + # values larger than 1kB are automatically compressed. Disable by + # passing compress: false or change the threshold by passing + # compress_threshold: 4.kilobytes. + # + # No expiry is set on cache entries by default. Redis is expected to + # be configured with an eviction policy that automatically deletes + # least-recently or -frequently used keys when it reaches max memory. + # See https://redis.io/topics/lru-cache for cache server setup. + # + # Race condition TTL is not set by default. This can be used to avoid + # "thundering herd" cache writes when hot cache entries are expired. + # See ActiveSupport::Cache::Store#fetch for more. + def initialize(namespace: nil, compress: true, compress_threshold: 1.kilobyte, expires_in: nil, race_condition_ttl: nil, error_handler: DEFAULT_ERROR_HANDLER, **redis_options) + @redis_options = redis_options + + @max_key_bytesize = MAX_KEY_BYTESIZE + @error_handler = error_handler + + super namespace: namespace, + compress: compress, compress_threshold: compress_threshold, + expires_in: expires_in, race_condition_ttl: race_condition_ttl + end + + def redis + @redis ||= begin + pool_options = self.class.send(:retrieve_pool_options, redis_options) + + if pool_options.any? + self.class.send(:ensure_connection_pool_added!) + ::ConnectionPool.new(pool_options) { self.class.build_redis(**redis_options) } + else + self.class.build_redis(**redis_options) + end + end + end + + def inspect + instance = @redis || @redis_options + "<##{self.class} options=#{options.inspect} redis=#{instance.inspect}>" + end + + # Cache Store API implementation. + # + # Read multiple values at once. Returns a hash of requested keys -> + # fetched values. + def read_multi(*names) + if mget_capable? + instrument(:read_multi, names, options) do |payload| + read_multi_mget(*names).tap do |results| + payload[:hits] = results.keys + end + end + else + super + end + end + + # Cache Store API implementation. + # + # Supports Redis KEYS glob patterns: + # + # h?llo matches hello, hallo and hxllo + # h*llo matches hllo and heeeello + # h[ae]llo matches hello and hallo, but not hillo + # h[^e]llo matches hallo, hbllo, ... but not hello + # h[a-b]llo matches hallo and hbllo + # + # Use \ to escape special characters if you want to match them verbatim. + # + # See https://redis.io/commands/KEYS for more. + # + # Failsafe: Raises errors. + def delete_matched(matcher, options = nil) + instrument :delete_matched, matcher do + unless String === matcher + raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}" + end + redis.with do |c| + pattern = namespace_key(matcher, options) + cursor = "0" + # Fetch keys in batches using SCAN to avoid blocking the Redis server. + begin + cursor, keys = c.scan(cursor, match: pattern, count: SCAN_BATCH_SIZE) + c.del(*keys) unless keys.empty? + end until cursor == "0" + end + end + end + + # Cache Store API implementation. + # + # Increment a cached value. This method uses the Redis incr atomic + # operator and can only be used on values written with the :raw option. + # Calling it on a value not stored with :raw will initialize that value + # to zero. + # + # Failsafe: Raises errors. + def increment(name, amount = 1, options = nil) + instrument :increment, name, amount: amount do + failsafe :increment do + redis.with { |c| c.incrby normalize_key(name, options), amount } + end + end + end + + # Cache Store API implementation. + # + # Decrement a cached value. This method uses the Redis decr atomic + # operator and can only be used on values written with the :raw option. + # Calling it on a value not stored with :raw will initialize that value + # to zero. + # + # Failsafe: Raises errors. + def decrement(name, amount = 1, options = nil) + instrument :decrement, name, amount: amount do + failsafe :decrement do + redis.with { |c| c.decrby normalize_key(name, options), amount } + end + end + end + + # Cache Store API implementation. + # + # Removes expired entries. Handled natively by Redis least-recently-/ + # least-frequently-used expiry, so manual cleanup is not supported. + def cleanup(options = nil) + super + end + + # Clear the entire cache on all Redis servers. Safe to use on + # shared servers if the cache is namespaced. + # + # Failsafe: Raises errors. + def clear(options = nil) + failsafe :clear do + if namespace = merged_options(options)[:namespace] + delete_matched "*", namespace: namespace + else + redis.with { |c| c.flushdb } + end + end + end + + def mget_capable? #:nodoc: + set_redis_capabilities unless defined? @mget_capable + @mget_capable + end + + def mset_capable? #:nodoc: + set_redis_capabilities unless defined? @mset_capable + @mset_capable + end + + private + def set_redis_capabilities + case redis + when Redis::Distributed + @mget_capable = true + @mset_capable = false + else + @mget_capable = true + @mset_capable = true + end + end + + # Store provider interface: + # Read an entry from the cache. + def read_entry(key, options = nil) + failsafe :read_entry do + deserialize_entry redis.with { |c| c.get(key) } + end + end + + def read_multi_entries(names, _options) + if mget_capable? + read_multi_mget(*names) + else + super + end + end + + def read_multi_mget(*names) + options = names.extract_options! + options = merged_options(options) + + keys = names.map { |name| normalize_key(name, options) } + + values = failsafe(:read_multi_mget, returning: {}) do + redis.with { |c| c.mget(*keys) } + end + + names.zip(values).each_with_object({}) do |(name, value), results| + if value + entry = deserialize_entry(value) + unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options)) + results[name] = entry.value + end + end + end + end + + # Write an entry to the cache. + # + # Requires Redis 2.6.12+ for extended SET options. + def write_entry(key, entry, unless_exist: false, raw: false, expires_in: nil, race_condition_ttl: nil, **options) + serialized_entry = serialize_entry(entry, raw: raw) + + # If race condition TTL is in use, ensure that cache entries + # stick around a bit longer after they would have expired + # so we can purposefully serve stale entries. + if race_condition_ttl && expires_in && expires_in > 0 && !raw + expires_in += 5.minutes + end + + failsafe :write_entry, returning: false do + if unless_exist || expires_in + modifiers = {} + modifiers[:nx] = unless_exist + modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in + + redis.with { |c| c.set key, serialized_entry, modifiers } + else + redis.with { |c| c.set key, serialized_entry } + end + end + end + + # Delete an entry from the cache. + def delete_entry(key, options) + failsafe :delete_entry, returning: false do + redis.with { |c| c.del key } + end + end + + # Nonstandard store provider API to write multiple values at once. + def write_multi_entries(entries, expires_in: nil, **options) + if entries.any? + if mset_capable? && expires_in.nil? + failsafe :write_multi_entries do + redis.with { |c| c.mapped_mset(serialize_entries(entries, raw: options[:raw])) } + end + else + super + end + end + end + + # Truncate keys that exceed 1kB. + def normalize_key(key, options) + truncate_key super.b + end + + def truncate_key(key) + if key.bytesize > max_key_bytesize + suffix = ":sha2:#{::Digest::SHA2.hexdigest(key)}" + truncate_at = max_key_bytesize - suffix.bytesize + "#{key.byteslice(0, truncate_at)}#{suffix}" + else + key + end + end + + def deserialize_entry(serialized_entry) + if serialized_entry + entry = Marshal.load(serialized_entry) rescue serialized_entry + entry.is_a?(Entry) ? entry : Entry.new(entry) + end + end + + def serialize_entry(entry, raw: false) + if raw + entry.value.to_s + else + Marshal.dump(entry) + end + end + + def serialize_entries(entries, raw: false) + entries.transform_values do |entry| + serialize_entry entry, raw: raw + end + end + + def failsafe(method, returning: nil) + yield + rescue ::Redis::BaseConnectionError => e + handle_exception exception: e, method: method, returning: returning + returning + end + + def handle_exception(exception:, method:, returning:) + if @error_handler + @error_handler.(method: method, exception: exception, returning: returning) + end + rescue => failsafe + warn "RedisCacheStore ignored exception in handle_exception: #{failsafe.class}: #{failsafe.message}\n #{failsafe.backtrace.join("\n ")}" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/cache/strategy/local_cache.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/cache/strategy/local_cache.rb new file mode 100644 index 00000000..39b32fc7 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/cache/strategy/local_cache.rb @@ -0,0 +1,194 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/duplicable" +require "active_support/core_ext/string/inflections" +require "active_support/per_thread_registry" + +module ActiveSupport + module Cache + module Strategy + # Caches that implement LocalCache will be backed by an in-memory cache for the + # duration of a block. Repeated calls to the cache for the same key will hit the + # in-memory cache for faster access. + module LocalCache + autoload :Middleware, "active_support/cache/strategy/local_cache_middleware" + + # Class for storing and registering the local caches. + class LocalCacheRegistry # :nodoc: + extend ActiveSupport::PerThreadRegistry + + def initialize + @registry = {} + end + + def cache_for(local_cache_key) + @registry[local_cache_key] + end + + def set_cache_for(local_cache_key, value) + @registry[local_cache_key] = value + end + + def self.set_cache_for(l, v); instance.set_cache_for l, v; end + def self.cache_for(l); instance.cache_for l; end + end + + # Simple memory backed cache. This cache is not thread safe and is intended only + # for serving as a temporary memory cache for a single thread. + class LocalStore < Store + def initialize + super + @data = {} + end + + # Don't allow synchronizing since it isn't thread safe. + def synchronize # :nodoc: + yield + end + + def clear(options = nil) + @data.clear + end + + def read_entry(key, options) + @data[key] + end + + def read_multi_entries(keys, options) + values = {} + + keys.each do |name| + entry = read_entry(name, options) + values[name] = entry.value if entry + end + + values + end + + def write_entry(key, value, options) + @data[key] = value + true + end + + def delete_entry(key, options) + !!@data.delete(key) + end + + def fetch_entry(key, options = nil) # :nodoc: + @data.fetch(key) { @data[key] = yield } + end + end + + # Use a local cache for the duration of block. + def with_local_cache + use_temporary_local_cache(LocalStore.new) { yield } + end + + # Middleware class can be inserted as a Rack handler to be local cache for the + # duration of request. + def middleware + @middleware ||= Middleware.new( + "ActiveSupport::Cache::Strategy::LocalCache", + local_cache_key) + end + + def clear(options = nil) # :nodoc: + return super unless cache = local_cache + cache.clear(options) + super + end + + def cleanup(options = nil) # :nodoc: + return super unless cache = local_cache + cache.clear + super + end + + def increment(name, amount = 1, options = nil) # :nodoc: + return super unless local_cache + value = bypass_local_cache { super } + write_cache_value(name, value, options) + value + end + + def decrement(name, amount = 1, options = nil) # :nodoc: + return super unless local_cache + value = bypass_local_cache { super } + write_cache_value(name, value, options) + value + end + + private + def read_entry(key, options) + if cache = local_cache + cache.fetch_entry(key) { super } + else + super + end + end + + def read_multi_entries(keys, options) + return super unless local_cache + + local_entries = local_cache.read_multi_entries(keys, options) + missed_keys = keys - local_entries.keys + + if missed_keys.any? + local_entries.merge!(super(missed_keys, options)) + else + local_entries + end + end + + def write_entry(key, entry, options) + if options[:unless_exist] + local_cache.delete_entry(key, options) if local_cache + else + local_cache.write_entry(key, entry, options) if local_cache + end + + super + end + + def delete_entry(key, options) + local_cache.delete_entry(key, options) if local_cache + super + end + + def write_cache_value(name, value, options) + name = normalize_key(name, options) + cache = local_cache + cache.mute do + if value + cache.write(name, value, options) + else + cache.delete(name, options) + end + end + end + + def local_cache_key + @local_cache_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, "_").to_sym + end + + def local_cache + LocalCacheRegistry.cache_for(local_cache_key) + end + + def bypass_local_cache + use_temporary_local_cache(nil) { yield } + end + + def use_temporary_local_cache(temporary_cache) + save_cache = LocalCacheRegistry.cache_for(local_cache_key) + begin + LocalCacheRegistry.set_cache_for(local_cache_key, temporary_cache) + yield + ensure + LocalCacheRegistry.set_cache_for(local_cache_key, save_cache) + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/cache/strategy/local_cache_middleware.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/cache/strategy/local_cache_middleware.rb new file mode 100644 index 00000000..62542bdb --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/cache/strategy/local_cache_middleware.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require "rack/body_proxy" +require "rack/utils" + +module ActiveSupport + module Cache + module Strategy + module LocalCache + #-- + # This class wraps up local storage for middlewares. Only the middleware method should + # construct them. + class Middleware # :nodoc: + attr_reader :name, :local_cache_key + + def initialize(name, local_cache_key) + @name = name + @local_cache_key = local_cache_key + @app = nil + end + + def new(app) + @app = app + self + end + + def call(env) + LocalCacheRegistry.set_cache_for(local_cache_key, LocalStore.new) + response = @app.call(env) + response[2] = ::Rack::BodyProxy.new(response[2]) do + LocalCacheRegistry.set_cache_for(local_cache_key, nil) + end + cleanup_on_body_close = true + response + rescue Rack::Utils::InvalidParameterError + [400, {}, []] + ensure + LocalCacheRegistry.set_cache_for(local_cache_key, nil) unless + cleanup_on_body_close + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/callbacks.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/callbacks.rb new file mode 100644 index 00000000..9a3728d9 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/callbacks.rb @@ -0,0 +1,845 @@ +# frozen_string_literal: true + +require "active_support/concern" +require "active_support/descendants_tracker" +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/class/attribute" +require "active_support/core_ext/kernel/reporting" +require "active_support/core_ext/kernel/singleton_class" +require "active_support/core_ext/string/filters" +require "active_support/deprecation" +require "thread" + +module ActiveSupport + # Callbacks are code hooks that are run at key points in an object's life cycle. + # The typical use case is to have a base class define a set of callbacks + # relevant to the other functionality it supplies, so that subclasses can + # install callbacks that enhance or modify the base functionality without + # needing to override or redefine methods of the base class. + # + # Mixing in this module allows you to define the events in the object's + # life cycle that will support callbacks (via +ClassMethods.define_callbacks+), + # set the instance methods, procs, or callback objects to be called (via + # +ClassMethods.set_callback+), and run the installed callbacks at the + # appropriate times (via +run_callbacks+). + # + # Three kinds of callbacks are supported: before callbacks, run before a + # certain event; after callbacks, run after the event; and around callbacks, + # blocks that surround the event, triggering it when they yield. Callback code + # can be contained in instance methods, procs or lambdas, or callback objects + # that respond to certain predetermined methods. See +ClassMethods.set_callback+ + # for details. + # + # class Record + # include ActiveSupport::Callbacks + # define_callbacks :save + # + # def save + # run_callbacks :save do + # puts "- save" + # end + # end + # end + # + # class PersonRecord < Record + # set_callback :save, :before, :saving_message + # def saving_message + # puts "saving..." + # end + # + # set_callback :save, :after do |object| + # puts "saved" + # end + # end + # + # person = PersonRecord.new + # person.save + # + # Output: + # saving... + # - save + # saved + module Callbacks + extend Concern + + included do + extend ActiveSupport::DescendantsTracker + class_attribute :__callbacks, instance_writer: false, default: {} + end + + CALLBACK_FILTER_TYPES = [:before, :after, :around] + + # Runs the callbacks for the given event. + # + # Calls the before and around callbacks in the order they were set, yields + # the block (if given one), and then runs the after callbacks in reverse + # order. + # + # If the callback chain was halted, returns +false+. Otherwise returns the + # result of the block, +nil+ if no callbacks have been set, or +true+ + # if callbacks have been set but no block is given. + # + # run_callbacks :save do + # save + # end + # + #-- + # + # As this method is used in many places, and often wraps large portions of + # user code, it has an additional design goal of minimizing its impact on + # the visible call stack. An exception from inside a :before or :after + # callback can be as noisy as it likes -- but when control has passed + # smoothly through and into the supplied block, we want as little evidence + # as possible that we were here. + def run_callbacks(kind) + callbacks = __callbacks[kind.to_sym] + + if callbacks.empty? + yield if block_given? + else + env = Filters::Environment.new(self, false, nil) + next_sequence = callbacks.compile + + invoke_sequence = Proc.new do + skipped = nil + while true + current = next_sequence + current.invoke_before(env) + if current.final? + env.value = !env.halted && (!block_given? || yield) + elsif current.skip?(env) + (skipped ||= []) << current + next_sequence = next_sequence.nested + next + else + next_sequence = next_sequence.nested + begin + target, block, method, *arguments = current.expand_call_template(env, invoke_sequence) + target.send(method, *arguments, &block) + ensure + next_sequence = current + end + end + current.invoke_after(env) + skipped.pop.invoke_after(env) while skipped && skipped.first + break env.value + end + end + + # Common case: no 'around' callbacks defined + if next_sequence.final? + next_sequence.invoke_before(env) + env.value = !env.halted && (!block_given? || yield) + next_sequence.invoke_after(env) + env.value + else + invoke_sequence.call + end + end + end + + private + + # A hook invoked every time a before callback is halted. + # This can be overridden in ActiveSupport::Callbacks implementors in order + # to provide better debugging/logging. + def halted_callback_hook(filter) + end + + module Conditionals # :nodoc: + class Value + def initialize(&block) + @block = block + end + def call(target, value); @block.call(value); end + end + end + + module Filters + Environment = Struct.new(:target, :halted, :value) + + class Before + def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter) + halted_lambda = chain_config[:terminator] + + if user_conditions.any? + halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter) + else + halting(callback_sequence, user_callback, halted_lambda, filter) + end + end + + def self.halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter) + callback_sequence.before do |env| + target = env.target + value = env.value + halted = env.halted + + if !halted && user_conditions.all? { |c| c.call(target, value) } + result_lambda = -> { user_callback.call target, value } + env.halted = halted_lambda.call(target, result_lambda) + if env.halted + target.send :halted_callback_hook, filter + end + end + + env + end + end + private_class_method :halting_and_conditional + + def self.halting(callback_sequence, user_callback, halted_lambda, filter) + callback_sequence.before do |env| + target = env.target + value = env.value + halted = env.halted + + unless halted + result_lambda = -> { user_callback.call target, value } + env.halted = halted_lambda.call(target, result_lambda) + + if env.halted + target.send :halted_callback_hook, filter + end + end + + env + end + end + private_class_method :halting + end + + class After + def self.build(callback_sequence, user_callback, user_conditions, chain_config) + if chain_config[:skip_after_callbacks_if_terminated] + if user_conditions.any? + halting_and_conditional(callback_sequence, user_callback, user_conditions) + else + halting(callback_sequence, user_callback) + end + else + if user_conditions.any? + conditional callback_sequence, user_callback, user_conditions + else + simple callback_sequence, user_callback + end + end + end + + def self.halting_and_conditional(callback_sequence, user_callback, user_conditions) + callback_sequence.after do |env| + target = env.target + value = env.value + halted = env.halted + + if !halted && user_conditions.all? { |c| c.call(target, value) } + user_callback.call target, value + end + + env + end + end + private_class_method :halting_and_conditional + + def self.halting(callback_sequence, user_callback) + callback_sequence.after do |env| + unless env.halted + user_callback.call env.target, env.value + end + + env + end + end + private_class_method :halting + + def self.conditional(callback_sequence, user_callback, user_conditions) + callback_sequence.after do |env| + target = env.target + value = env.value + + if user_conditions.all? { |c| c.call(target, value) } + user_callback.call target, value + end + + env + end + end + private_class_method :conditional + + def self.simple(callback_sequence, user_callback) + callback_sequence.after do |env| + user_callback.call env.target, env.value + + env + end + end + private_class_method :simple + end + end + + class Callback #:nodoc:# + def self.build(chain, filter, kind, options) + if filter.is_a?(String) + raise ArgumentError, <<-MSG.squish + Passing string to define a callback is not supported. See the `.set_callback` + documentation to see supported values. + MSG + end + + new chain.name, filter, kind, options, chain.config + end + + attr_accessor :kind, :name + attr_reader :chain_config + + def initialize(name, filter, kind, options, chain_config) + @chain_config = chain_config + @name = name + @kind = kind + @filter = filter + @key = compute_identifier filter + @if = check_conditionals(Array(options[:if])) + @unless = check_conditionals(Array(options[:unless])) + end + + def filter; @key; end + def raw_filter; @filter; end + + def merge_conditional_options(chain, if_option:, unless_option:) + options = { + if: @if.dup, + unless: @unless.dup + } + + options[:if].concat Array(unless_option) + options[:unless].concat Array(if_option) + + self.class.build chain, @filter, @kind, options + end + + def matches?(_kind, _filter) + @kind == _kind && filter == _filter + end + + def duplicates?(other) + case @filter + when Symbol + matches?(other.kind, other.filter) + else + false + end + end + + # Wraps code with filter + def apply(callback_sequence) + user_conditions = conditions_lambdas + user_callback = CallTemplate.build(@filter, self) + + case kind + when :before + Filters::Before.build(callback_sequence, user_callback.make_lambda, user_conditions, chain_config, @filter) + when :after + Filters::After.build(callback_sequence, user_callback.make_lambda, user_conditions, chain_config) + when :around + callback_sequence.around(user_callback, user_conditions) + end + end + + def current_scopes + Array(chain_config[:scope]).map { |s| public_send(s) } + end + + private + def check_conditionals(conditionals) + if conditionals.any? { |c| c.is_a?(String) } + raise ArgumentError, <<-MSG.squish + Passing string to be evaluated in :if and :unless conditional + options is not supported. Pass a symbol for an instance method, + or a lambda, proc or block, instead. + MSG + end + + conditionals + end + + def compute_identifier(filter) + case filter + when ::Proc + filter.object_id + else + filter + end + end + + def conditions_lambdas + @if.map { |c| CallTemplate.build(c, self).make_lambda } + + @unless.map { |c| CallTemplate.build(c, self).inverted_lambda } + end + end + + # A future invocation of user-supplied code (either as a callback, + # or a condition filter). + class CallTemplate # :nodoc: + def initialize(target, method, arguments, block) + @override_target = target + @method_name = method + @arguments = arguments + @override_block = block + end + + # Return the parts needed to make this call, with the given + # input values. + # + # Returns an array of the form: + # + # [target, block, method, *arguments] + # + # This array can be used as such: + # + # target.send(method, *arguments, &block) + # + # The actual invocation is left up to the caller to minimize + # call stack pollution. + def expand(target, value, block) + result = @arguments.map { |arg| + case arg + when :value; value + when :target; target + when :block; block || raise(ArgumentError) + end + } + + result.unshift @method_name + result.unshift @override_block || block + result.unshift @override_target || target + + # target, block, method, *arguments = result + # target.send(method, *arguments, &block) + result + end + + # Return a lambda that will make this call when given the input + # values. + def make_lambda + lambda do |target, value, &block| + target, block, method, *arguments = expand(target, value, block) + target.send(method, *arguments, &block) + end + end + + # Return a lambda that will make this call when given the input + # values, but then return the boolean inverse of that result. + def inverted_lambda + lambda do |target, value, &block| + target, block, method, *arguments = expand(target, value, block) + ! target.send(method, *arguments, &block) + end + end + + # Filters support: + # + # Symbols:: A method to call. + # Procs:: A proc to call with the object. + # Objects:: An object with a before_foo method on it to call. + # + # All of these objects are converted into a CallTemplate and handled + # the same after this point. + def self.build(filter, callback) + case filter + when Symbol + new(nil, filter, [], nil) + when Conditionals::Value + new(filter, :call, [:target, :value], nil) + when ::Proc + if filter.arity > 1 + new(nil, :instance_exec, [:target, :block], filter) + elsif filter.arity > 0 + new(nil, :instance_exec, [:target], filter) + else + new(nil, :instance_exec, [], filter) + end + else + method_to_call = callback.current_scopes.join("_") + + new(filter, method_to_call, [:target], nil) + end + end + end + + # Execute before and after filters in a sequence instead of + # chaining them with nested lambda calls, see: + # https://github.com/rails/rails/issues/18011 + class CallbackSequence # :nodoc: + def initialize(nested = nil, call_template = nil, user_conditions = nil) + @nested = nested + @call_template = call_template + @user_conditions = user_conditions + + @before = [] + @after = [] + end + + def before(&before) + @before.unshift(before) + self + end + + def after(&after) + @after.push(after) + self + end + + def around(call_template, user_conditions) + CallbackSequence.new(self, call_template, user_conditions) + end + + def skip?(arg) + arg.halted || !@user_conditions.all? { |c| c.call(arg.target, arg.value) } + end + + def nested + @nested + end + + def final? + !@call_template + end + + def expand_call_template(arg, block) + @call_template.expand(arg.target, arg.value, block) + end + + def invoke_before(arg) + @before.each { |b| b.call(arg) } + end + + def invoke_after(arg) + @after.each { |a| a.call(arg) } + end + end + + class CallbackChain #:nodoc:# + include Enumerable + + attr_reader :name, :config + + def initialize(name, config) + @name = name + @config = { + scope: [:kind], + terminator: default_terminator + }.merge!(config) + @chain = [] + @callbacks = nil + @mutex = Mutex.new + end + + def each(&block); @chain.each(&block); end + def index(o); @chain.index(o); end + def empty?; @chain.empty?; end + + def insert(index, o) + @callbacks = nil + @chain.insert(index, o) + end + + def delete(o) + @callbacks = nil + @chain.delete(o) + end + + def clear + @callbacks = nil + @chain.clear + self + end + + def initialize_copy(other) + @callbacks = nil + @chain = other.chain.dup + @mutex = Mutex.new + end + + def compile + @callbacks || @mutex.synchronize do + final_sequence = CallbackSequence.new + @callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback| + callback.apply callback_sequence + end + end + end + + def append(*callbacks) + callbacks.each { |c| append_one(c) } + end + + def prepend(*callbacks) + callbacks.each { |c| prepend_one(c) } + end + + protected + def chain; @chain; end + + private + + def append_one(callback) + @callbacks = nil + remove_duplicates(callback) + @chain.push(callback) + end + + def prepend_one(callback) + @callbacks = nil + remove_duplicates(callback) + @chain.unshift(callback) + end + + def remove_duplicates(callback) + @callbacks = nil + @chain.delete_if { |c| callback.duplicates?(c) } + end + + def default_terminator + Proc.new do |target, result_lambda| + terminate = true + catch(:abort) do + result_lambda.call + terminate = false + end + terminate + end + end + end + + module ClassMethods + def normalize_callback_params(filters, block) # :nodoc: + type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before + options = filters.extract_options! + filters.unshift(block) if block + [type, filters, options.dup] + end + + # This is used internally to append, prepend and skip callbacks to the + # CallbackChain. + def __update_callbacks(name) #:nodoc: + ([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse_each do |target| + chain = target.get_callbacks name + yield target, chain.dup + end + end + + # Install a callback for the given event. + # + # set_callback :save, :before, :before_method + # set_callback :save, :after, :after_method, if: :condition + # set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff } + # + # The second argument indicates whether the callback is to be run +:before+, + # +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This + # means the first example above can also be written as: + # + # set_callback :save, :before_method + # + # The callback can be specified as a symbol naming an instance method; as a + # proc, lambda, or block; or as an object that responds to a certain method + # determined by the :scope argument to +define_callbacks+. + # + # If a proc, lambda, or block is given, its body is evaluated in the context + # of the current object. It can also optionally accept the current object as + # an argument. + # + # Before and around callbacks are called in the order that they are set; + # after callbacks are called in the reverse order. + # + # Around callbacks can access the return value from the event, if it + # wasn't halted, from the +yield+ call. + # + # ===== Options + # + # * :if - A symbol or an array of symbols, each naming an instance + # method or a proc; the callback will be called only when they all return + # a true value. + # * :unless - A symbol or an array of symbols, each naming an + # instance method or a proc; the callback will be called only when they + # all return a false value. + # * :prepend - If +true+, the callback will be prepended to the + # existing chain rather than appended. + def set_callback(name, *filter_list, &block) + type, filters, options = normalize_callback_params(filter_list, block) + + self_chain = get_callbacks name + mapped = filters.map do |filter| + Callback.build(self_chain, filter, type, options) + end + + __update_callbacks(name) do |target, chain| + options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped) + target.set_callbacks name, chain + end + end + + # Skip a previously set callback. Like +set_callback+, :if or + # :unless options may be passed in order to control when the + # callback is skipped. + # + # class Writer < Person + # skip_callback :validate, :before, :check_membership, if: -> { age > 18 } + # end + # + # An ArgumentError will be raised if the callback has not + # already been set (unless the :raise option is set to false). + def skip_callback(name, *filter_list, &block) + type, filters, options = normalize_callback_params(filter_list, block) + + options[:raise] = true unless options.key?(:raise) + + __update_callbacks(name) do |target, chain| + filters.each do |filter| + callback = chain.find { |c| c.matches?(type, filter) } + + if !callback && options[:raise] + raise ArgumentError, "#{type.to_s.capitalize} #{name} callback #{filter.inspect} has not been defined" + end + + if callback && (options.key?(:if) || options.key?(:unless)) + new_callback = callback.merge_conditional_options(chain, if_option: options[:if], unless_option: options[:unless]) + chain.insert(chain.index(callback), new_callback) + end + + chain.delete(callback) + end + target.set_callbacks name, chain + end + end + + # Remove all set callbacks for the given event. + def reset_callbacks(name) + callbacks = get_callbacks name + + ActiveSupport::DescendantsTracker.descendants(self).each do |target| + chain = target.get_callbacks(name).dup + callbacks.each { |c| chain.delete(c) } + target.set_callbacks name, chain + end + + set_callbacks(name, callbacks.dup.clear) + end + + # Define sets of events in the object life cycle that support callbacks. + # + # define_callbacks :validate + # define_callbacks :initialize, :save, :destroy + # + # ===== Options + # + # * :terminator - Determines when a before filter will halt the + # callback chain, preventing following before and around callbacks from + # being called and the event from being triggered. + # This should be a lambda to be executed. + # The current object and the result lambda of the callback will be provided + # to the terminator lambda. + # + # define_callbacks :validate, terminator: ->(target, result_lambda) { result_lambda.call == false } + # + # In this example, if any before validate callbacks returns +false+, + # any successive before and around callback is not executed. + # + # The default terminator halts the chain when a callback throws +:abort+. + # + # * :skip_after_callbacks_if_terminated - Determines if after + # callbacks should be terminated by the :terminator option. By + # default after callbacks are executed no matter if callback chain was + # terminated or not. This option has no effect if :terminator + # option is set to +nil+. + # + # * :scope - Indicates which methods should be executed when an + # object is used as a callback. + # + # class Audit + # def before(caller) + # puts 'Audit: before is called' + # end + # + # def before_save(caller) + # puts 'Audit: before_save is called' + # end + # end + # + # class Account + # include ActiveSupport::Callbacks + # + # define_callbacks :save + # set_callback :save, :before, Audit.new + # + # def save + # run_callbacks :save do + # puts 'save in main' + # end + # end + # end + # + # In the above case whenever you save an account the method + # Audit#before will be called. On the other hand + # + # define_callbacks :save, scope: [:kind, :name] + # + # would trigger Audit#before_save instead. That's constructed + # by calling #{kind}_#{name} on the given instance. In this + # case "kind" is "before" and "name" is "save". In this context +:kind+ + # and +:name+ have special meanings: +:kind+ refers to the kind of + # callback (before/after/around) and +:name+ refers to the method on + # which callbacks are being defined. + # + # A declaration like + # + # define_callbacks :save, scope: [:name] + # + # would call Audit#save. + # + # ===== Notes + # + # +names+ passed to +define_callbacks+ must not end with + # !, ? or =. + # + # Calling +define_callbacks+ multiple times with the same +names+ will + # overwrite previous callbacks registered with +set_callback+. + def define_callbacks(*names) + options = names.extract_options! + + names.each do |name| + name = name.to_sym + + set_callbacks name, CallbackChain.new(name, options) + + module_eval <<-RUBY, __FILE__, __LINE__ + 1 + def _run_#{name}_callbacks(&block) + run_callbacks #{name.inspect}, &block + end + + def self._#{name}_callbacks + get_callbacks(#{name.inspect}) + end + + def self._#{name}_callbacks=(value) + set_callbacks(#{name.inspect}, value) + end + + def _#{name}_callbacks + __callbacks[#{name.inspect}] + end + RUBY + end + end + + protected + + def get_callbacks(name) # :nodoc: + __callbacks[name.to_sym] + end + + def set_callbacks(name, callbacks) # :nodoc: + self.__callbacks = __callbacks.merge(name.to_sym => callbacks) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/concern.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/concern.rb new file mode 100644 index 00000000..5d356a0a --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/concern.rb @@ -0,0 +1,148 @@ +# frozen_string_literal: true + +module ActiveSupport + # A typical module looks like this: + # + # module M + # def self.included(base) + # base.extend ClassMethods + # base.class_eval do + # scope :disabled, -> { where(disabled: true) } + # end + # end + # + # module ClassMethods + # ... + # end + # end + # + # By using ActiveSupport::Concern the above module could instead be + # written as: + # + # require 'active_support/concern' + # + # module M + # extend ActiveSupport::Concern + # + # included do + # scope :disabled, -> { where(disabled: true) } + # end + # + # class_methods do + # ... + # end + # end + # + # Moreover, it gracefully handles module dependencies. Given a +Foo+ module + # and a +Bar+ module which depends on the former, we would typically write the + # following: + # + # module Foo + # def self.included(base) + # base.class_eval do + # def self.method_injected_by_foo + # ... + # end + # end + # end + # end + # + # module Bar + # def self.included(base) + # base.method_injected_by_foo + # end + # end + # + # class Host + # include Foo # We need to include this dependency for Bar + # include Bar # Bar is the module that Host really needs + # end + # + # But why should +Host+ care about +Bar+'s dependencies, namely +Foo+? We + # could try to hide these from +Host+ directly including +Foo+ in +Bar+: + # + # module Bar + # include Foo + # def self.included(base) + # base.method_injected_by_foo + # end + # end + # + # class Host + # include Bar + # end + # + # Unfortunately this won't work, since when +Foo+ is included, its base + # is the +Bar+ module, not the +Host+ class. With ActiveSupport::Concern, + # module dependencies are properly resolved: + # + # require 'active_support/concern' + # + # module Foo + # extend ActiveSupport::Concern + # included do + # def self.method_injected_by_foo + # ... + # end + # end + # end + # + # module Bar + # extend ActiveSupport::Concern + # include Foo + # + # included do + # self.method_injected_by_foo + # end + # end + # + # class Host + # include Bar # It works, now Bar takes care of its dependencies + # end + module Concern + class MultipleIncludedBlocks < StandardError #:nodoc: + def initialize + super "Cannot define multiple 'included' blocks for a Concern" + end + end + + def self.extended(base) #:nodoc: + base.instance_variable_set(:@_dependencies, []) + end + + def append_features(base) + if base.instance_variable_defined?(:@_dependencies) + base.instance_variable_get(:@_dependencies) << self + false + else + return false if base < self + @_dependencies.each { |dep| base.include(dep) } + super + base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods) + base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block) + end + end + + def included(base = nil, &block) + if base.nil? + if instance_variable_defined?(:@_included_block) + if @_included_block.source_location != block.source_location + raise MultipleIncludedBlocks + end + else + @_included_block = block + end + else + super + end + end + + def class_methods(&class_methods_module_definition) + mod = const_defined?(:ClassMethods, false) ? + const_get(:ClassMethods) : + const_set(:ClassMethods, Module.new) + + mod.module_eval(&class_methods_module_definition) + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/concurrency/load_interlock_aware_monitor.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/concurrency/load_interlock_aware_monitor.rb new file mode 100644 index 00000000..a8455c00 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/concurrency/load_interlock_aware_monitor.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require "monitor" + +module ActiveSupport + module Concurrency + # A monitor that will permit dependency loading while blocked waiting for + # the lock. + class LoadInterlockAwareMonitor < Monitor + # Enters an exclusive section, but allows dependency loading while blocked + def mon_enter + mon_try_enter || + ActiveSupport::Dependencies.interlock.permit_concurrent_loads { super } + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/concurrency/share_lock.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/concurrency/share_lock.rb new file mode 100644 index 00000000..f18ccf1c --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/concurrency/share_lock.rb @@ -0,0 +1,227 @@ +# frozen_string_literal: true + +require "thread" +require "monitor" + +module ActiveSupport + module Concurrency + # A share/exclusive lock, otherwise known as a read/write lock. + # + # https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock + class ShareLock + include MonitorMixin + + # We track Thread objects, instead of just using counters, because + # we need exclusive locks to be reentrant, and we need to be able + # to upgrade share locks to exclusive. + + def raw_state # :nodoc: + synchronize do + threads = @sleeping.keys | @sharing.keys | @waiting.keys + threads |= [@exclusive_thread] if @exclusive_thread + + data = {} + + threads.each do |thread| + purpose, compatible = @waiting[thread] + + data[thread] = { + thread: thread, + sharing: @sharing[thread], + exclusive: @exclusive_thread == thread, + purpose: purpose, + compatible: compatible, + waiting: !!@waiting[thread], + sleeper: @sleeping[thread], + } + end + + # NB: Yields while holding our *internal* synchronize lock, + # which is supposed to be used only for a few instructions at + # a time. This allows the caller to inspect additional state + # without things changing out from underneath, but would have + # disastrous effects upon normal operation. Fortunately, this + # method is only intended to be called when things have + # already gone wrong. + yield data + end + end + + def initialize + super() + + @cv = new_cond + + @sharing = Hash.new(0) + @waiting = {} + @sleeping = {} + @exclusive_thread = nil + @exclusive_depth = 0 + end + + # Returns false if +no_wait+ is set and the lock is not + # immediately available. Otherwise, returns true after the lock + # has been acquired. + # + # +purpose+ and +compatible+ work together; while this thread is + # waiting for the exclusive lock, it will yield its share (if any) + # to any other attempt whose +purpose+ appears in this attempt's + # +compatible+ list. This allows a "loose" upgrade, which, being + # less strict, prevents some classes of deadlocks. + # + # For many resources, loose upgrades are sufficient: if a thread + # is awaiting a lock, it is not running any other code. With + # +purpose+ matching, it is possible to yield only to other + # threads whose activity will not interfere. + def start_exclusive(purpose: nil, compatible: [], no_wait: false) + synchronize do + unless @exclusive_thread == Thread.current + if busy_for_exclusive?(purpose) + return false if no_wait + + yield_shares(purpose: purpose, compatible: compatible, block_share: true) do + wait_for(:start_exclusive) { busy_for_exclusive?(purpose) } + end + end + @exclusive_thread = Thread.current + end + @exclusive_depth += 1 + + true + end + end + + # Relinquish the exclusive lock. Must only be called by the thread + # that called start_exclusive (and currently holds the lock). + def stop_exclusive(compatible: []) + synchronize do + raise "invalid unlock" if @exclusive_thread != Thread.current + + @exclusive_depth -= 1 + if @exclusive_depth == 0 + @exclusive_thread = nil + + if eligible_waiters?(compatible) + yield_shares(compatible: compatible, block_share: true) do + wait_for(:stop_exclusive) { @exclusive_thread || eligible_waiters?(compatible) } + end + end + @cv.broadcast + end + end + end + + def start_sharing + synchronize do + if @sharing[Thread.current] > 0 || @exclusive_thread == Thread.current + # We already hold a lock; nothing to wait for + elsif @waiting[Thread.current] + # We're nested inside a +yield_shares+ call: we'll resume as + # soon as there isn't an exclusive lock in our way + wait_for(:start_sharing) { @exclusive_thread } + else + # This is an initial / outermost share call: any outstanding + # requests for an exclusive lock get to go first + wait_for(:start_sharing) { busy_for_sharing?(false) } + end + @sharing[Thread.current] += 1 + end + end + + def stop_sharing + synchronize do + if @sharing[Thread.current] > 1 + @sharing[Thread.current] -= 1 + else + @sharing.delete Thread.current + @cv.broadcast + end + end + end + + # Execute the supplied block while holding the Exclusive lock. If + # +no_wait+ is set and the lock is not immediately available, + # returns +nil+ without yielding. Otherwise, returns the result of + # the block. + # + # See +start_exclusive+ for other options. + def exclusive(purpose: nil, compatible: [], after_compatible: [], no_wait: false) + if start_exclusive(purpose: purpose, compatible: compatible, no_wait: no_wait) + begin + yield + ensure + stop_exclusive(compatible: after_compatible) + end + end + end + + # Execute the supplied block while holding the Share lock. + def sharing + start_sharing + begin + yield + ensure + stop_sharing + end + end + + # Temporarily give up all held Share locks while executing the + # supplied block, allowing any +compatible+ exclusive lock request + # to proceed. + def yield_shares(purpose: nil, compatible: [], block_share: false) + loose_shares = previous_wait = nil + synchronize do + if loose_shares = @sharing.delete(Thread.current) + if previous_wait = @waiting[Thread.current] + purpose = nil unless purpose == previous_wait[0] + compatible &= previous_wait[1] + end + compatible |= [false] unless block_share + @waiting[Thread.current] = [purpose, compatible] + end + + @cv.broadcast + end + + begin + yield + ensure + synchronize do + wait_for(:yield_shares) { @exclusive_thread && @exclusive_thread != Thread.current } + + if previous_wait + @waiting[Thread.current] = previous_wait + else + @waiting.delete Thread.current + end + @sharing[Thread.current] = loose_shares if loose_shares + end + end + end + + private + + # Must be called within synchronize + def busy_for_exclusive?(purpose) + busy_for_sharing?(purpose) || + @sharing.size > (@sharing[Thread.current] > 0 ? 1 : 0) + end + + def busy_for_sharing?(purpose) + (@exclusive_thread && @exclusive_thread != Thread.current) || + @waiting.any? { |t, (_, c)| t != Thread.current && !c.include?(purpose) } + end + + def eligible_waiters?(compatible) + @waiting.any? { |t, (p, _)| compatible.include?(p) && @waiting.all? { |t2, (_, c2)| t == t2 || c2.include?(p) } } + end + + def wait_for(method) + @sleeping[Thread.current] = method + @cv.wait_while { yield } + ensure + @sleeping.delete Thread.current + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/configurable.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/configurable.rb new file mode 100644 index 00000000..4d6f7819 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/configurable.rb @@ -0,0 +1,150 @@ +# frozen_string_literal: true + +require "active_support/concern" +require "active_support/ordered_options" +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/regexp" + +module ActiveSupport + # Configurable provides a config method to store and retrieve + # configuration options as an OrderedHash. + module Configurable + extend ActiveSupport::Concern + + class Configuration < ActiveSupport::InheritableOptions + def compile_methods! + self.class.compile_methods!(keys) + end + + # Compiles reader methods so we don't have to go through method_missing. + def self.compile_methods!(keys) + keys.reject { |m| method_defined?(m) }.each do |key| + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{key}; _get(#{key.inspect}); end + RUBY + end + end + end + + module ClassMethods + def config + @_config ||= if respond_to?(:superclass) && superclass.respond_to?(:config) + superclass.config.inheritable_copy + else + # create a new "anonymous" class that will host the compiled reader methods + Class.new(Configuration).new + end + end + + def configure + yield config + end + + # Allows you to add shortcut so that you don't have to refer to attribute + # through config. Also look at the example for config to contrast. + # + # Defines both class and instance config accessors. + # + # class User + # include ActiveSupport::Configurable + # config_accessor :allowed_access + # end + # + # User.allowed_access # => nil + # User.allowed_access = false + # User.allowed_access # => false + # + # user = User.new + # user.allowed_access # => false + # user.allowed_access = true + # user.allowed_access # => true + # + # User.allowed_access # => false + # + # The attribute name must be a valid method name in Ruby. + # + # class User + # include ActiveSupport::Configurable + # config_accessor :"1_Badname" + # end + # # => NameError: invalid config attribute name + # + # To opt out of the instance writer method, pass instance_writer: false. + # To opt out of the instance reader method, pass instance_reader: false. + # + # class User + # include ActiveSupport::Configurable + # config_accessor :allowed_access, instance_reader: false, instance_writer: false + # end + # + # User.allowed_access = false + # User.allowed_access # => false + # + # User.new.allowed_access = true # => NoMethodError + # User.new.allowed_access # => NoMethodError + # + # Or pass instance_accessor: false, to opt out both instance methods. + # + # class User + # include ActiveSupport::Configurable + # config_accessor :allowed_access, instance_accessor: false + # end + # + # User.allowed_access = false + # User.allowed_access # => false + # + # User.new.allowed_access = true # => NoMethodError + # User.new.allowed_access # => NoMethodError + # + # Also you can pass a block to set up the attribute with a default value. + # + # class User + # include ActiveSupport::Configurable + # config_accessor :hair_colors do + # [:brown, :black, :blonde, :red] + # end + # end + # + # User.hair_colors # => [:brown, :black, :blonde, :red] + def config_accessor(*names) + options = names.extract_options! + + names.each do |name| + raise NameError.new("invalid config attribute name") unless /\A[_A-Za-z]\w*\z/.match?(name) + + reader, reader_line = "def #{name}; config.#{name}; end", __LINE__ + writer, writer_line = "def #{name}=(value); config.#{name} = value; end", __LINE__ + + singleton_class.class_eval reader, __FILE__, reader_line + singleton_class.class_eval writer, __FILE__, writer_line + + unless options[:instance_accessor] == false + class_eval reader, __FILE__, reader_line unless options[:instance_reader] == false + class_eval writer, __FILE__, writer_line unless options[:instance_writer] == false + end + send("#{name}=", yield) if block_given? + end + end + private :config_accessor + end + + # Reads and writes attributes from a configuration OrderedHash. + # + # require 'active_support/configurable' + # + # class User + # include ActiveSupport::Configurable + # end + # + # user = User.new + # + # user.config.allowed_access = true + # user.config.level = 1 + # + # user.config.allowed_access # => true + # user.config.level # => 1 + def config + @_config ||= self.class.config.inheritable_copy + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext.rb new file mode 100644 index 00000000..f590605d --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +Dir.glob(File.expand_path("core_ext/*.rb", __dir__)).each do |path| + require path +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/array.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/array.rb new file mode 100644 index 00000000..6d83b768 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/array.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array/wrap" +require "active_support/core_ext/array/access" +require "active_support/core_ext/array/conversions" +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/array/grouping" +require "active_support/core_ext/array/prepend_and_append" +require "active_support/core_ext/array/inquiry" diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/array/access.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/array/access.rb new file mode 100644 index 00000000..b7ff7a39 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/array/access.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +class Array + # Returns the tail of the array from +position+. + # + # %w( a b c d ).from(0) # => ["a", "b", "c", "d"] + # %w( a b c d ).from(2) # => ["c", "d"] + # %w( a b c d ).from(10) # => [] + # %w().from(0) # => [] + # %w( a b c d ).from(-2) # => ["c", "d"] + # %w( a b c ).from(-10) # => [] + def from(position) + self[position, length] || [] + end + + # Returns the beginning of the array up to +position+. + # + # %w( a b c d ).to(0) # => ["a"] + # %w( a b c d ).to(2) # => ["a", "b", "c"] + # %w( a b c d ).to(10) # => ["a", "b", "c", "d"] + # %w().to(0) # => [] + # %w( a b c d ).to(-2) # => ["a", "b", "c"] + # %w( a b c ).to(-10) # => [] + def to(position) + if position >= 0 + take position + 1 + else + self[0..position] + end + end + + # Returns a copy of the Array without the specified elements. + # + # people = ["David", "Rafael", "Aaron", "Todd"] + # people.without "Aaron", "Todd" + # # => ["David", "Rafael"] + # + # Note: This is an optimization of Enumerable#without that uses Array#- + # instead of Array#reject for performance reasons. + def without(*elements) + self - elements + end + + # Equal to self[1]. + # + # %w( a b c d e ).second # => "b" + def second + self[1] + end + + # Equal to self[2]. + # + # %w( a b c d e ).third # => "c" + def third + self[2] + end + + # Equal to self[3]. + # + # %w( a b c d e ).fourth # => "d" + def fourth + self[3] + end + + # Equal to self[4]. + # + # %w( a b c d e ).fifth # => "e" + def fifth + self[4] + end + + # Equal to self[41]. Also known as accessing "the reddit". + # + # (1..42).to_a.forty_two # => 42 + def forty_two + self[41] + end + + # Equal to self[-3]. + # + # %w( a b c d e ).third_to_last # => "c" + def third_to_last + self[-3] + end + + # Equal to self[-2]. + # + # %w( a b c d e ).second_to_last # => "d" + def second_to_last + self[-2] + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/array/conversions.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/array/conversions.rb new file mode 100644 index 00000000..ea688ed2 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/array/conversions.rb @@ -0,0 +1,213 @@ +# frozen_string_literal: true + +require "active_support/xml_mini" +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/string/inflections" +require "active_support/core_ext/object/to_param" +require "active_support/core_ext/object/to_query" + +class Array + # Converts the array to a comma-separated sentence where the last element is + # joined by the connector word. + # + # You can pass the following options to change the default behavior. If you + # pass an option key that doesn't exist in the list below, it will raise an + # ArgumentError. + # + # ==== Options + # + # * :words_connector - The sign or word used to join the elements + # in arrays with two or more elements (default: ", "). + # * :two_words_connector - The sign or word used to join the elements + # in arrays with two elements (default: " and "). + # * :last_word_connector - The sign or word used to join the last element + # in arrays with three or more elements (default: ", and "). + # * :locale - If +i18n+ is available, you can set a locale and use + # the connector options defined on the 'support.array' namespace in the + # corresponding dictionary file. + # + # ==== Examples + # + # [].to_sentence # => "" + # ['one'].to_sentence # => "one" + # ['one', 'two'].to_sentence # => "one and two" + # ['one', 'two', 'three'].to_sentence # => "one, two, and three" + # + # ['one', 'two'].to_sentence(passing: 'invalid option') + # # => ArgumentError: Unknown key: :passing. Valid keys are: :words_connector, :two_words_connector, :last_word_connector, :locale + # + # ['one', 'two'].to_sentence(two_words_connector: '-') + # # => "one-two" + # + # ['one', 'two', 'three'].to_sentence(words_connector: ' or ', last_word_connector: ' or at least ') + # # => "one or two or at least three" + # + # Using :locale option: + # + # # Given this locale dictionary: + # # + # # es: + # # support: + # # array: + # # words_connector: " o " + # # two_words_connector: " y " + # # last_word_connector: " o al menos " + # + # ['uno', 'dos'].to_sentence(locale: :es) + # # => "uno y dos" + # + # ['uno', 'dos', 'tres'].to_sentence(locale: :es) + # # => "uno o dos o al menos tres" + def to_sentence(options = {}) + options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale) + + default_connectors = { + words_connector: ", ", + two_words_connector: " and ", + last_word_connector: ", and " + } + if defined?(I18n) + i18n_connectors = I18n.translate(:'support.array', locale: options[:locale], default: {}) + default_connectors.merge!(i18n_connectors) + end + options = default_connectors.merge!(options) + + case length + when 0 + "" + when 1 + "#{self[0]}" + when 2 + "#{self[0]}#{options[:two_words_connector]}#{self[1]}" + else + "#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}" + end + end + + # Extends Array#to_s to convert a collection of elements into a + # comma separated id list if :db argument is given as the format. + # + # Blog.all.to_formatted_s(:db) # => "1,2,3" + # Blog.none.to_formatted_s(:db) # => "null" + # [1,2].to_formatted_s # => "[1, 2]" + def to_formatted_s(format = :default) + case format + when :db + if empty? + "null" + else + collect(&:id).join(",") + end + else + to_default_s + end + end + alias_method :to_default_s, :to_s + alias_method :to_s, :to_formatted_s + + # Returns a string that represents the array in XML by invoking +to_xml+ + # on each element. Active Record collections delegate their representation + # in XML to this method. + # + # All elements are expected to respond to +to_xml+, if any of them does + # not then an exception is raised. + # + # The root node reflects the class name of the first element in plural + # if all elements belong to the same type and that's not Hash: + # + # customer.projects.to_xml + # + # + # + # + # 20000.0 + # 1567 + # 2008-04-09 + # ... + # + # + # 57230.0 + # 1567 + # 2008-04-15 + # ... + # + # + # + # Otherwise the root element is "objects": + # + # [{ foo: 1, bar: 2}, { baz: 3}].to_xml + # + # + # + # + # 2 + # 1 + # + # + # 3 + # + # + # + # If the collection is empty the root element is "nil-classes" by default: + # + # [].to_xml + # + # + # + # + # To ensure a meaningful root element use the :root option: + # + # customer_with_no_projects.projects.to_xml(root: 'projects') + # + # + # + # + # By default name of the node for the children of root is root.singularize. + # You can change it with the :children option. + # + # The +options+ hash is passed downwards: + # + # Message.all.to_xml(skip_types: true) + # + # + # + # + # 2008-03-07T09:58:18+01:00 + # 1 + # 1 + # 2008-03-07T09:58:18+01:00 + # 1 + # + # + # + def to_xml(options = {}) + require "active_support/builder" unless defined?(Builder) + + options = options.dup + options[:indent] ||= 2 + options[:builder] ||= Builder::XmlMarkup.new(indent: options[:indent]) + options[:root] ||= \ + if first.class != Hash && all? { |e| e.is_a?(first.class) } + underscored = ActiveSupport::Inflector.underscore(first.class.name) + ActiveSupport::Inflector.pluralize(underscored).tr("/", "_") + else + "objects" + end + + builder = options[:builder] + builder.instruct! unless options.delete(:skip_instruct) + + root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options) + children = options.delete(:children) || root.singularize + attributes = options[:skip_types] ? {} : { type: "array" } + + if empty? + builder.tag!(root, attributes) + else + builder.tag!(root, attributes) do + each { |value| ActiveSupport::XmlMini.to_tag(children, value, options) } + yield builder if block_given? + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/array/extract_options.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/array/extract_options.rb new file mode 100644 index 00000000..8c7cb2e7 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/array/extract_options.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +class Hash + # By default, only instances of Hash itself are extractable. + # Subclasses of Hash may implement this method and return + # true to declare themselves as extractable. If a Hash + # is extractable, Array#extract_options! pops it from + # the Array when it is the last element of the Array. + def extractable_options? + instance_of?(Hash) + end +end + +class Array + # Extracts options from a set of arguments. Removes and returns the last + # element in the array if it's a hash, otherwise returns a blank hash. + # + # def options(*args) + # args.extract_options! + # end + # + # options(1, 2) # => {} + # options(1, 2, a: :b) # => {:a=>:b} + def extract_options! + if last.is_a?(Hash) && last.extractable_options? + pop + else + {} + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/array/grouping.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/array/grouping.rb new file mode 100644 index 00000000..67e760bc --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/array/grouping.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +class Array + # Splits or iterates over the array in groups of size +number+, + # padding any remaining slots with +fill_with+ unless it is +false+. + # + # %w(1 2 3 4 5 6 7 8 9 10).in_groups_of(3) {|group| p group} + # ["1", "2", "3"] + # ["4", "5", "6"] + # ["7", "8", "9"] + # ["10", nil, nil] + # + # %w(1 2 3 4 5).in_groups_of(2, ' ') {|group| p group} + # ["1", "2"] + # ["3", "4"] + # ["5", " "] + # + # %w(1 2 3 4 5).in_groups_of(2, false) {|group| p group} + # ["1", "2"] + # ["3", "4"] + # ["5"] + def in_groups_of(number, fill_with = nil) + if number.to_i <= 0 + raise ArgumentError, + "Group size must be a positive integer, was #{number.inspect}" + end + + if fill_with == false + collection = self + else + # size % number gives how many extra we have; + # subtracting from number gives how many to add; + # modulo number ensures we don't add group of just fill. + padding = (number - size % number) % number + collection = dup.concat(Array.new(padding, fill_with)) + end + + if block_given? + collection.each_slice(number) { |slice| yield(slice) } + else + collection.each_slice(number).to_a + end + end + + # Splits or iterates over the array in +number+ of groups, padding any + # remaining slots with +fill_with+ unless it is +false+. + # + # %w(1 2 3 4 5 6 7 8 9 10).in_groups(3) {|group| p group} + # ["1", "2", "3", "4"] + # ["5", "6", "7", nil] + # ["8", "9", "10", nil] + # + # %w(1 2 3 4 5 6 7 8 9 10).in_groups(3, ' ') {|group| p group} + # ["1", "2", "3", "4"] + # ["5", "6", "7", " "] + # ["8", "9", "10", " "] + # + # %w(1 2 3 4 5 6 7).in_groups(3, false) {|group| p group} + # ["1", "2", "3"] + # ["4", "5"] + # ["6", "7"] + def in_groups(number, fill_with = nil) + # size.div number gives minor group size; + # size % number gives how many objects need extra accommodation; + # each group hold either division or division + 1 items. + division = size.div number + modulo = size % number + + # create a new array avoiding dup + groups = [] + start = 0 + + number.times do |index| + length = division + (modulo > 0 && modulo > index ? 1 : 0) + groups << last_group = slice(start, length) + last_group << fill_with if fill_with != false && + modulo > 0 && length == division + start += length + end + + if block_given? + groups.each { |g| yield(g) } + else + groups + end + end + + # Divides the array into one or more subarrays based on a delimiting +value+ + # or the result of an optional block. + # + # [1, 2, 3, 4, 5].split(3) # => [[1, 2], [4, 5]] + # (1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]] + def split(value = nil) + arr = dup + result = [] + if block_given? + while (idx = arr.index { |i| yield i }) + result << arr.shift(idx) + arr.shift + end + else + while (idx = arr.index(value)) + result << arr.shift(idx) + arr.shift + end + end + result << arr + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/array/inquiry.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/array/inquiry.rb new file mode 100644 index 00000000..92c61bf2 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/array/inquiry.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require "active_support/array_inquirer" + +class Array + # Wraps the array in an +ArrayInquirer+ object, which gives a friendlier way + # to check its string-like contents. + # + # pets = [:cat, :dog].inquiry + # + # pets.cat? # => true + # pets.ferret? # => false + # + # pets.any?(:cat, :ferret) # => true + # pets.any?(:ferret, :alligator) # => false + def inquiry + ActiveSupport::ArrayInquirer.new(self) + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/array/prepend_and_append.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/array/prepend_and_append.rb new file mode 100644 index 00000000..661971d7 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/array/prepend_and_append.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Array + # The human way of thinking about adding stuff to the end of a list is with append. + alias_method :append, :push unless [].respond_to?(:append) + + # The human way of thinking about adding stuff to the beginning of a list is with prepend. + alias_method :prepend, :unshift unless [].respond_to?(:prepend) +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/array/wrap.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/array/wrap.rb new file mode 100644 index 00000000..d62f97ed --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/array/wrap.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +class Array + # Wraps its argument in an array unless it is already an array (or array-like). + # + # Specifically: + # + # * If the argument is +nil+ an empty array is returned. + # * Otherwise, if the argument responds to +to_ary+ it is invoked, and its result returned. + # * Otherwise, returns an array with the argument as its single element. + # + # Array.wrap(nil) # => [] + # Array.wrap([1, 2, 3]) # => [1, 2, 3] + # Array.wrap(0) # => [0] + # + # This method is similar in purpose to Kernel#Array, but there are some differences: + # + # * If the argument responds to +to_ary+ the method is invoked. Kernel#Array + # moves on to try +to_a+ if the returned value is +nil+, but Array.wrap returns + # an array with the argument as its single element right away. + # * If the returned value from +to_ary+ is neither +nil+ nor an +Array+ object, Kernel#Array + # raises an exception, while Array.wrap does not, it just returns the value. + # * It does not call +to_a+ on the argument, if the argument does not respond to +to_ary+ + # it returns an array with the argument as its single element. + # + # The last point is easily explained with some enumerables: + # + # Array(foo: :bar) # => [[:foo, :bar]] + # Array.wrap(foo: :bar) # => [{:foo=>:bar}] + # + # There's also a related idiom that uses the splat operator: + # + # [*object] + # + # which returns [] for +nil+, but calls to Array(object) otherwise. + # + # The differences with Kernel#Array explained above + # apply to the rest of objects. + def self.wrap(object) + if object.nil? + [] + elsif object.respond_to?(:to_ary) + object.to_ary || [object] + else + [object] + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/benchmark.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/benchmark.rb new file mode 100644 index 00000000..641b58c8 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/benchmark.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "benchmark" + +class << Benchmark + # Benchmark realtime in milliseconds. + # + # Benchmark.realtime { User.all } + # # => 8.0e-05 + # + # Benchmark.ms { User.all } + # # => 0.074 + def ms + 1000 * realtime { yield } + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/big_decimal.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/big_decimal.rb new file mode 100644 index 00000000..9e6a9d63 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/big_decimal.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require "active_support/core_ext/big_decimal/conversions" diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/big_decimal/conversions.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/big_decimal/conversions.rb new file mode 100644 index 00000000..52bd2294 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/big_decimal/conversions.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "bigdecimal" +require "bigdecimal/util" + +module ActiveSupport + module BigDecimalWithDefaultFormat #:nodoc: + def to_s(format = "F") + super(format) + end + end +end + +BigDecimal.prepend(ActiveSupport::BigDecimalWithDefaultFormat) diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/class.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/class.rb new file mode 100644 index 00000000..1c110fd0 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/class.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +require "active_support/core_ext/class/attribute" +require "active_support/core_ext/class/subclasses" diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/class/attribute.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/class/attribute.rb new file mode 100644 index 00000000..7928efb8 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/class/attribute.rb @@ -0,0 +1,146 @@ +# frozen_string_literal: true + +require "active_support/core_ext/kernel/singleton_class" +require "active_support/core_ext/module/redefine_method" +require "active_support/core_ext/array/extract_options" + +class Class + # Declare a class-level attribute whose value is inheritable by subclasses. + # Subclasses can change their own value and it will not impact parent class. + # + # ==== Options + # + # * :instance_reader - Sets the instance reader method (defaults to true). + # * :instance_writer - Sets the instance writer method (defaults to true). + # * :instance_accessor - Sets both instance methods (defaults to true). + # * :instance_predicate - Sets a predicate method (defaults to true). + # * :default - Sets a default value for the attribute (defaults to nil). + # + # ==== Examples + # + # class Base + # class_attribute :setting + # end + # + # class Subclass < Base + # end + # + # Base.setting = true + # Subclass.setting # => true + # Subclass.setting = false + # Subclass.setting # => false + # Base.setting # => true + # + # In the above case as long as Subclass does not assign a value to setting + # by performing Subclass.setting = _something_, Subclass.setting + # would read value assigned to parent class. Once Subclass assigns a value then + # the value assigned by Subclass would be returned. + # + # This matches normal Ruby method inheritance: think of writing an attribute + # on a subclass as overriding the reader method. However, you need to be aware + # when using +class_attribute+ with mutable structures as +Array+ or +Hash+. + # In such cases, you don't want to do changes in place. Instead use setters: + # + # Base.setting = [] + # Base.setting # => [] + # Subclass.setting # => [] + # + # # Appending in child changes both parent and child because it is the same object: + # Subclass.setting << :foo + # Base.setting # => [:foo] + # Subclass.setting # => [:foo] + # + # # Use setters to not propagate changes: + # Base.setting = [] + # Subclass.setting += [:foo] + # Base.setting # => [] + # Subclass.setting # => [:foo] + # + # For convenience, an instance predicate method is defined as well. + # To skip it, pass instance_predicate: false. + # + # Subclass.setting? # => false + # + # Instances may overwrite the class value in the same way: + # + # Base.setting = true + # object = Base.new + # object.setting # => true + # object.setting = false + # object.setting # => false + # Base.setting # => true + # + # To opt out of the instance reader method, pass instance_reader: false. + # + # object.setting # => NoMethodError + # object.setting? # => NoMethodError + # + # To opt out of the instance writer method, pass instance_writer: false. + # + # object.setting = false # => NoMethodError + # + # To opt out of both instance methods, pass instance_accessor: false. + # + # To set a default value for the attribute, pass default:, like so: + # + # class_attribute :settings, default: {} + def class_attribute(*attrs) + options = attrs.extract_options! + instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true) + instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true) + instance_predicate = options.fetch(:instance_predicate, true) + default_value = options.fetch(:default, nil) + + attrs.each do |name| + singleton_class.silence_redefinition_of_method(name) + define_singleton_method(name) { nil } + + singleton_class.silence_redefinition_of_method("#{name}?") + define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate + + ivar = "@#{name}" + + singleton_class.silence_redefinition_of_method("#{name}=") + define_singleton_method("#{name}=") do |val| + singleton_class.class_eval do + redefine_method(name) { val } + end + + if singleton_class? + class_eval do + redefine_method(name) do + if instance_variable_defined? ivar + instance_variable_get ivar + else + singleton_class.send name + end + end + end + end + val + end + + if instance_reader + redefine_method(name) do + if instance_variable_defined?(ivar) + instance_variable_get ivar + else + self.class.public_send name + end + end + + redefine_method("#{name}?") { !!public_send(name) } if instance_predicate + end + + if instance_writer + redefine_method("#{name}=") do |val| + instance_variable_set ivar, val + end + end + + unless default_value.nil? + self.send("#{name}=", default_value) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/class/attribute_accessors.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/class/attribute_accessors.rb new file mode 100644 index 00000000..a77354e1 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/class/attribute_accessors.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +# cattr_* became mattr_* aliases in 7dfbd91b0780fbd6a1dd9bfbc176e10894871d2d, +# but we keep this around for libraries that directly require it knowing they +# want cattr_*. No need to deprecate. +require "active_support/core_ext/module/attribute_accessors" diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/class/subclasses.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/class/subclasses.rb new file mode 100644 index 00000000..75e65337 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/class/subclasses.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +class Class + begin + # Test if this Ruby supports each_object against singleton_class + ObjectSpace.each_object(Numeric.singleton_class) {} + + # Returns an array with all classes that are < than its receiver. + # + # class C; end + # C.descendants # => [] + # + # class B < C; end + # C.descendants # => [B] + # + # class A < B; end + # C.descendants # => [B, A] + # + # class D < C; end + # C.descendants # => [B, A, D] + def descendants + descendants = [] + ObjectSpace.each_object(singleton_class) do |k| + next if k.singleton_class? + descendants.unshift k unless k == self + end + descendants + end + rescue StandardError # JRuby 9.0.4.0 and earlier + def descendants + descendants = [] + ObjectSpace.each_object(Class) do |k| + descendants.unshift k if k < self + end + descendants.uniq! + descendants + end + end + + # Returns an array with the direct children of +self+. + # + # class Foo; end + # class Bar < Foo; end + # class Baz < Bar; end + # + # Foo.subclasses # => [Bar] + def subclasses + subclasses, chain = [], descendants + chain.each do |k| + subclasses << k unless chain.any? { |c| c > k } + end + subclasses + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date.rb new file mode 100644 index 00000000..cce73f2d --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "active_support/core_ext/date/acts_like" +require "active_support/core_ext/date/blank" +require "active_support/core_ext/date/calculations" +require "active_support/core_ext/date/conversions" +require "active_support/core_ext/date/zones" diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date/acts_like.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date/acts_like.rb new file mode 100644 index 00000000..c8077f37 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date/acts_like.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/acts_like" + +class Date + # Duck-types as a Date-like class. See Object#acts_like?. + def acts_like_date? + true + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date/blank.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date/blank.rb new file mode 100644 index 00000000..e6271c79 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date/blank.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "date" + +class Date #:nodoc: + # No Date is blank: + # + # Date.today.blank? # => false + # + # @return [false] + def blank? + false + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date/calculations.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date/calculations.rb new file mode 100644 index 00000000..1cd7acb0 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date/calculations.rb @@ -0,0 +1,145 @@ +# frozen_string_literal: true + +require "date" +require "active_support/duration" +require "active_support/core_ext/object/acts_like" +require "active_support/core_ext/date/zones" +require "active_support/core_ext/time/zones" +require "active_support/core_ext/date_and_time/calculations" + +class Date + include DateAndTime::Calculations + + class << self + attr_accessor :beginning_of_week_default + + # Returns the week start (e.g. :monday) for the current request, if this has been set (via Date.beginning_of_week=). + # If Date.beginning_of_week has not been set for the current request, returns the week start specified in config.beginning_of_week. + # If no config.beginning_of_week was specified, returns :monday. + def beginning_of_week + Thread.current[:beginning_of_week] || beginning_of_week_default || :monday + end + + # Sets Date.beginning_of_week to a week start (e.g. :monday) for current request/thread. + # + # This method accepts any of the following day symbols: + # :monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday + def beginning_of_week=(week_start) + Thread.current[:beginning_of_week] = find_beginning_of_week!(week_start) + end + + # Returns week start day symbol (e.g. :monday), or raises an +ArgumentError+ for invalid day symbol. + def find_beginning_of_week!(week_start) + raise ArgumentError, "Invalid beginning of week: #{week_start}" unless ::Date::DAYS_INTO_WEEK.key?(week_start) + week_start + end + + # Returns a new Date representing the date 1 day ago (i.e. yesterday's date). + def yesterday + ::Date.current.yesterday + end + + # Returns a new Date representing the date 1 day after today (i.e. tomorrow's date). + def tomorrow + ::Date.current.tomorrow + end + + # Returns Time.zone.today when Time.zone or config.time_zone are set, otherwise just returns Date.today. + def current + ::Time.zone ? ::Time.zone.today : ::Date.today + end + end + + # Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00) + # and then subtracts the specified number of seconds. + def ago(seconds) + in_time_zone.since(-seconds) + end + + # Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00) + # and then adds the specified number of seconds + def since(seconds) + in_time_zone.since(seconds) + end + alias :in :since + + # Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00) + def beginning_of_day + in_time_zone + end + alias :midnight :beginning_of_day + alias :at_midnight :beginning_of_day + alias :at_beginning_of_day :beginning_of_day + + # Converts Date to a Time (or DateTime if necessary) with the time portion set to the middle of the day (12:00) + def middle_of_day + in_time_zone.middle_of_day + end + alias :midday :middle_of_day + alias :noon :middle_of_day + alias :at_midday :middle_of_day + alias :at_noon :middle_of_day + alias :at_middle_of_day :middle_of_day + + # Converts Date to a Time (or DateTime if necessary) with the time portion set to the end of the day (23:59:59) + def end_of_day + in_time_zone.end_of_day + end + alias :at_end_of_day :end_of_day + + def plus_with_duration(other) #:nodoc: + if ActiveSupport::Duration === other + other.since(self) + else + plus_without_duration(other) + end + end + alias_method :plus_without_duration, :+ + alias_method :+, :plus_with_duration + + def minus_with_duration(other) #:nodoc: + if ActiveSupport::Duration === other + plus_with_duration(-other) + else + minus_without_duration(other) + end + end + alias_method :minus_without_duration, :- + alias_method :-, :minus_with_duration + + # Provides precise Date calculations for years, months, and days. The +options+ parameter takes a hash with + # any of these keys: :years, :months, :weeks, :days. + def advance(options) + options = options.dup + d = self + d = d >> options.delete(:years) * 12 if options[:years] + d = d >> options.delete(:months) if options[:months] + d = d + options.delete(:weeks) * 7 if options[:weeks] + d = d + options.delete(:days) if options[:days] + d + end + + # Returns a new Date where one or more of the elements have been changed according to the +options+ parameter. + # The +options+ parameter is a hash with a combination of these keys: :year, :month, :day. + # + # Date.new(2007, 5, 12).change(day: 1) # => Date.new(2007, 5, 1) + # Date.new(2007, 5, 12).change(year: 2005, month: 1) # => Date.new(2005, 1, 12) + def change(options) + ::Date.new( + options.fetch(:year, year), + options.fetch(:month, month), + options.fetch(:day, day) + ) + end + + # Allow Date to be compared with Time by converting to DateTime and relying on the <=> from there. + def compare_with_coercion(other) + if other.is_a?(Time) + to_datetime <=> other + else + compare_without_coercion(other) + end + end + alias_method :compare_without_coercion, :<=> + alias_method :<=>, :compare_with_coercion +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date/conversions.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date/conversions.rb new file mode 100644 index 00000000..870119dc --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date/conversions.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +require "date" +require "active_support/inflector/methods" +require "active_support/core_ext/date/zones" +require "active_support/core_ext/module/redefine_method" + +class Date + DATE_FORMATS = { + short: "%d %b", + long: "%B %d, %Y", + db: "%Y-%m-%d", + number: "%Y%m%d", + long_ordinal: lambda { |date| + day_format = ActiveSupport::Inflector.ordinalize(date.day) + date.strftime("%B #{day_format}, %Y") # => "April 25th, 2007" + }, + rfc822: "%d %b %Y", + iso8601: lambda { |date| date.iso8601 } + } + + # Convert to a formatted string. See DATE_FORMATS for predefined formats. + # + # This method is aliased to to_s. + # + # date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007 + # + # date.to_formatted_s(:db) # => "2007-11-10" + # date.to_s(:db) # => "2007-11-10" + # + # date.to_formatted_s(:short) # => "10 Nov" + # date.to_formatted_s(:number) # => "20071110" + # date.to_formatted_s(:long) # => "November 10, 2007" + # date.to_formatted_s(:long_ordinal) # => "November 10th, 2007" + # date.to_formatted_s(:rfc822) # => "10 Nov 2007" + # date.to_formatted_s(:iso8601) # => "2007-11-10" + # + # == Adding your own date formats to to_formatted_s + # You can add your own formats to the Date::DATE_FORMATS hash. + # Use the format name as the hash key and either a strftime string + # or Proc instance that takes a date argument as the value. + # + # # config/initializers/date_formats.rb + # Date::DATE_FORMATS[:month_and_year] = '%B %Y' + # Date::DATE_FORMATS[:short_ordinal] = ->(date) { date.strftime("%B #{date.day.ordinalize}") } + def to_formatted_s(format = :default) + if formatter = DATE_FORMATS[format] + if formatter.respond_to?(:call) + formatter.call(self).to_s + else + strftime(formatter) + end + else + to_default_s + end + end + alias_method :to_default_s, :to_s + alias_method :to_s, :to_formatted_s + + # Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005" + def readable_inspect + strftime("%a, %d %b %Y") + end + alias_method :default_inspect, :inspect + alias_method :inspect, :readable_inspect + + silence_redefinition_of_method :to_time + + # Converts a Date instance to a Time, where the time is set to the beginning of the day. + # The timezone can be either :local or :utc (default :local). + # + # date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007 + # + # date.to_time # => 2007-11-10 00:00:00 0800 + # date.to_time(:local) # => 2007-11-10 00:00:00 0800 + # + # date.to_time(:utc) # => 2007-11-10 00:00:00 UTC + # + # NOTE: The :local timezone is Ruby's *process* timezone, i.e. ENV['TZ']. + # If the *application's* timezone is needed, then use +in_time_zone+ instead. + def to_time(form = :local) + raise ArgumentError, "Expected :local or :utc, got #{form.inspect}." unless [:local, :utc].include?(form) + ::Time.send(form, year, month, day) + end + + silence_redefinition_of_method :xmlschema + + # Returns a string which represents the time in used time zone as DateTime + # defined by XML Schema: + # + # date = Date.new(2015, 05, 23) # => Sat, 23 May 2015 + # date.xmlschema # => "2015-05-23T00:00:00+04:00" + def xmlschema + in_time_zone.xmlschema + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date/zones.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date/zones.rb new file mode 100644 index 00000000..2dcf97cf --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date/zones.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require "date" +require "active_support/core_ext/date_and_time/zones" + +class Date + include DateAndTime::Zones +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date_and_time/calculations.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date_and_time/calculations.rb new file mode 100644 index 00000000..f6cb1a38 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date_and_time/calculations.rb @@ -0,0 +1,374 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/try" + +module DateAndTime + module Calculations + DAYS_INTO_WEEK = { + monday: 0, + tuesday: 1, + wednesday: 2, + thursday: 3, + friday: 4, + saturday: 5, + sunday: 6 + } + WEEKEND_DAYS = [ 6, 0 ] + + # Returns a new date/time representing yesterday. + def yesterday + advance(days: -1) + end + + # Returns a new date/time the specified number of days ago. + def prev_day(days = 1) + advance(days: -days) + end + + # Returns a new date/time representing tomorrow. + def tomorrow + advance(days: 1) + end + + # Returns a new date/time the specified number of days in the future. + def next_day(days = 1) + advance(days: days) + end + + # Returns true if the date/time is today. + def today? + to_date == ::Date.current + end + + # Returns true if the date/time is in the past. + def past? + self < self.class.current + end + + # Returns true if the date/time is in the future. + def future? + self > self.class.current + end + + # Returns true if the date/time falls on a Saturday or Sunday. + def on_weekend? + WEEKEND_DAYS.include?(wday) + end + + # Returns true if the date/time does not fall on a Saturday or Sunday. + def on_weekday? + !WEEKEND_DAYS.include?(wday) + end + + # Returns a new date/time the specified number of days ago. + def days_ago(days) + advance(days: -days) + end + + # Returns a new date/time the specified number of days in the future. + def days_since(days) + advance(days: days) + end + + # Returns a new date/time the specified number of weeks ago. + def weeks_ago(weeks) + advance(weeks: -weeks) + end + + # Returns a new date/time the specified number of weeks in the future. + def weeks_since(weeks) + advance(weeks: weeks) + end + + # Returns a new date/time the specified number of months ago. + def months_ago(months) + advance(months: -months) + end + + # Returns a new date/time the specified number of months in the future. + def months_since(months) + advance(months: months) + end + + # Returns a new date/time the specified number of years ago. + def years_ago(years) + advance(years: -years) + end + + # Returns a new date/time the specified number of years in the future. + def years_since(years) + advance(years: years) + end + + # Returns a new date/time at the start of the month. + # + # today = Date.today # => Thu, 18 Jun 2015 + # today.beginning_of_month # => Mon, 01 Jun 2015 + # + # +DateTime+ objects will have a time set to 0:00. + # + # now = DateTime.current # => Thu, 18 Jun 2015 15:23:13 +0000 + # now.beginning_of_month # => Mon, 01 Jun 2015 00:00:00 +0000 + def beginning_of_month + first_hour(change(day: 1)) + end + alias :at_beginning_of_month :beginning_of_month + + # Returns a new date/time at the start of the quarter. + # + # today = Date.today # => Fri, 10 Jul 2015 + # today.beginning_of_quarter # => Wed, 01 Jul 2015 + # + # +DateTime+ objects will have a time set to 0:00. + # + # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000 + # now.beginning_of_quarter # => Wed, 01 Jul 2015 00:00:00 +0000 + def beginning_of_quarter + first_quarter_month = [10, 7, 4, 1].detect { |m| m <= month } + beginning_of_month.change(month: first_quarter_month) + end + alias :at_beginning_of_quarter :beginning_of_quarter + + # Returns a new date/time at the end of the quarter. + # + # today = Date.today # => Fri, 10 Jul 2015 + # today.end_of_quarter # => Wed, 30 Sep 2015 + # + # +DateTime+ objects will have a time set to 23:59:59. + # + # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000 + # now.end_of_quarter # => Wed, 30 Sep 2015 23:59:59 +0000 + def end_of_quarter + last_quarter_month = [3, 6, 9, 12].detect { |m| m >= month } + beginning_of_month.change(month: last_quarter_month).end_of_month + end + alias :at_end_of_quarter :end_of_quarter + + # Returns a new date/time at the beginning of the year. + # + # today = Date.today # => Fri, 10 Jul 2015 + # today.beginning_of_year # => Thu, 01 Jan 2015 + # + # +DateTime+ objects will have a time set to 0:00. + # + # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000 + # now.beginning_of_year # => Thu, 01 Jan 2015 00:00:00 +0000 + def beginning_of_year + change(month: 1).beginning_of_month + end + alias :at_beginning_of_year :beginning_of_year + + # Returns a new date/time representing the given day in the next week. + # + # today = Date.today # => Thu, 07 May 2015 + # today.next_week # => Mon, 11 May 2015 + # + # The +given_day_in_next_week+ defaults to the beginning of the week + # which is determined by +Date.beginning_of_week+ or +config.beginning_of_week+ + # when set. + # + # today = Date.today # => Thu, 07 May 2015 + # today.next_week(:friday) # => Fri, 15 May 2015 + # + # +DateTime+ objects have their time set to 0:00 unless +same_time+ is true. + # + # now = DateTime.current # => Thu, 07 May 2015 13:31:16 +0000 + # now.next_week # => Mon, 11 May 2015 00:00:00 +0000 + def next_week(given_day_in_next_week = Date.beginning_of_week, same_time: false) + result = first_hour(weeks_since(1).beginning_of_week.days_since(days_span(given_day_in_next_week))) + same_time ? copy_time_to(result) : result + end + + # Returns a new date/time representing the next weekday. + def next_weekday + if next_day.on_weekend? + next_week(:monday, same_time: true) + else + next_day + end + end + + # Returns a new date/time the specified number of months in the future. + def next_month(months = 1) + advance(months: months) + end + + # Short-hand for months_since(3) + def next_quarter + months_since(3) + end + + # Returns a new date/time the specified number of years in the future. + def next_year(years = 1) + advance(years: years) + end + + # Returns a new date/time representing the given day in the previous week. + # Week is assumed to start on +start_day+, default is + # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. + # DateTime objects have their time set to 0:00 unless +same_time+ is true. + def prev_week(start_day = Date.beginning_of_week, same_time: false) + result = first_hour(weeks_ago(1).beginning_of_week.days_since(days_span(start_day))) + same_time ? copy_time_to(result) : result + end + alias_method :last_week, :prev_week + + # Returns a new date/time representing the previous weekday. + def prev_weekday + if prev_day.on_weekend? + copy_time_to(beginning_of_week(:friday)) + else + prev_day + end + end + alias_method :last_weekday, :prev_weekday + + # Returns a new date/time the specified number of months ago. + def prev_month(months = 1) + advance(months: -months) + end + + # Short-hand for months_ago(1). + def last_month + months_ago(1) + end + + # Short-hand for months_ago(3). + def prev_quarter + months_ago(3) + end + alias_method :last_quarter, :prev_quarter + + # Returns a new date/time the specified number of years ago. + def prev_year(years = 1) + advance(years: -years) + end + + # Short-hand for years_ago(1). + def last_year + years_ago(1) + end + + # Returns the number of days to the start of the week on the given day. + # Week is assumed to start on +start_day+, default is + # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. + def days_to_week_start(start_day = Date.beginning_of_week) + start_day_number = DAYS_INTO_WEEK[start_day] + current_day_number = wday != 0 ? wday - 1 : 6 + (current_day_number - start_day_number) % 7 + end + + # Returns a new date/time representing the start of this week on the given day. + # Week is assumed to start on +start_day+, default is + # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. + # +DateTime+ objects have their time set to 0:00. + def beginning_of_week(start_day = Date.beginning_of_week) + result = days_ago(days_to_week_start(start_day)) + acts_like?(:time) ? result.midnight : result + end + alias :at_beginning_of_week :beginning_of_week + + # Returns Monday of this week assuming that week starts on Monday. + # +DateTime+ objects have their time set to 0:00. + def monday + beginning_of_week(:monday) + end + + # Returns a new date/time representing the end of this week on the given day. + # Week is assumed to start on +start_day+, default is + # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. + # DateTime objects have their time set to 23:59:59. + def end_of_week(start_day = Date.beginning_of_week) + last_hour(days_since(6 - days_to_week_start(start_day))) + end + alias :at_end_of_week :end_of_week + + # Returns Sunday of this week assuming that week starts on Monday. + # +DateTime+ objects have their time set to 23:59:59. + def sunday + end_of_week(:monday) + end + + # Returns a new date/time representing the end of the month. + # DateTime objects will have a time set to 23:59:59. + def end_of_month + last_day = ::Time.days_in_month(month, year) + last_hour(days_since(last_day - day)) + end + alias :at_end_of_month :end_of_month + + # Returns a new date/time representing the end of the year. + # DateTime objects will have a time set to 23:59:59. + def end_of_year + change(month: 12).end_of_month + end + alias :at_end_of_year :end_of_year + + # Returns a Range representing the whole day of the current date/time. + def all_day + beginning_of_day..end_of_day + end + + # Returns a Range representing the whole week of the current date/time. + # Week starts on start_day, default is Date.beginning_of_week or config.beginning_of_week when set. + def all_week(start_day = Date.beginning_of_week) + beginning_of_week(start_day)..end_of_week(start_day) + end + + # Returns a Range representing the whole month of the current date/time. + def all_month + beginning_of_month..end_of_month + end + + # Returns a Range representing the whole quarter of the current date/time. + def all_quarter + beginning_of_quarter..end_of_quarter + end + + # Returns a Range representing the whole year of the current date/time. + def all_year + beginning_of_year..end_of_year + end + + # Returns a new date/time representing the next occurrence of the specified day of week. + # + # today = Date.today # => Thu, 14 Dec 2017 + # today.next_occurring(:monday) # => Mon, 18 Dec 2017 + # today.next_occurring(:thursday) # => Thu, 21 Dec 2017 + def next_occurring(day_of_week) + current_day_number = wday != 0 ? wday - 1 : 6 + from_now = DAYS_INTO_WEEK.fetch(day_of_week) - current_day_number + from_now += 7 unless from_now > 0 + advance(days: from_now) + end + + # Returns a new date/time representing the previous occurrence of the specified day of week. + # + # today = Date.today # => Thu, 14 Dec 2017 + # today.prev_occurring(:monday) # => Mon, 11 Dec 2017 + # today.prev_occurring(:thursday) # => Thu, 07 Dec 2017 + def prev_occurring(day_of_week) + current_day_number = wday != 0 ? wday - 1 : 6 + ago = current_day_number - DAYS_INTO_WEEK.fetch(day_of_week) + ago += 7 unless ago > 0 + advance(days: -ago) + end + + private + def first_hour(date_or_time) + date_or_time.acts_like?(:time) ? date_or_time.beginning_of_day : date_or_time + end + + def last_hour(date_or_time) + date_or_time.acts_like?(:time) ? date_or_time.end_of_day : date_or_time + end + + def days_span(day) + (DAYS_INTO_WEEK[day] - DAYS_INTO_WEEK[Date.beginning_of_week]) % 7 + end + + def copy_time_to(other) + other.change(hour: hour, min: min, sec: sec, nsec: try(:nsec)) + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date_and_time/compatibility.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date_and_time/compatibility.rb new file mode 100644 index 00000000..d33c36ef --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date_and_time/compatibility.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attribute_accessors" + +module DateAndTime + module Compatibility + # If true, +to_time+ preserves the timezone offset of receiver. + # + # NOTE: With Ruby 2.4+ the default for +to_time+ changed from + # converting to the local system time, to preserving the offset + # of the receiver. For backwards compatibility we're overriding + # this behavior, but new apps will have an initializer that sets + # this to true, because the new behavior is preferred. + mattr_accessor :preserve_timezone, instance_writer: false, default: false + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date_and_time/zones.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date_and_time/zones.rb new file mode 100644 index 00000000..894fd9b7 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date_and_time/zones.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module DateAndTime + module Zones + # Returns the simultaneous time in Time.zone if a zone is given or + # if Time.zone_default is set. Otherwise, it returns the current time. + # + # Time.zone = 'Hawaii' # => 'Hawaii' + # Time.utc(2000).in_time_zone # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # Date.new(2000).in_time_zone # => Sat, 01 Jan 2000 00:00:00 HST -10:00 + # + # This method is similar to Time#localtime, except that it uses Time.zone as the local zone + # instead of the operating system's time zone. + # + # You can also pass in a TimeZone instance or string that identifies a TimeZone as an argument, + # and the conversion will be based on that zone instead of Time.zone. + # + # Time.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00 + # Date.new(2000).in_time_zone('Alaska') # => Sat, 01 Jan 2000 00:00:00 AKST -09:00 + def in_time_zone(zone = ::Time.zone) + time_zone = ::Time.find_zone! zone + time = acts_like?(:time) ? self : nil + + if time_zone + time_with_zone(time, time_zone) + else + time || to_time + end + end + + private + + def time_with_zone(time, zone) + if time + ActiveSupport::TimeWithZone.new(time.utc? ? time : time.getutc, zone) + else + ActiveSupport::TimeWithZone.new(nil, zone, to_time(:utc)) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date_time.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date_time.rb new file mode 100644 index 00000000..790dbeec --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date_time.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "active_support/core_ext/date_time/acts_like" +require "active_support/core_ext/date_time/blank" +require "active_support/core_ext/date_time/calculations" +require "active_support/core_ext/date_time/compatibility" +require "active_support/core_ext/date_time/conversions" diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date_time/acts_like.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date_time/acts_like.rb new file mode 100644 index 00000000..5dccdfe2 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date_time/acts_like.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "date" +require "active_support/core_ext/object/acts_like" + +class DateTime + # Duck-types as a Date-like class. See Object#acts_like?. + def acts_like_date? + true + end + + # Duck-types as a Time-like class. See Object#acts_like?. + def acts_like_time? + true + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date_time/blank.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date_time/blank.rb new file mode 100644 index 00000000..a52c8bc1 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date_time/blank.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "date" + +class DateTime #:nodoc: + # No DateTime is ever blank: + # + # DateTime.now.blank? # => false + # + # @return [false] + def blank? + false + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date_time/calculations.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date_time/calculations.rb new file mode 100644 index 00000000..e61b23f8 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date_time/calculations.rb @@ -0,0 +1,211 @@ +# frozen_string_literal: true + +require "date" + +class DateTime + class << self + # Returns Time.zone.now.to_datetime when Time.zone or + # config.time_zone are set, otherwise returns + # Time.now.to_datetime. + def current + ::Time.zone ? ::Time.zone.now.to_datetime : ::Time.now.to_datetime + end + end + + # Returns the number of seconds since 00:00:00. + # + # DateTime.new(2012, 8, 29, 0, 0, 0).seconds_since_midnight # => 0 + # DateTime.new(2012, 8, 29, 12, 34, 56).seconds_since_midnight # => 45296 + # DateTime.new(2012, 8, 29, 23, 59, 59).seconds_since_midnight # => 86399 + def seconds_since_midnight + sec + (min * 60) + (hour * 3600) + end + + # Returns the number of seconds until 23:59:59. + # + # DateTime.new(2012, 8, 29, 0, 0, 0).seconds_until_end_of_day # => 86399 + # DateTime.new(2012, 8, 29, 12, 34, 56).seconds_until_end_of_day # => 41103 + # DateTime.new(2012, 8, 29, 23, 59, 59).seconds_until_end_of_day # => 0 + def seconds_until_end_of_day + end_of_day.to_i - to_i + end + + # Returns the fraction of a second as a +Rational+ + # + # DateTime.new(2012, 8, 29, 0, 0, 0.5).subsec # => (1/2) + def subsec + sec_fraction + end + + # Returns a new DateTime where one or more of the elements have been changed + # according to the +options+ parameter. The time options (:hour, + # :min, :sec) reset cascadingly, so if only the hour is + # passed, then minute and sec is set to 0. If the hour and minute is passed, + # then sec is set to 0. The +options+ parameter takes a hash with any of these + # keys: :year, :month, :day, :hour, + # :min, :sec, :offset, :start. + # + # DateTime.new(2012, 8, 29, 22, 35, 0).change(day: 1) # => DateTime.new(2012, 8, 1, 22, 35, 0) + # DateTime.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => DateTime.new(1981, 8, 1, 22, 35, 0) + # DateTime.new(2012, 8, 29, 22, 35, 0).change(year: 1981, hour: 0) # => DateTime.new(1981, 8, 29, 0, 0, 0) + def change(options) + if new_nsec = options[:nsec] + raise ArgumentError, "Can't change both :nsec and :usec at the same time: #{options.inspect}" if options[:usec] + new_fraction = Rational(new_nsec, 1000000000) + else + new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000)) + new_fraction = Rational(new_usec, 1000000) + end + + raise ArgumentError, "argument out of range" if new_fraction >= 1 + + ::DateTime.civil( + options.fetch(:year, year), + options.fetch(:month, month), + options.fetch(:day, day), + options.fetch(:hour, hour), + options.fetch(:min, options[:hour] ? 0 : min), + options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec) + new_fraction, + options.fetch(:offset, offset), + options.fetch(:start, start) + ) + end + + # Uses Date to provide precise Time calculations for years, months, and days. + # The +options+ parameter takes a hash with any of these keys: :years, + # :months, :weeks, :days, :hours, + # :minutes, :seconds. + def advance(options) + unless options[:weeks].nil? + options[:weeks], partial_weeks = options[:weeks].divmod(1) + options[:days] = options.fetch(:days, 0) + 7 * partial_weeks + end + + unless options[:days].nil? + options[:days], partial_days = options[:days].divmod(1) + options[:hours] = options.fetch(:hours, 0) + 24 * partial_days + end + + d = to_date.advance(options) + datetime_advanced_by_date = change(year: d.year, month: d.month, day: d.day) + seconds_to_advance = \ + options.fetch(:seconds, 0) + + options.fetch(:minutes, 0) * 60 + + options.fetch(:hours, 0) * 3600 + + if seconds_to_advance.zero? + datetime_advanced_by_date + else + datetime_advanced_by_date.since(seconds_to_advance) + end + end + + # Returns a new DateTime representing the time a number of seconds ago. + # Do not use this method in combination with x.months, use months_ago instead! + def ago(seconds) + since(-seconds) + end + + # Returns a new DateTime representing the time a number of seconds since the + # instance time. Do not use this method in combination with x.months, use + # months_since instead! + def since(seconds) + self + Rational(seconds.round, 86400) + end + alias :in :since + + # Returns a new DateTime representing the start of the day (0:00). + def beginning_of_day + change(hour: 0) + end + alias :midnight :beginning_of_day + alias :at_midnight :beginning_of_day + alias :at_beginning_of_day :beginning_of_day + + # Returns a new DateTime representing the middle of the day (12:00) + def middle_of_day + change(hour: 12) + end + alias :midday :middle_of_day + alias :noon :middle_of_day + alias :at_midday :middle_of_day + alias :at_noon :middle_of_day + alias :at_middle_of_day :middle_of_day + + # Returns a new DateTime representing the end of the day (23:59:59). + def end_of_day + change(hour: 23, min: 59, sec: 59, usec: Rational(999999999, 1000)) + end + alias :at_end_of_day :end_of_day + + # Returns a new DateTime representing the start of the hour (hh:00:00). + def beginning_of_hour + change(min: 0) + end + alias :at_beginning_of_hour :beginning_of_hour + + # Returns a new DateTime representing the end of the hour (hh:59:59). + def end_of_hour + change(min: 59, sec: 59, usec: Rational(999999999, 1000)) + end + alias :at_end_of_hour :end_of_hour + + # Returns a new DateTime representing the start of the minute (hh:mm:00). + def beginning_of_minute + change(sec: 0) + end + alias :at_beginning_of_minute :beginning_of_minute + + # Returns a new DateTime representing the end of the minute (hh:mm:59). + def end_of_minute + change(sec: 59, usec: Rational(999999999, 1000)) + end + alias :at_end_of_minute :end_of_minute + + # Returns a Time instance of the simultaneous time in the system timezone. + def localtime(utc_offset = nil) + utc = new_offset(0) + + Time.utc( + utc.year, utc.month, utc.day, + utc.hour, utc.min, utc.sec + utc.sec_fraction + ).getlocal(utc_offset) + end + alias_method :getlocal, :localtime + + # Returns a Time instance of the simultaneous time in the UTC timezone. + # + # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)) # => Mon, 21 Feb 2005 10:11:12 -0600 + # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc # => Mon, 21 Feb 2005 16:11:12 UTC + def utc + utc = new_offset(0) + + Time.utc( + utc.year, utc.month, utc.day, + utc.hour, utc.min, utc.sec + utc.sec_fraction + ) + end + alias_method :getgm, :utc + alias_method :getutc, :utc + alias_method :gmtime, :utc + + # Returns +true+ if offset == 0. + def utc? + offset == 0 + end + + # Returns the offset value in seconds. + def utc_offset + (offset * 86400).to_i + end + + # Layers additional behavior on DateTime#<=> so that Time and + # ActiveSupport::TimeWithZone instances can be compared with a DateTime. + def <=>(other) + if other.respond_to? :to_datetime + super other.to_datetime rescue nil + else + super + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date_time/compatibility.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date_time/compatibility.rb new file mode 100644 index 00000000..7600a067 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date_time/compatibility.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require "active_support/core_ext/date_and_time/compatibility" +require "active_support/core_ext/module/redefine_method" + +class DateTime + include DateAndTime::Compatibility + + silence_redefinition_of_method :to_time + + # Either return an instance of +Time+ with the same UTC offset + # as +self+ or an instance of +Time+ representing the same time + # in the local system timezone depending on the setting of + # on the setting of +ActiveSupport.to_time_preserves_timezone+. + def to_time + preserve_timezone ? getlocal(utc_offset) : getlocal + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date_time/conversions.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date_time/conversions.rb new file mode 100644 index 00000000..29725c89 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/date_time/conversions.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +require "date" +require "active_support/inflector/methods" +require "active_support/core_ext/time/conversions" +require "active_support/core_ext/date_time/calculations" +require "active_support/values/time_zone" + +class DateTime + # Convert to a formatted string. See Time::DATE_FORMATS for predefined formats. + # + # This method is aliased to to_s. + # + # === Examples + # datetime = DateTime.civil(2007, 12, 4, 0, 0, 0, 0) # => Tue, 04 Dec 2007 00:00:00 +0000 + # + # datetime.to_formatted_s(:db) # => "2007-12-04 00:00:00" + # datetime.to_s(:db) # => "2007-12-04 00:00:00" + # datetime.to_s(:number) # => "20071204000000" + # datetime.to_formatted_s(:short) # => "04 Dec 00:00" + # datetime.to_formatted_s(:long) # => "December 04, 2007 00:00" + # datetime.to_formatted_s(:long_ordinal) # => "December 4th, 2007 00:00" + # datetime.to_formatted_s(:rfc822) # => "Tue, 04 Dec 2007 00:00:00 +0000" + # datetime.to_formatted_s(:iso8601) # => "2007-12-04T00:00:00+00:00" + # + # == Adding your own datetime formats to to_formatted_s + # DateTime formats are shared with Time. You can add your own to the + # Time::DATE_FORMATS hash. Use the format name as the hash key and + # either a strftime string or Proc instance that takes a time or + # datetime argument as the value. + # + # # config/initializers/time_formats.rb + # Time::DATE_FORMATS[:month_and_year] = '%B %Y' + # Time::DATE_FORMATS[:short_ordinal] = lambda { |time| time.strftime("%B #{time.day.ordinalize}") } + def to_formatted_s(format = :default) + if formatter = ::Time::DATE_FORMATS[format] + formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter) + else + to_default_s + end + end + alias_method :to_default_s, :to_s if instance_methods(false).include?(:to_s) + alias_method :to_s, :to_formatted_s + + # Returns a formatted string of the offset from UTC, or an alternative + # string if the time zone is already UTC. + # + # datetime = DateTime.civil(2000, 1, 1, 0, 0, 0, Rational(-6, 24)) + # datetime.formatted_offset # => "-06:00" + # datetime.formatted_offset(false) # => "-0600" + def formatted_offset(colon = true, alternate_utc_string = nil) + utc? && alternate_utc_string || ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, colon) + end + + # Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005 14:30:00 +0000". + def readable_inspect + to_s(:rfc822) + end + alias_method :default_inspect, :inspect + alias_method :inspect, :readable_inspect + + # Returns DateTime with local offset for given year if format is local else + # offset is zero. + # + # DateTime.civil_from_format :local, 2012 + # # => Sun, 01 Jan 2012 00:00:00 +0300 + # DateTime.civil_from_format :local, 2012, 12, 17 + # # => Mon, 17 Dec 2012 00:00:00 +0000 + def self.civil_from_format(utc_or_local, year, month = 1, day = 1, hour = 0, min = 0, sec = 0) + if utc_or_local.to_sym == :local + offset = ::Time.local(year, month, day).utc_offset.to_r / 86400 + else + offset = 0 + end + civil(year, month, day, hour, min, sec, offset) + end + + # Converts +self+ to a floating-point number of seconds, including fractional microseconds, since the Unix epoch. + def to_f + seconds_since_unix_epoch.to_f + sec_fraction + end + + # Converts +self+ to an integer number of seconds since the Unix epoch. + def to_i + seconds_since_unix_epoch.to_i + end + + # Returns the fraction of a second as microseconds + def usec + (sec_fraction * 1_000_000).to_i + end + + # Returns the fraction of a second as nanoseconds + def nsec + (sec_fraction * 1_000_000_000).to_i + end + + private + + def offset_in_seconds + (offset * 86400).to_i + end + + def seconds_since_unix_epoch + (jd - 2440588) * 86400 - offset_in_seconds + seconds_since_midnight + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/digest/uuid.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/digest/uuid.rb new file mode 100644 index 00000000..6e949a2d --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/digest/uuid.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require "securerandom" + +module Digest + module UUID + DNS_NAMESPACE = "k\xA7\xB8\x10\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc: + URL_NAMESPACE = "k\xA7\xB8\x11\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc: + OID_NAMESPACE = "k\xA7\xB8\x12\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc: + X500_NAMESPACE = "k\xA7\xB8\x14\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc: + + # Generates a v5 non-random UUID (Universally Unique IDentifier). + # + # Using Digest::MD5 generates version 3 UUIDs; Digest::SHA1 generates version 5 UUIDs. + # uuid_from_hash always generates the same UUID for a given name and namespace combination. + # + # See RFC 4122 for details of UUID at: https://www.ietf.org/rfc/rfc4122.txt + def self.uuid_from_hash(hash_class, uuid_namespace, name) + if hash_class == Digest::MD5 + version = 3 + elsif hash_class == Digest::SHA1 + version = 5 + else + raise ArgumentError, "Expected Digest::SHA1 or Digest::MD5, got #{hash_class.name}." + end + + hash = hash_class.new + hash.update(uuid_namespace) + hash.update(name) + + ary = hash.digest.unpack("NnnnnN") + ary[2] = (ary[2] & 0x0FFF) | (version << 12) + ary[3] = (ary[3] & 0x3FFF) | 0x8000 + + "%08x-%04x-%04x-%04x-%04x%08x" % ary + end + + # Convenience method for uuid_from_hash using Digest::MD5. + def self.uuid_v3(uuid_namespace, name) + uuid_from_hash(Digest::MD5, uuid_namespace, name) + end + + # Convenience method for uuid_from_hash using Digest::SHA1. + def self.uuid_v5(uuid_namespace, name) + uuid_from_hash(Digest::SHA1, uuid_namespace, name) + end + + # Convenience method for SecureRandom.uuid. + def self.uuid_v4 + SecureRandom.uuid + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/enumerable.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/enumerable.rb new file mode 100644 index 00000000..cea6f98c --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/enumerable.rb @@ -0,0 +1,164 @@ +# frozen_string_literal: true + +module Enumerable + # Enumerable#sum was added in Ruby 2.4, but it only works with Numeric elements + # when we omit an identity. + # + # We tried shimming it to attempt the fast native method, rescue TypeError, + # and fall back to the compatible implementation, but that's much slower than + # just calling the compat method in the first place. + if Enumerable.instance_methods(false).include?(:sum) && !((?a..?b).sum rescue false) + # :stopdoc: + + # We can't use Refinements here because Refinements with Module which will be prepended + # doesn't work well https://bugs.ruby-lang.org/issues/13446 + alias :_original_sum_with_required_identity :sum + private :_original_sum_with_required_identity + + # :startdoc: + + # Calculates a sum from the elements. + # + # payments.sum { |p| p.price * p.tax_rate } + # payments.sum(&:price) + # + # The latter is a shortcut for: + # + # payments.inject(0) { |sum, p| sum + p.price } + # + # It can also calculate the sum without the use of a block. + # + # [5, 15, 10].sum # => 30 + # ['foo', 'bar'].sum # => "foobar" + # [[1, 2], [3, 1, 5]].sum # => [1, 2, 3, 1, 5] + # + # The default sum of an empty list is zero. You can override this default: + # + # [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0) + def sum(identity = nil, &block) + if identity + _original_sum_with_required_identity(identity, &block) + elsif block_given? + map(&block).sum(identity) + else + inject(:+) || 0 + end + end + else + def sum(identity = nil, &block) + if block_given? + map(&block).sum(identity) + else + sum = identity ? inject(identity, :+) : inject(:+) + sum || identity || 0 + end + end + end + + # Convert an enumerable to a hash. + # + # people.index_by(&:login) + # # => { "nextangle" => , "chade-" => , ...} + # people.index_by { |person| "#{person.first_name} #{person.last_name}" } + # # => { "Chade- Fowlersburg-e" => , "David Heinemeier Hansson" => , ...} + def index_by + if block_given? + result = {} + each { |elem| result[yield(elem)] = elem } + result + else + to_enum(:index_by) { size if respond_to?(:size) } + end + end + + # Returns +true+ if the enumerable has more than 1 element. Functionally + # equivalent to enum.to_a.size > 1. Can be called with a block too, + # much like any?, so people.many? { |p| p.age > 26 } returns +true+ + # if more than one person is over 26. + def many? + cnt = 0 + if block_given? + any? do |element| + cnt += 1 if yield element + cnt > 1 + end + else + any? { (cnt += 1) > 1 } + end + end + + # The negative of the Enumerable#include?. Returns +true+ if the + # collection does not include the object. + def exclude?(object) + !include?(object) + end + + # Returns a copy of the enumerable without the specified elements. + # + # ["David", "Rafael", "Aaron", "Todd"].without "Aaron", "Todd" + # # => ["David", "Rafael"] + # + # {foo: 1, bar: 2, baz: 3}.without :bar + # # => {foo: 1, baz: 3} + def without(*elements) + reject { |element| elements.include?(element) } + end + + # Convert an enumerable to an array based on the given key. + # + # [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name) + # # => ["David", "Rafael", "Aaron"] + # + # [{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pluck(:id, :name) + # # => [[1, "David"], [2, "Rafael"]] + def pluck(*keys) + if keys.many? + map { |element| keys.map { |key| element[key] } } + else + map { |element| element[keys.first] } + end + end +end + +class Range #:nodoc: + # Optimize range sum to use arithmetic progression if a block is not given and + # we have a range of numeric values. + def sum(identity = nil) + if block_given? || !(first.is_a?(Integer) && last.is_a?(Integer)) + super + else + actual_last = exclude_end? ? (last - 1) : last + if actual_last >= first + sum = identity || 0 + sum + (actual_last - first + 1) * (actual_last + first) / 2 + else + identity || 0 + end + end + end +end + +# Array#sum was added in Ruby 2.4 but it only works with Numeric elements. +# +# We tried shimming it to attempt the fast native method, rescue TypeError, +# and fall back to the compatible implementation, but that's much slower than +# just calling the compat method in the first place. +if Array.instance_methods(false).include?(:sum) && !(%w[a].sum rescue false) + # Using Refinements here in order not to expose our internal method + using Module.new { + refine Array do + alias :orig_sum :sum + end + } + + class Array + def sum(init = nil, &block) #:nodoc: + if init.is_a?(Numeric) || first.is_a?(Numeric) + init ||= 0 + orig_sum(init, &block) + else + super + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/file.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/file.rb new file mode 100644 index 00000000..64553bfa --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/file.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require "active_support/core_ext/file/atomic" diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/file/atomic.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/file/atomic.rb new file mode 100644 index 00000000..9deceb1b --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/file/atomic.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require "fileutils" + +class File + # Write to a file atomically. Useful for situations where you don't + # want other processes or threads to see half-written files. + # + # File.atomic_write('important.file') do |file| + # file.write('hello') + # end + # + # This method needs to create a temporary file. By default it will create it + # in the same directory as the destination file. If you don't like this + # behavior you can provide a different directory but it must be on the + # same physical filesystem as the file you're trying to write. + # + # File.atomic_write('/data/something.important', '/data/tmp') do |file| + # file.write('hello') + # end + def self.atomic_write(file_name, temp_dir = dirname(file_name)) + require "tempfile" unless defined?(Tempfile) + + Tempfile.open(".#{basename(file_name)}", temp_dir) do |temp_file| + temp_file.binmode + return_val = yield temp_file + temp_file.close + + old_stat = if exist?(file_name) + # Get original file permissions + stat(file_name) + else + # If not possible, probe which are the default permissions in the + # destination directory. + probe_stat_in(dirname(file_name)) + end + + if old_stat + # Set correct permissions on new file + begin + chown(old_stat.uid, old_stat.gid, temp_file.path) + # This operation will affect filesystem ACL's + chmod(old_stat.mode, temp_file.path) + rescue Errno::EPERM, Errno::EACCES + # Changing file ownership failed, moving on. + end + end + + # Overwrite original file with temp file + rename(temp_file.path, file_name) + return_val + end + end + + # Private utility method. + def self.probe_stat_in(dir) #:nodoc: + basename = [ + ".permissions_check", + Thread.current.object_id, + Process.pid, + rand(1000000) + ].join(".") + + file_name = join(dir, basename) + FileUtils.touch(file_name) + stat(file_name) + ensure + FileUtils.rm_f(file_name) if file_name + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash.rb new file mode 100644 index 00000000..e19aeaa9 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/compact" +require "active_support/core_ext/hash/conversions" +require "active_support/core_ext/hash/deep_merge" +require "active_support/core_ext/hash/except" +require "active_support/core_ext/hash/indifferent_access" +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/hash/reverse_merge" +require "active_support/core_ext/hash/slice" +require "active_support/core_ext/hash/transform_values" diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/compact.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/compact.rb new file mode 100644 index 00000000..d6364dd9 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/compact.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class Hash + unless Hash.instance_methods(false).include?(:compact) + # Returns a hash with non +nil+ values. + # + # hash = { a: true, b: false, c: nil } + # hash.compact # => { a: true, b: false } + # hash # => { a: true, b: false, c: nil } + # { c: nil }.compact # => {} + # { c: true }.compact # => { c: true } + def compact + select { |_, value| !value.nil? } + end + end + + unless Hash.instance_methods(false).include?(:compact!) + # Replaces current hash with non +nil+ values. + # Returns +nil+ if no changes were made, otherwise returns the hash. + # + # hash = { a: true, b: false, c: nil } + # hash.compact! # => { a: true, b: false } + # hash # => { a: true, b: false } + # { c: true }.compact! # => nil + def compact! + reject! { |_, value| value.nil? } + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/conversions.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/conversions.rb new file mode 100644 index 00000000..5b482546 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/conversions.rb @@ -0,0 +1,263 @@ +# frozen_string_literal: true + +require "active_support/xml_mini" +require "active_support/time" +require "active_support/core_ext/object/blank" +require "active_support/core_ext/object/to_param" +require "active_support/core_ext/object/to_query" +require "active_support/core_ext/array/wrap" +require "active_support/core_ext/hash/reverse_merge" +require "active_support/core_ext/string/inflections" + +class Hash + # Returns a string containing an XML representation of its receiver: + # + # { foo: 1, bar: 2 }.to_xml + # # => + # # + # # + # # 1 + # # 2 + # # + # + # To do so, the method loops over the pairs and builds nodes that depend on + # the _values_. Given a pair +key+, +value+: + # + # * If +value+ is a hash there's a recursive call with +key+ as :root. + # + # * If +value+ is an array there's a recursive call with +key+ as :root, + # and +key+ singularized as :children. + # + # * If +value+ is a callable object it must expect one or two arguments. Depending + # on the arity, the callable is invoked with the +options+ hash as first argument + # with +key+ as :root, and +key+ singularized as second argument. The + # callable can add nodes by using options[:builder]. + # + # {foo: lambda { |options, key| options[:builder].b(key) }}.to_xml + # # => "foo" + # + # * If +value+ responds to +to_xml+ the method is invoked with +key+ as :root. + # + # class Foo + # def to_xml(options) + # options[:builder].bar 'fooing!' + # end + # end + # + # { foo: Foo.new }.to_xml(skip_instruct: true) + # # => + # # + # # fooing! + # # + # + # * Otherwise, a node with +key+ as tag is created with a string representation of + # +value+ as text node. If +value+ is +nil+ an attribute "nil" set to "true" is added. + # Unless the option :skip_types exists and is true, an attribute "type" is + # added as well according to the following mapping: + # + # XML_TYPE_NAMES = { + # "Symbol" => "symbol", + # "Integer" => "integer", + # "BigDecimal" => "decimal", + # "Float" => "float", + # "TrueClass" => "boolean", + # "FalseClass" => "boolean", + # "Date" => "date", + # "DateTime" => "dateTime", + # "Time" => "dateTime" + # } + # + # By default the root node is "hash", but that's configurable via the :root option. + # + # The default XML builder is a fresh instance of Builder::XmlMarkup. You can + # configure your own builder with the :builder option. The method also accepts + # options like :dasherize and friends, they are forwarded to the builder. + def to_xml(options = {}) + require "active_support/builder" unless defined?(Builder) + + options = options.dup + options[:indent] ||= 2 + options[:root] ||= "hash" + options[:builder] ||= Builder::XmlMarkup.new(indent: options[:indent]) + + builder = options[:builder] + builder.instruct! unless options.delete(:skip_instruct) + + root = ActiveSupport::XmlMini.rename_key(options[:root].to_s, options) + + builder.tag!(root) do + each { |key, value| ActiveSupport::XmlMini.to_tag(key, value, options) } + yield builder if block_given? + end + end + + class << self + # Returns a Hash containing a collection of pairs when the key is the node name and the value is + # its content + # + # xml = <<-XML + # + # + # 1 + # 2 + # + # XML + # + # hash = Hash.from_xml(xml) + # # => {"hash"=>{"foo"=>1, "bar"=>2}} + # + # +DisallowedType+ is raised if the XML contains attributes with type="yaml" or + # type="symbol". Use Hash.from_trusted_xml to + # parse this XML. + # + # Custom +disallowed_types+ can also be passed in the form of an + # array. + # + # xml = <<-XML + # + # + # 1 + # "David" + # + # XML + # + # hash = Hash.from_xml(xml, ['integer']) + # # => ActiveSupport::XMLConverter::DisallowedType: Disallowed type attribute: "integer" + # + # Note that passing custom disallowed types will override the default types, + # which are Symbol and YAML. + def from_xml(xml, disallowed_types = nil) + ActiveSupport::XMLConverter.new(xml, disallowed_types).to_h + end + + # Builds a Hash from XML just like Hash.from_xml, but also allows Symbol and YAML. + def from_trusted_xml(xml) + from_xml xml, [] + end + end +end + +module ActiveSupport + class XMLConverter # :nodoc: + # Raised if the XML contains attributes with type="yaml" or + # type="symbol". Read Hash#from_xml for more details. + class DisallowedType < StandardError + def initialize(type) + super "Disallowed type attribute: #{type.inspect}" + end + end + + DISALLOWED_TYPES = %w(symbol yaml) + + def initialize(xml, disallowed_types = nil) + @xml = normalize_keys(XmlMini.parse(xml)) + @disallowed_types = disallowed_types || DISALLOWED_TYPES + end + + def to_h + deep_to_h(@xml) + end + + private + def normalize_keys(params) + case params + when Hash + Hash[params.map { |k, v| [k.to_s.tr("-", "_"), normalize_keys(v)] } ] + when Array + params.map { |v| normalize_keys(v) } + else + params + end + end + + def deep_to_h(value) + case value + when Hash + process_hash(value) + when Array + process_array(value) + when String + value + else + raise "can't typecast #{value.class.name} - #{value.inspect}" + end + end + + def process_hash(value) + if value.include?("type") && !value["type"].is_a?(Hash) && @disallowed_types.include?(value["type"]) + raise DisallowedType, value["type"] + end + + if become_array?(value) + _, entries = Array.wrap(value.detect { |k, v| not v.is_a?(String) }) + if entries.nil? || value["__content__"].try(:empty?) + [] + else + case entries + when Array + entries.collect { |v| deep_to_h(v) } + when Hash + [deep_to_h(entries)] + else + raise "can't typecast #{entries.inspect}" + end + end + elsif become_content?(value) + process_content(value) + + elsif become_empty_string?(value) + "" + elsif become_hash?(value) + xml_value = Hash[value.map { |k, v| [k, deep_to_h(v)] }] + + # Turn { files: { file: # } } into { files: # } so it is compatible with + # how multipart uploaded files from HTML appear + xml_value["file"].is_a?(StringIO) ? xml_value["file"] : xml_value + end + end + + def become_content?(value) + value["type"] == "file" || (value["__content__"] && (value.keys.size == 1 || value["__content__"].present?)) + end + + def become_array?(value) + value["type"] == "array" + end + + def become_empty_string?(value) + # { "string" => true } + # No tests fail when the second term is removed. + value["type"] == "string" && value["nil"] != "true" + end + + def become_hash?(value) + !nothing?(value) && !garbage?(value) + end + + def nothing?(value) + # blank or nil parsed values are represented by nil + value.blank? || value["nil"] == "true" + end + + def garbage?(value) + # If the type is the only element which makes it then + # this still makes the value nil, except if type is + # an XML node(where type['value'] is a Hash) + value["type"] && !value["type"].is_a?(::Hash) && value.size == 1 + end + + def process_content(value) + content = value["__content__"] + if parser = ActiveSupport::XmlMini::PARSING[value["type"]] + parser.arity == 1 ? parser.call(content) : parser.call(content, value) + else + content + end + end + + def process_array(value) + value.map! { |i| deep_to_h(i) } + value.length > 1 ? value : value.first + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/deep_merge.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/deep_merge.rb new file mode 100644 index 00000000..9bc50b7b --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/deep_merge.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +class Hash + # Returns a new hash with +self+ and +other_hash+ merged recursively. + # + # h1 = { a: true, b: { c: [1, 2, 3] } } + # h2 = { a: false, b: { x: [3, 4, 5] } } + # + # h1.deep_merge(h2) # => { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } } + # + # Like with Hash#merge in the standard library, a block can be provided + # to merge values: + # + # h1 = { a: 100, b: 200, c: { c1: 100 } } + # h2 = { b: 250, c: { c1: 200 } } + # h1.deep_merge(h2) { |key, this_val, other_val| this_val + other_val } + # # => { a: 100, b: 450, c: { c1: 300 } } + def deep_merge(other_hash, &block) + dup.deep_merge!(other_hash, &block) + end + + # Same as +deep_merge+, but modifies +self+. + def deep_merge!(other_hash, &block) + merge!(other_hash) do |key, this_val, other_val| + if this_val.is_a?(Hash) && other_val.is_a?(Hash) + this_val.deep_merge(other_val, &block) + elsif block_given? + block.call(key, this_val, other_val) + else + other_val + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/except.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/except.rb new file mode 100644 index 00000000..6258610c --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/except.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class Hash + # Returns a hash that includes everything except given keys. + # hash = { a: true, b: false, c: nil } + # hash.except(:c) # => { a: true, b: false } + # hash.except(:a, :b) # => { c: nil } + # hash # => { a: true, b: false, c: nil } + # + # This is useful for limiting a set of parameters to everything but a few known toggles: + # @person.update(params[:person].except(:admin)) + def except(*keys) + dup.except!(*keys) + end + + # Removes the given keys from hash and returns it. + # hash = { a: true, b: false, c: nil } + # hash.except!(:c) # => { a: true, b: false } + # hash # => { a: true, b: false } + def except!(*keys) + keys.each { |key| delete(key) } + self + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/indifferent_access.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/indifferent_access.rb new file mode 100644 index 00000000..a38f33f1 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/indifferent_access.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "active_support/hash_with_indifferent_access" + +class Hash + # Returns an ActiveSupport::HashWithIndifferentAccess out of its receiver: + # + # { a: 1 }.with_indifferent_access['a'] # => 1 + def with_indifferent_access + ActiveSupport::HashWithIndifferentAccess.new(self) + end + + # Called when object is nested under an object that receives + # #with_indifferent_access. This method will be called on the current object + # by the enclosing object and is aliased to #with_indifferent_access by + # default. Subclasses of Hash may overwrite this method to return +self+ if + # converting to an ActiveSupport::HashWithIndifferentAccess would not be + # desirable. + # + # b = { b: 1 } + # { a: b }.with_indifferent_access['a'] # calls b.nested_under_indifferent_access + # # => {"b"=>1} + alias nested_under_indifferent_access with_indifferent_access +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/keys.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/keys.rb new file mode 100644 index 00000000..bdf196ec --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/keys.rb @@ -0,0 +1,172 @@ +# frozen_string_literal: true + +class Hash + # Returns a new hash with all keys converted using the +block+ operation. + # + # hash = { name: 'Rob', age: '28' } + # + # hash.transform_keys { |key| key.to_s.upcase } # => {"NAME"=>"Rob", "AGE"=>"28"} + # + # If you do not provide a +block+, it will return an Enumerator + # for chaining with other methods: + # + # hash.transform_keys.with_index { |k, i| [k, i].join } # => {"name0"=>"Rob", "age1"=>"28"} + def transform_keys + return enum_for(:transform_keys) { size } unless block_given? + result = {} + each_key do |key| + result[yield(key)] = self[key] + end + result + end unless method_defined? :transform_keys + + # Destructively converts all keys using the +block+ operations. + # Same as +transform_keys+ but modifies +self+. + def transform_keys! + return enum_for(:transform_keys!) { size } unless block_given? + keys.each do |key| + self[yield(key)] = delete(key) + end + self + end unless method_defined? :transform_keys! + + # Returns a new hash with all keys converted to strings. + # + # hash = { name: 'Rob', age: '28' } + # + # hash.stringify_keys + # # => {"name"=>"Rob", "age"=>"28"} + def stringify_keys + transform_keys(&:to_s) + end + + # Destructively converts all keys to strings. Same as + # +stringify_keys+, but modifies +self+. + def stringify_keys! + transform_keys!(&:to_s) + end + + # Returns a new hash with all keys converted to symbols, as long as + # they respond to +to_sym+. + # + # hash = { 'name' => 'Rob', 'age' => '28' } + # + # hash.symbolize_keys + # # => {:name=>"Rob", :age=>"28"} + def symbolize_keys + transform_keys { |key| key.to_sym rescue key } + end + alias_method :to_options, :symbolize_keys + + # Destructively converts all keys to symbols, as long as they respond + # to +to_sym+. Same as +symbolize_keys+, but modifies +self+. + def symbolize_keys! + transform_keys! { |key| key.to_sym rescue key } + end + alias_method :to_options!, :symbolize_keys! + + # Validates all keys in a hash match *valid_keys, raising + # +ArgumentError+ on a mismatch. + # + # Note that keys are treated differently than HashWithIndifferentAccess, + # meaning that string and symbol keys will not match. + # + # { name: 'Rob', years: '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: :years. Valid keys are: :name, :age" + # { name: 'Rob', age: '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: :name. Valid keys are: 'name', 'age'" + # { name: 'Rob', age: '28' }.assert_valid_keys(:name, :age) # => passes, raises nothing + def assert_valid_keys(*valid_keys) + valid_keys.flatten! + each_key do |k| + unless valid_keys.include?(k) + raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{valid_keys.map(&:inspect).join(', ')}") + end + end + end + + # Returns a new hash with all keys converted by the block operation. + # This includes the keys from the root hash and from all + # nested hashes and arrays. + # + # hash = { person: { name: 'Rob', age: '28' } } + # + # hash.deep_transform_keys{ |key| key.to_s.upcase } + # # => {"PERSON"=>{"NAME"=>"Rob", "AGE"=>"28"}} + def deep_transform_keys(&block) + _deep_transform_keys_in_object(self, &block) + end + + # Destructively converts all keys by using the block operation. + # This includes the keys from the root hash and from all + # nested hashes and arrays. + def deep_transform_keys!(&block) + _deep_transform_keys_in_object!(self, &block) + end + + # Returns a new hash with all keys converted to strings. + # This includes the keys from the root hash and from all + # nested hashes and arrays. + # + # hash = { person: { name: 'Rob', age: '28' } } + # + # hash.deep_stringify_keys + # # => {"person"=>{"name"=>"Rob", "age"=>"28"}} + def deep_stringify_keys + deep_transform_keys(&:to_s) + end + + # Destructively converts all keys to strings. + # This includes the keys from the root hash and from all + # nested hashes and arrays. + def deep_stringify_keys! + deep_transform_keys!(&:to_s) + end + + # Returns a new hash with all keys converted to symbols, as long as + # they respond to +to_sym+. This includes the keys from the root hash + # and from all nested hashes and arrays. + # + # hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } } + # + # hash.deep_symbolize_keys + # # => {:person=>{:name=>"Rob", :age=>"28"}} + def deep_symbolize_keys + deep_transform_keys { |key| key.to_sym rescue key } + end + + # Destructively converts all keys to symbols, as long as they respond + # to +to_sym+. This includes the keys from the root hash and from all + # nested hashes and arrays. + def deep_symbolize_keys! + deep_transform_keys! { |key| key.to_sym rescue key } + end + + private + # support methods for deep transforming nested hashes and arrays + def _deep_transform_keys_in_object(object, &block) + case object + when Hash + object.each_with_object({}) do |(key, value), result| + result[yield(key)] = _deep_transform_keys_in_object(value, &block) + end + when Array + object.map { |e| _deep_transform_keys_in_object(e, &block) } + else + object + end + end + + def _deep_transform_keys_in_object!(object, &block) + case object + when Hash + object.keys.each do |key| + value = object.delete(key) + object[yield(key)] = _deep_transform_keys_in_object!(value, &block) + end + object + when Array + object.map! { |e| _deep_transform_keys_in_object!(e, &block) } + else + object + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/reverse_merge.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/reverse_merge.rb new file mode 100644 index 00000000..ef8d5928 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/reverse_merge.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class Hash + # Merges the caller into +other_hash+. For example, + # + # options = options.reverse_merge(size: 25, velocity: 10) + # + # is equivalent to + # + # options = { size: 25, velocity: 10 }.merge(options) + # + # This is particularly useful for initializing an options hash + # with default values. + def reverse_merge(other_hash) + other_hash.merge(self) + end + alias_method :with_defaults, :reverse_merge + + # Destructive +reverse_merge+. + def reverse_merge!(other_hash) + replace(reverse_merge(other_hash)) + end + alias_method :reverse_update, :reverse_merge! + alias_method :with_defaults!, :reverse_merge! +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/slice.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/slice.rb new file mode 100644 index 00000000..2bd0a56e --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/slice.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +class Hash + # Slices a hash to include only the given keys. Returns a hash containing + # the given keys. + # + # { a: 1, b: 2, c: 3, d: 4 }.slice(:a, :b) + # # => {:a=>1, :b=>2} + # + # This is useful for limiting an options hash to valid keys before + # passing to a method: + # + # def search(criteria = {}) + # criteria.assert_valid_keys(:mass, :velocity, :time) + # end + # + # search(options.slice(:mass, :velocity, :time)) + # + # If you have an array of keys you want to limit to, you should splat them: + # + # valid_keys = [:mass, :velocity, :time] + # search(options.slice(*valid_keys)) + def slice(*keys) + keys.each_with_object(Hash.new) { |k, hash| hash[k] = self[k] if has_key?(k) } + end unless method_defined?(:slice) + + # Replaces the hash with only the given keys. + # Returns a hash containing the removed key/value pairs. + # + # { a: 1, b: 2, c: 3, d: 4 }.slice!(:a, :b) + # # => {:c=>3, :d=>4} + def slice!(*keys) + omit = slice(*self.keys - keys) + hash = slice(*keys) + hash.default = default + hash.default_proc = default_proc if default_proc + replace(hash) + omit + end + + # Removes and returns the key/value pairs matching the given keys. + # + # { a: 1, b: 2, c: 3, d: 4 }.extract!(:a, :b) # => {:a=>1, :b=>2} + # { a: 1, b: 2 }.extract!(:a, :x) # => {:a=>1} + def extract!(*keys) + keys.each_with_object(self.class.new) { |key, result| result[key] = delete(key) if has_key?(key) } + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/transform_values.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/transform_values.rb new file mode 100644 index 00000000..4b19c9fc --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/hash/transform_values.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +class Hash + # Returns a new hash with the results of running +block+ once for every value. + # The keys are unchanged. + # + # { a: 1, b: 2, c: 3 }.transform_values { |x| x * 2 } # => { a: 2, b: 4, c: 6 } + # + # If you do not provide a +block+, it will return an Enumerator + # for chaining with other methods: + # + # { a: 1, b: 2 }.transform_values.with_index { |v, i| [v, i].join.to_i } # => { a: 10, b: 21 } + def transform_values + return enum_for(:transform_values) { size } unless block_given? + return {} if empty? + result = self.class.new + each do |key, value| + result[key] = yield(value) + end + result + end unless method_defined? :transform_values + + # Destructively converts all values using the +block+ operations. + # Same as +transform_values+ but modifies +self+. + def transform_values! + return enum_for(:transform_values!) { size } unless block_given? + each do |key, value| + self[key] = yield(value) + end + end unless method_defined? :transform_values! + # TODO: Remove this file when supporting only Ruby 2.4+. +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/integer.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/integer.rb new file mode 100644 index 00000000..d2270130 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/integer.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require "active_support/core_ext/integer/multiple" +require "active_support/core_ext/integer/inflections" +require "active_support/core_ext/integer/time" diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/integer/inflections.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/integer/inflections.rb new file mode 100644 index 00000000..aef3266f --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/integer/inflections.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require "active_support/inflector" + +class Integer + # Ordinalize turns a number into an ordinal string used to denote the + # position in an ordered sequence such as 1st, 2nd, 3rd, 4th. + # + # 1.ordinalize # => "1st" + # 2.ordinalize # => "2nd" + # 1002.ordinalize # => "1002nd" + # 1003.ordinalize # => "1003rd" + # -11.ordinalize # => "-11th" + # -1001.ordinalize # => "-1001st" + def ordinalize + ActiveSupport::Inflector.ordinalize(self) + end + + # Ordinal returns the suffix used to denote the position + # in an ordered sequence such as 1st, 2nd, 3rd, 4th. + # + # 1.ordinal # => "st" + # 2.ordinal # => "nd" + # 1002.ordinal # => "nd" + # 1003.ordinal # => "rd" + # -11.ordinal # => "th" + # -1001.ordinal # => "st" + def ordinal + ActiveSupport::Inflector.ordinal(self) + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/integer/multiple.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/integer/multiple.rb new file mode 100644 index 00000000..e7606662 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/integer/multiple.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class Integer + # Check whether the integer is evenly divisible by the argument. + # + # 0.multiple_of?(0) # => true + # 6.multiple_of?(5) # => false + # 10.multiple_of?(2) # => true + def multiple_of?(number) + number != 0 ? self % number == 0 : zero? + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/integer/time.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/integer/time.rb new file mode 100644 index 00000000..5efb89cf --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/integer/time.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require "active_support/duration" +require "active_support/core_ext/numeric/time" + +class Integer + # Returns a Duration instance matching the number of months provided. + # + # 2.months # => 2 months + def months + ActiveSupport::Duration.months(self) + end + alias :month :months + + # Returns a Duration instance matching the number of years provided. + # + # 2.years # => 2 years + def years + ActiveSupport::Duration.years(self) + end + alias :year :years +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/kernel.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/kernel.rb new file mode 100644 index 00000000..0f4356fb --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/kernel.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +require "active_support/core_ext/kernel/agnostics" +require "active_support/core_ext/kernel/concern" +require "active_support/core_ext/kernel/reporting" +require "active_support/core_ext/kernel/singleton_class" diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/kernel/agnostics.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/kernel/agnostics.rb new file mode 100644 index 00000000..403b5f31 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/kernel/agnostics.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class Object + # Makes backticks behave (somewhat more) similarly on all platforms. + # On win32 `nonexistent_command` raises Errno::ENOENT; on Unix, the + # spawned shell prints a message to stderr and sets $?. We emulate + # Unix on the former but not the latter. + def `(command) #:nodoc: + super + rescue Errno::ENOENT => e + STDERR.puts "#$0: #{e}" + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/kernel/concern.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/kernel/concern.rb new file mode 100644 index 00000000..0b2baed7 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/kernel/concern.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/concerning" + +module Kernel + module_function + + # A shortcut to define a toplevel concern, not within a module. + # + # See Module::Concerning for more. + def concern(topic, &module_definition) + Object.concern topic, &module_definition + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/kernel/reporting.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/kernel/reporting.rb new file mode 100644 index 00000000..9155bd6c --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/kernel/reporting.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Kernel + module_function + + # Sets $VERBOSE to +nil+ for the duration of the block and back to its original + # value afterwards. + # + # silence_warnings do + # value = noisy_call # no warning voiced + # end + # + # noisy_call # warning voiced + def silence_warnings + with_warnings(nil) { yield } + end + + # Sets $VERBOSE to +true+ for the duration of the block and back to its + # original value afterwards. + def enable_warnings + with_warnings(true) { yield } + end + + # Sets $VERBOSE for the duration of the block and back to its original + # value afterwards. + def with_warnings(flag) + old_verbose, $VERBOSE = $VERBOSE, flag + yield + ensure + $VERBOSE = old_verbose + end + + # Blocks and ignores any exception passed as argument if raised within the block. + # + # suppress(ZeroDivisionError) do + # 1/0 + # puts 'This code is NOT reached' + # end + # + # puts 'This code gets executed and nothing related to ZeroDivisionError was seen' + def suppress(*exception_classes) + yield + rescue *exception_classes + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/kernel/singleton_class.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/kernel/singleton_class.rb new file mode 100644 index 00000000..6715eba8 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/kernel/singleton_class.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Kernel + # class_eval on an object acts like singleton_class.class_eval. + def class_eval(*args, &block) + singleton_class.class_eval(*args, &block) + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/load_error.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/load_error.rb new file mode 100644 index 00000000..750f858f --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/load_error.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class LoadError + # Returns true if the given path name (except perhaps for the ".rb" + # extension) is the missing file which caused the exception to be raised. + def is_missing?(location) + location.sub(/\.rb$/, "".freeze) == path.sub(/\.rb$/, "".freeze) + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/marshal.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/marshal.rb new file mode 100644 index 00000000..0c72cd7b --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/marshal.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module ActiveSupport + module MarshalWithAutoloading # :nodoc: + def load(source, proc = nil) + super(source, proc) + rescue ArgumentError, NameError => exc + if exc.message.match(%r|undefined class/module (.+?)(?:::)?\z|) + # try loading the class/module + loaded = $1.constantize + + raise unless $1 == loaded.name + + # if it is an IO we need to go back to read the object + source.rewind if source.respond_to?(:rewind) + retry + else + raise exc + end + end + end +end + +Marshal.singleton_class.prepend(ActiveSupport::MarshalWithAutoloading) diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module.rb new file mode 100644 index 00000000..d91e3fba --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/aliasing" +require "active_support/core_ext/module/introspection" +require "active_support/core_ext/module/anonymous" +require "active_support/core_ext/module/reachable" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/module/attribute_accessors_per_thread" +require "active_support/core_ext/module/attr_internal" +require "active_support/core_ext/module/concerning" +require "active_support/core_ext/module/delegation" +require "active_support/core_ext/module/deprecation" +require "active_support/core_ext/module/redefine_method" +require "active_support/core_ext/module/remove_method" diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/aliasing.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/aliasing.rb new file mode 100644 index 00000000..6f64d116 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/aliasing.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +class Module + # Allows you to make aliases for attributes, which includes + # getter, setter, and a predicate. + # + # class Content < ActiveRecord::Base + # # has a title attribute + # end + # + # class Email < Content + # alias_attribute :subject, :title + # end + # + # e = Email.find(1) + # e.title # => "Superstars" + # e.subject # => "Superstars" + # e.subject? # => true + # e.subject = "Megastars" + # e.title # => "Megastars" + def alias_attribute(new_name, old_name) + # The following reader methods use an explicit `self` receiver in order to + # support aliases that start with an uppercase letter. Otherwise, they would + # be resolved as constants instead. + module_eval <<-STR, __FILE__, __LINE__ + 1 + def #{new_name}; self.#{old_name}; end # def subject; self.title; end + def #{new_name}?; self.#{old_name}?; end # def subject?; self.title?; end + def #{new_name}=(v); self.#{old_name} = v; end # def subject=(v); self.title = v; end + STR + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/anonymous.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/anonymous.rb new file mode 100644 index 00000000..d1c86b87 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/anonymous.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +class Module + # A module may or may not have a name. + # + # module M; end + # M.name # => "M" + # + # m = Module.new + # m.name # => nil + # + # +anonymous?+ method returns true if module does not have a name, false otherwise: + # + # Module.new.anonymous? # => true + # + # module M; end + # M.anonymous? # => false + # + # A module gets a name when it is first assigned to a constant. Either + # via the +module+ or +class+ keyword or by an explicit assignment: + # + # m = Module.new # creates an anonymous module + # m.anonymous? # => true + # M = m # m gets a name here as a side-effect + # m.name # => "M" + # m.anonymous? # => false + def anonymous? + name.nil? + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/attr_internal.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/attr_internal.rb new file mode 100644 index 00000000..7801f6d1 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/attr_internal.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +class Module + # Declares an attribute reader backed by an internally-named instance variable. + def attr_internal_reader(*attrs) + attrs.each { |attr_name| attr_internal_define(attr_name, :reader) } + end + + # Declares an attribute writer backed by an internally-named instance variable. + def attr_internal_writer(*attrs) + attrs.each { |attr_name| attr_internal_define(attr_name, :writer) } + end + + # Declares an attribute reader and writer backed by an internally-named instance + # variable. + def attr_internal_accessor(*attrs) + attr_internal_reader(*attrs) + attr_internal_writer(*attrs) + end + alias_method :attr_internal, :attr_internal_accessor + + class << self; attr_accessor :attr_internal_naming_format end + self.attr_internal_naming_format = "@_%s" + + private + def attr_internal_ivar_name(attr) + Module.attr_internal_naming_format % attr + end + + def attr_internal_define(attr_name, type) + internal_name = attr_internal_ivar_name(attr_name).sub(/\A@/, "") + # use native attr_* methods as they are faster on some Ruby implementations + send("attr_#{type}", internal_name) + attr_name, internal_name = "#{attr_name}=", "#{internal_name}=" if type == :writer + alias_method attr_name, internal_name + remove_method internal_name + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/attribute_accessors.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/attribute_accessors.rb new file mode 100644 index 00000000..580baffa --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/attribute_accessors.rb @@ -0,0 +1,215 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/regexp" + +# Extends the module object with class/module and instance accessors for +# class/module attributes, just like the native attr* accessors for instance +# attributes. +class Module + # Defines a class attribute and creates a class and instance reader methods. + # The underlying class variable is set to +nil+, if it is not previously + # defined. All class and instance methods created will be public, even if + # this method is called with a private or protected access modifier. + # + # module HairColors + # mattr_reader :hair_colors + # end + # + # HairColors.hair_colors # => nil + # HairColors.class_variable_set("@@hair_colors", [:brown, :black]) + # HairColors.hair_colors # => [:brown, :black] + # + # The attribute name must be a valid method name in Ruby. + # + # module Foo + # mattr_reader :"1_Badname" + # end + # # => NameError: invalid attribute name: 1_Badname + # + # If you want to opt out the creation on the instance reader method, pass + # instance_reader: false or instance_accessor: false. + # + # module HairColors + # mattr_reader :hair_colors, instance_reader: false + # end + # + # class Person + # include HairColors + # end + # + # Person.new.hair_colors # => NoMethodError + # + # You can set a default value for the attribute. + # + # module HairColors + # mattr_reader :hair_colors, default: [:brown, :black, :blonde, :red] + # end + # + # class Person + # include HairColors + # end + # + # Person.new.hair_colors # => [:brown, :black, :blonde, :red] + def mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil) + syms.each do |sym| + raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym) + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + @@#{sym} = nil unless defined? @@#{sym} + + def self.#{sym} + @@#{sym} + end + EOS + + if instance_reader && instance_accessor + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{sym} + @@#{sym} + end + EOS + end + + sym_default_value = (block_given? && default.nil?) ? yield : default + class_variable_set("@@#{sym}", sym_default_value) unless sym_default_value.nil? + end + end + alias :cattr_reader :mattr_reader + + # Defines a class attribute and creates a class and instance writer methods to + # allow assignment to the attribute. All class and instance methods created + # will be public, even if this method is called with a private or protected + # access modifier. + # + # module HairColors + # mattr_writer :hair_colors + # end + # + # class Person + # include HairColors + # end + # + # HairColors.hair_colors = [:brown, :black] + # Person.class_variable_get("@@hair_colors") # => [:brown, :black] + # Person.new.hair_colors = [:blonde, :red] + # HairColors.class_variable_get("@@hair_colors") # => [:blonde, :red] + # + # If you want to opt out the instance writer method, pass + # instance_writer: false or instance_accessor: false. + # + # module HairColors + # mattr_writer :hair_colors, instance_writer: false + # end + # + # class Person + # include HairColors + # end + # + # Person.new.hair_colors = [:blonde, :red] # => NoMethodError + # + # You can set a default value for the attribute. + # + # module HairColors + # mattr_writer :hair_colors, default: [:brown, :black, :blonde, :red] + # end + # + # class Person + # include HairColors + # end + # + # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red] + def mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil) + syms.each do |sym| + raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym) + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + @@#{sym} = nil unless defined? @@#{sym} + + def self.#{sym}=(obj) + @@#{sym} = obj + end + EOS + + if instance_writer && instance_accessor + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{sym}=(obj) + @@#{sym} = obj + end + EOS + end + + sym_default_value = (block_given? && default.nil?) ? yield : default + send("#{sym}=", sym_default_value) unless sym_default_value.nil? + end + end + alias :cattr_writer :mattr_writer + + # Defines both class and instance accessors for class attributes. + # All class and instance methods created will be public, even if + # this method is called with a private or protected access modifier. + # + # module HairColors + # mattr_accessor :hair_colors + # end + # + # class Person + # include HairColors + # end + # + # HairColors.hair_colors = [:brown, :black, :blonde, :red] + # HairColors.hair_colors # => [:brown, :black, :blonde, :red] + # Person.new.hair_colors # => [:brown, :black, :blonde, :red] + # + # If a subclass changes the value then that would also change the value for + # parent class. Similarly if parent class changes the value then that would + # change the value of subclasses too. + # + # class Male < Person + # end + # + # Male.new.hair_colors << :blue + # Person.new.hair_colors # => [:brown, :black, :blonde, :red, :blue] + # + # To opt out of the instance writer method, pass instance_writer: false. + # To opt out of the instance reader method, pass instance_reader: false. + # + # module HairColors + # mattr_accessor :hair_colors, instance_writer: false, instance_reader: false + # end + # + # class Person + # include HairColors + # end + # + # Person.new.hair_colors = [:brown] # => NoMethodError + # Person.new.hair_colors # => NoMethodError + # + # Or pass instance_accessor: false, to opt out both instance methods. + # + # module HairColors + # mattr_accessor :hair_colors, instance_accessor: false + # end + # + # class Person + # include HairColors + # end + # + # Person.new.hair_colors = [:brown] # => NoMethodError + # Person.new.hair_colors # => NoMethodError + # + # You can set a default value for the attribute. + # + # module HairColors + # mattr_accessor :hair_colors, default: [:brown, :black, :blonde, :red] + # end + # + # class Person + # include HairColors + # end + # + # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red] + def mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil, &blk) + mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default, &blk) + mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor, default: default) + end + alias :cattr_accessor :mattr_accessor +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb new file mode 100644 index 00000000..4b9b6ea9 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb @@ -0,0 +1,150 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/regexp" + +# Extends the module object with class/module and instance accessors for +# class/module attributes, just like the native attr* accessors for instance +# attributes, but does so on a per-thread basis. +# +# So the values are scoped within the Thread.current space under the class name +# of the module. +class Module + # Defines a per-thread class attribute and creates class and instance reader methods. + # The underlying per-thread class variable is set to +nil+, if it is not previously defined. + # + # module Current + # thread_mattr_reader :user + # end + # + # Current.user # => nil + # Thread.current[:attr_Current_user] = "DHH" + # Current.user # => "DHH" + # + # The attribute name must be a valid method name in Ruby. + # + # module Foo + # thread_mattr_reader :"1_Badname" + # end + # # => NameError: invalid attribute name: 1_Badname + # + # If you want to opt out of the creation of the instance reader method, pass + # instance_reader: false or instance_accessor: false. + # + # class Current + # thread_mattr_reader :user, instance_reader: false + # end + # + # Current.new.user # => NoMethodError + def thread_mattr_reader(*syms) # :nodoc: + options = syms.extract_options! + + syms.each do |sym| + raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym) + + # The following generated method concatenates `name` because we want it + # to work with inheritance via polymorphism. + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def self.#{sym} + Thread.current["attr_" + name + "_#{sym}"] + end + EOS + + unless options[:instance_reader] == false || options[:instance_accessor] == false + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{sym} + self.class.#{sym} + end + EOS + end + end + end + alias :thread_cattr_reader :thread_mattr_reader + + # Defines a per-thread class attribute and creates a class and instance writer methods to + # allow assignment to the attribute. + # + # module Current + # thread_mattr_writer :user + # end + # + # Current.user = "DHH" + # Thread.current[:attr_Current_user] # => "DHH" + # + # If you want to opt out of the creation of the instance writer method, pass + # instance_writer: false or instance_accessor: false. + # + # class Current + # thread_mattr_writer :user, instance_writer: false + # end + # + # Current.new.user = "DHH" # => NoMethodError + def thread_mattr_writer(*syms) # :nodoc: + options = syms.extract_options! + syms.each do |sym| + raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym) + + # The following generated method concatenates `name` because we want it + # to work with inheritance via polymorphism. + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def self.#{sym}=(obj) + Thread.current["attr_" + name + "_#{sym}"] = obj + end + EOS + + unless options[:instance_writer] == false || options[:instance_accessor] == false + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{sym}=(obj) + self.class.#{sym} = obj + end + EOS + end + end + end + alias :thread_cattr_writer :thread_mattr_writer + + # Defines both class and instance accessors for class attributes. + # + # class Account + # thread_mattr_accessor :user + # end + # + # Account.user = "DHH" + # Account.user # => "DHH" + # Account.new.user # => "DHH" + # + # If a subclass changes the value, the parent class' value is not changed. + # Similarly, if the parent class changes the value, the value of subclasses + # is not changed. + # + # class Customer < Account + # end + # + # Customer.user = "Rafael" + # Customer.user # => "Rafael" + # Account.user # => "DHH" + # + # To opt out of the instance writer method, pass instance_writer: false. + # To opt out of the instance reader method, pass instance_reader: false. + # + # class Current + # thread_mattr_accessor :user, instance_writer: false, instance_reader: false + # end + # + # Current.new.user = "DHH" # => NoMethodError + # Current.new.user # => NoMethodError + # + # Or pass instance_accessor: false, to opt out both instance methods. + # + # class Current + # mattr_accessor :user, instance_accessor: false + # end + # + # Current.new.user = "DHH" # => NoMethodError + # Current.new.user # => NoMethodError + def thread_mattr_accessor(*syms) + thread_mattr_reader(*syms) + thread_mattr_writer(*syms) + end + alias :thread_cattr_accessor :thread_mattr_accessor +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/concerning.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/concerning.rb new file mode 100644 index 00000000..7bbbf321 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/concerning.rb @@ -0,0 +1,134 @@ +# frozen_string_literal: true + +require "active_support/concern" + +class Module + # = Bite-sized separation of concerns + # + # We often find ourselves with a medium-sized chunk of behavior that we'd + # like to extract, but only mix in to a single class. + # + # Extracting a plain old Ruby object to encapsulate it and collaborate or + # delegate to the original object is often a good choice, but when there's + # no additional state to encapsulate or we're making DSL-style declarations + # about the parent class, introducing new collaborators can obfuscate rather + # than simplify. + # + # The typical route is to just dump everything in a monolithic class, perhaps + # with a comment, as a least-bad alternative. Using modules in separate files + # means tedious sifting to get a big-picture view. + # + # = Dissatisfying ways to separate small concerns + # + # == Using comments: + # + # class Todo < ApplicationRecord + # # Other todo implementation + # # ... + # + # ## Event tracking + # has_many :events + # + # before_create :track_creation + # + # private + # def track_creation + # # ... + # end + # end + # + # == With an inline module: + # + # Noisy syntax. + # + # class Todo < ApplicationRecord + # # Other todo implementation + # # ... + # + # module EventTracking + # extend ActiveSupport::Concern + # + # included do + # has_many :events + # before_create :track_creation + # end + # + # private + # def track_creation + # # ... + # end + # end + # include EventTracking + # end + # + # == Mix-in noise exiled to its own file: + # + # Once our chunk of behavior starts pushing the scroll-to-understand-it + # boundary, we give in and move it to a separate file. At this size, the + # increased overhead can be a reasonable tradeoff even if it reduces our + # at-a-glance perception of how things work. + # + # class Todo < ApplicationRecord + # # Other todo implementation + # # ... + # + # include TodoEventTracking + # end + # + # = Introducing Module#concerning + # + # By quieting the mix-in noise, we arrive at a natural, low-ceremony way to + # separate bite-sized concerns. + # + # class Todo < ApplicationRecord + # # Other todo implementation + # # ... + # + # concerning :EventTracking do + # included do + # has_many :events + # before_create :track_creation + # end + # + # private + # def track_creation + # # ... + # end + # end + # end + # + # Todo.ancestors + # # => [Todo, Todo::EventTracking, ApplicationRecord, Object] + # + # This small step has some wonderful ripple effects. We can + # * grok the behavior of our class in one glance, + # * clean up monolithic junk-drawer classes by separating their concerns, and + # * stop leaning on protected/private for crude "this is internal stuff" modularity. + module Concerning + # Define a new concern and mix it in. + def concerning(topic, &block) + include concern(topic, &block) + end + + # A low-cruft shortcut to define a concern. + # + # concern :EventTracking do + # ... + # end + # + # is equivalent to + # + # module EventTracking + # extend ActiveSupport::Concern + # + # ... + # end + def concern(topic, &module_definition) + const_set topic, Module.new { + extend ::ActiveSupport::Concern + module_eval(&module_definition) + } + end + end + include Concerning +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/delegation.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/delegation.rb new file mode 100644 index 00000000..4310df30 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/delegation.rb @@ -0,0 +1,287 @@ +# frozen_string_literal: true + +require "set" +require "active_support/core_ext/regexp" + +class Module + # Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+ + # option is not used. + class DelegationError < NoMethodError; end + + RUBY_RESERVED_KEYWORDS = %w(alias and BEGIN begin break case class def defined? do + else elsif END end ensure false for if in module next nil not or redo rescue retry + return self super then true undef unless until when while yield) + DELEGATION_RESERVED_KEYWORDS = %w(_ arg args block) + DELEGATION_RESERVED_METHOD_NAMES = Set.new( + RUBY_RESERVED_KEYWORDS + DELEGATION_RESERVED_KEYWORDS + ).freeze + + # Provides a +delegate+ class method to easily expose contained objects' + # public methods as your own. + # + # ==== Options + # * :to - Specifies the target object + # * :prefix - Prefixes the new method with the target name or a custom prefix + # * :allow_nil - if set to true, prevents a +Module::DelegationError+ + # from being raised + # + # The macro receives one or more method names (specified as symbols or + # strings) and the name of the target object via the :to option + # (also a symbol or string). + # + # Delegation is particularly useful with Active Record associations: + # + # class Greeter < ActiveRecord::Base + # def hello + # 'hello' + # end + # + # def goodbye + # 'goodbye' + # end + # end + # + # class Foo < ActiveRecord::Base + # belongs_to :greeter + # delegate :hello, to: :greeter + # end + # + # Foo.new.hello # => "hello" + # Foo.new.goodbye # => NoMethodError: undefined method `goodbye' for # + # + # Multiple delegates to the same target are allowed: + # + # class Foo < ActiveRecord::Base + # belongs_to :greeter + # delegate :hello, :goodbye, to: :greeter + # end + # + # Foo.new.goodbye # => "goodbye" + # + # Methods can be delegated to instance variables, class variables, or constants + # by providing them as a symbols: + # + # class Foo + # CONSTANT_ARRAY = [0,1,2,3] + # @@class_array = [4,5,6,7] + # + # def initialize + # @instance_array = [8,9,10,11] + # end + # delegate :sum, to: :CONSTANT_ARRAY + # delegate :min, to: :@@class_array + # delegate :max, to: :@instance_array + # end + # + # Foo.new.sum # => 6 + # Foo.new.min # => 4 + # Foo.new.max # => 11 + # + # It's also possible to delegate a method to the class by using +:class+: + # + # class Foo + # def self.hello + # "world" + # end + # + # delegate :hello, to: :class + # end + # + # Foo.new.hello # => "world" + # + # Delegates can optionally be prefixed using the :prefix option. If the value + # is true, the delegate methods are prefixed with the name of the object being + # delegated to. + # + # Person = Struct.new(:name, :address) + # + # class Invoice < Struct.new(:client) + # delegate :name, :address, to: :client, prefix: true + # end + # + # john_doe = Person.new('John Doe', 'Vimmersvej 13') + # invoice = Invoice.new(john_doe) + # invoice.client_name # => "John Doe" + # invoice.client_address # => "Vimmersvej 13" + # + # It is also possible to supply a custom prefix. + # + # class Invoice < Struct.new(:client) + # delegate :name, :address, to: :client, prefix: :customer + # end + # + # invoice = Invoice.new(john_doe) + # invoice.customer_name # => 'John Doe' + # invoice.customer_address # => 'Vimmersvej 13' + # + # If the target is +nil+ and does not respond to the delegated method a + # +Module::DelegationError+ is raised. If you wish to instead return +nil+, + # use the :allow_nil option. + # + # class User < ActiveRecord::Base + # has_one :profile + # delegate :age, to: :profile + # end + # + # User.new.age + # # => Module::DelegationError: User#age delegated to profile.age, but profile is nil + # + # But if not having a profile yet is fine and should not be an error + # condition: + # + # class User < ActiveRecord::Base + # has_one :profile + # delegate :age, to: :profile, allow_nil: true + # end + # + # User.new.age # nil + # + # Note that if the target is not +nil+ then the call is attempted regardless of the + # :allow_nil option, and thus an exception is still raised if said object + # does not respond to the method: + # + # class Foo + # def initialize(bar) + # @bar = bar + # end + # + # delegate :name, to: :@bar, allow_nil: true + # end + # + # Foo.new("Bar").name # raises NoMethodError: undefined method `name' + # + # The target method must be public, otherwise it will raise +NoMethodError+. + def delegate(*methods, to: nil, prefix: nil, allow_nil: nil) + unless to + raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter)." + end + + if prefix == true && /^[^a-z_]/.match?(to) + raise ArgumentError, "Can only automatically set the delegation prefix when delegating to a method." + end + + method_prefix = \ + if prefix + "#{prefix == true ? to : prefix}_" + else + "" + end + + location = caller_locations(1, 1).first + file, line = location.path, location.lineno + + to = to.to_s + to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to) + + methods.map do |method| + # Attribute writer methods only accept one argument. Makes sure []= + # methods still accept two arguments. + definition = /[^\]]=$/.match?(method) ? "arg" : "*args, &block" + + # The following generated method calls the target exactly once, storing + # the returned value in a dummy variable. + # + # Reason is twofold: On one hand doing less calls is in general better. + # On the other hand it could be that the target has side-effects, + # whereas conceptually, from the user point of view, the delegator should + # be doing one call. + if allow_nil + method_def = [ + "def #{method_prefix}#{method}(#{definition})", + "_ = #{to}", + "if !_.nil? || nil.respond_to?(:#{method})", + " _.#{method}(#{definition})", + "end", + "end" + ].join ";" + else + exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") + + method_def = [ + "def #{method_prefix}#{method}(#{definition})", + " _ = #{to}", + " _.#{method}(#{definition})", + "rescue NoMethodError => e", + " if _.nil? && e.name == :#{method}", + " #{exception}", + " else", + " raise", + " end", + "end" + ].join ";" + end + + module_eval(method_def, file, line) + end + end + + # When building decorators, a common pattern may emerge: + # + # class Partition + # def initialize(event) + # @event = event + # end + # + # def person + # @event.detail.person || @event.creator + # end + # + # private + # def respond_to_missing?(name, include_private = false) + # @event.respond_to?(name, include_private) + # end + # + # def method_missing(method, *args, &block) + # @event.send(method, *args, &block) + # end + # end + # + # With Module#delegate_missing_to, the above is condensed to: + # + # class Partition + # delegate_missing_to :@event + # + # def initialize(event) + # @event = event + # end + # + # def person + # @event.detail.person || @event.creator + # end + # end + # + # The target can be anything callable within the object, e.g. instance + # variables, methods, constants, etc. + # + # The delegated method must be public on the target, otherwise it will + # raise +NoMethodError+. + def delegate_missing_to(target) + target = target.to_s + target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target) + + module_eval <<-RUBY, __FILE__, __LINE__ + 1 + def respond_to_missing?(name, include_private = false) + # It may look like an oversight, but we deliberately do not pass + # +include_private+, because they do not get delegated. + + #{target}.respond_to?(name) || super + end + + def method_missing(method, *args, &block) + if #{target}.respond_to?(method) + #{target}.public_send(method, *args, &block) + else + begin + super + rescue NoMethodError + if #{target}.nil? + raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil" + else + raise + end + end + end + end + RUBY + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/deprecation.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/deprecation.rb new file mode 100644 index 00000000..71c42eb3 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/deprecation.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class Module + # deprecate :foo + # deprecate bar: 'message' + # deprecate :foo, :bar, baz: 'warning!', qux: 'gone!' + # + # You can also use custom deprecator instance: + # + # deprecate :foo, deprecator: MyLib::Deprecator.new + # deprecate :foo, bar: "warning!", deprecator: MyLib::Deprecator.new + # + # \Custom deprecators must respond to deprecation_warning(deprecated_method_name, message, caller_backtrace) + # method where you can implement your custom warning behavior. + # + # class MyLib::Deprecator + # def deprecation_warning(deprecated_method_name, message, caller_backtrace = nil) + # message = "#{deprecated_method_name} is deprecated and will be removed from MyLibrary | #{message}" + # Kernel.warn message + # end + # end + def deprecate(*method_names) + ActiveSupport::Deprecation.deprecate_methods(self, *method_names) + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/introspection.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/introspection.rb new file mode 100644 index 00000000..c5bb598b --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/introspection.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require "active_support/inflector" + +class Module + # Returns the name of the module containing this one. + # + # M::N.parent_name # => "M" + def parent_name + if defined?(@parent_name) + @parent_name + else + parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil + @parent_name = parent_name unless frozen? + parent_name + end + end + + # Returns the module which contains this one according to its name. + # + # module M + # module N + # end + # end + # X = M::N + # + # M::N.parent # => M + # X.parent # => M + # + # The parent of top-level and anonymous modules is Object. + # + # M.parent # => Object + # Module.new.parent # => Object + def parent + parent_name ? ActiveSupport::Inflector.constantize(parent_name) : Object + end + + # Returns all the parents of this module according to its name, ordered from + # nested outwards. The receiver is not contained within the result. + # + # module M + # module N + # end + # end + # X = M::N + # + # M.parents # => [Object] + # M::N.parents # => [M, Object] + # X.parents # => [M, Object] + def parents + parents = [] + if parent_name + parts = parent_name.split("::") + until parts.empty? + parents << ActiveSupport::Inflector.constantize(parts * "::") + parts.pop + end + end + parents << Object unless parents.include? Object + parents + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/reachable.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/reachable.rb new file mode 100644 index 00000000..e9cbda52 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/reachable.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/anonymous" +require "active_support/core_ext/string/inflections" + +class Module + def reachable? #:nodoc: + !anonymous? && name.safe_constantize.equal?(self) + end + deprecate :reachable? +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/redefine_method.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/redefine_method.rb new file mode 100644 index 00000000..a0a6622c --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/redefine_method.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +class Module + if RUBY_VERSION >= "2.3" + # Marks the named method as intended to be redefined, if it exists. + # Suppresses the Ruby method redefinition warning. Prefer + # #redefine_method where possible. + def silence_redefinition_of_method(method) + if method_defined?(method) || private_method_defined?(method) + # This suppresses the "method redefined" warning; the self-alias + # looks odd, but means we don't need to generate a unique name + alias_method method, method + end + end + else + def silence_redefinition_of_method(method) + if method_defined?(method) || private_method_defined?(method) + alias_method :__rails_redefine, method + remove_method :__rails_redefine + end + end + end + + # Replaces the existing method definition, if there is one, with the passed + # block as its body. + def redefine_method(method, &block) + visibility = method_visibility(method) + silence_redefinition_of_method(method) + define_method(method, &block) + send(visibility, method) + end + + # Replaces the existing singleton method definition, if there is one, with + # the passed block as its body. + def redefine_singleton_method(method, &block) + singleton_class.redefine_method(method, &block) + end + + def method_visibility(method) # :nodoc: + case + when private_method_defined?(method) + :private + when protected_method_defined?(method) + :protected + else + :public + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/remove_method.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/remove_method.rb new file mode 100644 index 00000000..97eb5f9e --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/module/remove_method.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/redefine_method" + +class Module + # Removes the named method, if it exists. + def remove_possible_method(method) + if method_defined?(method) || private_method_defined?(method) + undef_method(method) + end + end + + # Removes the named singleton method, if it exists. + def remove_possible_singleton_method(method) + singleton_class.remove_possible_method(method) + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/name_error.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/name_error.rb new file mode 100644 index 00000000..6d37cd9d --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/name_error.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +class NameError + # Extract the name of the missing constant from the exception message. + # + # begin + # HelloWorld + # rescue NameError => e + # e.missing_name + # end + # # => "HelloWorld" + def missing_name + # Since ruby v2.3.0 `did_you_mean` gem is loaded by default. + # It extends NameError#message with spell corrections which are SLOW. + # We should use original_message message instead. + message = respond_to?(:original_message) ? original_message : self.message + + if /undefined local variable or method/ !~ message + $1 if /((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/ =~ message + end + end + + # Was this exception raised because the given name was missing? + # + # begin + # HelloWorld + # rescue NameError => e + # e.missing_name?("HelloWorld") + # end + # # => true + def missing_name?(name) + if name.is_a? Symbol + self.name == name + else + missing_name == name.to_s + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/numeric.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/numeric.rb new file mode 100644 index 00000000..0b04e359 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/numeric.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +require "active_support/core_ext/numeric/bytes" +require "active_support/core_ext/numeric/time" +require "active_support/core_ext/numeric/inquiry" +require "active_support/core_ext/numeric/conversions" diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/numeric/bytes.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/numeric/bytes.rb new file mode 100644 index 00000000..b002eba4 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/numeric/bytes.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +class Numeric + KILOBYTE = 1024 + MEGABYTE = KILOBYTE * 1024 + GIGABYTE = MEGABYTE * 1024 + TERABYTE = GIGABYTE * 1024 + PETABYTE = TERABYTE * 1024 + EXABYTE = PETABYTE * 1024 + + # Enables the use of byte calculations and declarations, like 45.bytes + 2.6.megabytes + # + # 2.bytes # => 2 + def bytes + self + end + alias :byte :bytes + + # Returns the number of bytes equivalent to the kilobytes provided. + # + # 2.kilobytes # => 2048 + def kilobytes + self * KILOBYTE + end + alias :kilobyte :kilobytes + + # Returns the number of bytes equivalent to the megabytes provided. + # + # 2.megabytes # => 2_097_152 + def megabytes + self * MEGABYTE + end + alias :megabyte :megabytes + + # Returns the number of bytes equivalent to the gigabytes provided. + # + # 2.gigabytes # => 2_147_483_648 + def gigabytes + self * GIGABYTE + end + alias :gigabyte :gigabytes + + # Returns the number of bytes equivalent to the terabytes provided. + # + # 2.terabytes # => 2_199_023_255_552 + def terabytes + self * TERABYTE + end + alias :terabyte :terabytes + + # Returns the number of bytes equivalent to the petabytes provided. + # + # 2.petabytes # => 2_251_799_813_685_248 + def petabytes + self * PETABYTE + end + alias :petabyte :petabytes + + # Returns the number of bytes equivalent to the exabytes provided. + # + # 2.exabytes # => 2_305_843_009_213_693_952 + def exabytes + self * EXABYTE + end + alias :exabyte :exabytes +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/numeric/conversions.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/numeric/conversions.rb new file mode 100644 index 00000000..f6c27139 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/numeric/conversions.rb @@ -0,0 +1,140 @@ +# frozen_string_literal: true + +require "active_support/core_ext/big_decimal/conversions" +require "active_support/number_helper" +require "active_support/core_ext/module/deprecation" + +module ActiveSupport::NumericWithFormat + # Provides options for converting numbers into formatted strings. + # Options are provided for phone numbers, currency, percentage, + # precision, positional notation, file size and pretty printing. + # + # ==== Options + # + # For details on which formats use which options, see ActiveSupport::NumberHelper + # + # ==== Examples + # + # Phone Numbers: + # 5551234.to_s(:phone) # => "555-1234" + # 1235551234.to_s(:phone) # => "123-555-1234" + # 1235551234.to_s(:phone, area_code: true) # => "(123) 555-1234" + # 1235551234.to_s(:phone, delimiter: ' ') # => "123 555 1234" + # 1235551234.to_s(:phone, area_code: true, extension: 555) # => "(123) 555-1234 x 555" + # 1235551234.to_s(:phone, country_code: 1) # => "+1-123-555-1234" + # 1235551234.to_s(:phone, country_code: 1, extension: 1343, delimiter: '.') + # # => "+1.123.555.1234 x 1343" + # + # Currency: + # 1234567890.50.to_s(:currency) # => "$1,234,567,890.50" + # 1234567890.506.to_s(:currency) # => "$1,234,567,890.51" + # 1234567890.506.to_s(:currency, precision: 3) # => "$1,234,567,890.506" + # 1234567890.506.to_s(:currency, locale: :fr) # => "1 234 567 890,51 €" + # -1234567890.50.to_s(:currency, negative_format: '(%u%n)') + # # => "($1,234,567,890.50)" + # 1234567890.50.to_s(:currency, unit: '£', separator: ',', delimiter: '') + # # => "£1234567890,50" + # 1234567890.50.to_s(:currency, unit: '£', separator: ',', delimiter: '', format: '%n %u') + # # => "1234567890,50 £" + # + # Percentage: + # 100.to_s(:percentage) # => "100.000%" + # 100.to_s(:percentage, precision: 0) # => "100%" + # 1000.to_s(:percentage, delimiter: '.', separator: ',') # => "1.000,000%" + # 302.24398923423.to_s(:percentage, precision: 5) # => "302.24399%" + # 1000.to_s(:percentage, locale: :fr) # => "1 000,000%" + # 100.to_s(:percentage, format: '%n %') # => "100.000 %" + # + # Delimited: + # 12345678.to_s(:delimited) # => "12,345,678" + # 12345678.05.to_s(:delimited) # => "12,345,678.05" + # 12345678.to_s(:delimited, delimiter: '.') # => "12.345.678" + # 12345678.to_s(:delimited, delimiter: ',') # => "12,345,678" + # 12345678.05.to_s(:delimited, separator: ' ') # => "12,345,678 05" + # 12345678.05.to_s(:delimited, locale: :fr) # => "12 345 678,05" + # 98765432.98.to_s(:delimited, delimiter: ' ', separator: ',') + # # => "98 765 432,98" + # + # Rounded: + # 111.2345.to_s(:rounded) # => "111.235" + # 111.2345.to_s(:rounded, precision: 2) # => "111.23" + # 13.to_s(:rounded, precision: 5) # => "13.00000" + # 389.32314.to_s(:rounded, precision: 0) # => "389" + # 111.2345.to_s(:rounded, significant: true) # => "111" + # 111.2345.to_s(:rounded, precision: 1, significant: true) # => "100" + # 13.to_s(:rounded, precision: 5, significant: true) # => "13.000" + # 111.234.to_s(:rounded, locale: :fr) # => "111,234" + # 13.to_s(:rounded, precision: 5, significant: true, strip_insignificant_zeros: true) + # # => "13" + # 389.32314.to_s(:rounded, precision: 4, significant: true) # => "389.3" + # 1111.2345.to_s(:rounded, precision: 2, separator: ',', delimiter: '.') + # # => "1.111,23" + # + # Human-friendly size in Bytes: + # 123.to_s(:human_size) # => "123 Bytes" + # 1234.to_s(:human_size) # => "1.21 KB" + # 12345.to_s(:human_size) # => "12.1 KB" + # 1234567.to_s(:human_size) # => "1.18 MB" + # 1234567890.to_s(:human_size) # => "1.15 GB" + # 1234567890123.to_s(:human_size) # => "1.12 TB" + # 1234567890123456.to_s(:human_size) # => "1.1 PB" + # 1234567890123456789.to_s(:human_size) # => "1.07 EB" + # 1234567.to_s(:human_size, precision: 2) # => "1.2 MB" + # 483989.to_s(:human_size, precision: 2) # => "470 KB" + # 1234567.to_s(:human_size, precision: 2, separator: ',') # => "1,2 MB" + # 1234567890123.to_s(:human_size, precision: 5) # => "1.1228 TB" + # 524288000.to_s(:human_size, precision: 5) # => "500 MB" + # + # Human-friendly format: + # 123.to_s(:human) # => "123" + # 1234.to_s(:human) # => "1.23 Thousand" + # 12345.to_s(:human) # => "12.3 Thousand" + # 1234567.to_s(:human) # => "1.23 Million" + # 1234567890.to_s(:human) # => "1.23 Billion" + # 1234567890123.to_s(:human) # => "1.23 Trillion" + # 1234567890123456.to_s(:human) # => "1.23 Quadrillion" + # 1234567890123456789.to_s(:human) # => "1230 Quadrillion" + # 489939.to_s(:human, precision: 2) # => "490 Thousand" + # 489939.to_s(:human, precision: 4) # => "489.9 Thousand" + # 1234567.to_s(:human, precision: 4, + # significant: false) # => "1.2346 Million" + # 1234567.to_s(:human, precision: 1, + # separator: ',', + # significant: false) # => "1,2 Million" + def to_s(format = nil, options = nil) + case format + when nil + super() + when Integer, String + super(format) + when :phone + ActiveSupport::NumberHelper.number_to_phone(self, options || {}) + when :currency + ActiveSupport::NumberHelper.number_to_currency(self, options || {}) + when :percentage + ActiveSupport::NumberHelper.number_to_percentage(self, options || {}) + when :delimited + ActiveSupport::NumberHelper.number_to_delimited(self, options || {}) + when :rounded + ActiveSupport::NumberHelper.number_to_rounded(self, options || {}) + when :human + ActiveSupport::NumberHelper.number_to_human(self, options || {}) + when :human_size + ActiveSupport::NumberHelper.number_to_human_size(self, options || {}) + when Symbol + super() + else + super(format) + end + end +end + +# Ruby 2.4+ unifies Fixnum & Bignum into Integer. +if 0.class == Integer + Integer.prepend ActiveSupport::NumericWithFormat +else + Fixnum.prepend ActiveSupport::NumericWithFormat + Bignum.prepend ActiveSupport::NumericWithFormat +end +Float.prepend ActiveSupport::NumericWithFormat +BigDecimal.prepend ActiveSupport::NumericWithFormat diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/numeric/inquiry.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/numeric/inquiry.rb new file mode 100644 index 00000000..15334c91 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/numeric/inquiry.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +unless 1.respond_to?(:positive?) # TODO: Remove this file when we drop support to ruby < 2.3 + class Numeric + # Returns true if the number is positive. + # + # 1.positive? # => true + # 0.positive? # => false + # -1.positive? # => false + def positive? + self > 0 + end + + # Returns true if the number is negative. + # + # -1.negative? # => true + # 0.negative? # => false + # 1.negative? # => false + def negative? + self < 0 + end + end + + class Complex + undef :positive? + undef :negative? + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/numeric/time.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/numeric/time.rb new file mode 100644 index 00000000..bc4627f7 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/numeric/time.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require "active_support/duration" +require "active_support/core_ext/time/calculations" +require "active_support/core_ext/time/acts_like" +require "active_support/core_ext/date/calculations" +require "active_support/core_ext/date/acts_like" + +class Numeric + # Returns a Duration instance matching the number of seconds provided. + # + # 2.seconds # => 2 seconds + def seconds + ActiveSupport::Duration.seconds(self) + end + alias :second :seconds + + # Returns a Duration instance matching the number of minutes provided. + # + # 2.minutes # => 2 minutes + def minutes + ActiveSupport::Duration.minutes(self) + end + alias :minute :minutes + + # Returns a Duration instance matching the number of hours provided. + # + # 2.hours # => 2 hours + def hours + ActiveSupport::Duration.hours(self) + end + alias :hour :hours + + # Returns a Duration instance matching the number of days provided. + # + # 2.days # => 2 days + def days + ActiveSupport::Duration.days(self) + end + alias :day :days + + # Returns a Duration instance matching the number of weeks provided. + # + # 2.weeks # => 2 weeks + def weeks + ActiveSupport::Duration.weeks(self) + end + alias :week :weeks + + # Returns a Duration instance matching the number of fortnights provided. + # + # 2.fortnights # => 4 weeks + def fortnights + ActiveSupport::Duration.weeks(self * 2) + end + alias :fortnight :fortnights + + # Returns the number of milliseconds equivalent to the seconds provided. + # Used with the standard time durations. + # + # 2.in_milliseconds # => 2000 + # 1.hour.in_milliseconds # => 3600000 + def in_milliseconds + self * 1000 + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object.rb new file mode 100644 index 00000000..efd34cc6 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/acts_like" +require "active_support/core_ext/object/blank" +require "active_support/core_ext/object/duplicable" +require "active_support/core_ext/object/deep_dup" +require "active_support/core_ext/object/try" +require "active_support/core_ext/object/inclusion" + +require "active_support/core_ext/object/conversions" +require "active_support/core_ext/object/instance_variables" + +require "active_support/core_ext/object/json" +require "active_support/core_ext/object/to_param" +require "active_support/core_ext/object/to_query" +require "active_support/core_ext/object/with_options" diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/acts_like.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/acts_like.rb new file mode 100644 index 00000000..403ee20e --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/acts_like.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class Object + # A duck-type assistant method. For example, Active Support extends Date + # to define an acts_like_date? method, and extends Time to define + # acts_like_time?. As a result, we can do x.acts_like?(:time) and + # x.acts_like?(:date) to do duck-type-safe comparisons, since classes that + # we want to act like Time simply need to define an acts_like_time? method. + def acts_like?(duck) + case duck + when :time + respond_to? :acts_like_time? + when :date + respond_to? :acts_like_date? + when :string + respond_to? :acts_like_string? + else + respond_to? :"acts_like_#{duck}?" + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/blank.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/blank.rb new file mode 100644 index 00000000..2ca431ab --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/blank.rb @@ -0,0 +1,156 @@ +# frozen_string_literal: true + +require "active_support/core_ext/regexp" +require "concurrent/map" + +class Object + # An object is blank if it's false, empty, or a whitespace string. + # For example, +false+, '', ' ', +nil+, [], and {} are all blank. + # + # This simplifies + # + # !address || address.empty? + # + # to + # + # address.blank? + # + # @return [true, false] + def blank? + respond_to?(:empty?) ? !!empty? : !self + end + + # An object is present if it's not blank. + # + # @return [true, false] + def present? + !blank? + end + + # Returns the receiver if it's present otherwise returns +nil+. + # object.presence is equivalent to + # + # object.present? ? object : nil + # + # For example, something like + # + # state = params[:state] if params[:state].present? + # country = params[:country] if params[:country].present? + # region = state || country || 'US' + # + # becomes + # + # region = params[:state].presence || params[:country].presence || 'US' + # + # @return [Object] + def presence + self if present? + end +end + +class NilClass + # +nil+ is blank: + # + # nil.blank? # => true + # + # @return [true] + def blank? + true + end +end + +class FalseClass + # +false+ is blank: + # + # false.blank? # => true + # + # @return [true] + def blank? + true + end +end + +class TrueClass + # +true+ is not blank: + # + # true.blank? # => false + # + # @return [false] + def blank? + false + end +end + +class Array + # An array is blank if it's empty: + # + # [].blank? # => true + # [1,2,3].blank? # => false + # + # @return [true, false] + alias_method :blank?, :empty? +end + +class Hash + # A hash is blank if it's empty: + # + # {}.blank? # => true + # { key: 'value' }.blank? # => false + # + # @return [true, false] + alias_method :blank?, :empty? +end + +class String + BLANK_RE = /\A[[:space:]]*\z/ + ENCODED_BLANKS = Concurrent::Map.new do |h, enc| + h[enc] = Regexp.new(BLANK_RE.source.encode(enc), BLANK_RE.options | Regexp::FIXEDENCODING) + end + + # A string is blank if it's empty or contains whitespaces only: + # + # ''.blank? # => true + # ' '.blank? # => true + # "\t\n\r".blank? # => true + # ' blah '.blank? # => false + # + # Unicode whitespace is supported: + # + # "\u00a0".blank? # => true + # + # @return [true, false] + def blank? + # The regexp that matches blank strings is expensive. For the case of empty + # strings we can speed up this method (~3.5x) with an empty? call. The + # penalty for the rest of strings is marginal. + empty? || + begin + BLANK_RE.match?(self) + rescue Encoding::CompatibilityError + ENCODED_BLANKS[self.encoding].match?(self) + end + end +end + +class Numeric #:nodoc: + # No number is blank: + # + # 1.blank? # => false + # 0.blank? # => false + # + # @return [false] + def blank? + false + end +end + +class Time #:nodoc: + # No Time is blank: + # + # Time.now.blank? # => false + # + # @return [false] + def blank? + false + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/conversions.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/conversions.rb new file mode 100644 index 00000000..624fb8d7 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/conversions.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/to_param" +require "active_support/core_ext/object/to_query" +require "active_support/core_ext/array/conversions" +require "active_support/core_ext/hash/conversions" diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/deep_dup.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/deep_dup.rb new file mode 100644 index 00000000..c66c5eb2 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/deep_dup.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/duplicable" + +class Object + # Returns a deep copy of object if it's duplicable. If it's + # not duplicable, returns +self+. + # + # object = Object.new + # dup = object.deep_dup + # dup.instance_variable_set(:@a, 1) + # + # object.instance_variable_defined?(:@a) # => false + # dup.instance_variable_defined?(:@a) # => true + def deep_dup + duplicable? ? dup : self + end +end + +class Array + # Returns a deep copy of array. + # + # array = [1, [2, 3]] + # dup = array.deep_dup + # dup[1][2] = 4 + # + # array[1][2] # => nil + # dup[1][2] # => 4 + def deep_dup + map(&:deep_dup) + end +end + +class Hash + # Returns a deep copy of hash. + # + # hash = { a: { b: 'b' } } + # dup = hash.deep_dup + # dup[:a][:c] = 'c' + # + # hash[:a][:c] # => nil + # dup[:a][:c] # => "c" + def deep_dup + hash = dup + each_pair do |key, value| + if key.frozen? && ::String === key + hash[key] = value.deep_dup + else + hash.delete(key) + hash[key.deep_dup] = value.deep_dup + end + end + hash + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/duplicable.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/duplicable.rb new file mode 100644 index 00000000..9bb99087 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/duplicable.rb @@ -0,0 +1,156 @@ +# frozen_string_literal: true + +#-- +# Most objects are cloneable, but not all. For example you can't dup methods: +# +# method(:puts).dup # => TypeError: allocator undefined for Method +# +# Classes may signal their instances are not duplicable removing +dup+/+clone+ +# or raising exceptions from them. So, to dup an arbitrary object you normally +# use an optimistic approach and are ready to catch an exception, say: +# +# arbitrary_object.dup rescue object +# +# Rails dups objects in a few critical spots where they are not that arbitrary. +# That rescue is very expensive (like 40 times slower than a predicate), and it +# is often triggered. +# +# That's why we hardcode the following cases and check duplicable? instead of +# using that rescue idiom. +#++ +class Object + # Can you safely dup this object? + # + # False for method objects; + # true otherwise. + def duplicable? + true + end +end + +class NilClass + begin + nil.dup + rescue TypeError + + # +nil+ is not duplicable: + # + # nil.duplicable? # => false + # nil.dup # => TypeError: can't dup NilClass + def duplicable? + false + end + end +end + +class FalseClass + begin + false.dup + rescue TypeError + + # +false+ is not duplicable: + # + # false.duplicable? # => false + # false.dup # => TypeError: can't dup FalseClass + def duplicable? + false + end + end +end + +class TrueClass + begin + true.dup + rescue TypeError + + # +true+ is not duplicable: + # + # true.duplicable? # => false + # true.dup # => TypeError: can't dup TrueClass + def duplicable? + false + end + end +end + +class Symbol + begin + :symbol.dup # Ruby 2.4.x. + "symbol_from_string".to_sym.dup # Some symbols can't `dup` in Ruby 2.4.0. + rescue TypeError + + # Symbols are not duplicable: + # + # :my_symbol.duplicable? # => false + # :my_symbol.dup # => TypeError: can't dup Symbol + def duplicable? + false + end + end +end + +class Numeric + begin + 1.dup + rescue TypeError + + # Numbers are not duplicable: + # + # 3.duplicable? # => false + # 3.dup # => TypeError: can't dup Integer + def duplicable? + false + end + end +end + +require "bigdecimal" +class BigDecimal + # BigDecimals are duplicable: + # + # BigDecimal("1.2").duplicable? # => true + # BigDecimal("1.2").dup # => # + def duplicable? + true + end +end + +class Method + # Methods are not duplicable: + # + # method(:puts).duplicable? # => false + # method(:puts).dup # => TypeError: allocator undefined for Method + def duplicable? + false + end +end + +class Complex + begin + Complex(1).dup + rescue TypeError + + # Complexes are not duplicable: + # + # Complex(1).duplicable? # => false + # Complex(1).dup # => TypeError: can't copy Complex + def duplicable? + false + end + end +end + +class Rational + begin + Rational(1).dup + rescue TypeError + + # Rationals are not duplicable: + # + # Rational(1).duplicable? # => false + # Rational(1).dup # => TypeError: can't copy Rational + def duplicable? + false + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/inclusion.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/inclusion.rb new file mode 100644 index 00000000..6064e92f --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/inclusion.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class Object + # Returns true if this object is included in the argument. Argument must be + # any object which responds to +#include?+. Usage: + # + # characters = ["Konata", "Kagami", "Tsukasa"] + # "Konata".in?(characters) # => true + # + # This will throw an +ArgumentError+ if the argument doesn't respond + # to +#include?+. + def in?(another_object) + another_object.include?(self) + rescue NoMethodError + raise ArgumentError.new("The parameter passed to #in? must respond to #include?") + end + + # Returns the receiver if it's included in the argument otherwise returns +nil+. + # Argument must be any object which responds to +#include?+. Usage: + # + # params[:bucket_type].presence_in %w( project calendar ) + # + # This will throw an +ArgumentError+ if the argument doesn't respond to +#include?+. + # + # @return [Object] + def presence_in(another_object) + in?(another_object) ? self : nil + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/instance_variables.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/instance_variables.rb new file mode 100644 index 00000000..12fdf840 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/instance_variables.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +class Object + # Returns a hash with string keys that maps instance variable names without "@" to their + # corresponding values. + # + # class C + # def initialize(x, y) + # @x, @y = x, y + # end + # end + # + # C.new(0, 1).instance_values # => {"x" => 0, "y" => 1} + def instance_values + Hash[instance_variables.map { |name| [name[1..-1], instance_variable_get(name)] }] + end + + # Returns an array of instance variable names as strings including "@". + # + # class C + # def initialize(x, y) + # @x, @y = x, y + # end + # end + # + # C.new(0, 1).instance_variable_names # => ["@y", "@x"] + def instance_variable_names + instance_variables.map(&:to_s) + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/json.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/json.rb new file mode 100644 index 00000000..f7c623fe --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/json.rb @@ -0,0 +1,227 @@ +# frozen_string_literal: true + +# Hack to load json gem first so we can overwrite its to_json. +require "json" +require "bigdecimal" +require "uri/generic" +require "pathname" +require "active_support/core_ext/big_decimal/conversions" # for #to_s +require "active_support/core_ext/hash/except" +require "active_support/core_ext/hash/slice" +require "active_support/core_ext/object/instance_variables" +require "time" +require "active_support/core_ext/time/conversions" +require "active_support/core_ext/date_time/conversions" +require "active_support/core_ext/date/conversions" + +# The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting +# their default behavior. That said, we need to define the basic to_json method in all of them, +# otherwise they will always use to_json gem implementation, which is backwards incompatible in +# several cases (for instance, the JSON implementation for Hash does not work) with inheritance +# and consequently classes as ActiveSupport::OrderedHash cannot be serialized to json. +# +# On the other hand, we should avoid conflict with ::JSON.{generate,dump}(obj). Unfortunately, the +# JSON gem's encoder relies on its own to_json implementation to encode objects. Since it always +# passes a ::JSON::State object as the only argument to to_json, we can detect that and forward the +# calls to the original to_json method. +# +# It should be noted that when using ::JSON.{generate,dump} directly, ActiveSupport's encoder is +# bypassed completely. This means that as_json won't be invoked and the JSON gem will simply +# ignore any options it does not natively understand. This also means that ::JSON.{generate,dump} +# should give exactly the same results with or without active support. + +module ActiveSupport + module ToJsonWithActiveSupportEncoder # :nodoc: + def to_json(options = nil) + if options.is_a?(::JSON::State) + # Called from JSON.{generate,dump}, forward it to JSON gem's to_json + super(options) + else + # to_json is being invoked directly, use ActiveSupport's encoder + ActiveSupport::JSON.encode(self, options) + end + end + end +end + +[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass, Enumerable].reverse_each do |klass| + klass.prepend(ActiveSupport::ToJsonWithActiveSupportEncoder) +end + +class Object + def as_json(options = nil) #:nodoc: + if respond_to?(:to_hash) + to_hash.as_json(options) + else + instance_values.as_json(options) + end + end +end + +class Struct #:nodoc: + def as_json(options = nil) + Hash[members.zip(values)].as_json(options) + end +end + +class TrueClass + def as_json(options = nil) #:nodoc: + self + end +end + +class FalseClass + def as_json(options = nil) #:nodoc: + self + end +end + +class NilClass + def as_json(options = nil) #:nodoc: + self + end +end + +class String + def as_json(options = nil) #:nodoc: + self + end +end + +class Symbol + def as_json(options = nil) #:nodoc: + to_s + end +end + +class Numeric + def as_json(options = nil) #:nodoc: + self + end +end + +class Float + # Encoding Infinity or NaN to JSON should return "null". The default returns + # "Infinity" or "NaN" which are not valid JSON. + def as_json(options = nil) #:nodoc: + finite? ? self : nil + end +end + +class BigDecimal + # A BigDecimal would be naturally represented as a JSON number. Most libraries, + # however, parse non-integer JSON numbers directly as floats. Clients using + # those libraries would get in general a wrong number and no way to recover + # other than manually inspecting the string with the JSON code itself. + # + # That's why a JSON string is returned. The JSON literal is not numeric, but + # if the other end knows by contract that the data is supposed to be a + # BigDecimal, it still has the chance to post-process the string and get the + # real value. + def as_json(options = nil) #:nodoc: + finite? ? to_s : nil + end +end + +class Regexp + def as_json(options = nil) #:nodoc: + to_s + end +end + +module Enumerable + def as_json(options = nil) #:nodoc: + to_a.as_json(options) + end +end + +class IO + def as_json(options = nil) #:nodoc: + to_s + end +end + +class Range + def as_json(options = nil) #:nodoc: + to_s + end +end + +class Array + def as_json(options = nil) #:nodoc: + map { |v| options ? v.as_json(options.dup) : v.as_json } + end +end + +class Hash + def as_json(options = nil) #:nodoc: + # create a subset of the hash by applying :only or :except + subset = if options + if attrs = options[:only] + slice(*Array(attrs)) + elsif attrs = options[:except] + except(*Array(attrs)) + else + self + end + else + self + end + + Hash[subset.map { |k, v| [k.to_s, options ? v.as_json(options.dup) : v.as_json] }] + end +end + +class Time + def as_json(options = nil) #:nodoc: + if ActiveSupport::JSON::Encoding.use_standard_json_time_format + xmlschema(ActiveSupport::JSON::Encoding.time_precision) + else + %(#{strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}) + end + end +end + +class Date + def as_json(options = nil) #:nodoc: + if ActiveSupport::JSON::Encoding.use_standard_json_time_format + strftime("%Y-%m-%d") + else + strftime("%Y/%m/%d") + end + end +end + +class DateTime + def as_json(options = nil) #:nodoc: + if ActiveSupport::JSON::Encoding.use_standard_json_time_format + xmlschema(ActiveSupport::JSON::Encoding.time_precision) + else + strftime("%Y/%m/%d %H:%M:%S %z") + end + end +end + +class URI::Generic #:nodoc: + def as_json(options = nil) + to_s + end +end + +class Pathname #:nodoc: + def as_json(options = nil) + to_s + end +end + +class Process::Status #:nodoc: + def as_json(options = nil) + { exitstatus: exitstatus, pid: pid } + end +end + +class Exception + def as_json(options = nil) + to_s + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/to_param.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/to_param.rb new file mode 100644 index 00000000..6d2bdd70 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/to_param.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/to_query" diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/to_query.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/to_query.rb new file mode 100644 index 00000000..bac6ff9c --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/to_query.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require "cgi" + +class Object + # Alias of to_s. + def to_param + to_s + end + + # Converts an object into a string suitable for use as a URL query string, + # using the given key as the param name. + def to_query(key) + "#{CGI.escape(key.to_param)}=#{CGI.escape(to_param.to_s)}" + end +end + +class NilClass + # Returns +self+. + def to_param + self + end +end + +class TrueClass + # Returns +self+. + def to_param + self + end +end + +class FalseClass + # Returns +self+. + def to_param + self + end +end + +class Array + # Calls to_param on all its elements and joins the result with + # slashes. This is used by url_for in Action Pack. + def to_param + collect(&:to_param).join "/" + end + + # Converts an array into a string suitable for use as a URL query string, + # using the given +key+ as the param name. + # + # ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding" + def to_query(key) + prefix = "#{key}[]" + + if empty? + nil.to_query(prefix) + else + collect { |value| value.to_query(prefix) }.join "&" + end + end +end + +class Hash + # Returns a string representation of the receiver suitable for use as a URL + # query string: + # + # {name: 'David', nationality: 'Danish'}.to_query + # # => "name=David&nationality=Danish" + # + # An optional namespace can be passed to enclose key names: + # + # {name: 'David', nationality: 'Danish'}.to_query('user') + # # => "user%5Bname%5D=David&user%5Bnationality%5D=Danish" + # + # The string pairs "key=value" that conform the query string + # are sorted lexicographically in ascending order. + # + # This method is also aliased as +to_param+. + def to_query(namespace = nil) + query = collect do |key, value| + unless (value.is_a?(Hash) || value.is_a?(Array)) && value.empty? + value.to_query(namespace ? "#{namespace}[#{key}]" : key) + end + end.compact + + query.sort! unless namespace.to_s.include?("[]") + query.join("&") + end + + alias_method :to_param, :to_query +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/try.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/try.rb new file mode 100644 index 00000000..c8746916 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/try.rb @@ -0,0 +1,148 @@ +# frozen_string_literal: true + +require "delegate" + +module ActiveSupport + module Tryable #:nodoc: + def try(*a, &b) + try!(*a, &b) if a.empty? || respond_to?(a.first) + end + + def try!(*a, &b) + if a.empty? && block_given? + if b.arity == 0 + instance_eval(&b) + else + yield self + end + else + public_send(*a, &b) + end + end + end +end + +class Object + include ActiveSupport::Tryable + + ## + # :method: try + # + # :call-seq: + # try(*a, &b) + # + # Invokes the public method whose name goes as first argument just like + # +public_send+ does, except that if the receiver does not respond to it the + # call returns +nil+ rather than raising an exception. + # + # This method is defined to be able to write + # + # @person.try(:name) + # + # instead of + # + # @person.name if @person + # + # +try+ calls can be chained: + # + # @person.try(:spouse).try(:name) + # + # instead of + # + # @person.spouse.name if @person && @person.spouse + # + # +try+ will also return +nil+ if the receiver does not respond to the method: + # + # @person.try(:non_existing_method) # => nil + # + # instead of + # + # @person.non_existing_method if @person.respond_to?(:non_existing_method) # => nil + # + # +try+ returns +nil+ when called on +nil+ regardless of whether it responds + # to the method: + # + # nil.try(:to_i) # => nil, rather than 0 + # + # Arguments and blocks are forwarded to the method if invoked: + # + # @posts.try(:each_slice, 2) do |a, b| + # ... + # end + # + # The number of arguments in the signature must match. If the object responds + # to the method the call is attempted and +ArgumentError+ is still raised + # in case of argument mismatch. + # + # If +try+ is called without arguments it yields the receiver to a given + # block unless it is +nil+: + # + # @person.try do |p| + # ... + # end + # + # You can also call try with a block without accepting an argument, and the block + # will be instance_eval'ed instead: + # + # @person.try { upcase.truncate(50) } + # + # Please also note that +try+ is defined on +Object+. Therefore, it won't work + # with instances of classes that do not have +Object+ among their ancestors, + # like direct subclasses of +BasicObject+. + + ## + # :method: try! + # + # :call-seq: + # try!(*a, &b) + # + # Same as #try, but raises a +NoMethodError+ exception if the receiver is + # not +nil+ and does not implement the tried method. + # + # "a".try!(:upcase) # => "A" + # nil.try!(:upcase) # => nil + # 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Integer +end + +class Delegator + include ActiveSupport::Tryable + + ## + # :method: try + # + # :call-seq: + # try(a*, &b) + # + # See Object#try + + ## + # :method: try! + # + # :call-seq: + # try!(a*, &b) + # + # See Object#try! +end + +class NilClass + # Calling +try+ on +nil+ always returns +nil+. + # It becomes especially helpful when navigating through associations that may return +nil+. + # + # nil.try(:name) # => nil + # + # Without +try+ + # @person && @person.children.any? && @person.children.first.name + # + # With +try+ + # @person.try(:children).try(:first).try(:name) + def try(*args) + nil + end + + # Calling +try!+ on +nil+ always returns +nil+. + # + # nil.try!(:name) # => nil + def try!(*args) + nil + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/with_options.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/with_options.rb new file mode 100644 index 00000000..2838fd76 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/object/with_options.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require "active_support/option_merger" + +class Object + # An elegant way to factor duplication out of options passed to a series of + # method calls. Each method called in the block, with the block variable as + # the receiver, will have its options merged with the default +options+ hash + # provided. Each method called on the block variable must take an options + # hash as its final argument. + # + # Without with_options, this code contains duplication: + # + # class Account < ActiveRecord::Base + # has_many :customers, dependent: :destroy + # has_many :products, dependent: :destroy + # has_many :invoices, dependent: :destroy + # has_many :expenses, dependent: :destroy + # end + # + # Using with_options, we can remove the duplication: + # + # class Account < ActiveRecord::Base + # with_options dependent: :destroy do |assoc| + # assoc.has_many :customers + # assoc.has_many :products + # assoc.has_many :invoices + # assoc.has_many :expenses + # end + # end + # + # It can also be used with an explicit receiver: + # + # I18n.with_options locale: user.locale, scope: 'newsletter' do |i18n| + # subject i18n.t :subject + # body i18n.t :body, user_name: user.name + # end + # + # When you don't pass an explicit receiver, it executes the whole block + # in merging options context: + # + # class Account < ActiveRecord::Base + # with_options dependent: :destroy do + # has_many :customers + # has_many :products + # has_many :invoices + # has_many :expenses + # end + # end + # + # with_options can also be nested since the call is forwarded to its receiver. + # + # NOTE: Each nesting level will merge inherited defaults in addition to their own. + # + # class Post < ActiveRecord::Base + # with_options if: :persisted?, length: { minimum: 50 } do + # validates :content, if: -> { content.present? } + # end + # end + # + # The code is equivalent to: + # + # validates :content, length: { minimum: 50 }, if: -> { content.present? } + # + # Hence the inherited default for +if+ key is ignored. + # + # NOTE: You cannot call class methods implicitly inside of with_options. + # You can access these methods using the class name instead: + # + # class Phone < ActiveRecord::Base + # enum phone_number_type: [home: 0, office: 1, mobile: 2] + # + # with_options presence: true do + # validates :phone_number_type, inclusion: { in: Phone.phone_number_types.keys } + # end + # end + # + def with_options(options, &block) + option_merger = ActiveSupport::OptionMerger.new(self, options) + block.arity.zero? ? option_merger.instance_eval(&block) : block.call(option_merger) + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/range.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/range.rb new file mode 100644 index 00000000..78814fd1 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/range.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "active_support/core_ext/range/conversions" +require "active_support/core_ext/range/compare_range" +require "active_support/core_ext/range/include_time_with_zone" +require "active_support/core_ext/range/overlaps" +require "active_support/core_ext/range/each" diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/range/compare_range.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/range/compare_range.rb new file mode 100644 index 00000000..704041f6 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/range/compare_range.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module ActiveSupport + module CompareWithRange #:nodoc: + # Extends the default Range#=== to support range comparisons. + # (1..5) === (1..5) # => true + # (1..5) === (2..3) # => true + # (1..5) === (2..6) # => false + # + # The native Range#=== behavior is untouched. + # ('a'..'f') === ('c') # => true + # (5..9) === (11) # => false + def ===(value) + if value.is_a?(::Range) + # 1...10 includes 1..9 but it does not include 1..10. + operator = exclude_end? && !value.exclude_end? ? :< : :<= + super(value.first) && value.last.send(operator, last) + else + super + end + end + + # Extends the default Range#include? to support range comparisons. + # (1..5).include?(1..5) # => true + # (1..5).include?(2..3) # => true + # (1..5).include?(2..6) # => false + # + # The native Range#include? behavior is untouched. + # ('a'..'f').include?('c') # => true + # (5..9).include?(11) # => false + def include?(value) + if value.is_a?(::Range) + # 1...10 includes 1..9 but it does not include 1..10. + operator = exclude_end? && !value.exclude_end? ? :< : :<= + super(value.first) && value.last.send(operator, last) + else + super + end + end + + # Extends the default Range#cover? to support range comparisons. + # (1..5).cover?(1..5) # => true + # (1..5).cover?(2..3) # => true + # (1..5).cover?(2..6) # => false + # + # The native Range#cover? behavior is untouched. + # ('a'..'f').cover?('c') # => true + # (5..9).cover?(11) # => false + def cover?(value) + if value.is_a?(::Range) + # 1...10 covers 1..9 but it does not cover 1..10. + operator = exclude_end? && !value.exclude_end? ? :< : :<= + super(value.first) && value.last.send(operator, last) + else + super + end + end + end +end + +Range.prepend(ActiveSupport::CompareWithRange) diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/range/conversions.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/range/conversions.rb new file mode 100644 index 00000000..8832fbcb --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/range/conversions.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module ActiveSupport::RangeWithFormat + RANGE_FORMATS = { + db: -> (start, stop) do + case start + when String then "BETWEEN '#{start}' AND '#{stop}'" + else + "BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'" + end + end + } + + # Convert range to a formatted string. See RANGE_FORMATS for predefined formats. + # + # range = (1..100) # => 1..100 + # + # range.to_s # => "1..100" + # range.to_s(:db) # => "BETWEEN '1' AND '100'" + # + # == Adding your own range formats to to_s + # You can add your own formats to the Range::RANGE_FORMATS hash. + # Use the format name as the hash key and a Proc instance. + # + # # config/initializers/range_formats.rb + # Range::RANGE_FORMATS[:short] = ->(start, stop) { "Between #{start.to_s(:db)} and #{stop.to_s(:db)}" } + def to_s(format = :default) + if formatter = RANGE_FORMATS[format] + formatter.call(first, last) + else + super() + end + end + + alias_method :to_default_s, :to_s + alias_method :to_formatted_s, :to_s +end + +Range.prepend(ActiveSupport::RangeWithFormat) diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/range/each.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/range/each.rb new file mode 100644 index 00000000..2f22cd0e --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/range/each.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require "active_support/time_with_zone" + +module ActiveSupport + module EachTimeWithZone #:nodoc: + def each(&block) + ensure_iteration_allowed + super + end + + def step(n = 1, &block) + ensure_iteration_allowed + super + end + + private + + def ensure_iteration_allowed + raise TypeError, "can't iterate from #{first.class}" if first.is_a?(TimeWithZone) + end + end +end + +Range.prepend(ActiveSupport::EachTimeWithZone) diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/range/include_range.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/range/include_range.rb new file mode 100644 index 00000000..4812e274 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/range/include_range.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require "active_support/core_ext/range/compare_range" diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/range/include_time_with_zone.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/range/include_time_with_zone.rb new file mode 100644 index 00000000..5f80acf6 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/range/include_time_with_zone.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require "active_support/time_with_zone" + +module ActiveSupport + module IncludeTimeWithZone #:nodoc: + # Extends the default Range#include? to support ActiveSupport::TimeWithZone. + # + # (1.hour.ago..1.hour.from_now).include?(Time.current) # => true + # + def include?(value) + if first.is_a?(TimeWithZone) + cover?(value) + elsif last.is_a?(TimeWithZone) + cover?(value) + else + super + end + end + end +end + +Range.prepend(ActiveSupport::IncludeTimeWithZone) diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/range/overlaps.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/range/overlaps.rb new file mode 100644 index 00000000..f753607f --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/range/overlaps.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class Range + # Compare two ranges and see if they overlap each other + # (1..5).overlaps?(4..6) # => true + # (1..5).overlaps?(7..9) # => false + def overlaps?(other) + cover?(other.first) || other.cover?(first) + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/regexp.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/regexp.rb new file mode 100644 index 00000000..efbd708a --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/regexp.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class Regexp #:nodoc: + def multiline? + options & MULTILINE == MULTILINE + end + + def match?(string, pos = 0) + !!match(string, pos) + end unless //.respond_to?(:match?) +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/securerandom.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/securerandom.rb new file mode 100644 index 00000000..b4a491f5 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/securerandom.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require "securerandom" + +module SecureRandom + BASE58_ALPHABET = ("0".."9").to_a + ("A".."Z").to_a + ("a".."z").to_a - ["0", "O", "I", "l"] + # SecureRandom.base58 generates a random base58 string. + # + # The argument _n_ specifies the length, of the random string to be generated. + # + # If _n_ is not specified or is +nil+, 16 is assumed. It may be larger in the future. + # + # The result may contain alphanumeric characters except 0, O, I and l + # + # p SecureRandom.base58 # => "4kUgL2pdQMSCQtjE" + # p SecureRandom.base58(24) # => "77TMHrHJFvFDwodq8w7Ev2m7" + # + def self.base58(n = 16) + SecureRandom.random_bytes(n).unpack("C*").map do |byte| + idx = byte % 64 + idx = SecureRandom.random_number(58) if idx >= 58 + BASE58_ALPHABET[idx] + end.join + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string.rb new file mode 100644 index 00000000..757d15c5 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/conversions" +require "active_support/core_ext/string/filters" +require "active_support/core_ext/string/multibyte" +require "active_support/core_ext/string/starts_ends_with" +require "active_support/core_ext/string/inflections" +require "active_support/core_ext/string/access" +require "active_support/core_ext/string/behavior" +require "active_support/core_ext/string/output_safety" +require "active_support/core_ext/string/exclude" +require "active_support/core_ext/string/strip" +require "active_support/core_ext/string/inquiry" +require "active_support/core_ext/string/indent" +require "active_support/core_ext/string/zones" diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/access.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/access.rb new file mode 100644 index 00000000..58591bba --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/access.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +class String + # If you pass a single integer, returns a substring of one character at that + # position. The first character of the string is at position 0, the next at + # position 1, and so on. If a range is supplied, a substring containing + # characters at offsets given by the range is returned. In both cases, if an + # offset is negative, it is counted from the end of the string. Returns +nil+ + # if the initial offset falls outside the string. Returns an empty string if + # the beginning of the range is greater than the end of the string. + # + # str = "hello" + # str.at(0) # => "h" + # str.at(1..3) # => "ell" + # str.at(-2) # => "l" + # str.at(-2..-1) # => "lo" + # str.at(5) # => nil + # str.at(5..-1) # => "" + # + # If a Regexp is given, the matching portion of the string is returned. + # If a String is given, that given string is returned if it occurs in + # the string. In both cases, +nil+ is returned if there is no match. + # + # str = "hello" + # str.at(/lo/) # => "lo" + # str.at(/ol/) # => nil + # str.at("lo") # => "lo" + # str.at("ol") # => nil + def at(position) + self[position] + end + + # Returns a substring from the given position to the end of the string. + # If the position is negative, it is counted from the end of the string. + # + # str = "hello" + # str.from(0) # => "hello" + # str.from(3) # => "lo" + # str.from(-2) # => "lo" + # + # You can mix it with +to+ method and do fun things like: + # + # str = "hello" + # str.from(0).to(-1) # => "hello" + # str.from(1).to(-2) # => "ell" + def from(position) + self[position..-1] + end + + # Returns a substring from the beginning of the string to the given position. + # If the position is negative, it is counted from the end of the string. + # + # str = "hello" + # str.to(0) # => "h" + # str.to(3) # => "hell" + # str.to(-2) # => "hell" + # + # You can mix it with +from+ method and do fun things like: + # + # str = "hello" + # str.from(0).to(-1) # => "hello" + # str.from(1).to(-2) # => "ell" + def to(position) + self[0..position] + end + + # Returns the first character. If a limit is supplied, returns a substring + # from the beginning of the string until it reaches the limit value. If the + # given limit is greater than or equal to the string length, returns a copy of self. + # + # str = "hello" + # str.first # => "h" + # str.first(1) # => "h" + # str.first(2) # => "he" + # str.first(0) # => "" + # str.first(6) # => "hello" + def first(limit = 1) + if limit == 0 + "" + elsif limit >= size + dup + else + to(limit - 1) + end + end + + # Returns the last character of the string. If a limit is supplied, returns a substring + # from the end of the string until it reaches the limit value (counting backwards). If + # the given limit is greater than or equal to the string length, returns a copy of self. + # + # str = "hello" + # str.last # => "o" + # str.last(1) # => "o" + # str.last(2) # => "lo" + # str.last(0) # => "" + # str.last(6) # => "hello" + def last(limit = 1) + if limit == 0 + "" + elsif limit >= size + dup + else + from(-limit) + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/behavior.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/behavior.rb new file mode 100644 index 00000000..35a5aa78 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/behavior.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class String + # Enables more predictable duck-typing on String-like classes. See Object#acts_like?. + def acts_like_string? + true + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/conversions.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/conversions.rb new file mode 100644 index 00000000..29a88b07 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/conversions.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require "date" +require "active_support/core_ext/time/calculations" + +class String + # Converts a string to a Time value. + # The +form+ can be either :utc or :local (default :local). + # + # The time is parsed using Time.parse method. + # If +form+ is :local, then the time is in the system timezone. + # If the date part is missing then the current date is used and if + # the time part is missing then it is assumed to be 00:00:00. + # + # "13-12-2012".to_time # => 2012-12-13 00:00:00 +0100 + # "06:12".to_time # => 2012-12-13 06:12:00 +0100 + # "2012-12-13 06:12".to_time # => 2012-12-13 06:12:00 +0100 + # "2012-12-13T06:12".to_time # => 2012-12-13 06:12:00 +0100 + # "2012-12-13T06:12".to_time(:utc) # => 2012-12-13 06:12:00 UTC + # "12/13/2012".to_time # => ArgumentError: argument out of range + def to_time(form = :local) + parts = Date._parse(self, false) + used_keys = %i(year mon mday hour min sec sec_fraction offset) + return if (parts.keys & used_keys).empty? + + now = Time.now + time = Time.new( + parts.fetch(:year, now.year), + parts.fetch(:mon, now.month), + parts.fetch(:mday, now.day), + parts.fetch(:hour, 0), + parts.fetch(:min, 0), + parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0), + parts.fetch(:offset, form == :utc ? 0 : nil) + ) + + form == :utc ? time.utc : time.to_time + end + + # Converts a string to a Date value. + # + # "1-1-2012".to_date # => Sun, 01 Jan 2012 + # "01/01/2012".to_date # => Sun, 01 Jan 2012 + # "2012-12-13".to_date # => Thu, 13 Dec 2012 + # "12/13/2012".to_date # => ArgumentError: invalid date + def to_date + ::Date.parse(self, false) unless blank? + end + + # Converts a string to a DateTime value. + # + # "1-1-2012".to_datetime # => Sun, 01 Jan 2012 00:00:00 +0000 + # "01/01/2012 23:59:59".to_datetime # => Sun, 01 Jan 2012 23:59:59 +0000 + # "2012-12-13 12:50".to_datetime # => Thu, 13 Dec 2012 12:50:00 +0000 + # "12/13/2012".to_datetime # => ArgumentError: invalid date + def to_datetime + ::DateTime.parse(self, false) unless blank? + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/exclude.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/exclude.rb new file mode 100644 index 00000000..8e462689 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/exclude.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class String + # The inverse of String#include?. Returns true if the string + # does not include the other string. + # + # "hello".exclude? "lo" # => false + # "hello".exclude? "ol" # => true + # "hello".exclude? ?h # => false + def exclude?(string) + !include?(string) + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/filters.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/filters.rb new file mode 100644 index 00000000..66e721ee --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/filters.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +class String + # Returns the string, first removing all whitespace on both ends of + # the string, and then changing remaining consecutive whitespace + # groups into one space each. + # + # Note that it handles both ASCII and Unicode whitespace. + # + # %{ Multi-line + # string }.squish # => "Multi-line string" + # " foo bar \n \t boo".squish # => "foo bar boo" + def squish + dup.squish! + end + + # Performs a destructive squish. See String#squish. + # str = " foo bar \n \t boo" + # str.squish! # => "foo bar boo" + # str # => "foo bar boo" + def squish! + gsub!(/[[:space:]]+/, " ") + strip! + self + end + + # Returns a new string with all occurrences of the patterns removed. + # str = "foo bar test" + # str.remove(" test") # => "foo bar" + # str.remove(" test", /bar/) # => "foo " + # str # => "foo bar test" + def remove(*patterns) + dup.remove!(*patterns) + end + + # Alters the string by removing all occurrences of the patterns. + # str = "foo bar test" + # str.remove!(" test", /bar/) # => "foo " + # str # => "foo " + def remove!(*patterns) + patterns.each do |pattern| + gsub! pattern, "" + end + + self + end + + # Truncates a given +text+ after a given length if +text+ is longer than length: + # + # 'Once upon a time in a world far far away'.truncate(27) + # # => "Once upon a time in a wo..." + # + # Pass a string or regexp :separator to truncate +text+ at a natural break: + # + # 'Once upon a time in a world far far away'.truncate(27, separator: ' ') + # # => "Once upon a time in a..." + # + # 'Once upon a time in a world far far away'.truncate(27, separator: /\s/) + # # => "Once upon a time in a..." + # + # The last characters will be replaced with the :omission string (defaults to "...") + # for a total length not exceeding length: + # + # 'And they found that many people were sleeping better.'.truncate(25, omission: '... (continued)') + # # => "And they f... (continued)" + def truncate(truncate_at, options = {}) + return dup unless length > truncate_at + + omission = options[:omission] || "..." + length_with_room_for_omission = truncate_at - omission.length + stop = \ + if options[:separator] + rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission + else + length_with_room_for_omission + end + + "#{self[0, stop]}#{omission}" + end + + # Truncates a given +text+ after a given number of words (words_count): + # + # 'Once upon a time in a world far far away'.truncate_words(4) + # # => "Once upon a time..." + # + # Pass a string or regexp :separator to specify a different separator of words: + # + # 'Once
    upon
    a
    time
    in
    a
    world'.truncate_words(5, separator: '
    ') + # # => "Once
    upon
    a
    time
    in..." + # + # The last characters will be replaced with the :omission string (defaults to "..."): + # + # 'And they found that many people were sleeping better.'.truncate_words(5, omission: '... (continued)') + # # => "And they found that many... (continued)" + def truncate_words(words_count, options = {}) + sep = options[:separator] || /\s+/ + sep = Regexp.escape(sep.to_s) unless Regexp === sep + if self =~ /\A((?>.+?#{sep}){#{words_count - 1}}.+?)#{sep}.*/m + $1 + (options[:omission] || "...") + else + dup + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/indent.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/indent.rb new file mode 100644 index 00000000..af9d1814 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/indent.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +class String + # Same as +indent+, except it indents the receiver in-place. + # + # Returns the indented string, or +nil+ if there was nothing to indent. + def indent!(amount, indent_string = nil, indent_empty_lines = false) + indent_string = indent_string || self[/^[ \t]/] || " " + re = indent_empty_lines ? /^/ : /^(?!$)/ + gsub!(re, indent_string * amount) + end + + # Indents the lines in the receiver: + # + # < + # def some_method + # some_code + # end + # + # The second argument, +indent_string+, specifies which indent string to + # use. The default is +nil+, which tells the method to make a guess by + # peeking at the first indented line, and fallback to a space if there is + # none. + # + # " foo".indent(2) # => " foo" + # "foo\n\t\tbar".indent(2) # => "\t\tfoo\n\t\t\t\tbar" + # "foo".indent(2, "\t") # => "\t\tfoo" + # + # While +indent_string+ is typically one space or tab, it may be any string. + # + # The third argument, +indent_empty_lines+, is a flag that says whether + # empty lines should be indented. Default is false. + # + # "foo\n\nbar".indent(2) # => " foo\n\n bar" + # "foo\n\nbar".indent(2, nil, true) # => " foo\n \n bar" + # + def indent(amount, indent_string = nil, indent_empty_lines = false) + dup.tap { |_| _.indent!(amount, indent_string, indent_empty_lines) } + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/inflections.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/inflections.rb new file mode 100644 index 00000000..8af30173 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/inflections.rb @@ -0,0 +1,254 @@ +# frozen_string_literal: true + +require "active_support/inflector/methods" +require "active_support/inflector/transliterate" + +# String inflections define new methods on the String class to transform names for different purposes. +# For instance, you can figure out the name of a table from the name of a class. +# +# 'ScaleScore'.tableize # => "scale_scores" +# +class String + # Returns the plural form of the word in the string. + # + # If the optional parameter +count+ is specified, + # the singular form will be returned if count == 1. + # For any other value of +count+ the plural will be returned. + # + # If the optional parameter +locale+ is specified, + # the word will be pluralized as a word of that language. + # By default, this parameter is set to :en. + # You must define your own inflection rules for languages other than English. + # + # 'post'.pluralize # => "posts" + # 'octopus'.pluralize # => "octopi" + # 'sheep'.pluralize # => "sheep" + # 'words'.pluralize # => "words" + # 'the blue mailman'.pluralize # => "the blue mailmen" + # 'CamelOctopus'.pluralize # => "CamelOctopi" + # 'apple'.pluralize(1) # => "apple" + # 'apple'.pluralize(2) # => "apples" + # 'ley'.pluralize(:es) # => "leyes" + # 'ley'.pluralize(1, :es) # => "ley" + def pluralize(count = nil, locale = :en) + locale = count if count.is_a?(Symbol) + if count == 1 + dup + else + ActiveSupport::Inflector.pluralize(self, locale) + end + end + + # The reverse of +pluralize+, returns the singular form of a word in a string. + # + # If the optional parameter +locale+ is specified, + # the word will be singularized as a word of that language. + # By default, this parameter is set to :en. + # You must define your own inflection rules for languages other than English. + # + # 'posts'.singularize # => "post" + # 'octopi'.singularize # => "octopus" + # 'sheep'.singularize # => "sheep" + # 'word'.singularize # => "word" + # 'the blue mailmen'.singularize # => "the blue mailman" + # 'CamelOctopi'.singularize # => "CamelOctopus" + # 'leyes'.singularize(:es) # => "ley" + def singularize(locale = :en) + ActiveSupport::Inflector.singularize(self, locale) + end + + # +constantize+ tries to find a declared constant with the name specified + # in the string. It raises a NameError when the name is not in CamelCase + # or is not initialized. See ActiveSupport::Inflector.constantize + # + # 'Module'.constantize # => Module + # 'Class'.constantize # => Class + # 'blargle'.constantize # => NameError: wrong constant name blargle + def constantize + ActiveSupport::Inflector.constantize(self) + end + + # +safe_constantize+ tries to find a declared constant with the name specified + # in the string. It returns +nil+ when the name is not in CamelCase + # or is not initialized. See ActiveSupport::Inflector.safe_constantize + # + # 'Module'.safe_constantize # => Module + # 'Class'.safe_constantize # => Class + # 'blargle'.safe_constantize # => nil + def safe_constantize + ActiveSupport::Inflector.safe_constantize(self) + end + + # By default, +camelize+ converts strings to UpperCamelCase. If the argument to camelize + # is set to :lower then camelize produces lowerCamelCase. + # + # +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces. + # + # 'active_record'.camelize # => "ActiveRecord" + # 'active_record'.camelize(:lower) # => "activeRecord" + # 'active_record/errors'.camelize # => "ActiveRecord::Errors" + # 'active_record/errors'.camelize(:lower) # => "activeRecord::Errors" + def camelize(first_letter = :upper) + case first_letter + when :upper + ActiveSupport::Inflector.camelize(self, true) + when :lower + ActiveSupport::Inflector.camelize(self, false) + else + raise ArgumentError, "Invalid option, use either :upper or :lower." + end + end + alias_method :camelcase, :camelize + + # Capitalizes all the words and replaces some characters in the string to create + # a nicer looking title. +titleize+ is meant for creating pretty output. It is not + # used in the Rails internals. + # + # The trailing '_id','Id'.. can be kept and capitalized by setting the + # optional parameter +keep_id_suffix+ to true. + # By default, this parameter is false. + # + # +titleize+ is also aliased as +titlecase+. + # + # 'man from the boondocks'.titleize # => "Man From The Boondocks" + # 'x-men: the last stand'.titleize # => "X Men: The Last Stand" + # 'string_ending_with_id'.titleize(keep_id_suffix: true) # => "String Ending With Id" + def titleize(keep_id_suffix: false) + ActiveSupport::Inflector.titleize(self, keep_id_suffix: keep_id_suffix) + end + alias_method :titlecase, :titleize + + # The reverse of +camelize+. Makes an underscored, lowercase form from the expression in the string. + # + # +underscore+ will also change '::' to '/' to convert namespaces to paths. + # + # 'ActiveModel'.underscore # => "active_model" + # 'ActiveModel::Errors'.underscore # => "active_model/errors" + def underscore + ActiveSupport::Inflector.underscore(self) + end + + # Replaces underscores with dashes in the string. + # + # 'puni_puni'.dasherize # => "puni-puni" + def dasherize + ActiveSupport::Inflector.dasherize(self) + end + + # Removes the module part from the constant expression in the string. + # + # 'ActiveSupport::Inflector::Inflections'.demodulize # => "Inflections" + # 'Inflections'.demodulize # => "Inflections" + # '::Inflections'.demodulize # => "Inflections" + # ''.demodulize # => '' + # + # See also +deconstantize+. + def demodulize + ActiveSupport::Inflector.demodulize(self) + end + + # Removes the rightmost segment from the constant expression in the string. + # + # 'Net::HTTP'.deconstantize # => "Net" + # '::Net::HTTP'.deconstantize # => "::Net" + # 'String'.deconstantize # => "" + # '::String'.deconstantize # => "" + # ''.deconstantize # => "" + # + # See also +demodulize+. + def deconstantize + ActiveSupport::Inflector.deconstantize(self) + end + + # Replaces special characters in a string so that it may be used as part of a 'pretty' URL. + # + # class Person + # def to_param + # "#{id}-#{name.parameterize}" + # end + # end + # + # @person = Person.find(1) + # # => # + # + # <%= link_to(@person.name, person_path) %> + # # => Donald E. Knuth + # + # To preserve the case of the characters in a string, use the +preserve_case+ argument. + # + # class Person + # def to_param + # "#{id}-#{name.parameterize(preserve_case: true)}" + # end + # end + # + # @person = Person.find(1) + # # => # + # + # <%= link_to(@person.name, person_path) %> + # # => Donald E. Knuth + def parameterize(separator: "-", preserve_case: false) + ActiveSupport::Inflector.parameterize(self, separator: separator, preserve_case: preserve_case) + end + + # Creates the name of a table like Rails does for models to table names. This method + # uses the +pluralize+ method on the last word in the string. + # + # 'RawScaledScorer'.tableize # => "raw_scaled_scorers" + # 'ham_and_egg'.tableize # => "ham_and_eggs" + # 'fancyCategory'.tableize # => "fancy_categories" + def tableize + ActiveSupport::Inflector.tableize(self) + end + + # Creates a class name from a plural table name like Rails does for table names to models. + # Note that this returns a string and not a class. (To convert to an actual class + # follow +classify+ with +constantize+.) + # + # 'ham_and_eggs'.classify # => "HamAndEgg" + # 'posts'.classify # => "Post" + def classify + ActiveSupport::Inflector.classify(self) + end + + # Capitalizes the first word, turns underscores into spaces, and (by default)strips a + # trailing '_id' if present. + # Like +titleize+, this is meant for creating pretty output. + # + # The capitalization of the first word can be turned off by setting the + # optional parameter +capitalize+ to false. + # By default, this parameter is true. + # + # The trailing '_id' can be kept and capitalized by setting the + # optional parameter +keep_id_suffix+ to true. + # By default, this parameter is false. + # + # 'employee_salary'.humanize # => "Employee salary" + # 'author_id'.humanize # => "Author" + # 'author_id'.humanize(capitalize: false) # => "author" + # '_id'.humanize # => "Id" + # 'author_id'.humanize(keep_id_suffix: true) # => "Author Id" + def humanize(capitalize: true, keep_id_suffix: false) + ActiveSupport::Inflector.humanize(self, capitalize: capitalize, keep_id_suffix: keep_id_suffix) + end + + # Converts just the first character to uppercase. + # + # 'what a Lovely Day'.upcase_first # => "What a Lovely Day" + # 'w'.upcase_first # => "W" + # ''.upcase_first # => "" + def upcase_first + ActiveSupport::Inflector.upcase_first(self) + end + + # Creates a foreign key name from a class name. + # +separate_class_name_and_id_with_underscore+ sets whether + # the method should put '_' between the name and 'id'. + # + # 'Message'.foreign_key # => "message_id" + # 'Message'.foreign_key(false) # => "messageid" + # 'Admin::Post'.foreign_key # => "post_id" + def foreign_key(separate_class_name_and_id_with_underscore = true) + ActiveSupport::Inflector.foreign_key(self, separate_class_name_and_id_with_underscore) + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/inquiry.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/inquiry.rb new file mode 100644 index 00000000..a796d5fb --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/inquiry.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require "active_support/string_inquirer" + +class String + # Wraps the current string in the ActiveSupport::StringInquirer class, + # which gives you a prettier way to test for equality. + # + # env = 'production'.inquiry + # env.production? # => true + # env.development? # => false + def inquiry + ActiveSupport::StringInquirer.new(self) + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/multibyte.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/multibyte.rb new file mode 100644 index 00000000..07c0d163 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/multibyte.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require "active_support/multibyte" + +class String + # == Multibyte proxy + # + # +mb_chars+ is a multibyte safe proxy for string methods. + # + # It creates and returns an instance of the ActiveSupport::Multibyte::Chars class which + # encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy + # class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsulated string. + # + # >> "lj".upcase + # => "lj" + # >> "lj".mb_chars.upcase.to_s + # => "LJ" + # + # NOTE: An above example is useful for pre Ruby 2.4. Ruby 2.4 supports Unicode case mappings. + # + # == Method chaining + # + # All the methods on the Chars proxy which normally return a string will return a Chars object. This allows + # method chaining on the result of any of these methods. + # + # name.mb_chars.reverse.length # => 12 + # + # == Interoperability and configuration + # + # The Chars object tries to be as interchangeable with String objects as possible: sorting and comparing between + # String and Char work like expected. The bang! methods change the internal string representation in the Chars + # object. Interoperability problems can be resolved easily with a +to_s+ call. + # + # For more information about the methods defined on the Chars proxy see ActiveSupport::Multibyte::Chars. For + # information about how to change the default Multibyte behavior see ActiveSupport::Multibyte. + def mb_chars + ActiveSupport::Multibyte.proxy_class.new(self) + end + + # Returns +true+ if string has utf_8 encoding. + # + # utf_8_str = "some string".encode "UTF-8" + # iso_str = "some string".encode "ISO-8859-1" + # + # utf_8_str.is_utf8? # => true + # iso_str.is_utf8? # => false + def is_utf8? + case encoding + when Encoding::UTF_8 + valid_encoding? + when Encoding::ASCII_8BIT, Encoding::US_ASCII + dup.force_encoding(Encoding::UTF_8).valid_encoding? + else + false + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/output_safety.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/output_safety.rb new file mode 100644 index 00000000..f3bdc297 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/output_safety.rb @@ -0,0 +1,258 @@ +# frozen_string_literal: true + +require "erb" +require "active_support/core_ext/kernel/singleton_class" +require "active_support/core_ext/module/redefine_method" +require "active_support/multibyte/unicode" + +class ERB + module Util + HTML_ESCAPE = { "&" => "&", ">" => ">", "<" => "<", '"' => """, "'" => "'" } + JSON_ESCAPE = { "&" => '\u0026', ">" => '\u003e', "<" => '\u003c', "\u2028" => '\u2028', "\u2029" => '\u2029' } + HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+)|(#[xX][\dA-Fa-f]+));)/ + JSON_ESCAPE_REGEXP = /[\u2028\u2029&><]/u + + # A utility method for escaping HTML tag characters. + # This method is also aliased as h. + # + # puts html_escape('is a > 0 & a < 10?') + # # => is a > 0 & a < 10? + def html_escape(s) + unwrapped_html_escape(s).html_safe + end + + silence_redefinition_of_method :h + alias h html_escape + + module_function :h + + singleton_class.silence_redefinition_of_method :html_escape + module_function :html_escape + + # HTML escapes strings but doesn't wrap them with an ActiveSupport::SafeBuffer. + # This method is not for public consumption! Seriously! + def unwrapped_html_escape(s) # :nodoc: + s = s.to_s + if s.html_safe? + s + else + CGI.escapeHTML(ActiveSupport::Multibyte::Unicode.tidy_bytes(s)) + end + end + module_function :unwrapped_html_escape + + # A utility method for escaping HTML without affecting existing escaped entities. + # + # html_escape_once('1 < 2 & 3') + # # => "1 < 2 & 3" + # + # html_escape_once('<< Accept & Checkout') + # # => "<< Accept & Checkout" + def html_escape_once(s) + result = ActiveSupport::Multibyte::Unicode.tidy_bytes(s.to_s).gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE) + s.html_safe? ? result.html_safe : result + end + + module_function :html_escape_once + + # A utility method for escaping HTML entities in JSON strings. Specifically, the + # &, > and < characters are replaced with their equivalent unicode escaped form - + # \u0026, \u003e, and \u003c. The Unicode sequences \u2028 and \u2029 are also + # escaped as they are treated as newline characters in some JavaScript engines. + # These sequences have identical meaning as the original characters inside the + # context of a JSON string, so assuming the input is a valid and well-formed + # JSON value, the output will have equivalent meaning when parsed: + # + # json = JSON.generate({ name: ""}) + # # => "{\"name\":\"\"}" + # + # json_escape(json) + # # => "{\"name\":\"\\u003C/script\\u003E\\u003Cscript\\u003Ealert('PWNED!!!')\\u003C/script\\u003E\"}" + # + # JSON.parse(json) == JSON.parse(json_escape(json)) + # # => true + # + # The intended use case for this method is to escape JSON strings before including + # them inside a script tag to avoid XSS vulnerability: + # + # + # + # It is necessary to +raw+ the result of +json_escape+, so that quotation marks + # don't get converted to " entities. +json_escape+ doesn't + # automatically flag the result as HTML safe, since the raw value is unsafe to + # use inside HTML attributes. + # + # If your JSON is being used downstream for insertion into the DOM, be aware of + # whether or not it is being inserted via +html()+. Most jQuery plugins do this. + # If that is the case, be sure to +html_escape+ or +sanitize+ any user-generated + # content returned by your JSON. + # + # If you need to output JSON elsewhere in your HTML, you can just do something + # like this, as any unsafe characters (including quotation marks) will be + # automatically escaped for you: + # + #
    ...
    + # + # WARNING: this helper only works with valid JSON. Using this on non-JSON values + # will open up serious XSS vulnerabilities. For example, if you replace the + # +current_user.to_json+ in the example above with user input instead, the browser + # will happily eval() that string as JavaScript. + # + # The escaping performed in this method is identical to those performed in the + # Active Support JSON encoder when +ActiveSupport.escape_html_entities_in_json+ is + # set to true. Because this transformation is idempotent, this helper can be + # applied even if +ActiveSupport.escape_html_entities_in_json+ is already true. + # + # Therefore, when you are unsure if +ActiveSupport.escape_html_entities_in_json+ + # is enabled, or if you are unsure where your JSON string originated from, it + # is recommended that you always apply this helper (other libraries, such as the + # JSON gem, do not provide this kind of protection by default; also some gems + # might override +to_json+ to bypass Active Support's encoder). + def json_escape(s) + result = s.to_s.gsub(JSON_ESCAPE_REGEXP, JSON_ESCAPE) + s.html_safe? ? result.html_safe : result + end + + module_function :json_escape + end +end + +class Object + def html_safe? + false + end +end + +class Numeric + def html_safe? + true + end +end + +module ActiveSupport #:nodoc: + class SafeBuffer < String + UNSAFE_STRING_METHODS = %w( + capitalize chomp chop delete downcase gsub lstrip next reverse rstrip + slice squeeze strip sub succ swapcase tr tr_s upcase + ) + + alias_method :original_concat, :concat + private :original_concat + + # Raised when ActiveSupport::SafeBuffer#safe_concat is called on unsafe buffers. + class SafeConcatError < StandardError + def initialize + super "Could not concatenate to the buffer because it is not html safe." + end + end + + def [](*args) + if args.size < 2 + super + elsif html_safe? + new_safe_buffer = super + + if new_safe_buffer + new_safe_buffer.instance_variable_set :@html_safe, true + end + + new_safe_buffer + else + to_str[*args] + end + end + + def safe_concat(value) + raise SafeConcatError unless html_safe? + original_concat(value) + end + + def initialize(str = "") + @html_safe = true + super + end + + def initialize_copy(other) + super + @html_safe = other.html_safe? + end + + def clone_empty + self[0, 0] + end + + def concat(value) + super(html_escape_interpolated_argument(value)) + end + alias << concat + + def prepend(value) + super(html_escape_interpolated_argument(value)) + end + + def +(other) + dup.concat(other) + end + + def %(args) + case args + when Hash + escaped_args = Hash[args.map { |k, arg| [k, html_escape_interpolated_argument(arg)] }] + else + escaped_args = Array(args).map { |arg| html_escape_interpolated_argument(arg) } + end + + self.class.new(super(escaped_args)) + end + + def html_safe? + defined?(@html_safe) && @html_safe + end + + def to_s + self + end + + def to_param + to_str + end + + def encode_with(coder) + coder.represent_object nil, to_str + end + + UNSAFE_STRING_METHODS.each do |unsafe_method| + if unsafe_method.respond_to?(unsafe_method) + class_eval <<-EOT, __FILE__, __LINE__ + 1 + def #{unsafe_method}(*args, &block) # def capitalize(*args, &block) + to_str.#{unsafe_method}(*args, &block) # to_str.capitalize(*args, &block) + end # end + + def #{unsafe_method}!(*args) # def capitalize!(*args) + @html_safe = false # @html_safe = false + super # super + end # end + EOT + end + end + + private + + def html_escape_interpolated_argument(arg) + (!html_safe? || arg.html_safe?) ? arg : CGI.escapeHTML(arg.to_s) + end + end +end + +class String + # Marks a string as trusted safe. It will be inserted into HTML with no + # additional escaping performed. It is your responsibility to ensure that the + # string contains no malicious content. This method is equivalent to the + # +raw+ helper in views. It is recommended that you use +sanitize+ instead of + # this method. It should never be called on user input. + def html_safe + ActiveSupport::SafeBuffer.new(self) + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/starts_ends_with.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/starts_ends_with.rb new file mode 100644 index 00000000..919eb7a5 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/starts_ends_with.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class String + alias_method :starts_with?, :start_with? + alias_method :ends_with?, :end_with? +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/strip.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/strip.rb new file mode 100644 index 00000000..cc26274e --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/strip.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class String + # Strips indentation in heredocs. + # + # For example in + # + # if options[:usage] + # puts <<-USAGE.strip_heredoc + # This command does such and such. + # + # Supported options are: + # -h This message + # ... + # USAGE + # end + # + # the user would see the usage message aligned against the left margin. + # + # Technically, it looks for the least indented non-empty line + # in the whole string, and removes that amount of leading whitespace. + def strip_heredoc + gsub(/^#{scan(/^[ \t]*(?=\S)/).min}/, "".freeze) + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/zones.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/zones.rb new file mode 100644 index 00000000..55dc2314 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/string/zones.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/conversions" +require "active_support/core_ext/time/zones" + +class String + # Converts String to a TimeWithZone in the current zone if Time.zone or Time.zone_default + # is set, otherwise converts String to a Time via String#to_time + def in_time_zone(zone = ::Time.zone) + if zone + ::Time.find_zone!(zone).parse(self) + else + to_time + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/time.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/time.rb new file mode 100644 index 00000000..c809def0 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/time.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require "active_support/core_ext/time/acts_like" +require "active_support/core_ext/time/calculations" +require "active_support/core_ext/time/compatibility" +require "active_support/core_ext/time/conversions" +require "active_support/core_ext/time/zones" diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/time/acts_like.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/time/acts_like.rb new file mode 100644 index 00000000..8572b496 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/time/acts_like.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/acts_like" + +class Time + # Duck-types as a Time-like class. See Object#acts_like?. + def acts_like_time? + true + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/time/calculations.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/time/calculations.rb new file mode 100644 index 00000000..120768de --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/time/calculations.rb @@ -0,0 +1,315 @@ +# frozen_string_literal: true + +require "active_support/duration" +require "active_support/core_ext/time/conversions" +require "active_support/time_with_zone" +require "active_support/core_ext/time/zones" +require "active_support/core_ext/date_and_time/calculations" +require "active_support/core_ext/date/calculations" + +class Time + include DateAndTime::Calculations + + COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + + class << self + # Overriding case equality method so that it returns true for ActiveSupport::TimeWithZone instances + def ===(other) + super || (self == Time && other.is_a?(ActiveSupport::TimeWithZone)) + end + + # Returns the number of days in the given month. + # If no year is specified, it will use the current year. + def days_in_month(month, year = current.year) + if month == 2 && ::Date.gregorian_leap?(year) + 29 + else + COMMON_YEAR_DAYS_IN_MONTH[month] + end + end + + # Returns the number of days in the given year. + # If no year is specified, it will use the current year. + def days_in_year(year = current.year) + days_in_month(2, year) + 337 + end + + # Returns Time.zone.now when Time.zone or config.time_zone are set, otherwise just returns Time.now. + def current + ::Time.zone ? ::Time.zone.now : ::Time.now + end + + # Layers additional behavior on Time.at so that ActiveSupport::TimeWithZone and DateTime + # instances can be used when called with a single argument + def at_with_coercion(*args) + return at_without_coercion(*args) if args.size != 1 + + # Time.at can be called with a time or numerical value + time_or_number = args.first + + if time_or_number.is_a?(ActiveSupport::TimeWithZone) || time_or_number.is_a?(DateTime) + at_without_coercion(time_or_number.to_f).getlocal + else + at_without_coercion(time_or_number) + end + end + alias_method :at_without_coercion, :at + alias_method :at, :at_with_coercion + + # Creates a +Time+ instance from an RFC 3339 string. + # + # Time.rfc3339('1999-12-31T14:00:00-10:00') # => 2000-01-01 00:00:00 -1000 + # + # If the time or offset components are missing then an +ArgumentError+ will be raised. + # + # Time.rfc3339('1999-12-31') # => ArgumentError: invalid date + def rfc3339(str) + parts = Date._rfc3339(str) + + raise ArgumentError, "invalid date" if parts.empty? + + Time.new( + parts.fetch(:year), + parts.fetch(:mon), + parts.fetch(:mday), + parts.fetch(:hour), + parts.fetch(:min), + parts.fetch(:sec) + parts.fetch(:sec_fraction, 0), + parts.fetch(:offset) + ) + end + end + + # Returns the number of seconds since 00:00:00. + # + # Time.new(2012, 8, 29, 0, 0, 0).seconds_since_midnight # => 0.0 + # Time.new(2012, 8, 29, 12, 34, 56).seconds_since_midnight # => 45296.0 + # Time.new(2012, 8, 29, 23, 59, 59).seconds_since_midnight # => 86399.0 + def seconds_since_midnight + to_i - change(hour: 0).to_i + (usec / 1.0e+6) + end + + # Returns the number of seconds until 23:59:59. + # + # Time.new(2012, 8, 29, 0, 0, 0).seconds_until_end_of_day # => 86399 + # Time.new(2012, 8, 29, 12, 34, 56).seconds_until_end_of_day # => 41103 + # Time.new(2012, 8, 29, 23, 59, 59).seconds_until_end_of_day # => 0 + def seconds_until_end_of_day + end_of_day.to_i - to_i + end + + # Returns the fraction of a second as a +Rational+ + # + # Time.new(2012, 8, 29, 0, 0, 0.5).sec_fraction # => (1/2) + def sec_fraction + subsec + end + + # Returns a new Time where one or more of the elements have been changed according + # to the +options+ parameter. The time options (:hour, :min, + # :sec, :usec, :nsec) reset cascadingly, so if only + # the hour is passed, then minute, sec, usec and nsec is set to 0. If the hour + # and minute is passed, then sec, usec and nsec is set to 0. The +options+ parameter + # takes a hash with any of these keys: :year, :month, :day, + # :hour, :min, :sec, :usec, :nsec, + # :offset. Pass either :usec or :nsec, not both. + # + # Time.new(2012, 8, 29, 22, 35, 0).change(day: 1) # => Time.new(2012, 8, 1, 22, 35, 0) + # Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => Time.new(1981, 8, 1, 22, 35, 0) + # Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, hour: 0) # => Time.new(1981, 8, 29, 0, 0, 0) + def change(options) + new_year = options.fetch(:year, year) + new_month = options.fetch(:month, month) + new_day = options.fetch(:day, day) + new_hour = options.fetch(:hour, hour) + new_min = options.fetch(:min, options[:hour] ? 0 : min) + new_sec = options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec) + new_offset = options.fetch(:offset, nil) + + if new_nsec = options[:nsec] + raise ArgumentError, "Can't change both :nsec and :usec at the same time: #{options.inspect}" if options[:usec] + new_usec = Rational(new_nsec, 1000) + else + new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000)) + end + + raise ArgumentError, "argument out of range" if new_usec >= 1000000 + + new_sec += Rational(new_usec, 1000000) + + if new_offset + ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, new_offset) + elsif utc? + ::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec) + elsif zone + ::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec) + else + ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, utc_offset) + end + end + + # Uses Date to provide precise Time calculations for years, months, and days + # according to the proleptic Gregorian calendar. The +options+ parameter + # takes a hash with any of these keys: :years, :months, + # :weeks, :days, :hours, :minutes, + # :seconds. + # + # Time.new(2015, 8, 1, 14, 35, 0).advance(seconds: 1) # => 2015-08-01 14:35:01 -0700 + # Time.new(2015, 8, 1, 14, 35, 0).advance(minutes: 1) # => 2015-08-01 14:36:00 -0700 + # Time.new(2015, 8, 1, 14, 35, 0).advance(hours: 1) # => 2015-08-01 15:35:00 -0700 + # Time.new(2015, 8, 1, 14, 35, 0).advance(days: 1) # => 2015-08-02 14:35:00 -0700 + # Time.new(2015, 8, 1, 14, 35, 0).advance(weeks: 1) # => 2015-08-08 14:35:00 -0700 + def advance(options) + unless options[:weeks].nil? + options[:weeks], partial_weeks = options[:weeks].divmod(1) + options[:days] = options.fetch(:days, 0) + 7 * partial_weeks + end + + unless options[:days].nil? + options[:days], partial_days = options[:days].divmod(1) + options[:hours] = options.fetch(:hours, 0) + 24 * partial_days + end + + d = to_date.advance(options) + d = d.gregorian if d.julian? + time_advanced_by_date = change(year: d.year, month: d.month, day: d.day) + seconds_to_advance = \ + options.fetch(:seconds, 0) + + options.fetch(:minutes, 0) * 60 + + options.fetch(:hours, 0) * 3600 + + if seconds_to_advance.zero? + time_advanced_by_date + else + time_advanced_by_date.since(seconds_to_advance) + end + end + + # Returns a new Time representing the time a number of seconds ago, this is basically a wrapper around the Numeric extension + def ago(seconds) + since(-seconds) + end + + # Returns a new Time representing the time a number of seconds since the instance time + def since(seconds) + self + seconds + rescue + to_datetime.since(seconds) + end + alias :in :since + + # Returns a new Time representing the start of the day (0:00) + def beginning_of_day + change(hour: 0) + end + alias :midnight :beginning_of_day + alias :at_midnight :beginning_of_day + alias :at_beginning_of_day :beginning_of_day + + # Returns a new Time representing the middle of the day (12:00) + def middle_of_day + change(hour: 12) + end + alias :midday :middle_of_day + alias :noon :middle_of_day + alias :at_midday :middle_of_day + alias :at_noon :middle_of_day + alias :at_middle_of_day :middle_of_day + + # Returns a new Time representing the end of the day, 23:59:59.999999 + def end_of_day + change( + hour: 23, + min: 59, + sec: 59, + usec: Rational(999999999, 1000) + ) + end + alias :at_end_of_day :end_of_day + + # Returns a new Time representing the start of the hour (x:00) + def beginning_of_hour + change(min: 0) + end + alias :at_beginning_of_hour :beginning_of_hour + + # Returns a new Time representing the end of the hour, x:59:59.999999 + def end_of_hour + change( + min: 59, + sec: 59, + usec: Rational(999999999, 1000) + ) + end + alias :at_end_of_hour :end_of_hour + + # Returns a new Time representing the start of the minute (x:xx:00) + def beginning_of_minute + change(sec: 0) + end + alias :at_beginning_of_minute :beginning_of_minute + + # Returns a new Time representing the end of the minute, x:xx:59.999999 + def end_of_minute + change( + sec: 59, + usec: Rational(999999999, 1000) + ) + end + alias :at_end_of_minute :end_of_minute + + def plus_with_duration(other) #:nodoc: + if ActiveSupport::Duration === other + other.since(self) + else + plus_without_duration(other) + end + end + alias_method :plus_without_duration, :+ + alias_method :+, :plus_with_duration + + def minus_with_duration(other) #:nodoc: + if ActiveSupport::Duration === other + other.until(self) + else + minus_without_duration(other) + end + end + alias_method :minus_without_duration, :- + alias_method :-, :minus_with_duration + + # Time#- can also be used to determine the number of seconds between two Time instances. + # We're layering on additional behavior so that ActiveSupport::TimeWithZone instances + # are coerced into values that Time#- will recognize + def minus_with_coercion(other) + other = other.comparable_time if other.respond_to?(:comparable_time) + other.is_a?(DateTime) ? to_f - other.to_f : minus_without_coercion(other) + end + alias_method :minus_without_coercion, :- + alias_method :-, :minus_with_coercion + + # Layers additional behavior on Time#<=> so that DateTime and ActiveSupport::TimeWithZone instances + # can be chronologically compared with a Time + def compare_with_coercion(other) + # we're avoiding Time#to_datetime and Time#to_time because they're expensive + if other.class == Time + compare_without_coercion(other) + elsif other.is_a?(Time) + compare_without_coercion(other.to_time) + else + to_datetime <=> other + end + end + alias_method :compare_without_coercion, :<=> + alias_method :<=>, :compare_with_coercion + + # Layers additional behavior on Time#eql? so that ActiveSupport::TimeWithZone instances + # can be eql? to an equivalent Time + def eql_with_coercion(other) + # if other is an ActiveSupport::TimeWithZone, coerce a Time instance from it so we can do eql? comparison + other = other.comparable_time if other.respond_to?(:comparable_time) + eql_without_coercion(other) + end + alias_method :eql_without_coercion, :eql? + alias_method :eql?, :eql_with_coercion +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/time/compatibility.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/time/compatibility.rb new file mode 100644 index 00000000..495e4f30 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/time/compatibility.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "active_support/core_ext/date_and_time/compatibility" +require "active_support/core_ext/module/redefine_method" + +class Time + include DateAndTime::Compatibility + + silence_redefinition_of_method :to_time + + # Either return +self+ or the time in the local system timezone depending + # on the setting of +ActiveSupport.to_time_preserves_timezone+. + def to_time + preserve_timezone ? self : getlocal + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/time/conversions.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/time/conversions.rb new file mode 100644 index 00000000..345cb283 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/time/conversions.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require "active_support/inflector/methods" +require "active_support/values/time_zone" + +class Time + DATE_FORMATS = { + db: "%Y-%m-%d %H:%M:%S", + number: "%Y%m%d%H%M%S", + nsec: "%Y%m%d%H%M%S%9N", + usec: "%Y%m%d%H%M%S%6N", + time: "%H:%M", + short: "%d %b %H:%M", + long: "%B %d, %Y %H:%M", + long_ordinal: lambda { |time| + day_format = ActiveSupport::Inflector.ordinalize(time.day) + time.strftime("%B #{day_format}, %Y %H:%M") + }, + rfc822: lambda { |time| + offset_format = time.formatted_offset(false) + time.strftime("%a, %d %b %Y %H:%M:%S #{offset_format}") + }, + iso8601: lambda { |time| time.iso8601 } + } + + # Converts to a formatted string. See DATE_FORMATS for built-in formats. + # + # This method is aliased to to_s. + # + # time = Time.now # => 2007-01-18 06:10:17 -06:00 + # + # time.to_formatted_s(:time) # => "06:10" + # time.to_s(:time) # => "06:10" + # + # time.to_formatted_s(:db) # => "2007-01-18 06:10:17" + # time.to_formatted_s(:number) # => "20070118061017" + # time.to_formatted_s(:short) # => "18 Jan 06:10" + # time.to_formatted_s(:long) # => "January 18, 2007 06:10" + # time.to_formatted_s(:long_ordinal) # => "January 18th, 2007 06:10" + # time.to_formatted_s(:rfc822) # => "Thu, 18 Jan 2007 06:10:17 -0600" + # time.to_formatted_s(:iso8601) # => "2007-01-18T06:10:17-06:00" + # + # == Adding your own time formats to +to_formatted_s+ + # You can add your own formats to the Time::DATE_FORMATS hash. + # Use the format name as the hash key and either a strftime string + # or Proc instance that takes a time argument as the value. + # + # # config/initializers/time_formats.rb + # Time::DATE_FORMATS[:month_and_year] = '%B %Y' + # Time::DATE_FORMATS[:short_ordinal] = ->(time) { time.strftime("%B #{time.day.ordinalize}") } + def to_formatted_s(format = :default) + if formatter = DATE_FORMATS[format] + formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter) + else + to_default_s + end + end + alias_method :to_default_s, :to_s + alias_method :to_s, :to_formatted_s + + # Returns a formatted string of the offset from UTC, or an alternative + # string if the time zone is already UTC. + # + # Time.local(2000).formatted_offset # => "-06:00" + # Time.local(2000).formatted_offset(false) # => "-0600" + def formatted_offset(colon = true, alternate_utc_string = nil) + utc? && alternate_utc_string || ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, colon) + end + + # Aliased to +xmlschema+ for compatibility with +DateTime+ + alias_method :rfc3339, :xmlschema +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/time/zones.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/time/zones.rb new file mode 100644 index 00000000..a5588fd4 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/time/zones.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +require "active_support/time_with_zone" +require "active_support/core_ext/time/acts_like" +require "active_support/core_ext/date_and_time/zones" + +class Time + include DateAndTime::Zones + class << self + attr_accessor :zone_default + + # Returns the TimeZone for the current request, if this has been set (via Time.zone=). + # If Time.zone has not been set for the current request, returns the TimeZone specified in config.time_zone. + def zone + Thread.current[:time_zone] || zone_default + end + + # Sets Time.zone to a TimeZone object for the current request/thread. + # + # This method accepts any of the following: + # + # * A Rails TimeZone object. + # * An identifier for a Rails TimeZone object (e.g., "Eastern Time (US & Canada)", -5.hours). + # * A TZInfo::Timezone object. + # * An identifier for a TZInfo::Timezone object (e.g., "America/New_York"). + # + # Here's an example of how you might set Time.zone on a per request basis and reset it when the request is done. + # current_user.time_zone just needs to return a string identifying the user's preferred time zone: + # + # class ApplicationController < ActionController::Base + # around_action :set_time_zone + # + # def set_time_zone + # if logged_in? + # Time.use_zone(current_user.time_zone) { yield } + # else + # yield + # end + # end + # end + def zone=(time_zone) + Thread.current[:time_zone] = find_zone!(time_zone) + end + + # Allows override of Time.zone locally inside supplied block; + # resets Time.zone to existing value when done. + # + # class ApplicationController < ActionController::Base + # around_action :set_time_zone + # + # private + # + # def set_time_zone + # Time.use_zone(current_user.timezone) { yield } + # end + # end + # + # NOTE: This won't affect any ActiveSupport::TimeWithZone + # objects that have already been created, e.g. any model timestamp + # attributes that have been read before the block will remain in + # the application's default timezone. + def use_zone(time_zone) + new_zone = find_zone!(time_zone) + begin + old_zone, ::Time.zone = ::Time.zone, new_zone + yield + ensure + ::Time.zone = old_zone + end + end + + # Returns a TimeZone instance matching the time zone provided. + # Accepts the time zone in any format supported by Time.zone=. + # Raises an +ArgumentError+ for invalid time zones. + # + # Time.find_zone! "America/New_York" # => # + # Time.find_zone! "EST" # => # + # Time.find_zone! -5.hours # => # + # Time.find_zone! nil # => nil + # Time.find_zone! false # => false + # Time.find_zone! "NOT-A-TIMEZONE" # => ArgumentError: Invalid Timezone: NOT-A-TIMEZONE + def find_zone!(time_zone) + if !time_zone || time_zone.is_a?(ActiveSupport::TimeZone) + time_zone + else + # Look up the timezone based on the identifier (unless we've been + # passed a TZInfo::Timezone) + unless time_zone.respond_to?(:period_for_local) + time_zone = ActiveSupport::TimeZone[time_zone] || TZInfo::Timezone.get(time_zone) + end + + # Return if a TimeZone instance, or wrap in a TimeZone instance if a TZInfo::Timezone + if time_zone.is_a?(ActiveSupport::TimeZone) + time_zone + else + ActiveSupport::TimeZone.create(time_zone.name, nil, time_zone) + end + end + rescue TZInfo::InvalidTimezoneIdentifier + raise ArgumentError, "Invalid Timezone: #{time_zone}" + end + + # Returns a TimeZone instance matching the time zone provided. + # Accepts the time zone in any format supported by Time.zone=. + # Returns +nil+ for invalid time zones. + # + # Time.find_zone "America/New_York" # => # + # Time.find_zone "NOT-A-TIMEZONE" # => nil + def find_zone(time_zone) + find_zone!(time_zone) rescue nil + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/uri.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/uri.rb new file mode 100644 index 00000000..9def947f --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/core_ext/uri.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "uri" +if RUBY_VERSION < "2.6.0" + require "active_support/core_ext/module/redefine_method" + URI::Parser.class_eval do + silence_redefinition_of_method :unescape + def unescape(str, escaped = /%[a-fA-F\d]{2}/) + # TODO: Are we actually sure that ASCII == UTF-8? + # YK: My initial experiments say yes, but let's be sure please + enc = str.encoding + enc = Encoding::UTF_8 if enc == Encoding::US_ASCII + str.dup.force_encoding(Encoding::ASCII_8BIT).gsub(escaped) { |match| [match[1, 2].hex].pack("C") }.force_encoding(enc) + end + end +end + +module URI + class << self + def parser + @parser ||= URI::Parser.new + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/current_attributes.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/current_attributes.rb new file mode 100644 index 00000000..4e6d8e45 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/current_attributes.rb @@ -0,0 +1,195 @@ +# frozen_string_literal: true + +module ActiveSupport + # Abstract super class that provides a thread-isolated attributes singleton, which resets automatically + # before and after each request. This allows you to keep all the per-request attributes easily + # available to the whole system. + # + # The following full app-like example demonstrates how to use a Current class to + # facilitate easy access to the global, per-request attributes without passing them deeply + # around everywhere: + # + # # app/models/current.rb + # class Current < ActiveSupport::CurrentAttributes + # attribute :account, :user + # attribute :request_id, :user_agent, :ip_address + # + # resets { Time.zone = nil } + # + # def user=(user) + # super + # self.account = user.account + # Time.zone = user.time_zone + # end + # end + # + # # app/controllers/concerns/authentication.rb + # module Authentication + # extend ActiveSupport::Concern + # + # included do + # before_action :authenticate + # end + # + # private + # def authenticate + # if authenticated_user = User.find_by(id: cookies.encrypted[:user_id]) + # Current.user = authenticated_user + # else + # redirect_to new_session_url + # end + # end + # end + # + # # app/controllers/concerns/set_current_request_details.rb + # module SetCurrentRequestDetails + # extend ActiveSupport::Concern + # + # included do + # before_action do + # Current.request_id = request.uuid + # Current.user_agent = request.user_agent + # Current.ip_address = request.ip + # end + # end + # end + # + # class ApplicationController < ActionController::Base + # include Authentication + # include SetCurrentRequestDetails + # end + # + # class MessagesController < ApplicationController + # def create + # Current.account.messages.create(message_params) + # end + # end + # + # class Message < ApplicationRecord + # belongs_to :creator, default: -> { Current.user } + # after_create { |message| Event.create(record: message) } + # end + # + # class Event < ApplicationRecord + # before_create do + # self.request_id = Current.request_id + # self.user_agent = Current.user_agent + # self.ip_address = Current.ip_address + # end + # end + # + # A word of caution: It's easy to overdo a global singleton like Current and tangle your model as a result. + # Current should only be used for a few, top-level globals, like account, user, and request details. + # The attributes stuck in Current should be used by more or less all actions on all requests. If you start + # sticking controller-specific attributes in there, you're going to create a mess. + class CurrentAttributes + include ActiveSupport::Callbacks + define_callbacks :reset + + class << self + # Returns singleton instance for this class in this thread. If none exists, one is created. + def instance + current_instances[name] ||= new + end + + # Declares one or more attributes that will be given both class and instance accessor methods. + def attribute(*names) + generated_attribute_methods.module_eval do + names.each do |name| + define_method(name) do + attributes[name.to_sym] + end + + define_method("#{name}=") do |attribute| + attributes[name.to_sym] = attribute + end + end + end + + names.each do |name| + define_singleton_method(name) do + instance.public_send(name) + end + + define_singleton_method("#{name}=") do |attribute| + instance.public_send("#{name}=", attribute) + end + end + end + + # Calls this block after #reset is called on the instance. Used for resetting external collaborators, like Time.zone. + def resets(&block) + set_callback :reset, :after, &block + end + + delegate :set, :reset, to: :instance + + def reset_all # :nodoc: + current_instances.each_value(&:reset) + end + + def clear_all # :nodoc: + reset_all + current_instances.clear + end + + private + def generated_attribute_methods + @generated_attribute_methods ||= Module.new.tap { |mod| include mod } + end + + def current_instances + Thread.current[:current_attributes_instances] ||= {} + end + + def method_missing(name, *args, &block) + # Caches the method definition as a singleton method of the receiver. + # + # By letting #delegate handle it, we avoid an enclosure that'll capture args. + singleton_class.delegate name, to: :instance + + send(name, *args, &block) + end + end + + attr_accessor :attributes + + def initialize + @attributes = {} + end + + # Expose one or more attributes within a block. Old values are returned after the block concludes. + # Example demonstrating the common use of needing to set Current attributes outside the request-cycle: + # + # class Chat::PublicationJob < ApplicationJob + # def perform(attributes, room_number, creator) + # Current.set(person: creator) do + # Chat::Publisher.publish(attributes: attributes, room_number: room_number) + # end + # end + # end + def set(set_attributes) + old_attributes = compute_attributes(set_attributes.keys) + assign_attributes(set_attributes) + yield + ensure + assign_attributes(old_attributes) + end + + # Reset all attributes. Should be called before and after actions, when used as a per-request singleton. + def reset + run_callbacks :reset do + self.attributes = {} + end + end + + private + def assign_attributes(new_attributes) + new_attributes.each { |key, value| public_send("#{key}=", value) } + end + + def compute_attributes(keys) + keys.collect { |key| [ key, public_send(key) ] }.to_h + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/dependencies.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/dependencies.rb new file mode 100644 index 00000000..39577009 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/dependencies.rb @@ -0,0 +1,753 @@ +# frozen_string_literal: true + +require "set" +require "thread" +require "concurrent/map" +require "pathname" +require "active_support/core_ext/module/aliasing" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/module/introspection" +require "active_support/core_ext/module/anonymous" +require "active_support/core_ext/object/blank" +require "active_support/core_ext/kernel/reporting" +require "active_support/core_ext/load_error" +require "active_support/core_ext/name_error" +require "active_support/core_ext/string/starts_ends_with" +require "active_support/dependencies/interlock" +require "active_support/inflector" + +module ActiveSupport #:nodoc: + module Dependencies #:nodoc: + extend self + + mattr_accessor :interlock, default: Interlock.new + + # :doc: + + # Execute the supplied block without interference from any + # concurrent loads. + def self.run_interlock + Dependencies.interlock.running { yield } + end + + # Execute the supplied block while holding an exclusive lock, + # preventing any other thread from being inside a #run_interlock + # block at the same time. + def self.load_interlock + Dependencies.interlock.loading { yield } + end + + # Execute the supplied block while holding an exclusive lock, + # preventing any other thread from being inside a #run_interlock + # block at the same time. + def self.unload_interlock + Dependencies.interlock.unloading { yield } + end + + # :nodoc: + + # Should we turn on Ruby warnings on the first load of dependent files? + mattr_accessor :warnings_on_first_load, default: false + + # All files ever loaded. + mattr_accessor :history, default: Set.new + + # All files currently loaded. + mattr_accessor :loaded, default: Set.new + + # Stack of files being loaded. + mattr_accessor :loading, default: [] + + # Should we load files or require them? + mattr_accessor :mechanism, default: ENV["NO_RELOAD"] ? :require : :load + + # The set of directories from which we may automatically load files. Files + # under these directories will be reloaded on each request in development mode, + # unless the directory also appears in autoload_once_paths. + mattr_accessor :autoload_paths, default: [] + + # The set of directories from which automatically loaded constants are loaded + # only once. All directories in this set must also be present in +autoload_paths+. + mattr_accessor :autoload_once_paths, default: [] + + # An array of qualified constant names that have been loaded. Adding a name + # to this array will cause it to be unloaded the next time Dependencies are + # cleared. + mattr_accessor :autoloaded_constants, default: [] + + # An array of constant names that need to be unloaded on every request. Used + # to allow arbitrary constants to be marked for unloading. + mattr_accessor :explicitly_unloadable_constants, default: [] + + # The WatchStack keeps a stack of the modules being watched as files are + # loaded. If a file in the process of being loaded (parent.rb) triggers the + # load of another file (child.rb) the stack will ensure that child.rb + # handles the new constants. + # + # If child.rb is being autoloaded, its constants will be added to + # autoloaded_constants. If it was being required, they will be discarded. + # + # This is handled by walking back up the watch stack and adding the constants + # found by child.rb to the list of original constants in parent.rb. + class WatchStack + include Enumerable + + # @watching is a stack of lists of constants being watched. For instance, + # if parent.rb is autoloaded, the stack will look like [[Object]]. If + # parent.rb then requires namespace/child.rb, the stack will look like + # [[Object], [Namespace]]. + + attr_reader :watching + + def initialize + @watching = [] + @stack = Hash.new { |h, k| h[k] = [] } + end + + def each(&block) + @stack.each(&block) + end + + def watching? + !@watching.empty? + end + + # Returns a list of new constants found since the last call to + # watch_namespaces. + def new_constants + constants = [] + + # Grab the list of namespaces that we're looking for new constants under + @watching.last.each do |namespace| + # Retrieve the constants that were present under the namespace when watch_namespaces + # was originally called + original_constants = @stack[namespace].last + + mod = Inflector.constantize(namespace) if Dependencies.qualified_const_defined?(namespace) + next unless mod.is_a?(Module) + + # Get a list of the constants that were added + new_constants = mod.constants(false) - original_constants + + # @stack[namespace] returns an Array of the constants that are being evaluated + # for that namespace. For instance, if parent.rb requires child.rb, the first + # element of @stack[Object] will be an Array of the constants that were present + # before parent.rb was required. The second element will be an Array of the + # constants that were present before child.rb was required. + @stack[namespace].each do |namespace_constants| + namespace_constants.concat(new_constants) + end + + # Normalize the list of new constants, and add them to the list we will return + new_constants.each do |suffix| + constants << ([namespace, suffix] - ["Object"]).join("::".freeze) + end + end + constants + ensure + # A call to new_constants is always called after a call to watch_namespaces + pop_modules(@watching.pop) + end + + # Add a set of modules to the watch stack, remembering the initial + # constants. + def watch_namespaces(namespaces) + @watching << namespaces.map do |namespace| + module_name = Dependencies.to_constant_name(namespace) + original_constants = Dependencies.qualified_const_defined?(module_name) ? + Inflector.constantize(module_name).constants(false) : [] + + @stack[module_name] << original_constants + module_name + end + end + + private + def pop_modules(modules) + modules.each { |mod| @stack[mod].pop } + end + end + + # An internal stack used to record which constants are loaded by any block. + mattr_accessor :constant_watch_stack, default: WatchStack.new + + # Module includes this module. + module ModuleConstMissing #:nodoc: + def self.append_features(base) + base.class_eval do + # Emulate #exclude via an ivar + return if defined?(@_const_missing) && @_const_missing + @_const_missing = instance_method(:const_missing) + remove_method(:const_missing) + end + super + end + + def self.exclude_from(base) + base.class_eval do + define_method :const_missing, @_const_missing + @_const_missing = nil + end + end + + def const_missing(const_name) + from_mod = anonymous? ? guess_for_anonymous(const_name) : self + Dependencies.load_missing_constant(from_mod, const_name) + end + + # We assume that the name of the module reflects the nesting + # (unless it can be proven that is not the case) and the path to the file + # that defines the constant. Anonymous modules cannot follow these + # conventions and therefore we assume that the user wants to refer to a + # top-level constant. + def guess_for_anonymous(const_name) + if Object.const_defined?(const_name) + raise NameError.new "#{const_name} cannot be autoloaded from an anonymous class or module", const_name + else + Object + end + end + + def unloadable(const_desc = self) + super(const_desc) + end + end + + # Object includes this module. + module Loadable #:nodoc: + def self.exclude_from(base) + base.class_eval do + define_method(:load, Kernel.instance_method(:load)) + private :load + end + end + + def require_or_load(file_name) + Dependencies.require_or_load(file_name) + end + + # :doc: + + # Interprets a file using mechanism and marks its defined + # constants as autoloaded. file_name can be either a string or + # respond to to_path. + # + # Use this method in code that absolutely needs a certain constant to be + # defined at that point. A typical use case is to make constant name + # resolution deterministic for constants with the same relative name in + # different namespaces whose evaluation would depend on load order + # otherwise. + def require_dependency(file_name, message = "No such file to load -- %s.rb") + file_name = file_name.to_path if file_name.respond_to?(:to_path) + unless file_name.is_a?(String) + raise ArgumentError, "the file name must either be a String or implement #to_path -- you passed #{file_name.inspect}" + end + + Dependencies.depend_on(file_name, message) + end + + # :nodoc: + + def load_dependency(file) + if Dependencies.load? && Dependencies.constant_watch_stack.watching? + descs = Dependencies.constant_watch_stack.watching.flatten.uniq + + Dependencies.new_constants_in(*descs) { yield } + else + yield + end + rescue Exception => exception # errors from loading file + exception.blame_file! file if exception.respond_to? :blame_file! + raise + end + + # Mark the given constant as unloadable. Unloadable constants are removed + # each time dependencies are cleared. + # + # Note that marking a constant for unloading need only be done once. Setup + # or init scripts may list each unloadable constant that may need unloading; + # each constant will be removed for every subsequent clear, as opposed to + # for the first clear. + # + # The provided constant descriptor may be a (non-anonymous) module or class, + # or a qualified constant name as a string or symbol. + # + # Returns +true+ if the constant was not previously marked for unloading, + # +false+ otherwise. + def unloadable(const_desc) + Dependencies.mark_for_unload const_desc + end + + private + + def load(file, wrap = false) + result = false + load_dependency(file) { result = super } + result + end + + def require(file) + result = false + load_dependency(file) { result = super } + result + end + end + + # Exception file-blaming. + module Blamable #:nodoc: + def blame_file!(file) + (@blamed_files ||= []).unshift file + end + + def blamed_files + @blamed_files ||= [] + end + + def describe_blame + return nil if blamed_files.empty? + "This error occurred while loading the following files:\n #{blamed_files.join "\n "}" + end + + def copy_blame!(exc) + @blamed_files = exc.blamed_files.clone + self + end + end + + def hook! + Object.class_eval { include Loadable } + Module.class_eval { include ModuleConstMissing } + Exception.class_eval { include Blamable } + end + + def unhook! + ModuleConstMissing.exclude_from(Module) + Loadable.exclude_from(Object) + end + + def load? + mechanism == :load + end + + def depend_on(file_name, message = "No such file to load -- %s.rb") + path = search_for_file(file_name) + require_or_load(path || file_name) + rescue LoadError => load_error + if file_name = load_error.message[/ -- (.*?)(\.rb)?$/, 1] + load_error.message.replace(message % file_name) + load_error.copy_blame!(load_error) + end + raise + end + + def clear + Dependencies.unload_interlock do + loaded.clear + loading.clear + remove_unloadable_constants! + end + end + + def require_or_load(file_name, const_path = nil) + file_name = $` if file_name =~ /\.rb\z/ + expanded = File.expand_path(file_name) + return if loaded.include?(expanded) + + Dependencies.load_interlock do + # Maybe it got loaded while we were waiting for our lock: + return if loaded.include?(expanded) + + # Record that we've seen this file *before* loading it to avoid an + # infinite loop with mutual dependencies. + loaded << expanded + loading << expanded + + begin + if load? + # Enable warnings if this file has not been loaded before and + # warnings_on_first_load is set. + load_args = ["#{file_name}.rb"] + load_args << const_path unless const_path.nil? + + if !warnings_on_first_load || history.include?(expanded) + result = load_file(*load_args) + else + enable_warnings { result = load_file(*load_args) } + end + else + result = require file_name + end + rescue Exception + loaded.delete expanded + raise + ensure + loading.pop + end + + # Record history *after* loading so first load gets warnings. + history << expanded + result + end + end + + # Is the provided constant path defined? + def qualified_const_defined?(path) + Object.const_defined?(path, false) + end + + # Given +path+, a filesystem path to a ruby file, return an array of + # constant paths which would cause Dependencies to attempt to load this + # file. + def loadable_constants_for_path(path, bases = autoload_paths) + path = $` if path =~ /\.rb\z/ + expanded_path = File.expand_path(path) + paths = [] + + bases.each do |root| + expanded_root = File.expand_path(root) + next unless expanded_path.start_with?(expanded_root) + + root_size = expanded_root.size + next if expanded_path[root_size] != ?/.freeze + + nesting = expanded_path[(root_size + 1)..-1] + paths << nesting.camelize unless nesting.blank? + end + + paths.uniq! + paths + end + + # Search for a file in autoload_paths matching the provided suffix. + def search_for_file(path_suffix) + path_suffix = path_suffix.sub(/(\.rb)?$/, ".rb".freeze) + + autoload_paths.each do |root| + path = File.join(root, path_suffix) + return path if File.file? path + end + nil # Gee, I sure wish we had first_match ;-) + end + + # Does the provided path_suffix correspond to an autoloadable module? + # Instead of returning a boolean, the autoload base for this module is + # returned. + def autoloadable_module?(path_suffix) + autoload_paths.each do |load_path| + return load_path if File.directory? File.join(load_path, path_suffix) + end + nil + end + + def load_once_path?(path) + # to_s works around a ruby issue where String#starts_with?(Pathname) + # will raise a TypeError: no implicit conversion of Pathname into String + autoload_once_paths.any? { |base| path.starts_with? base.to_s } + end + + # Attempt to autoload the provided module name by searching for a directory + # matching the expected path suffix. If found, the module is created and + # assigned to +into+'s constants with the name +const_name+. Provided that + # the directory was loaded from a reloadable base path, it is added to the + # set of constants that are to be unloaded. + def autoload_module!(into, const_name, qualified_name, path_suffix) + return nil unless base_path = autoloadable_module?(path_suffix) + mod = Module.new + into.const_set const_name, mod + autoloaded_constants << qualified_name unless autoload_once_paths.include?(base_path) + autoloaded_constants.uniq! + mod + end + + # Load the file at the provided path. +const_paths+ is a set of qualified + # constant names. When loading the file, Dependencies will watch for the + # addition of these constants. Each that is defined will be marked as + # autoloaded, and will be removed when Dependencies.clear is next called. + # + # If the second parameter is left off, then Dependencies will construct a + # set of names that the file at +path+ may define. See + # +loadable_constants_for_path+ for more details. + def load_file(path, const_paths = loadable_constants_for_path(path)) + const_paths = [const_paths].compact unless const_paths.is_a? Array + parent_paths = const_paths.collect { |const_path| const_path[/.*(?=::)/] || ::Object } + + result = nil + newly_defined_paths = new_constants_in(*parent_paths) do + result = Kernel.load path + end + + autoloaded_constants.concat newly_defined_paths unless load_once_path?(path) + autoloaded_constants.uniq! + result + end + + # Returns the constant path for the provided parent and constant name. + def qualified_name_for(mod, name) + mod_name = to_constant_name mod + mod_name == "Object" ? name.to_s : "#{mod_name}::#{name}" + end + + # Load the constant named +const_name+ which is missing from +from_mod+. If + # it is not possible to load the constant into from_mod, try its parent + # module using +const_missing+. + def load_missing_constant(from_mod, const_name) + unless qualified_const_defined?(from_mod.name) && Inflector.constantize(from_mod.name).equal?(from_mod) + raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!" + end + + qualified_name = qualified_name_for from_mod, const_name + path_suffix = qualified_name.underscore + + file_path = search_for_file(path_suffix) + + if file_path + expanded = File.expand_path(file_path) + expanded.sub!(/\.rb\z/, "".freeze) + + if loading.include?(expanded) + raise "Circular dependency detected while autoloading constant #{qualified_name}" + else + require_or_load(expanded, qualified_name) + raise LoadError, "Unable to autoload constant #{qualified_name}, expected #{file_path} to define it" unless from_mod.const_defined?(const_name, false) + return from_mod.const_get(const_name) + end + elsif mod = autoload_module!(from_mod, const_name, qualified_name, path_suffix) + return mod + elsif (parent = from_mod.parent) && parent != from_mod && + ! from_mod.parents.any? { |p| p.const_defined?(const_name, false) } + # If our parents do not have a constant named +const_name+ then we are free + # to attempt to load upwards. If they do have such a constant, then this + # const_missing must be due to from_mod::const_name, which should not + # return constants from from_mod's parents. + begin + # Since Ruby does not pass the nesting at the point the unknown + # constant triggered the callback we cannot fully emulate constant + # name lookup and need to make a trade-off: we are going to assume + # that the nesting in the body of Foo::Bar is [Foo::Bar, Foo] even + # though it might not be. Counterexamples are + # + # class Foo::Bar + # Module.nesting # => [Foo::Bar] + # end + # + # or + # + # module M::N + # module S::T + # Module.nesting # => [S::T, M::N] + # end + # end + # + # for example. + return parent.const_missing(const_name) + rescue NameError => e + raise unless e.missing_name? qualified_name_for(parent, const_name) + end + end + + name_error = NameError.new("uninitialized constant #{qualified_name}", const_name) + name_error.set_backtrace(caller.reject { |l| l.starts_with? __FILE__ }) + raise name_error + end + + # Remove the constants that have been autoloaded, and those that have been + # marked for unloading. Before each constant is removed a callback is sent + # to its class/module if it implements +before_remove_const+. + # + # The callback implementation should be restricted to cleaning up caches, etc. + # as the environment will be in an inconsistent state, e.g. other constants + # may have already been unloaded and not accessible. + def remove_unloadable_constants! + autoloaded_constants.each { |const| remove_constant const } + autoloaded_constants.clear + Reference.clear! + explicitly_unloadable_constants.each { |const| remove_constant const } + end + + class ClassCache + def initialize + @store = Concurrent::Map.new + end + + def empty? + @store.empty? + end + + def key?(key) + @store.key?(key) + end + + def get(key) + key = key.name if key.respond_to?(:name) + @store[key] ||= Inflector.constantize(key) + end + alias :[] :get + + def safe_get(key) + key = key.name if key.respond_to?(:name) + @store[key] ||= Inflector.safe_constantize(key) + end + + def store(klass) + return self unless klass.respond_to?(:name) + raise(ArgumentError, "anonymous classes cannot be cached") if klass.name.empty? + @store[klass.name] = klass + self + end + + def clear! + @store.clear + end + end + + Reference = ClassCache.new + + # Store a reference to a class +klass+. + def reference(klass) + Reference.store klass + end + + # Get the reference for class named +name+. + # Raises an exception if referenced class does not exist. + def constantize(name) + Reference.get(name) + end + + # Get the reference for class named +name+ if one exists. + # Otherwise returns +nil+. + def safe_constantize(name) + Reference.safe_get(name) + end + + # Determine if the given constant has been automatically loaded. + def autoloaded?(desc) + return false if desc.is_a?(Module) && desc.anonymous? + name = to_constant_name desc + return false unless qualified_const_defined?(name) + autoloaded_constants.include?(name) + end + + # Will the provided constant descriptor be unloaded? + def will_unload?(const_desc) + autoloaded?(const_desc) || + explicitly_unloadable_constants.include?(to_constant_name(const_desc)) + end + + # Mark the provided constant name for unloading. This constant will be + # unloaded on each request, not just the next one. + def mark_for_unload(const_desc) + name = to_constant_name const_desc + if explicitly_unloadable_constants.include? name + false + else + explicitly_unloadable_constants << name + true + end + end + + # Run the provided block and detect the new constants that were loaded during + # its execution. Constants may only be regarded as 'new' once -- so if the + # block calls +new_constants_in+ again, then the constants defined within the + # inner call will not be reported in this one. + # + # If the provided block does not run to completion, and instead raises an + # exception, any new constants are regarded as being only partially defined + # and will be removed immediately. + def new_constants_in(*descs) + constant_watch_stack.watch_namespaces(descs) + success = false + + begin + yield # Now yield to the code that is to define new constants. + success = true + ensure + new_constants = constant_watch_stack.new_constants + + return new_constants if success + + # Remove partially loaded constants. + new_constants.each { |c| remove_constant(c) } + end + end + + # Convert the provided const desc to a qualified constant name (as a string). + # A module, class, symbol, or string may be provided. + def to_constant_name(desc) #:nodoc: + case desc + when String then desc.sub(/^::/, "") + when Symbol then desc.to_s + when Module + desc.name || + raise(ArgumentError, "Anonymous modules have no name to be referenced by") + else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}" + end + end + + def remove_constant(const) #:nodoc: + # Normalize ::Foo, ::Object::Foo, Object::Foo, Object::Object::Foo, etc. as Foo. + normalized = const.to_s.sub(/\A::/, "") + normalized.sub!(/\A(Object::)+/, "") + + constants = normalized.split("::") + to_remove = constants.pop + + # Remove the file path from the loaded list. + file_path = search_for_file(const.underscore) + if file_path + expanded = File.expand_path(file_path) + expanded.sub!(/\.rb\z/, "") + loaded.delete(expanded) + end + + if constants.empty? + parent = Object + else + # This method is robust to non-reachable constants. + # + # Non-reachable constants may be passed if some of the parents were + # autoloaded and already removed. It is easier to do a sanity check + # here than require the caller to be clever. We check the parent + # rather than the very const argument because we do not want to + # trigger Kernel#autoloads, see the comment below. + parent_name = constants.join("::") + return unless qualified_const_defined?(parent_name) + parent = constantize(parent_name) + end + + # In an autoloaded user.rb like this + # + # autoload :Foo, 'foo' + # + # class User < ActiveRecord::Base + # end + # + # we correctly register "Foo" as being autoloaded. But if the app does + # not use the "Foo" constant we need to be careful not to trigger + # loading "foo.rb" ourselves. While #const_defined? and #const_get? do + # require the file, #autoload? and #remove_const don't. + # + # We are going to remove the constant nonetheless ---which exists as + # far as Ruby is concerned--- because if the user removes the macro + # call from a class or module that were not autoloaded, as in the + # example above with Object, accessing to that constant must err. + unless parent.autoload?(to_remove) + begin + constantized = parent.const_get(to_remove, false) + rescue NameError + # The constant is no longer reachable, just skip it. + return + else + constantized.before_remove_const if constantized.respond_to?(:before_remove_const) + end + end + + begin + parent.instance_eval { remove_const to_remove } + rescue NameError + # The constant is no longer reachable, just skip it. + end + end + end +end + +ActiveSupport::Dependencies.hook! diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/dependencies/autoload.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/dependencies/autoload.rb new file mode 100644 index 00000000..1cee85d9 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/dependencies/autoload.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require "active_support/inflector/methods" + +module ActiveSupport + # Autoload and eager load conveniences for your library. + # + # This module allows you to define autoloads based on + # Rails conventions (i.e. no need to define the path + # it is automatically guessed based on the filename) + # and also define a set of constants that needs to be + # eager loaded: + # + # module MyLib + # extend ActiveSupport::Autoload + # + # autoload :Model + # + # eager_autoload do + # autoload :Cache + # end + # end + # + # Then your library can be eager loaded by simply calling: + # + # MyLib.eager_load! + module Autoload + def self.extended(base) # :nodoc: + base.class_eval do + @_autoloads = {} + @_under_path = nil + @_at_path = nil + @_eager_autoload = false + end + end + + def autoload(const_name, path = @_at_path) + unless path + full = [name, @_under_path, const_name.to_s].compact.join("::") + path = Inflector.underscore(full) + end + + if @_eager_autoload + @_autoloads[const_name] = path + end + + super const_name, path + end + + def autoload_under(path) + @_under_path, old_path = path, @_under_path + yield + ensure + @_under_path = old_path + end + + def autoload_at(path) + @_at_path, old_path = path, @_at_path + yield + ensure + @_at_path = old_path + end + + def eager_autoload + old_eager, @_eager_autoload = @_eager_autoload, true + yield + ensure + @_eager_autoload = old_eager + end + + def eager_load! + @_autoloads.each_value { |file| require file } + end + + def autoloads + @_autoloads + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/dependencies/interlock.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/dependencies/interlock.rb new file mode 100644 index 00000000..948be756 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/dependencies/interlock.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require "active_support/concurrency/share_lock" + +module ActiveSupport #:nodoc: + module Dependencies #:nodoc: + class Interlock + def initialize # :nodoc: + @lock = ActiveSupport::Concurrency::ShareLock.new + end + + def loading + @lock.exclusive(purpose: :load, compatible: [:load], after_compatible: [:load]) do + yield + end + end + + def unloading + @lock.exclusive(purpose: :unload, compatible: [:load, :unload], after_compatible: [:load, :unload]) do + yield + end + end + + def start_unloading + @lock.start_exclusive(purpose: :unload, compatible: [:load, :unload]) + end + + def done_unloading + @lock.stop_exclusive(compatible: [:load, :unload]) + end + + def start_running + @lock.start_sharing + end + + def done_running + @lock.stop_sharing + end + + def running + @lock.sharing do + yield + end + end + + def permit_concurrent_loads + @lock.yield_shares(compatible: [:load]) do + yield + end + end + + def raw_state(&block) # :nodoc: + @lock.raw_state(&block) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/deprecation.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/deprecation.rb new file mode 100644 index 00000000..a1ad2ca4 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/deprecation.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require "singleton" + +module ActiveSupport + # \Deprecation specifies the API used by Rails to deprecate methods, instance + # variables, objects and constants. + class Deprecation + # active_support.rb sets an autoload for ActiveSupport::Deprecation. + # + # If these requires were at the top of the file the constant would not be + # defined by the time their files were loaded. Since some of them reopen + # ActiveSupport::Deprecation its autoload would be triggered, resulting in + # a circular require warning for active_support/deprecation.rb. + # + # So, we define the constant first, and load dependencies later. + require "active_support/deprecation/instance_delegator" + require "active_support/deprecation/behaviors" + require "active_support/deprecation/reporting" + require "active_support/deprecation/constant_accessor" + require "active_support/deprecation/method_wrappers" + require "active_support/deprecation/proxy_wrappers" + require "active_support/core_ext/module/deprecation" + + include Singleton + include InstanceDelegator + include Behavior + include Reporting + include MethodWrapper + + # The version number in which the deprecated behavior will be removed, by default. + attr_accessor :deprecation_horizon + + # It accepts two parameters on initialization. The first is a version of library + # and the second is a library name. + # + # ActiveSupport::Deprecation.new('2.0', 'MyLibrary') + def initialize(deprecation_horizon = "6.0", gem_name = "Rails") + self.gem_name = gem_name + self.deprecation_horizon = deprecation_horizon + # By default, warnings are not silenced and debugging is off. + self.silenced = false + self.debug = false + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/deprecation/behaviors.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/deprecation/behaviors.rb new file mode 100644 index 00000000..3abd25aa --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/deprecation/behaviors.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +require "active_support/notifications" + +module ActiveSupport + # Raised when ActiveSupport::Deprecation::Behavior#behavior is set with :raise. + # You would set :raise, as a behavior to raise errors and proactively report exceptions from deprecations. + class DeprecationException < StandardError + end + + class Deprecation + # Default warning behaviors per Rails.env. + DEFAULT_BEHAVIORS = { + raise: ->(message, callstack, deprecation_horizon, gem_name) { + e = DeprecationException.new(message) + e.set_backtrace(callstack.map(&:to_s)) + raise e + }, + + stderr: ->(message, callstack, deprecation_horizon, gem_name) { + $stderr.puts(message) + $stderr.puts callstack.join("\n ") if debug + }, + + log: ->(message, callstack, deprecation_horizon, gem_name) { + logger = + if defined?(Rails.logger) && Rails.logger + Rails.logger + else + require "active_support/logger" + ActiveSupport::Logger.new($stderr) + end + logger.warn message + logger.debug callstack.join("\n ") if debug + }, + + notify: ->(message, callstack, deprecation_horizon, gem_name) { + notification_name = "deprecation.#{gem_name.underscore.tr('/', '_')}" + ActiveSupport::Notifications.instrument(notification_name, + message: message, + callstack: callstack, + gem_name: gem_name, + deprecation_horizon: deprecation_horizon) + }, + + silence: ->(message, callstack, deprecation_horizon, gem_name) {}, + } + + # Behavior module allows to determine how to display deprecation messages. + # You can create a custom behavior or set any from the +DEFAULT_BEHAVIORS+ + # constant. Available behaviors are: + # + # [+raise+] Raise ActiveSupport::DeprecationException. + # [+stderr+] Log all deprecation warnings to +$stderr+. + # [+log+] Log all deprecation warnings to +Rails.logger+. + # [+notify+] Use +ActiveSupport::Notifications+ to notify +deprecation.rails+. + # [+silence+] Do nothing. + # + # Setting behaviors only affects deprecations that happen after boot time. + # For more information you can read the documentation of the +behavior=+ method. + module Behavior + # Whether to print a backtrace along with the warning. + attr_accessor :debug + + # Returns the current behavior or if one isn't set, defaults to +:stderr+. + def behavior + @behavior ||= [DEFAULT_BEHAVIORS[:stderr]] + end + + # Sets the behavior to the specified value. Can be a single value, array, + # or an object that responds to +call+. + # + # Available behaviors: + # + # [+raise+] Raise ActiveSupport::DeprecationException. + # [+stderr+] Log all deprecation warnings to +$stderr+. + # [+log+] Log all deprecation warnings to +Rails.logger+. + # [+notify+] Use +ActiveSupport::Notifications+ to notify +deprecation.rails+. + # [+silence+] Do nothing. + # + # Setting behaviors only affects deprecations that happen after boot time. + # Deprecation warnings raised by gems are not affected by this setting + # because they happen before Rails boots up. + # + # ActiveSupport::Deprecation.behavior = :stderr + # ActiveSupport::Deprecation.behavior = [:stderr, :log] + # ActiveSupport::Deprecation.behavior = MyCustomHandler + # ActiveSupport::Deprecation.behavior = ->(message, callstack, deprecation_horizon, gem_name) { + # # custom stuff + # } + def behavior=(behavior) + @behavior = Array(behavior).map { |b| DEFAULT_BEHAVIORS[b] || arity_coerce(b) } + end + + private + def arity_coerce(behavior) + unless behavior.respond_to?(:call) + raise ArgumentError, "#{behavior.inspect} is not a valid deprecation behavior." + end + + if behavior.arity == 4 || behavior.arity == -1 + behavior + else + -> message, callstack, _, _ { behavior.call(message, callstack) } + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/deprecation/constant_accessor.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/deprecation/constant_accessor.rb new file mode 100644 index 00000000..1ed00158 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/deprecation/constant_accessor.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module ActiveSupport + class Deprecation + # DeprecatedConstantAccessor transforms a constant into a deprecated one by + # hooking +const_missing+. + # + # It takes the names of an old (deprecated) constant and of a new constant + # (both in string form) and optionally a deprecator. The deprecator defaults + # to +ActiveSupport::Deprecator+ if none is specified. + # + # The deprecated constant now returns the same object as the new one rather + # than a proxy object, so it can be used transparently in +rescue+ blocks + # etc. + # + # PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto) + # + # # (In a later update, the original implementation of `PLANETS` has been removed.) + # + # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune) + # include ActiveSupport::Deprecation::DeprecatedConstantAccessor + # deprecate_constant 'PLANETS', 'PLANETS_POST_2006' + # + # PLANETS.map { |planet| planet.capitalize } + # # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead. + # (Backtrace information…) + # ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"] + module DeprecatedConstantAccessor + def self.included(base) + require "active_support/inflector/methods" + + extension = Module.new do + def const_missing(missing_const_name) + if class_variable_defined?(:@@_deprecated_constants) + if (replacement = class_variable_get(:@@_deprecated_constants)[missing_const_name.to_s]) + replacement[:deprecator].warn(replacement[:message] || "#{name}::#{missing_const_name} is deprecated! Use #{replacement[:new]} instead.", caller_locations) + return ActiveSupport::Inflector.constantize(replacement[:new].to_s) + end + end + super + end + + def deprecate_constant(const_name, new_constant, message: nil, deprecator: ActiveSupport::Deprecation.instance) + class_variable_set(:@@_deprecated_constants, {}) unless class_variable_defined?(:@@_deprecated_constants) + class_variable_get(:@@_deprecated_constants)[const_name.to_s] = { new: new_constant, message: message, deprecator: deprecator } + end + end + base.singleton_class.prepend extension + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/deprecation/instance_delegator.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/deprecation/instance_delegator.rb new file mode 100644 index 00000000..8beda373 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/deprecation/instance_delegator.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require "active_support/core_ext/kernel/singleton_class" +require "active_support/core_ext/module/delegation" + +module ActiveSupport + class Deprecation + module InstanceDelegator # :nodoc: + def self.included(base) + base.extend(ClassMethods) + base.singleton_class.prepend(OverrideDelegators) + base.public_class_method :new + end + + module ClassMethods # :nodoc: + def include(included_module) + included_module.instance_methods.each { |m| method_added(m) } + super + end + + def method_added(method_name) + singleton_class.delegate(method_name, to: :instance) + end + end + + module OverrideDelegators # :nodoc: + def warn(message = nil, callstack = nil) + callstack ||= caller_locations(2) + super + end + + def deprecation_warning(deprecated_method_name, message = nil, caller_backtrace = nil) + caller_backtrace ||= caller_locations(2) + super + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/deprecation/method_wrappers.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/deprecation/method_wrappers.rb new file mode 100644 index 00000000..3c5d9cef --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/deprecation/method_wrappers.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/aliasing" +require "active_support/core_ext/array/extract_options" + +module ActiveSupport + class Deprecation + module MethodWrapper + # Declare that a method has been deprecated. + # + # class Fred + # def aaa; end + # def bbb; end + # def ccc; end + # def ddd; end + # def eee; end + # end + # + # Using the default deprecator: + # ActiveSupport::Deprecation.deprecate_methods(Fred, :aaa, bbb: :zzz, ccc: 'use Bar#ccc instead') + # # => Fred + # + # Fred.new.aaa + # # DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 5.1. (called from irb_binding at (irb):10) + # # => nil + # + # Fred.new.bbb + # # DEPRECATION WARNING: bbb is deprecated and will be removed from Rails 5.1 (use zzz instead). (called from irb_binding at (irb):11) + # # => nil + # + # Fred.new.ccc + # # DEPRECATION WARNING: ccc is deprecated and will be removed from Rails 5.1 (use Bar#ccc instead). (called from irb_binding at (irb):12) + # # => nil + # + # Passing in a custom deprecator: + # custom_deprecator = ActiveSupport::Deprecation.new('next-release', 'MyGem') + # ActiveSupport::Deprecation.deprecate_methods(Fred, ddd: :zzz, deprecator: custom_deprecator) + # # => [:ddd] + # + # Fred.new.ddd + # DEPRECATION WARNING: ddd is deprecated and will be removed from MyGem next-release (use zzz instead). (called from irb_binding at (irb):15) + # # => nil + # + # Using a custom deprecator directly: + # custom_deprecator = ActiveSupport::Deprecation.new('next-release', 'MyGem') + # custom_deprecator.deprecate_methods(Fred, eee: :zzz) + # # => [:eee] + # + # Fred.new.eee + # DEPRECATION WARNING: eee is deprecated and will be removed from MyGem next-release (use zzz instead). (called from irb_binding at (irb):18) + # # => nil + def deprecate_methods(target_module, *method_names) + options = method_names.extract_options! + deprecator = options.delete(:deprecator) || self + method_names += options.keys + mod = Module.new + + method_names.each do |method_name| + if target_module.method_defined?(method_name) || target_module.private_method_defined?(method_name) + aliased_method, punctuation = method_name.to_s.sub(/([?!=])$/, ""), $1 + with_method = "#{aliased_method}_with_deprecation#{punctuation}" + without_method = "#{aliased_method}_without_deprecation#{punctuation}" + + target_module.send(:define_method, with_method) do |*args, &block| + deprecator.deprecation_warning(method_name, options[method_name]) + send(without_method, *args, &block) + end + + target_module.send(:alias_method, without_method, method_name) + target_module.send(:alias_method, method_name, with_method) + + case + when target_module.protected_method_defined?(without_method) + target_module.send(:protected, method_name) + when target_module.private_method_defined?(without_method) + target_module.send(:private, method_name) + end + else + mod.send(:define_method, method_name) do |*args, &block| + deprecator.deprecation_warning(method_name, options[method_name]) + super(*args, &block) + end + end + end + + target_module.prepend(mod) unless mod.instance_methods(false).empty? + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/deprecation/proxy_wrappers.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/deprecation/proxy_wrappers.rb new file mode 100644 index 00000000..896c0d2d --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/deprecation/proxy_wrappers.rb @@ -0,0 +1,154 @@ +# frozen_string_literal: true + +require "active_support/core_ext/regexp" + +module ActiveSupport + class Deprecation + class DeprecationProxy #:nodoc: + def self.new(*args, &block) + object = args.first + + return object unless object + super + end + + instance_methods.each { |m| undef_method m unless /^__|^object_id$/.match?(m) } + + # Don't give a deprecation warning on inspect since test/unit and error + # logs rely on it for diagnostics. + def inspect + target.inspect + end + + private + def method_missing(called, *args, &block) + warn caller_locations, called, args + target.__send__(called, *args, &block) + end + end + + # DeprecatedObjectProxy transforms an object into a deprecated one. It + # takes an object, a deprecation message and optionally a deprecator. The + # deprecator defaults to +ActiveSupport::Deprecator+ if none is specified. + # + # deprecated_object = ActiveSupport::Deprecation::DeprecatedObjectProxy.new(Object.new, "This object is now deprecated") + # # => # + # + # deprecated_object.to_s + # DEPRECATION WARNING: This object is now deprecated. + # (Backtrace) + # # => "#" + class DeprecatedObjectProxy < DeprecationProxy + def initialize(object, message, deprecator = ActiveSupport::Deprecation.instance) + @object = object + @message = message + @deprecator = deprecator + end + + private + def target + @object + end + + def warn(callstack, called, args) + @deprecator.warn(@message, callstack) + end + end + + # DeprecatedInstanceVariableProxy transforms an instance variable into a + # deprecated one. It takes an instance of a class, a method on that class + # and an instance variable. It optionally takes a deprecator as the last + # argument. The deprecator defaults to +ActiveSupport::Deprecator+ if none + # is specified. + # + # class Example + # def initialize + # @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request) + # @_request = :special_request + # end + # + # def request + # @_request + # end + # + # def old_request + # @request + # end + # end + # + # example = Example.new + # # => # + # + # example.old_request.to_s + # # => DEPRECATION WARNING: @request is deprecated! Call request.to_s instead of + # @request.to_s + # (Backtrace information…) + # "special_request" + # + # example.request.to_s + # # => "special_request" + class DeprecatedInstanceVariableProxy < DeprecationProxy + def initialize(instance, method, var = "@#{method}", deprecator = ActiveSupport::Deprecation.instance) + @instance = instance + @method = method + @var = var + @deprecator = deprecator + end + + private + def target + @instance.__send__(@method) + end + + def warn(callstack, called, args) + @deprecator.warn("#{@var} is deprecated! Call #{@method}.#{called} instead of #{@var}.#{called}. Args: #{args.inspect}", callstack) + end + end + + # DeprecatedConstantProxy transforms a constant into a deprecated one. It + # takes the names of an old (deprecated) constant and of a new constant + # (both in string form) and optionally a deprecator. The deprecator defaults + # to +ActiveSupport::Deprecator+ if none is specified. The deprecated constant + # now returns the value of the new one. + # + # PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto) + # + # # (In a later update, the original implementation of `PLANETS` has been removed.) + # + # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune) + # PLANETS = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('PLANETS', 'PLANETS_POST_2006') + # + # PLANETS.map { |planet| planet.capitalize } + # # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead. + # (Backtrace information…) + # ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"] + class DeprecatedConstantProxy < DeprecationProxy + def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance, message: "#{old_const} is deprecated! Use #{new_const} instead.") + require "active_support/inflector/methods" + + @old_const = old_const + @new_const = new_const + @deprecator = deprecator + @message = message + end + + # Returns the class of the new constant. + # + # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune) + # PLANETS = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('PLANETS', 'PLANETS_POST_2006') + # PLANETS.class # => Array + def class + target.class + end + + private + def target + ActiveSupport::Inflector.constantize(@new_const.to_s) + end + + def warn(callstack, called, args) + @deprecator.warn(@message, callstack) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/deprecation/reporting.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/deprecation/reporting.rb new file mode 100644 index 00000000..7075b5b8 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/deprecation/reporting.rb @@ -0,0 +1,114 @@ +# frozen_string_literal: true + +require "rbconfig" + +module ActiveSupport + class Deprecation + module Reporting + # Whether to print a message (silent mode) + attr_accessor :silenced + # Name of gem where method is deprecated + attr_accessor :gem_name + + # Outputs a deprecation warning to the output configured by + # ActiveSupport::Deprecation.behavior. + # + # ActiveSupport::Deprecation.warn('something broke!') + # # => "DEPRECATION WARNING: something broke! (called from your_code.rb:1)" + def warn(message = nil, callstack = nil) + return if silenced + + callstack ||= caller_locations(2) + deprecation_message(callstack, message).tap do |m| + behavior.each { |b| b.call(m, callstack, deprecation_horizon, gem_name) } + end + end + + # Silence deprecation warnings within the block. + # + # ActiveSupport::Deprecation.warn('something broke!') + # # => "DEPRECATION WARNING: something broke! (called from your_code.rb:1)" + # + # ActiveSupport::Deprecation.silence do + # ActiveSupport::Deprecation.warn('something broke!') + # end + # # => nil + def silence + old_silenced, @silenced = @silenced, true + yield + ensure + @silenced = old_silenced + end + + def deprecation_warning(deprecated_method_name, message = nil, caller_backtrace = nil) + caller_backtrace ||= caller_locations(2) + deprecated_method_warning(deprecated_method_name, message).tap do |msg| + warn(msg, caller_backtrace) + end + end + + private + # Outputs a deprecation warning message + # + # deprecated_method_warning(:method_name) + # # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon}" + # deprecated_method_warning(:method_name, :another_method) + # # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (use another_method instead)" + # deprecated_method_warning(:method_name, "Optional message") + # # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (Optional message)" + def deprecated_method_warning(method_name, message = nil) + warning = "#{method_name} is deprecated and will be removed from #{gem_name} #{deprecation_horizon}" + case message + when Symbol then "#{warning} (use #{message} instead)" + when String then "#{warning} (#{message})" + else warning + end + end + + def deprecation_message(callstack, message = nil) + message ||= "You are using deprecated behavior which will be removed from the next major or minor release." + "DEPRECATION WARNING: #{message} #{deprecation_caller_message(callstack)}" + end + + def deprecation_caller_message(callstack) + file, line, method = extract_callstack(callstack) + if file + if line && method + "(called from #{method} at #{file}:#{line})" + else + "(called from #{file}:#{line})" + end + end + end + + def extract_callstack(callstack) + return _extract_callstack(callstack) if callstack.first.is_a? String + + offending_line = callstack.find { |frame| + frame.absolute_path && !ignored_callstack(frame.absolute_path) + } || callstack.first + + [offending_line.path, offending_line.lineno, offending_line.label] + end + + def _extract_callstack(callstack) + warn "Please pass `caller_locations` to the deprecation API" if $VERBOSE + offending_line = callstack.find { |line| !ignored_callstack(line) } || callstack.first + + if offending_line + if md = offending_line.match(/^(.+?):(\d+)(?::in `(.*?)')?/) + md.captures + else + offending_line + end + end + end + + RAILS_GEM_ROOT = File.expand_path("../../../..", __dir__) + "/" + + def ignored_callstack(path) + path.start_with?(RAILS_GEM_ROOT) || path.start_with?(RbConfig::CONFIG["rubylibdir"]) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/descendants_tracker.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/descendants_tracker.rb new file mode 100644 index 00000000..a4cee788 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/descendants_tracker.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module ActiveSupport + # This module provides an internal implementation to track descendants + # which is faster than iterating through ObjectSpace. + module DescendantsTracker + @@direct_descendants = {} + + class << self + def direct_descendants(klass) + @@direct_descendants[klass] || [] + end + + def descendants(klass) + arr = [] + accumulate_descendants(klass, arr) + arr + end + + def clear + if defined? ActiveSupport::Dependencies + @@direct_descendants.each do |klass, descendants| + if ActiveSupport::Dependencies.autoloaded?(klass) + @@direct_descendants.delete(klass) + else + descendants.reject! { |v| ActiveSupport::Dependencies.autoloaded?(v) } + end + end + else + @@direct_descendants.clear + end + end + + # This is the only method that is not thread safe, but is only ever called + # during the eager loading phase. + def store_inherited(klass, descendant) + (@@direct_descendants[klass] ||= []) << descendant + end + + private + def accumulate_descendants(klass, acc) + if direct_descendants = @@direct_descendants[klass] + acc.concat(direct_descendants) + direct_descendants.each { |direct_descendant| accumulate_descendants(direct_descendant, acc) } + end + end + end + + def inherited(base) + DescendantsTracker.store_inherited(self, base) + super + end + + def direct_descendants + DescendantsTracker.direct_descendants(self) + end + + def descendants + DescendantsTracker.descendants(self) + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/digest.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/digest.rb new file mode 100644 index 00000000..fba10fbd --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/digest.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module ActiveSupport + class Digest #:nodoc: + class <(other) + if Scalar === other || Duration === other + value <=> other.value + elsif Numeric === other + value <=> other + else + nil + end + end + + def +(other) + if Duration === other + seconds = value + other.parts[:seconds] + new_parts = other.parts.merge(seconds: seconds) + new_value = value + other.value + + Duration.new(new_value, new_parts) + else + calculate(:+, other) + end + end + + def -(other) + if Duration === other + seconds = value - other.parts[:seconds] + new_parts = other.parts.map { |part, other_value| [part, -other_value] }.to_h + new_parts = new_parts.merge(seconds: seconds) + new_value = value - other.value + + Duration.new(new_value, new_parts) + else + calculate(:-, other) + end + end + + def *(other) + if Duration === other + new_parts = other.parts.map { |part, other_value| [part, value * other_value] }.to_h + new_value = value * other.value + + Duration.new(new_value, new_parts) + else + calculate(:*, other) + end + end + + def /(other) + if Duration === other + value / other.value + else + calculate(:/, other) + end + end + + def %(other) + if Duration === other + Duration.build(value % other.value) + else + calculate(:%, other) + end + end + + private + def calculate(op, other) + if Scalar === other + Scalar.new(value.public_send(op, other.value)) + elsif Numeric === other + Scalar.new(value.public_send(op, other)) + else + raise_type_error(other) + end + end + + def raise_type_error(other) + raise TypeError, "no implicit conversion of #{other.class} into #{self.class}" + end + end + + SECONDS_PER_MINUTE = 60 + SECONDS_PER_HOUR = 3600 + SECONDS_PER_DAY = 86400 + SECONDS_PER_WEEK = 604800 + SECONDS_PER_MONTH = 2629746 # 1/12 of a gregorian year + SECONDS_PER_YEAR = 31556952 # length of a gregorian year (365.2425 days) + + PARTS_IN_SECONDS = { + seconds: 1, + minutes: SECONDS_PER_MINUTE, + hours: SECONDS_PER_HOUR, + days: SECONDS_PER_DAY, + weeks: SECONDS_PER_WEEK, + months: SECONDS_PER_MONTH, + years: SECONDS_PER_YEAR + }.freeze + + PARTS = [:years, :months, :weeks, :days, :hours, :minutes, :seconds].freeze + + attr_accessor :value, :parts + + autoload :ISO8601Parser, "active_support/duration/iso8601_parser" + autoload :ISO8601Serializer, "active_support/duration/iso8601_serializer" + + class << self + # Creates a new Duration from string formatted according to ISO 8601 Duration. + # + # See {ISO 8601}[https://en.wikipedia.org/wiki/ISO_8601#Durations] for more information. + # This method allows negative parts to be present in pattern. + # If invalid string is provided, it will raise +ActiveSupport::Duration::ISO8601Parser::ParsingError+. + def parse(iso8601duration) + parts = ISO8601Parser.new(iso8601duration).parse! + new(calculate_total_seconds(parts), parts) + end + + def ===(other) #:nodoc: + other.is_a?(Duration) + rescue ::NoMethodError + false + end + + def seconds(value) #:nodoc: + new(value, [[:seconds, value]]) + end + + def minutes(value) #:nodoc: + new(value * SECONDS_PER_MINUTE, [[:minutes, value]]) + end + + def hours(value) #:nodoc: + new(value * SECONDS_PER_HOUR, [[:hours, value]]) + end + + def days(value) #:nodoc: + new(value * SECONDS_PER_DAY, [[:days, value]]) + end + + def weeks(value) #:nodoc: + new(value * SECONDS_PER_WEEK, [[:weeks, value]]) + end + + def months(value) #:nodoc: + new(value * SECONDS_PER_MONTH, [[:months, value]]) + end + + def years(value) #:nodoc: + new(value * SECONDS_PER_YEAR, [[:years, value]]) + end + + # Creates a new Duration from a seconds value that is converted + # to the individual parts: + # + # ActiveSupport::Duration.build(31556952).parts # => {:years=>1} + # ActiveSupport::Duration.build(2716146).parts # => {:months=>1, :days=>1} + # + def build(value) + parts = {} + remainder = value.to_f + + PARTS.each do |part| + unless part == :seconds + part_in_seconds = PARTS_IN_SECONDS[part] + parts[part] = remainder.div(part_in_seconds) + remainder = (remainder % part_in_seconds).round(9) + end + end + + parts[:seconds] = remainder + + new(value, parts) + end + + private + + def calculate_total_seconds(parts) + parts.inject(0) do |total, (part, value)| + total + value * PARTS_IN_SECONDS[part] + end + end + end + + def initialize(value, parts) #:nodoc: + @value, @parts = value, parts.to_h + @parts.default = 0 + @parts.reject! { |k, v| v.zero? } + end + + def coerce(other) #:nodoc: + if Scalar === other + [other, self] + else + [Scalar.new(other), self] + end + end + + # Compares one Duration with another or a Numeric to this Duration. + # Numeric values are treated as seconds. + def <=>(other) + if Duration === other + value <=> other.value + elsif Numeric === other + value <=> other + end + end + + # Adds another Duration or a Numeric to this Duration. Numeric values + # are treated as seconds. + def +(other) + if Duration === other + parts = @parts.dup + other.parts.each do |(key, value)| + parts[key] += value + end + Duration.new(value + other.value, parts) + else + seconds = @parts[:seconds] + other + Duration.new(value + other, @parts.merge(seconds: seconds)) + end + end + + # Subtracts another Duration or a Numeric from this Duration. Numeric + # values are treated as seconds. + def -(other) + self + (-other) + end + + # Multiplies this Duration by a Numeric and returns a new Duration. + def *(other) + if Scalar === other || Duration === other + Duration.new(value * other.value, parts.map { |type, number| [type, number * other.value] }) + elsif Numeric === other + Duration.new(value * other, parts.map { |type, number| [type, number * other] }) + else + raise_type_error(other) + end + end + + # Divides this Duration by a Numeric and returns a new Duration. + def /(other) + if Scalar === other + Duration.new(value / other.value, parts.map { |type, number| [type, number / other.value] }) + elsif Duration === other + value / other.value + elsif Numeric === other + Duration.new(value / other, parts.map { |type, number| [type, number / other] }) + else + raise_type_error(other) + end + end + + # Returns the modulo of this Duration by another Duration or Numeric. + # Numeric values are treated as seconds. + def %(other) + if Duration === other || Scalar === other + Duration.build(value % other.value) + elsif Numeric === other + Duration.build(value % other) + else + raise_type_error(other) + end + end + + def -@ #:nodoc: + Duration.new(-value, parts.map { |type, number| [type, -number] }) + end + + def is_a?(klass) #:nodoc: + Duration == klass || value.is_a?(klass) + end + alias :kind_of? :is_a? + + def instance_of?(klass) # :nodoc: + Duration == klass || value.instance_of?(klass) + end + + # Returns +true+ if +other+ is also a Duration instance with the + # same +value+, or if other == value. + def ==(other) + if Duration === other + other.value == value + else + other == value + end + end + + # Returns the amount of seconds a duration covers as a string. + # For more information check to_i method. + # + # 1.day.to_s # => "86400" + def to_s + @value.to_s + end + + # Returns the number of seconds that this Duration represents. + # + # 1.minute.to_i # => 60 + # 1.hour.to_i # => 3600 + # 1.day.to_i # => 86400 + # + # Note that this conversion makes some assumptions about the + # duration of some periods, e.g. months are always 1/12 of year + # and years are 365.2425 days: + # + # # equivalent to (1.year / 12).to_i + # 1.month.to_i # => 2629746 + # + # # equivalent to 365.2425.days.to_i + # 1.year.to_i # => 31556952 + # + # In such cases, Ruby's core + # Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and + # Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision + # date and time arithmetic. + def to_i + @value.to_i + end + + # Returns +true+ if +other+ is also a Duration instance, which has the + # same parts as this one. + def eql?(other) + Duration === other && other.value.eql?(value) + end + + def hash + @value.hash + end + + # Calculates a new Time or Date that is as far in the future + # as this Duration represents. + def since(time = ::Time.current) + sum(1, time) + end + alias :from_now :since + alias :after :since + + # Calculates a new Time or Date that is as far in the past + # as this Duration represents. + def ago(time = ::Time.current) + sum(-1, time) + end + alias :until :ago + alias :before :ago + + def inspect #:nodoc: + return "0 seconds" if parts.empty? + + parts. + reduce(::Hash.new(0)) { |h, (l, r)| h[l] += r; h }. + sort_by { |unit, _ | PARTS.index(unit) }. + map { |unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}" }. + to_sentence(locale: ::I18n.default_locale) + end + + def as_json(options = nil) #:nodoc: + to_i + end + + def init_with(coder) #:nodoc: + initialize(coder["value"], coder["parts"]) + end + + def encode_with(coder) #:nodoc: + coder.map = { "value" => @value, "parts" => @parts } + end + + # Build ISO 8601 Duration string for this duration. + # The +precision+ parameter can be used to limit seconds' precision of duration. + def iso8601(precision: nil) + ISO8601Serializer.new(self, precision: precision).serialize + end + + private + + def sum(sign, time = ::Time.current) + parts.inject(time) do |t, (type, number)| + if t.acts_like?(:time) || t.acts_like?(:date) + if type == :seconds + t.since(sign * number) + elsif type == :minutes + t.since(sign * number * 60) + elsif type == :hours + t.since(sign * number * 3600) + else + t.advance(type => sign * number) + end + else + raise ::ArgumentError, "expected a time or date, got #{time.inspect}" + end + end + end + + def respond_to_missing?(method, _) + value.respond_to?(method) + end + + def method_missing(method, *args, &block) + value.public_send(method, *args, &block) + end + + def raise_type_error(other) + raise TypeError, "no implicit conversion of #{other.class} into #{self.class}" + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/duration/iso8601_parser.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/duration/iso8601_parser.rb new file mode 100644 index 00000000..1847eeaa --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/duration/iso8601_parser.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +require "strscan" +require "active_support/core_ext/regexp" + +module ActiveSupport + class Duration + # Parses a string formatted according to ISO 8601 Duration into the hash. + # + # See {ISO 8601}[https://en.wikipedia.org/wiki/ISO_8601#Durations] for more information. + # + # This parser allows negative parts to be present in pattern. + class ISO8601Parser # :nodoc: + class ParsingError < ::ArgumentError; end + + PERIOD_OR_COMMA = /\.|,/ + PERIOD = ".".freeze + COMMA = ",".freeze + + SIGN_MARKER = /\A\-|\+|/ + DATE_MARKER = /P/ + TIME_MARKER = /T/ + DATE_COMPONENT = /(\-?\d+(?:[.,]\d+)?)(Y|M|D|W)/ + TIME_COMPONENT = /(\-?\d+(?:[.,]\d+)?)(H|M|S)/ + + DATE_TO_PART = { "Y" => :years, "M" => :months, "W" => :weeks, "D" => :days } + TIME_TO_PART = { "H" => :hours, "M" => :minutes, "S" => :seconds } + + DATE_COMPONENTS = [:years, :months, :days] + TIME_COMPONENTS = [:hours, :minutes, :seconds] + + attr_reader :parts, :scanner + attr_accessor :mode, :sign + + def initialize(string) + @scanner = StringScanner.new(string) + @parts = {} + @mode = :start + @sign = 1 + end + + def parse! + while !finished? + case mode + when :start + if scan(SIGN_MARKER) + self.sign = (scanner.matched == "-") ? -1 : 1 + self.mode = :sign + else + raise_parsing_error + end + + when :sign + if scan(DATE_MARKER) + self.mode = :date + else + raise_parsing_error + end + + when :date + if scan(TIME_MARKER) + self.mode = :time + elsif scan(DATE_COMPONENT) + parts[DATE_TO_PART[scanner[2]]] = number * sign + else + raise_parsing_error + end + + when :time + if scan(TIME_COMPONENT) + parts[TIME_TO_PART[scanner[2]]] = number * sign + else + raise_parsing_error + end + + end + end + + validate! + parts + end + + private + + def finished? + scanner.eos? + end + + # Parses number which can be a float with either comma or period. + def number + PERIOD_OR_COMMA.match?(scanner[1]) ? scanner[1].tr(COMMA, PERIOD).to_f : scanner[1].to_i + end + + def scan(pattern) + scanner.scan(pattern) + end + + def raise_parsing_error(reason = nil) + raise ParsingError, "Invalid ISO 8601 duration: #{scanner.string.inspect} #{reason}".strip + end + + # Checks for various semantic errors as stated in ISO 8601 standard. + def validate! + raise_parsing_error("is empty duration") if parts.empty? + + # Mixing any of Y, M, D with W is invalid. + if parts.key?(:weeks) && (parts.keys & DATE_COMPONENTS).any? + raise_parsing_error("mixing weeks with other date parts not allowed") + end + + # Specifying an empty T part is invalid. + if mode == :time && (parts.keys & TIME_COMPONENTS).empty? + raise_parsing_error("time part marker is present but time part is empty") + end + + fractions = parts.values.reject(&:zero?).select { |a| (a % 1) != 0 } + unless fractions.empty? || (fractions.size == 1 && fractions.last == @parts.values.reject(&:zero?).last) + raise_parsing_error "(only last part can be fractional)" + end + + true + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/duration/iso8601_serializer.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/duration/iso8601_serializer.rb new file mode 100644 index 00000000..bb177ae5 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/duration/iso8601_serializer.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/blank" +require "active_support/core_ext/hash/transform_values" + +module ActiveSupport + class Duration + # Serializes duration to string according to ISO 8601 Duration format. + class ISO8601Serializer # :nodoc: + def initialize(duration, precision: nil) + @duration = duration + @precision = precision + end + + # Builds and returns output string. + def serialize + parts, sign = normalize + return "PT0S".freeze if parts.empty? + + output = "P".dup + output << "#{parts[:years]}Y" if parts.key?(:years) + output << "#{parts[:months]}M" if parts.key?(:months) + output << "#{parts[:weeks]}W" if parts.key?(:weeks) + output << "#{parts[:days]}D" if parts.key?(:days) + time = "".dup + time << "#{parts[:hours]}H" if parts.key?(:hours) + time << "#{parts[:minutes]}M" if parts.key?(:minutes) + if parts.key?(:seconds) + time << "#{sprintf(@precision ? "%0.0#{@precision}f" : '%g', parts[:seconds])}S" + end + output << "T#{time}" unless time.empty? + "#{sign}#{output}" + end + + private + + # Return pair of duration's parts and whole duration sign. + # Parts are summarized (as they can become repetitive due to addition, etc). + # Zero parts are removed as not significant. + # If all parts are negative it will negate all of them and return minus as a sign. + def normalize + parts = @duration.parts.each_with_object(Hash.new(0)) do |(k, v), p| + p[k] += v unless v.zero? + end + # If all parts are negative - let's make a negative duration + sign = "" + if parts.values.all? { |v| v < 0 } + sign = "-" + parts.transform_values!(&:-@) + end + [parts, sign] + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/encrypted_configuration.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/encrypted_configuration.rb new file mode 100644 index 00000000..b17695fe --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/encrypted_configuration.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require "yaml" +require "active_support/encrypted_file" +require "active_support/ordered_options" +require "active_support/core_ext/object/inclusion" +require "active_support/core_ext/module/delegation" + +module ActiveSupport + class EncryptedConfiguration < EncryptedFile + delegate :[], :fetch, to: :config + delegate_missing_to :options + + def initialize(config_path:, key_path:, env_key:, raise_if_missing_key:) + super content_path: config_path, key_path: key_path, + env_key: env_key, raise_if_missing_key: raise_if_missing_key + end + + # Allow a config to be started without a file present + def read + super + rescue ActiveSupport::EncryptedFile::MissingContentError + "" + end + + def write(contents) + deserialize(contents) + + super + end + + def config + @config ||= deserialize(read).deep_symbolize_keys + end + + private + def options + @options ||= ActiveSupport::InheritableOptions.new(config) + end + + def serialize(config) + config.present? ? YAML.dump(config) : "" + end + + def deserialize(config) + YAML.load(config).presence || {} + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/encrypted_file.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/encrypted_file.rb new file mode 100644 index 00000000..c66f1b55 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/encrypted_file.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +require "pathname" +require "active_support/message_encryptor" + +module ActiveSupport + class EncryptedFile + class MissingContentError < RuntimeError + def initialize(content_path) + super "Missing encrypted content file in #{content_path}." + end + end + + class MissingKeyError < RuntimeError + def initialize(key_path:, env_key:) + super \ + "Missing encryption key to decrypt file with. " + + "Ask your team for your master key and write it to #{key_path} or put it in the ENV['#{env_key}']." + end + end + + CIPHER = "aes-128-gcm" + + def self.generate_key + SecureRandom.hex(ActiveSupport::MessageEncryptor.key_len(CIPHER)) + end + + + attr_reader :content_path, :key_path, :env_key, :raise_if_missing_key + + def initialize(content_path:, key_path:, env_key:, raise_if_missing_key:) + @content_path, @key_path = Pathname.new(content_path), Pathname.new(key_path) + @env_key, @raise_if_missing_key = env_key, raise_if_missing_key + end + + def key + read_env_key || read_key_file || handle_missing_key + end + + def read + if !key.nil? && content_path.exist? + decrypt content_path.binread + else + raise MissingContentError, content_path + end + end + + def write(contents) + IO.binwrite "#{content_path}.tmp", encrypt(contents) + FileUtils.mv "#{content_path}.tmp", content_path + end + + def change(&block) + writing read, &block + end + + + private + def writing(contents) + tmp_file = "#{Process.pid}.#{content_path.basename.to_s.chomp('.enc')}" + tmp_path = Pathname.new File.join(Dir.tmpdir, tmp_file) + tmp_path.binwrite contents + + yield tmp_path + + updated_contents = tmp_path.binread + + write(updated_contents) if updated_contents != contents + ensure + FileUtils.rm(tmp_path) if tmp_path.exist? + end + + + def encrypt(contents) + encryptor.encrypt_and_sign contents + end + + def decrypt(contents) + encryptor.decrypt_and_verify contents + end + + def encryptor + @encryptor ||= ActiveSupport::MessageEncryptor.new([ key ].pack("H*"), cipher: CIPHER) + end + + + def read_env_key + ENV[env_key] + end + + def read_key_file + key_path.binread.strip if key_path.exist? + end + + def handle_missing_key + raise MissingKeyError, key_path: key_path, env_key: env_key if raise_if_missing_key + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/evented_file_update_checker.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/evented_file_update_checker.rb new file mode 100644 index 00000000..97e982eb --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/evented_file_update_checker.rb @@ -0,0 +1,205 @@ +# frozen_string_literal: true + +require "set" +require "pathname" +require "concurrent/atomic/atomic_boolean" + +module ActiveSupport + # Allows you to "listen" to changes in a file system. + # The evented file updater does not hit disk when checking for updates + # instead it uses platform specific file system events to trigger a change + # in state. + # + # The file checker takes an array of files to watch or a hash specifying directories + # and file extensions to watch. It also takes a block that is called when + # EventedFileUpdateChecker#execute is run or when EventedFileUpdateChecker#execute_if_updated + # is run and there have been changes to the file system. + # + # Note: Forking will cause the first call to `updated?` to return `true`. + # + # Example: + # + # checker = ActiveSupport::EventedFileUpdateChecker.new(["/tmp/foo"]) { puts "changed" } + # checker.updated? + # # => false + # checker.execute_if_updated + # # => nil + # + # FileUtils.touch("/tmp/foo") + # + # checker.updated? + # # => true + # checker.execute_if_updated + # # => "changed" + # + class EventedFileUpdateChecker #:nodoc: all + def initialize(files, dirs = {}, &block) + unless block + raise ArgumentError, "A block is required to initialize an EventedFileUpdateChecker" + end + + @ph = PathHelper.new + @files = files.map { |f| @ph.xpath(f) }.to_set + + @dirs = {} + dirs.each do |dir, exts| + @dirs[@ph.xpath(dir)] = Array(exts).map { |ext| @ph.normalize_extension(ext) } + end + + @block = block + @updated = Concurrent::AtomicBoolean.new(false) + @lcsp = @ph.longest_common_subpath(@dirs.keys) + @pid = Process.pid + @boot_mutex = Mutex.new + + if (@dtw = directories_to_watch).any? + # Loading listen triggers warnings. These are originated by a legit + # usage of attr_* macros for private attributes, but adds a lot of noise + # to our test suite. Thus, we lazy load it and disable warnings locally. + silence_warnings do + begin + require "listen" + rescue LoadError => e + raise LoadError, "Could not load the 'listen' gem. Add `gem 'listen'` to the development group of your Gemfile", e.backtrace + end + end + end + boot! + end + + def updated? + @boot_mutex.synchronize do + if @pid != Process.pid + boot! + @pid = Process.pid + @updated.make_true + end + end + @updated.true? + end + + def execute + @updated.make_false + @block.call + end + + def execute_if_updated + if updated? + yield if block_given? + execute + true + end + end + + private + def boot! + Listen.to(*@dtw, &method(:changed)).start + end + + def changed(modified, added, removed) + unless updated? + @updated.make_true if (modified + added + removed).any? { |f| watching?(f) } + end + end + + def watching?(file) + file = @ph.xpath(file) + + if @files.member?(file) + true + elsif file.directory? + false + else + ext = @ph.normalize_extension(file.extname) + + file.dirname.ascend do |dir| + if @dirs.fetch(dir, []).include?(ext) + break true + elsif dir == @lcsp || dir.root? + break false + end + end + end + end + + def directories_to_watch + dtw = (@files + @dirs.keys).map { |f| @ph.existing_parent(f) } + dtw.compact! + dtw.uniq! + + normalized_gem_paths = Gem.path.map { |path| File.join path, "" } + dtw = dtw.reject do |path| + normalized_gem_paths.any? { |gem_path| path.to_s.start_with?(gem_path) } + end + + @ph.filter_out_descendants(dtw) + end + + class PathHelper + def xpath(path) + Pathname.new(path).expand_path + end + + def normalize_extension(ext) + ext.to_s.sub(/\A\./, "") + end + + # Given a collection of Pathname objects returns the longest subpath + # common to all of them, or +nil+ if there is none. + def longest_common_subpath(paths) + return if paths.empty? + + lcsp = Pathname.new(paths[0]) + + paths[1..-1].each do |path| + until ascendant_of?(lcsp, path) + if lcsp.root? + # If we get here a root directory is not an ascendant of path. + # This may happen if there are paths in different drives on + # Windows. + return + else + lcsp = lcsp.parent + end + end + end + + lcsp + end + + # Returns the deepest existing ascendant, which could be the argument itself. + def existing_parent(dir) + dir.ascend do |ascendant| + break ascendant if ascendant.directory? + end + end + + # Filters out directories which are descendants of others in the collection (stable). + def filter_out_descendants(dirs) + return dirs if dirs.length < 2 + + dirs_sorted_by_nparts = dirs.sort_by { |dir| dir.each_filename.to_a.length } + descendants = [] + + until dirs_sorted_by_nparts.empty? + dir = dirs_sorted_by_nparts.shift + + dirs_sorted_by_nparts.reject! do |possible_descendant| + ascendant_of?(dir, possible_descendant) && descendants << possible_descendant + end + end + + # Array#- preserves order. + dirs - descendants + end + + private + + def ascendant_of?(base, other) + base != other && other.ascend do |ascendant| + break true if base == ascendant + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/execution_wrapper.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/execution_wrapper.rb new file mode 100644 index 00000000..f48c586c --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/execution_wrapper.rb @@ -0,0 +1,128 @@ +# frozen_string_literal: true + +require "active_support/callbacks" + +module ActiveSupport + class ExecutionWrapper + include ActiveSupport::Callbacks + + Null = Object.new # :nodoc: + def Null.complete! # :nodoc: + end + + define_callbacks :run + define_callbacks :complete + + def self.to_run(*args, &block) + set_callback(:run, *args, &block) + end + + def self.to_complete(*args, &block) + set_callback(:complete, *args, &block) + end + + RunHook = Struct.new(:hook) do # :nodoc: + def before(target) + hook_state = target.send(:hook_state) + hook_state[hook] = hook.run + end + end + + CompleteHook = Struct.new(:hook) do # :nodoc: + def before(target) + hook_state = target.send(:hook_state) + if hook_state.key?(hook) + hook.complete hook_state[hook] + end + end + alias after before + end + + # Register an object to be invoked during both the +run+ and + # +complete+ steps. + # + # +hook.complete+ will be passed the value returned from +hook.run+, + # and will only be invoked if +run+ has previously been called. + # (Mostly, this means it won't be invoked if an exception occurs in + # a preceding +to_run+ block; all ordinary +to_complete+ blocks are + # invoked in that situation.) + def self.register_hook(hook, outer: false) + if outer + to_run RunHook.new(hook), prepend: true + to_complete :after, CompleteHook.new(hook) + else + to_run RunHook.new(hook) + to_complete CompleteHook.new(hook) + end + end + + # Run this execution. + # + # Returns an instance, whose +complete!+ method *must* be invoked + # after the work has been performed. + # + # Where possible, prefer +wrap+. + def self.run! + if active? + Null + else + new.tap do |instance| + success = nil + begin + instance.run! + success = true + ensure + instance.complete! unless success + end + end + end + end + + # Perform the work in the supplied block as an execution. + def self.wrap + return yield if active? + + instance = run! + begin + yield + ensure + instance.complete! + end + end + + class << self # :nodoc: + attr_accessor :active + end + + def self.inherited(other) # :nodoc: + super + other.active = Concurrent::Hash.new + end + + self.active = Concurrent::Hash.new + + def self.active? # :nodoc: + @active[Thread.current] + end + + def run! # :nodoc: + self.class.active[Thread.current] = true + run_callbacks(:run) + end + + # Complete this in-flight execution. This method *must* be called + # exactly once on the result of any call to +run!+. + # + # Where possible, prefer +wrap+. + def complete! + run_callbacks(:complete) + ensure + self.class.active.delete Thread.current + end + + private + def hook_state + @_hook_state ||= {} + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/executor.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/executor.rb new file mode 100644 index 00000000..ce391b07 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/executor.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require "active_support/execution_wrapper" + +module ActiveSupport + class Executor < ExecutionWrapper + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/file_update_checker.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/file_update_checker.rb new file mode 100644 index 00000000..1a0bb108 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/file_update_checker.rb @@ -0,0 +1,163 @@ +# frozen_string_literal: true + +require "active_support/core_ext/time/calculations" + +module ActiveSupport + # FileUpdateChecker specifies the API used by Rails to watch files + # and control reloading. The API depends on four methods: + # + # * +initialize+ which expects two parameters and one block as + # described below. + # + # * +updated?+ which returns a boolean if there were updates in + # the filesystem or not. + # + # * +execute+ which executes the given block on initialization + # and updates the latest watched files and timestamp. + # + # * +execute_if_updated+ which just executes the block if it was updated. + # + # After initialization, a call to +execute_if_updated+ must execute + # the block only if there was really a change in the filesystem. + # + # This class is used by Rails to reload the I18n framework whenever + # they are changed upon a new request. + # + # i18n_reloader = ActiveSupport::FileUpdateChecker.new(paths) do + # I18n.reload! + # end + # + # ActiveSupport::Reloader.to_prepare do + # i18n_reloader.execute_if_updated + # end + class FileUpdateChecker + # It accepts two parameters on initialization. The first is an array + # of files and the second is an optional hash of directories. The hash must + # have directories as keys and the value is an array of extensions to be + # watched under that directory. + # + # This method must also receive a block that will be called once a path + # changes. The array of files and list of directories cannot be changed + # after FileUpdateChecker has been initialized. + def initialize(files, dirs = {}, &block) + unless block + raise ArgumentError, "A block is required to initialize a FileUpdateChecker" + end + + @files = files.freeze + @glob = compile_glob(dirs) + @block = block + + @watched = nil + @updated_at = nil + + @last_watched = watched + @last_update_at = updated_at(@last_watched) + end + + # Check if any of the entries were updated. If so, the watched and/or + # updated_at values are cached until the block is executed via +execute+ + # or +execute_if_updated+. + def updated? + current_watched = watched + if @last_watched.size != current_watched.size + @watched = current_watched + true + else + current_updated_at = updated_at(current_watched) + if @last_update_at < current_updated_at + @watched = current_watched + @updated_at = current_updated_at + true + else + false + end + end + end + + # Executes the given block and updates the latest watched files and + # timestamp. + def execute + @last_watched = watched + @last_update_at = updated_at(@last_watched) + @block.call + ensure + @watched = nil + @updated_at = nil + end + + # Execute the block given if updated. + def execute_if_updated + if updated? + yield if block_given? + execute + true + else + false + end + end + + private + + def watched + @watched || begin + all = @files.select { |f| File.exist?(f) } + all.concat(Dir[@glob]) if @glob + all + end + end + + def updated_at(paths) + @updated_at || max_mtime(paths) || Time.at(0) + end + + # This method returns the maximum mtime of the files in +paths+, or +nil+ + # if the array is empty. + # + # Files with a mtime in the future are ignored. Such abnormal situation + # can happen for example if the user changes the clock by hand. It is + # healthy to consider this edge case because with mtimes in the future + # reloading is not triggered. + def max_mtime(paths) + time_now = Time.now + max_mtime = nil + + # Time comparisons are performed with #compare_without_coercion because + # AS redefines these operators in a way that is much slower and does not + # bring any benefit in this particular code. + # + # Read t1.compare_without_coercion(t2) < 0 as t1 < t2. + paths.each do |path| + mtime = File.mtime(path) + + next if time_now.compare_without_coercion(mtime) < 0 + + if max_mtime.nil? || max_mtime.compare_without_coercion(mtime) < 0 + max_mtime = mtime + end + end + + max_mtime + end + + def compile_glob(hash) + hash.freeze # Freeze so changes aren't accidentally pushed + return if hash.empty? + + globs = hash.map do |key, value| + "#{escape(key)}/**/*#{compile_ext(value)}" + end + "{#{globs.join(",")}}" + end + + def escape(key) + key.gsub(",", '\,') + end + + def compile_ext(array) + array = Array(array) + return if array.empty? + ".{#{array.join(",")}}" + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/gem_version.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/gem_version.rb new file mode 100644 index 00000000..e7b802d7 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/gem_version.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module ActiveSupport + # Returns the version of the currently loaded Active Support as a Gem::Version. + def self.gem_version + Gem::Version.new VERSION::STRING + end + + module VERSION + MAJOR = 5 + MINOR = 2 + TINY = 3 + PRE = nil + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/gzip.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/gzip.rb new file mode 100644 index 00000000..7ffa6d90 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/gzip.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "zlib" +require "stringio" + +module ActiveSupport + # A convenient wrapper for the zlib standard library that allows + # compression/decompression of strings with gzip. + # + # gzip = ActiveSupport::Gzip.compress('compress me!') + # # => "\x1F\x8B\b\x00o\x8D\xCDO\x00\x03K\xCE\xCF-(J-.V\xC8MU\x04\x00R>n\x83\f\x00\x00\x00" + # + # ActiveSupport::Gzip.decompress(gzip) + # # => "compress me!" + module Gzip + class Stream < StringIO + def initialize(*) + super + set_encoding "BINARY" + end + def close; rewind; end + end + + # Decompresses a gzipped string. + def self.decompress(source) + Zlib::GzipReader.wrap(StringIO.new(source), &:read) + end + + # Compresses a string using gzip. + def self.compress(source, level = Zlib::DEFAULT_COMPRESSION, strategy = Zlib::DEFAULT_STRATEGY) + output = Stream.new + gz = Zlib::GzipWriter.new(output, level, strategy) + gz.write(source) + gz.close + output.string + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/hash_with_indifferent_access.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/hash_with_indifferent_access.rb new file mode 100644 index 00000000..24d3aed4 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/hash_with_indifferent_access.rb @@ -0,0 +1,395 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/hash/reverse_merge" + +module ActiveSupport + # Implements a hash where keys :foo and "foo" are considered + # to be the same. + # + # rgb = ActiveSupport::HashWithIndifferentAccess.new + # + # rgb[:black] = '#000000' + # rgb[:black] # => '#000000' + # rgb['black'] # => '#000000' + # + # rgb['white'] = '#FFFFFF' + # rgb[:white] # => '#FFFFFF' + # rgb['white'] # => '#FFFFFF' + # + # Internally symbols are mapped to strings when used as keys in the entire + # writing interface (calling []=, merge, etc). This + # mapping belongs to the public interface. For example, given: + # + # hash = ActiveSupport::HashWithIndifferentAccess.new(a: 1) + # + # You are guaranteed that the key is returned as a string: + # + # hash.keys # => ["a"] + # + # Technically other types of keys are accepted: + # + # hash = ActiveSupport::HashWithIndifferentAccess.new(a: 1) + # hash[0] = 0 + # hash # => {"a"=>1, 0=>0} + # + # but this class is intended for use cases where strings or symbols are the + # expected keys and it is convenient to understand both as the same. For + # example the +params+ hash in Ruby on Rails. + # + # Note that core extensions define Hash#with_indifferent_access: + # + # rgb = { black: '#000000', white: '#FFFFFF' }.with_indifferent_access + # + # which may be handy. + # + # To access this class outside of Rails, require the core extension with: + # + # require "active_support/core_ext/hash/indifferent_access" + # + # which will, in turn, require this file. + class HashWithIndifferentAccess < Hash + # Returns +true+ so that Array#extract_options! finds members of + # this class. + def extractable_options? + true + end + + def with_indifferent_access + dup + end + + def nested_under_indifferent_access + self + end + + def initialize(constructor = {}) + if constructor.respond_to?(:to_hash) + super() + update(constructor) + + hash = constructor.to_hash + self.default = hash.default if hash.default + self.default_proc = hash.default_proc if hash.default_proc + else + super(constructor) + end + end + + def self.[](*args) + new.merge!(Hash[*args]) + end + + alias_method :regular_writer, :[]= unless method_defined?(:regular_writer) + alias_method :regular_update, :update unless method_defined?(:regular_update) + + # Assigns a new value to the hash: + # + # hash = ActiveSupport::HashWithIndifferentAccess.new + # hash[:key] = 'value' + # + # This value can be later fetched using either +:key+ or 'key'. + def []=(key, value) + regular_writer(convert_key(key), convert_value(value, for: :assignment)) + end + + alias_method :store, :[]= + + # Updates the receiver in-place, merging in the hash passed as argument: + # + # hash_1 = ActiveSupport::HashWithIndifferentAccess.new + # hash_1[:key] = 'value' + # + # hash_2 = ActiveSupport::HashWithIndifferentAccess.new + # hash_2[:key] = 'New Value!' + # + # hash_1.update(hash_2) # => {"key"=>"New Value!"} + # + # The argument can be either an + # ActiveSupport::HashWithIndifferentAccess or a regular +Hash+. + # In either case the merge respects the semantics of indifferent access. + # + # If the argument is a regular hash with keys +:key+ and +"key"+ only one + # of the values end up in the receiver, but which one is unspecified. + # + # When given a block, the value for duplicated keys will be determined + # by the result of invoking the block with the duplicated key, the value + # in the receiver, and the value in +other_hash+. The rules for duplicated + # keys follow the semantics of indifferent access: + # + # hash_1[:key] = 10 + # hash_2['key'] = 12 + # hash_1.update(hash_2) { |key, old, new| old + new } # => {"key"=>22} + def update(other_hash) + if other_hash.is_a? HashWithIndifferentAccess + super(other_hash) + else + other_hash.to_hash.each_pair do |key, value| + if block_given? && key?(key) + value = yield(convert_key(key), self[key], value) + end + regular_writer(convert_key(key), convert_value(value)) + end + self + end + end + + alias_method :merge!, :update + + # Checks the hash for a key matching the argument passed in: + # + # hash = ActiveSupport::HashWithIndifferentAccess.new + # hash['key'] = 'value' + # hash.key?(:key) # => true + # hash.key?('key') # => true + def key?(key) + super(convert_key(key)) + end + + alias_method :include?, :key? + alias_method :has_key?, :key? + alias_method :member?, :key? + + # Same as Hash#[] where the key passed as argument can be + # either a string or a symbol: + # + # counters = ActiveSupport::HashWithIndifferentAccess.new + # counters[:foo] = 1 + # + # counters['foo'] # => 1 + # counters[:foo] # => 1 + # counters[:zoo] # => nil + def [](key) + super(convert_key(key)) + end + + # Same as Hash#assoc where the key passed as argument can be + # either a string or a symbol: + # + # counters = ActiveSupport::HashWithIndifferentAccess.new + # counters[:foo] = 1 + # + # counters.assoc('foo') # => ["foo", 1] + # counters.assoc(:foo) # => ["foo", 1] + # counters.assoc(:zoo) # => nil + def assoc(key) + super(convert_key(key)) + end + + # Same as Hash#fetch where the key passed as argument can be + # either a string or a symbol: + # + # counters = ActiveSupport::HashWithIndifferentAccess.new + # counters[:foo] = 1 + # + # counters.fetch('foo') # => 1 + # counters.fetch(:bar, 0) # => 0 + # counters.fetch(:bar) { |key| 0 } # => 0 + # counters.fetch(:zoo) # => KeyError: key not found: "zoo" + def fetch(key, *extras) + super(convert_key(key), *extras) + end + + if Hash.new.respond_to?(:dig) + # Same as Hash#dig where the key passed as argument can be + # either a string or a symbol: + # + # counters = ActiveSupport::HashWithIndifferentAccess.new + # counters[:foo] = { bar: 1 } + # + # counters.dig('foo', 'bar') # => 1 + # counters.dig(:foo, :bar) # => 1 + # counters.dig(:zoo) # => nil + def dig(*args) + args[0] = convert_key(args[0]) if args.size > 0 + super(*args) + end + end + + # Same as Hash#default where the key passed as argument can be + # either a string or a symbol: + # + # hash = ActiveSupport::HashWithIndifferentAccess.new(1) + # hash.default # => 1 + # + # hash = ActiveSupport::HashWithIndifferentAccess.new { |hash, key| key } + # hash.default # => nil + # hash.default('foo') # => 'foo' + # hash.default(:foo) # => 'foo' + def default(*args) + super(*args.map { |arg| convert_key(arg) }) + end + + # Returns an array of the values at the specified indices: + # + # hash = ActiveSupport::HashWithIndifferentAccess.new + # hash[:a] = 'x' + # hash[:b] = 'y' + # hash.values_at('a', 'b') # => ["x", "y"] + def values_at(*indices) + indices.collect { |key| self[convert_key(key)] } + end + + # Returns an array of the values at the specified indices, but also + # raises an exception when one of the keys can't be found. + # + # hash = ActiveSupport::HashWithIndifferentAccess.new + # hash[:a] = 'x' + # hash[:b] = 'y' + # hash.fetch_values('a', 'b') # => ["x", "y"] + # hash.fetch_values('a', 'c') { |key| 'z' } # => ["x", "z"] + # hash.fetch_values('a', 'c') # => KeyError: key not found: "c" + def fetch_values(*indices, &block) + indices.collect { |key| fetch(key, &block) } + end if Hash.method_defined?(:fetch_values) + + # Returns a shallow copy of the hash. + # + # hash = ActiveSupport::HashWithIndifferentAccess.new({ a: { b: 'b' } }) + # dup = hash.dup + # dup[:a][:c] = 'c' + # + # hash[:a][:c] # => "c" + # dup[:a][:c] # => "c" + def dup + self.class.new(self).tap do |new_hash| + set_defaults(new_hash) + end + end + + # This method has the same semantics of +update+, except it does not + # modify the receiver but rather returns a new hash with indifferent + # access with the result of the merge. + def merge(hash, &block) + dup.update(hash, &block) + end + + # Like +merge+ but the other way around: Merges the receiver into the + # argument and returns a new hash with indifferent access as result: + # + # hash = ActiveSupport::HashWithIndifferentAccess.new + # hash['a'] = nil + # hash.reverse_merge(a: 0, b: 1) # => {"a"=>nil, "b"=>1} + def reverse_merge(other_hash) + super(self.class.new(other_hash)) + end + alias_method :with_defaults, :reverse_merge + + # Same semantics as +reverse_merge+ but modifies the receiver in-place. + def reverse_merge!(other_hash) + super(self.class.new(other_hash)) + end + alias_method :with_defaults!, :reverse_merge! + + # Replaces the contents of this hash with other_hash. + # + # h = { "a" => 100, "b" => 200 } + # h.replace({ "c" => 300, "d" => 400 }) # => {"c"=>300, "d"=>400} + def replace(other_hash) + super(self.class.new(other_hash)) + end + + # Removes the specified key from the hash. + def delete(key) + super(convert_key(key)) + end + + def stringify_keys!; self end + def deep_stringify_keys!; self end + def stringify_keys; dup end + def deep_stringify_keys; dup end + undef :symbolize_keys! + undef :deep_symbolize_keys! + def symbolize_keys; to_hash.symbolize_keys! end + alias_method :to_options, :symbolize_keys + def deep_symbolize_keys; to_hash.deep_symbolize_keys! end + def to_options!; self end + + def select(*args, &block) + return to_enum(:select) unless block_given? + dup.tap { |hash| hash.select!(*args, &block) } + end + + def reject(*args, &block) + return to_enum(:reject) unless block_given? + dup.tap { |hash| hash.reject!(*args, &block) } + end + + def transform_values(*args, &block) + return to_enum(:transform_values) unless block_given? + dup.tap { |hash| hash.transform_values!(*args, &block) } + end + + def transform_keys(*args, &block) + return to_enum(:transform_keys) unless block_given? + dup.tap { |hash| hash.transform_keys!(*args, &block) } + end + + def transform_keys! + return enum_for(:transform_keys!) { size } unless block_given? + keys.each do |key| + self[yield(key)] = delete(key) + end + self + end + + def slice(*keys) + keys.map! { |key| convert_key(key) } + self.class.new(super) + end + + def slice!(*keys) + keys.map! { |key| convert_key(key) } + super + end + + def compact + dup.tap(&:compact!) + end + + # Convert to a regular hash with string keys. + def to_hash + _new_hash = Hash.new + set_defaults(_new_hash) + + each do |key, value| + _new_hash[key] = convert_value(value, for: :to_hash) + end + _new_hash + end + + private + def convert_key(key) # :doc: + key.kind_of?(Symbol) ? key.to_s : key + end + + def convert_value(value, options = {}) # :doc: + if value.is_a? Hash + if options[:for] == :to_hash + value.to_hash + else + value.nested_under_indifferent_access + end + elsif value.is_a?(Array) + if options[:for] != :assignment || value.frozen? + value = value.dup + end + value.map! { |e| convert_value(e, options) } + else + value + end + end + + def set_defaults(target) # :doc: + if default_proc + target.default_proc = default_proc.dup + else + target.default = default + end + end + end +end + +# :stopdoc: + +HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/i18n.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/i18n.rb new file mode 100644 index 00000000..d60b3eff --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/i18n.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/deep_merge" +require "active_support/core_ext/hash/except" +require "active_support/core_ext/hash/slice" +begin + require "i18n" +rescue LoadError => e + $stderr.puts "The i18n gem is not available. Please add it to your Gemfile and run bundle install" + raise e +end +require "active_support/lazy_load_hooks" + +ActiveSupport.run_load_hooks(:i18n) +I18n.load_path << File.expand_path("locale/en.yml", __dir__) diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/i18n_railtie.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/i18n_railtie.rb new file mode 100644 index 00000000..ee24ebd6 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/i18n_railtie.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +require "active_support" +require "active_support/file_update_checker" +require "active_support/core_ext/array/wrap" + +# :enddoc: + +module I18n + class Railtie < Rails::Railtie + config.i18n = ActiveSupport::OrderedOptions.new + config.i18n.railties_load_path = [] + config.i18n.load_path = [] + config.i18n.fallbacks = ActiveSupport::OrderedOptions.new + + # Set the i18n configuration after initialization since a lot of + # configuration is still usually done in application initializers. + config.after_initialize do |app| + I18n::Railtie.initialize_i18n(app) + end + + # Trigger i18n config before any eager loading has happened + # so it's ready if any classes require it when eager loaded. + config.before_eager_load do |app| + I18n::Railtie.initialize_i18n(app) + end + + @i18n_inited = false + + # Setup i18n configuration. + def self.initialize_i18n(app) + return if @i18n_inited + + fallbacks = app.config.i18n.delete(:fallbacks) + + # Avoid issues with setting the default_locale by disabling available locales + # check while configuring. + enforce_available_locales = app.config.i18n.delete(:enforce_available_locales) + enforce_available_locales = I18n.enforce_available_locales if enforce_available_locales.nil? + I18n.enforce_available_locales = false + + reloadable_paths = [] + app.config.i18n.each do |setting, value| + case setting + when :railties_load_path + reloadable_paths = value + app.config.i18n.load_path.unshift(*value.flat_map(&:existent)) + when :load_path + I18n.load_path += value + else + I18n.send("#{setting}=", value) + end + end + + init_fallbacks(fallbacks) if fallbacks && validate_fallbacks(fallbacks) + + # Restore available locales check so it will take place from now on. + I18n.enforce_available_locales = enforce_available_locales + + directories = watched_dirs_with_extensions(reloadable_paths) + reloader = app.config.file_watcher.new(I18n.load_path.dup, directories) do + I18n.load_path.keep_if { |p| File.exist?(p) } + I18n.load_path |= reloadable_paths.flat_map(&:existent) + + I18n.reload! + end + + app.reloaders << reloader + app.reloader.to_run do + reloader.execute_if_updated { require_unload_lock! } + end + reloader.execute + + @i18n_inited = true + end + + def self.include_fallbacks_module + I18n.backend.class.include(I18n::Backend::Fallbacks) + end + + def self.init_fallbacks(fallbacks) + include_fallbacks_module + + args = \ + case fallbacks + when ActiveSupport::OrderedOptions + [*(fallbacks[:defaults] || []) << fallbacks[:map]].compact + when Hash, Array + Array.wrap(fallbacks) + else # TrueClass + [I18n.default_locale] + end + + if args.empty? || args.first.is_a?(Hash) + args.unshift I18n.default_locale + end + + I18n.fallbacks = I18n::Locale::Fallbacks.new(*args) + end + + def self.validate_fallbacks(fallbacks) + case fallbacks + when ActiveSupport::OrderedOptions + !fallbacks.empty? + when TrueClass, Array, Hash + true + else + raise "Unexpected fallback type #{fallbacks.inspect}" + end + end + + def self.watched_dirs_with_extensions(paths) + paths.each_with_object({}) do |path, result| + result[path.absolute_current] = path.extensions + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/inflections.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/inflections.rb new file mode 100644 index 00000000..baf1cb30 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/inflections.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require "active_support/inflector/inflections" + +#-- +# Defines the standard inflection rules. These are the starting point for +# new projects and are not considered complete. The current set of inflection +# rules is frozen. This means, we do not change them to become more complete. +# This is a safety measure to keep existing applications from breaking. +#++ +module ActiveSupport + Inflector.inflections(:en) do |inflect| + inflect.plural(/$/, "s") + inflect.plural(/s$/i, "s") + inflect.plural(/^(ax|test)is$/i, '\1es') + inflect.plural(/(octop|vir)us$/i, '\1i') + inflect.plural(/(octop|vir)i$/i, '\1i') + inflect.plural(/(alias|status)$/i, '\1es') + inflect.plural(/(bu)s$/i, '\1ses') + inflect.plural(/(buffal|tomat)o$/i, '\1oes') + inflect.plural(/([ti])um$/i, '\1a') + inflect.plural(/([ti])a$/i, '\1a') + inflect.plural(/sis$/i, "ses") + inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves') + inflect.plural(/(hive)$/i, '\1s') + inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies') + inflect.plural(/(x|ch|ss|sh)$/i, '\1es') + inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices') + inflect.plural(/^(m|l)ouse$/i, '\1ice') + inflect.plural(/^(m|l)ice$/i, '\1ice') + inflect.plural(/^(ox)$/i, '\1en') + inflect.plural(/^(oxen)$/i, '\1') + inflect.plural(/(quiz)$/i, '\1zes') + + inflect.singular(/s$/i, "") + inflect.singular(/(ss)$/i, '\1') + inflect.singular(/(n)ews$/i, '\1ews') + inflect.singular(/([ti])a$/i, '\1um') + inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$/i, '\1sis') + inflect.singular(/(^analy)(sis|ses)$/i, '\1sis') + inflect.singular(/([^f])ves$/i, '\1fe') + inflect.singular(/(hive)s$/i, '\1') + inflect.singular(/(tive)s$/i, '\1') + inflect.singular(/([lr])ves$/i, '\1f') + inflect.singular(/([^aeiouy]|qu)ies$/i, '\1y') + inflect.singular(/(s)eries$/i, '\1eries') + inflect.singular(/(m)ovies$/i, '\1ovie') + inflect.singular(/(x|ch|ss|sh)es$/i, '\1') + inflect.singular(/^(m|l)ice$/i, '\1ouse') + inflect.singular(/(bus)(es)?$/i, '\1') + inflect.singular(/(o)es$/i, '\1') + inflect.singular(/(shoe)s$/i, '\1') + inflect.singular(/(cris|test)(is|es)$/i, '\1is') + inflect.singular(/^(a)x[ie]s$/i, '\1xis') + inflect.singular(/(octop|vir)(us|i)$/i, '\1us') + inflect.singular(/(alias|status)(es)?$/i, '\1') + inflect.singular(/^(ox)en/i, '\1') + inflect.singular(/(vert|ind)ices$/i, '\1ex') + inflect.singular(/(matr)ices$/i, '\1ix') + inflect.singular(/(quiz)zes$/i, '\1') + inflect.singular(/(database)s$/i, '\1') + + inflect.irregular("person", "people") + inflect.irregular("man", "men") + inflect.irregular("child", "children") + inflect.irregular("sex", "sexes") + inflect.irregular("move", "moves") + inflect.irregular("zombie", "zombies") + + inflect.uncountable(%w(equipment information rice money species series fish sheep jeans police)) + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/inflector.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/inflector.rb new file mode 100644 index 00000000..d77f04c9 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/inflector.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# in case active_support/inflector is required without the rest of active_support +require "active_support/inflector/inflections" +require "active_support/inflector/transliterate" +require "active_support/inflector/methods" + +require "active_support/inflections" +require "active_support/core_ext/string/inflections" diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/inflector/inflections.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/inflector/inflections.rb new file mode 100644 index 00000000..7e5dff1d --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/inflector/inflections.rb @@ -0,0 +1,260 @@ +# frozen_string_literal: true + +require "concurrent/map" +require "active_support/core_ext/array/prepend_and_append" +require "active_support/core_ext/regexp" +require "active_support/i18n" +require "active_support/deprecation" + +module ActiveSupport + module Inflector + extend self + + # A singleton instance of this class is yielded by Inflector.inflections, + # which can then be used to specify additional inflection rules. If passed + # an optional locale, rules for other languages can be specified. The + # default locale is :en. Only rules for English are provided. + # + # ActiveSupport::Inflector.inflections(:en) do |inflect| + # inflect.plural /^(ox)$/i, '\1\2en' + # inflect.singular /^(ox)en/i, '\1' + # + # inflect.irregular 'octopus', 'octopi' + # + # inflect.uncountable 'equipment' + # end + # + # New rules are added at the top. So in the example above, the irregular + # rule for octopus will now be the first of the pluralization and + # singularization rules that is runs. This guarantees that your rules run + # before any of the rules that may already have been loaded. + class Inflections + @__instance__ = Concurrent::Map.new + + class Uncountables < Array + def initialize + @regex_array = [] + super + end + + def delete(entry) + super entry + @regex_array.delete(to_regex(entry)) + end + + def <<(*word) + add(word) + end + + def add(words) + words = words.flatten.map(&:downcase) + concat(words) + @regex_array += words.map { |word| to_regex(word) } + self + end + + def uncountable?(str) + @regex_array.any? { |regex| regex.match? str } + end + + private + def to_regex(string) + /\b#{::Regexp.escape(string)}\Z/i + end + end + + def self.instance(locale = :en) + @__instance__[locale] ||= new + end + + attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex + deprecate :acronym_regex + + attr_reader :acronyms_camelize_regex, :acronyms_underscore_regex # :nodoc: + + def initialize + @plurals, @singulars, @uncountables, @humans, @acronyms = [], [], Uncountables.new, [], {} + define_acronym_regex_patterns + end + + # Private, for the test suite. + def initialize_dup(orig) # :nodoc: + %w(plurals singulars uncountables humans acronyms).each do |scope| + instance_variable_set("@#{scope}", orig.send(scope).dup) + end + define_acronym_regex_patterns + end + + # Specifies a new acronym. An acronym must be specified as it will appear + # in a camelized string. An underscore string that contains the acronym + # will retain the acronym when passed to +camelize+, +humanize+, or + # +titleize+. A camelized string that contains the acronym will maintain + # the acronym when titleized or humanized, and will convert the acronym + # into a non-delimited single lowercase word when passed to +underscore+. + # + # acronym 'HTML' + # titleize 'html' # => 'HTML' + # camelize 'html' # => 'HTML' + # underscore 'MyHTML' # => 'my_html' + # + # The acronym, however, must occur as a delimited unit and not be part of + # another word for conversions to recognize it: + # + # acronym 'HTTP' + # camelize 'my_http_delimited' # => 'MyHTTPDelimited' + # camelize 'https' # => 'Https', not 'HTTPs' + # underscore 'HTTPS' # => 'http_s', not 'https' + # + # acronym 'HTTPS' + # camelize 'https' # => 'HTTPS' + # underscore 'HTTPS' # => 'https' + # + # Note: Acronyms that are passed to +pluralize+ will no longer be + # recognized, since the acronym will not occur as a delimited unit in the + # pluralized result. To work around this, you must specify the pluralized + # form as an acronym as well: + # + # acronym 'API' + # camelize(pluralize('api')) # => 'Apis' + # + # acronym 'APIs' + # camelize(pluralize('api')) # => 'APIs' + # + # +acronym+ may be used to specify any word that contains an acronym or + # otherwise needs to maintain a non-standard capitalization. The only + # restriction is that the word must begin with a capital letter. + # + # acronym 'RESTful' + # underscore 'RESTful' # => 'restful' + # underscore 'RESTfulController' # => 'restful_controller' + # titleize 'RESTfulController' # => 'RESTful Controller' + # camelize 'restful' # => 'RESTful' + # camelize 'restful_controller' # => 'RESTfulController' + # + # acronym 'McDonald' + # underscore 'McDonald' # => 'mcdonald' + # camelize 'mcdonald' # => 'McDonald' + def acronym(word) + @acronyms[word.downcase] = word + define_acronym_regex_patterns + end + + # Specifies a new pluralization rule and its replacement. The rule can + # either be a string or a regular expression. The replacement should + # always be a string that may include references to the matched data from + # the rule. + def plural(rule, replacement) + @uncountables.delete(rule) if rule.is_a?(String) + @uncountables.delete(replacement) + @plurals.prepend([rule, replacement]) + end + + # Specifies a new singularization rule and its replacement. The rule can + # either be a string or a regular expression. The replacement should + # always be a string that may include references to the matched data from + # the rule. + def singular(rule, replacement) + @uncountables.delete(rule) if rule.is_a?(String) + @uncountables.delete(replacement) + @singulars.prepend([rule, replacement]) + end + + # Specifies a new irregular that applies to both pluralization and + # singularization at the same time. This can only be used for strings, not + # regular expressions. You simply pass the irregular in singular and + # plural form. + # + # irregular 'octopus', 'octopi' + # irregular 'person', 'people' + def irregular(singular, plural) + @uncountables.delete(singular) + @uncountables.delete(plural) + + s0 = singular[0] + srest = singular[1..-1] + + p0 = plural[0] + prest = plural[1..-1] + + if s0.upcase == p0.upcase + plural(/(#{s0})#{srest}$/i, '\1' + prest) + plural(/(#{p0})#{prest}$/i, '\1' + prest) + + singular(/(#{s0})#{srest}$/i, '\1' + srest) + singular(/(#{p0})#{prest}$/i, '\1' + srest) + else + plural(/#{s0.upcase}(?i)#{srest}$/, p0.upcase + prest) + plural(/#{s0.downcase}(?i)#{srest}$/, p0.downcase + prest) + plural(/#{p0.upcase}(?i)#{prest}$/, p0.upcase + prest) + plural(/#{p0.downcase}(?i)#{prest}$/, p0.downcase + prest) + + singular(/#{s0.upcase}(?i)#{srest}$/, s0.upcase + srest) + singular(/#{s0.downcase}(?i)#{srest}$/, s0.downcase + srest) + singular(/#{p0.upcase}(?i)#{prest}$/, s0.upcase + srest) + singular(/#{p0.downcase}(?i)#{prest}$/, s0.downcase + srest) + end + end + + # Specifies words that are uncountable and should not be inflected. + # + # uncountable 'money' + # uncountable 'money', 'information' + # uncountable %w( money information rice ) + def uncountable(*words) + @uncountables.add(words) + end + + # Specifies a humanized form of a string by a regular expression rule or + # by a string mapping. When using a regular expression based replacement, + # the normal humanize formatting is called after the replacement. When a + # string is used, the human form should be specified as desired (example: + # 'The name', not 'the_name'). + # + # human /_cnt$/i, '\1_count' + # human 'legacy_col_person_name', 'Name' + def human(rule, replacement) + @humans.prepend([rule, replacement]) + end + + # Clears the loaded inflections within a given scope (default is + # :all). Give the scope as a symbol of the inflection type, the + # options are: :plurals, :singulars, :uncountables, + # :humans. + # + # clear :all + # clear :plurals + def clear(scope = :all) + case scope + when :all + @plurals, @singulars, @uncountables, @humans = [], [], Uncountables.new, [] + else + instance_variable_set "@#{scope}", [] + end + end + + private + + def define_acronym_regex_patterns + @acronym_regex = @acronyms.empty? ? /(?=a)b/ : /#{@acronyms.values.join("|")}/ + @acronyms_camelize_regex = /^(?:#{@acronym_regex}(?=\b|[A-Z_])|\w)/ + @acronyms_underscore_regex = /(?:(?<=([A-Za-z\d]))|\b)(#{@acronym_regex})(?=\b|[^a-z])/ + end + end + + # Yields a singleton instance of Inflector::Inflections so you can specify + # additional inflector rules. If passed an optional locale, rules for other + # languages can be specified. If not specified, defaults to :en. + # Only rules for English are provided. + # + # ActiveSupport::Inflector.inflections(:en) do |inflect| + # inflect.uncountable 'rails' + # end + def inflections(locale = :en) + if block_given? + yield Inflections.instance(locale) + else + Inflections.instance(locale) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/inflector/methods.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/inflector/methods.rb new file mode 100644 index 00000000..ad90a0b7 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/inflector/methods.rb @@ -0,0 +1,410 @@ +# frozen_string_literal: true + +require "active_support/inflections" +require "active_support/core_ext/regexp" + +module ActiveSupport + # The Inflector transforms words from singular to plural, class names to table + # names, modularized class names to ones without, and class names to foreign + # keys. The default inflections for pluralization, singularization, and + # uncountable words are kept in inflections.rb. + # + # The Rails core team has stated patches for the inflections library will not + # be accepted in order to avoid breaking legacy applications which may be + # relying on errant inflections. If you discover an incorrect inflection and + # require it for your application or wish to define rules for languages other + # than English, please correct or add them yourself (explained below). + module Inflector + extend self + + # Returns the plural form of the word in the string. + # + # If passed an optional +locale+ parameter, the word will be + # pluralized using rules defined for that language. By default, + # this parameter is set to :en. + # + # pluralize('post') # => "posts" + # pluralize('octopus') # => "octopi" + # pluralize('sheep') # => "sheep" + # pluralize('words') # => "words" + # pluralize('CamelOctopus') # => "CamelOctopi" + # pluralize('ley', :es) # => "leyes" + def pluralize(word, locale = :en) + apply_inflections(word, inflections(locale).plurals, locale) + end + + # The reverse of #pluralize, returns the singular form of a word in a + # string. + # + # If passed an optional +locale+ parameter, the word will be + # singularized using rules defined for that language. By default, + # this parameter is set to :en. + # + # singularize('posts') # => "post" + # singularize('octopi') # => "octopus" + # singularize('sheep') # => "sheep" + # singularize('word') # => "word" + # singularize('CamelOctopi') # => "CamelOctopus" + # singularize('leyes', :es) # => "ley" + def singularize(word, locale = :en) + apply_inflections(word, inflections(locale).singulars, locale) + end + + # Converts strings to UpperCamelCase. + # If the +uppercase_first_letter+ parameter is set to false, then produces + # lowerCamelCase. + # + # Also converts '/' to '::' which is useful for converting + # paths to namespaces. + # + # camelize('active_model') # => "ActiveModel" + # camelize('active_model', false) # => "activeModel" + # camelize('active_model/errors') # => "ActiveModel::Errors" + # camelize('active_model/errors', false) # => "activeModel::Errors" + # + # As a rule of thumb you can think of +camelize+ as the inverse of + # #underscore, though there are cases where that does not hold: + # + # camelize(underscore('SSLError')) # => "SslError" + def camelize(term, uppercase_first_letter = true) + string = term.to_s + if uppercase_first_letter + string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize } + else + string = string.sub(inflections.acronyms_camelize_regex) { |match| match.downcase } + end + string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" } + string.gsub!("/".freeze, "::".freeze) + string + end + + # Makes an underscored, lowercase form from the expression in the string. + # + # Changes '::' to '/' to convert namespaces to paths. + # + # underscore('ActiveModel') # => "active_model" + # underscore('ActiveModel::Errors') # => "active_model/errors" + # + # As a rule of thumb you can think of +underscore+ as the inverse of + # #camelize, though there are cases where that does not hold: + # + # camelize(underscore('SSLError')) # => "SslError" + def underscore(camel_cased_word) + return camel_cased_word unless /[A-Z-]|::/.match?(camel_cased_word) + word = camel_cased_word.to_s.gsub("::".freeze, "/".freeze) + word.gsub!(inflections.acronyms_underscore_regex) { "#{$1 && '_'.freeze }#{$2.downcase}" } + word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze) + word.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze) + word.tr!("-".freeze, "_".freeze) + word.downcase! + word + end + + # Tweaks an attribute name for display to end users. + # + # Specifically, performs these transformations: + # + # * Applies human inflection rules to the argument. + # * Deletes leading underscores, if any. + # * Removes a "_id" suffix if present. + # * Replaces underscores with spaces, if any. + # * Downcases all words except acronyms. + # * Capitalizes the first word. + # The capitalization of the first word can be turned off by setting the + # +:capitalize+ option to false (default is true). + # + # The trailing '_id' can be kept and capitalized by setting the + # optional parameter +keep_id_suffix+ to true (default is false). + # + # humanize('employee_salary') # => "Employee salary" + # humanize('author_id') # => "Author" + # humanize('author_id', capitalize: false) # => "author" + # humanize('_id') # => "Id" + # humanize('author_id', keep_id_suffix: true) # => "Author Id" + # + # If "SSL" was defined to be an acronym: + # + # humanize('ssl_error') # => "SSL error" + # + def humanize(lower_case_and_underscored_word, capitalize: true, keep_id_suffix: false) + result = lower_case_and_underscored_word.to_s.dup + + inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) } + + result.sub!(/\A_+/, "".freeze) + unless keep_id_suffix + result.sub!(/_id\z/, "".freeze) + end + result.tr!("_".freeze, " ".freeze) + + result.gsub!(/([a-z\d]*)/i) do |match| + "#{inflections.acronyms[match.downcase] || match.downcase}" + end + + if capitalize + result.sub!(/\A\w/) { |match| match.upcase } + end + + result + end + + # Converts just the first character to uppercase. + # + # upcase_first('what a Lovely Day') # => "What a Lovely Day" + # upcase_first('w') # => "W" + # upcase_first('') # => "" + def upcase_first(string) + string.length > 0 ? string[0].upcase.concat(string[1..-1]) : "" + end + + # Capitalizes all the words and replaces some characters in the string to + # create a nicer looking title. +titleize+ is meant for creating pretty + # output. It is not used in the Rails internals. + # + # The trailing '_id','Id'.. can be kept and capitalized by setting the + # optional parameter +keep_id_suffix+ to true. + # By default, this parameter is false. + # + # +titleize+ is also aliased as +titlecase+. + # + # titleize('man from the boondocks') # => "Man From The Boondocks" + # titleize('x-men: the last stand') # => "X Men: The Last Stand" + # titleize('TheManWithoutAPast') # => "The Man Without A Past" + # titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark" + # titleize('string_ending_with_id', keep_id_suffix: true) # => "String Ending With Id" + def titleize(word, keep_id_suffix: false) + humanize(underscore(word), keep_id_suffix: keep_id_suffix).gsub(/\b(? "raw_scaled_scorers" + # tableize('ham_and_egg') # => "ham_and_eggs" + # tableize('fancyCategory') # => "fancy_categories" + def tableize(class_name) + pluralize(underscore(class_name)) + end + + # Creates a class name from a plural table name like Rails does for table + # names to models. Note that this returns a string and not a Class (To + # convert to an actual class follow +classify+ with #constantize). + # + # classify('ham_and_eggs') # => "HamAndEgg" + # classify('posts') # => "Post" + # + # Singular names are not handled correctly: + # + # classify('calculus') # => "Calculus" + def classify(table_name) + # strip out any leading schema name + camelize(singularize(table_name.to_s.sub(/.*\./, "".freeze))) + end + + # Replaces underscores with dashes in the string. + # + # dasherize('puni_puni') # => "puni-puni" + def dasherize(underscored_word) + underscored_word.tr("_".freeze, "-".freeze) + end + + # Removes the module part from the expression in the string. + # + # demodulize('ActiveSupport::Inflector::Inflections') # => "Inflections" + # demodulize('Inflections') # => "Inflections" + # demodulize('::Inflections') # => "Inflections" + # demodulize('') # => "" + # + # See also #deconstantize. + def demodulize(path) + path = path.to_s + if i = path.rindex("::") + path[(i + 2)..-1] + else + path + end + end + + # Removes the rightmost segment from the constant expression in the string. + # + # deconstantize('Net::HTTP') # => "Net" + # deconstantize('::Net::HTTP') # => "::Net" + # deconstantize('String') # => "" + # deconstantize('::String') # => "" + # deconstantize('') # => "" + # + # See also #demodulize. + def deconstantize(path) + path.to_s[0, path.rindex("::") || 0] # implementation based on the one in facets' Module#spacename + end + + # Creates a foreign key name from a class name. + # +separate_class_name_and_id_with_underscore+ sets whether + # the method should put '_' between the name and 'id'. + # + # foreign_key('Message') # => "message_id" + # foreign_key('Message', false) # => "messageid" + # foreign_key('Admin::Post') # => "post_id" + def foreign_key(class_name, separate_class_name_and_id_with_underscore = true) + underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id") + end + + # Tries to find a constant with the name specified in the argument string. + # + # constantize('Module') # => Module + # constantize('Foo::Bar') # => Foo::Bar + # + # The name is assumed to be the one of a top-level constant, no matter + # whether it starts with "::" or not. No lexical context is taken into + # account: + # + # C = 'outside' + # module M + # C = 'inside' + # C # => 'inside' + # constantize('C') # => 'outside', same as ::C + # end + # + # NameError is raised when the name is not in CamelCase or the constant is + # unknown. + def constantize(camel_cased_word) + names = camel_cased_word.split("::".freeze) + + # Trigger a built-in NameError exception including the ill-formed constant in the message. + Object.const_get(camel_cased_word) if names.empty? + + # Remove the first blank element in case of '::ClassName' notation. + names.shift if names.size > 1 && names.first.empty? + + names.inject(Object) do |constant, name| + if constant == Object + constant.const_get(name) + else + candidate = constant.const_get(name) + next candidate if constant.const_defined?(name, false) + next candidate unless Object.const_defined?(name) + + # Go down the ancestors to check if it is owned directly. The check + # stops when we reach Object or the end of ancestors tree. + constant = constant.ancestors.inject(constant) do |const, ancestor| + break const if ancestor == Object + break ancestor if ancestor.const_defined?(name, false) + const + end + + # owner is in Object, so raise + constant.const_get(name, false) + end + end + end + + # Tries to find a constant with the name specified in the argument string. + # + # safe_constantize('Module') # => Module + # safe_constantize('Foo::Bar') # => Foo::Bar + # + # The name is assumed to be the one of a top-level constant, no matter + # whether it starts with "::" or not. No lexical context is taken into + # account: + # + # C = 'outside' + # module M + # C = 'inside' + # C # => 'inside' + # safe_constantize('C') # => 'outside', same as ::C + # end + # + # +nil+ is returned when the name is not in CamelCase or the constant (or + # part of it) is unknown. + # + # safe_constantize('blargle') # => nil + # safe_constantize('UnknownModule') # => nil + # safe_constantize('UnknownModule::Foo::Bar') # => nil + def safe_constantize(camel_cased_word) + constantize(camel_cased_word) + rescue NameError => e + raise if e.name && !(camel_cased_word.to_s.split("::").include?(e.name.to_s) || + e.name.to_s == camel_cased_word.to_s) + rescue ArgumentError => e + raise unless /not missing constant #{const_regexp(camel_cased_word)}!$/.match?(e.message) + rescue LoadError => e + raise unless /Unable to autoload constant #{const_regexp(camel_cased_word)}/.match?(e.message) + end + + # Returns the suffix that should be added to a number to denote the position + # in an ordered sequence such as 1st, 2nd, 3rd, 4th. + # + # ordinal(1) # => "st" + # ordinal(2) # => "nd" + # ordinal(1002) # => "nd" + # ordinal(1003) # => "rd" + # ordinal(-11) # => "th" + # ordinal(-1021) # => "st" + def ordinal(number) + abs_number = number.to_i.abs + + if (11..13).include?(abs_number % 100) + "th" + else + case abs_number % 10 + when 1; "st" + when 2; "nd" + when 3; "rd" + else "th" + end + end + end + + # Turns a number into an ordinal string used to denote the position in an + # ordered sequence such as 1st, 2nd, 3rd, 4th. + # + # ordinalize(1) # => "1st" + # ordinalize(2) # => "2nd" + # ordinalize(1002) # => "1002nd" + # ordinalize(1003) # => "1003rd" + # ordinalize(-11) # => "-11th" + # ordinalize(-1021) # => "-1021st" + def ordinalize(number) + "#{number}#{ordinal(number)}" + end + + private + + # Mounts a regular expression, returned as a string to ease interpolation, + # that will match part by part the given constant. + # + # const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?" + # const_regexp("::") # => "::" + def const_regexp(camel_cased_word) + parts = camel_cased_word.split("::".freeze) + + return Regexp.escape(camel_cased_word) if parts.blank? + + last = parts.pop + + parts.reverse.inject(last) do |acc, part| + part.empty? ? acc : "#{part}(::#{acc})?" + end + end + + # Applies inflection rules for +singularize+ and +pluralize+. + # + # If passed an optional +locale+ parameter, the uncountables will be + # found for that locale. + # + # apply_inflections('post', inflections.plurals, :en) # => "posts" + # apply_inflections('posts', inflections.singulars, :en) # => "post" + def apply_inflections(word, rules, locale = :en) + result = word.to_s.dup + + if word.empty? || inflections(locale).uncountables.uncountable?(result) + result + else + rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) } + result + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/inflector/transliterate.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/inflector/transliterate.rb new file mode 100644 index 00000000..6f2ca499 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/inflector/transliterate.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +require "active_support/core_ext/string/multibyte" +require "active_support/i18n" + +module ActiveSupport + module Inflector + # Replaces non-ASCII characters with an ASCII approximation, or if none + # exists, a replacement character which defaults to "?". + # + # transliterate('Ærøskøbing') + # # => "AEroskobing" + # + # Default approximations are provided for Western/Latin characters, + # e.g, "ø", "ñ", "é", "ß", etc. + # + # This method is I18n aware, so you can set up custom approximations for a + # locale. This can be useful, for example, to transliterate German's "ü" + # and "ö" to "ue" and "oe", or to add support for transliterating Russian + # to ASCII. + # + # In order to make your custom transliterations available, you must set + # them as the i18n.transliterate.rule i18n key: + # + # # Store the transliterations in locales/de.yml + # i18n: + # transliterate: + # rule: + # ü: "ue" + # ö: "oe" + # + # # Or set them using Ruby + # I18n.backend.store_translations(:de, i18n: { + # transliterate: { + # rule: { + # 'ü' => 'ue', + # 'ö' => 'oe' + # } + # } + # }) + # + # The value for i18n.transliterate.rule can be a simple Hash that + # maps characters to ASCII approximations as shown above, or, for more + # complex requirements, a Proc: + # + # I18n.backend.store_translations(:de, i18n: { + # transliterate: { + # rule: ->(string) { MyTransliterator.transliterate(string) } + # } + # }) + # + # Now you can have different transliterations for each locale: + # + # I18n.locale = :en + # transliterate('Jürgen') + # # => "Jurgen" + # + # I18n.locale = :de + # transliterate('Jürgen') + # # => "Juergen" + def transliterate(string, replacement = "?".freeze) + raise ArgumentError, "Can only transliterate strings. Received #{string.class.name}" unless string.is_a?(String) + + I18n.transliterate( + ActiveSupport::Multibyte::Unicode.normalize( + ActiveSupport::Multibyte::Unicode.tidy_bytes(string), :c), + replacement: replacement) + end + + # Replaces special characters in a string so that it may be used as part of + # a 'pretty' URL. + # + # parameterize("Donald E. Knuth") # => "donald-e-knuth" + # parameterize("^très|Jolie-- ") # => "tres-jolie" + # + # To use a custom separator, override the +separator+ argument. + # + # parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth" + # parameterize("^très|Jolie__ ", separator: '_') # => "tres_jolie" + # + # To preserve the case of the characters in a string, use the +preserve_case+ argument. + # + # parameterize("Donald E. Knuth", preserve_case: true) # => "Donald-E-Knuth" + # parameterize("^très|Jolie-- ", preserve_case: true) # => "tres-Jolie" + # + # It preserves dashes and underscores unless they are used as separators: + # + # parameterize("^très|Jolie__ ") # => "tres-jolie__" + # parameterize("^très|Jolie-- ", separator: "_") # => "tres_jolie--" + # parameterize("^très_Jolie-- ", separator: ".") # => "tres_jolie--" + # + def parameterize(string, separator: "-", preserve_case: false) + # Replace accented chars with their ASCII equivalents. + parameterized_string = transliterate(string) + + # Turn unwanted chars into the separator. + parameterized_string.gsub!(/[^a-z0-9\-_]+/i, separator) + + unless separator.nil? || separator.empty? + if separator == "-".freeze + re_duplicate_separator = /-{2,}/ + re_leading_trailing_separator = /^-|-$/i + else + re_sep = Regexp.escape(separator) + re_duplicate_separator = /#{re_sep}{2,}/ + re_leading_trailing_separator = /^#{re_sep}|#{re_sep}$/i + end + # No more than one of the separator in a row. + parameterized_string.gsub!(re_duplicate_separator, separator) + # Remove leading/trailing separator. + parameterized_string.gsub!(re_leading_trailing_separator, "".freeze) + end + + parameterized_string.downcase! unless preserve_case + parameterized_string + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/json.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/json.rb new file mode 100644 index 00000000..d7887175 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/json.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +require "active_support/json/decoding" +require "active_support/json/encoding" diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/json/decoding.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/json/decoding.rb new file mode 100644 index 00000000..8c0e016d --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/json/decoding.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/module/delegation" +require "json" + +module ActiveSupport + # Look for and parse json strings that look like ISO 8601 times. + mattr_accessor :parse_json_times + + module JSON + # matches YAML-formatted dates + DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/ + DATETIME_REGEX = /^(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[T \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?)?)$/ + + class << self + # Parses a JSON string (JavaScript Object Notation) into a hash. + # See http://www.json.org for more info. + # + # ActiveSupport::JSON.decode("{\"team\":\"rails\",\"players\":\"36\"}") + # => {"team" => "rails", "players" => "36"} + def decode(json) + data = ::JSON.parse(json, quirks_mode: true) + + if ActiveSupport.parse_json_times + convert_dates_from(data) + else + data + end + end + + # Returns the class of the error that will be raised when there is an + # error in decoding JSON. Using this method means you won't directly + # depend on the ActiveSupport's JSON implementation, in case it changes + # in the future. + # + # begin + # obj = ActiveSupport::JSON.decode(some_string) + # rescue ActiveSupport::JSON.parse_error + # Rails.logger.warn("Attempted to decode invalid JSON: #{some_string}") + # end + def parse_error + ::JSON::ParserError + end + + private + + def convert_dates_from(data) + case data + when nil + nil + when DATE_REGEX + begin + Date.parse(data) + rescue ArgumentError + data + end + when DATETIME_REGEX + begin + Time.zone.parse(data) + rescue ArgumentError + data + end + when Array + data.map! { |d| convert_dates_from(d) } + when Hash + data.each do |key, value| + data[key] = convert_dates_from(value) + end + else + data + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/json/encoding.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/json/encoding.rb new file mode 100644 index 00000000..1339c75f --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/json/encoding.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/json" +require "active_support/core_ext/module/delegation" + +module ActiveSupport + class << self + delegate :use_standard_json_time_format, :use_standard_json_time_format=, + :time_precision, :time_precision=, + :escape_html_entities_in_json, :escape_html_entities_in_json=, + :json_encoder, :json_encoder=, + to: :'ActiveSupport::JSON::Encoding' + end + + module JSON + # Dumps objects in JSON (JavaScript Object Notation). + # See http://www.json.org for more info. + # + # ActiveSupport::JSON.encode({ team: 'rails', players: '36' }) + # # => "{\"team\":\"rails\",\"players\":\"36\"}" + def self.encode(value, options = nil) + Encoding.json_encoder.new(options).encode(value) + end + + module Encoding #:nodoc: + class JSONGemEncoder #:nodoc: + attr_reader :options + + def initialize(options = nil) + @options = options || {} + end + + # Encode the given object into a JSON string + def encode(value) + stringify jsonify value.as_json(options.dup) + end + + private + # Rails does more escaping than the JSON gem natively does (we + # escape \u2028 and \u2029 and optionally >, <, & to work around + # certain browser problems). + ESCAPED_CHARS = { + "\u2028" => '\u2028', + "\u2029" => '\u2029', + ">" => '\u003e', + "<" => '\u003c', + "&" => '\u0026', + } + + ESCAPE_REGEX_WITH_HTML_ENTITIES = /[\u2028\u2029><&]/u + ESCAPE_REGEX_WITHOUT_HTML_ENTITIES = /[\u2028\u2029]/u + + # This class wraps all the strings we see and does the extra escaping + class EscapedString < String #:nodoc: + def to_json(*) + if Encoding.escape_html_entities_in_json + super.gsub ESCAPE_REGEX_WITH_HTML_ENTITIES, ESCAPED_CHARS + else + super.gsub ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, ESCAPED_CHARS + end + end + + def to_s + self + end + end + + # Mark these as private so we don't leak encoding-specific constructs + private_constant :ESCAPED_CHARS, :ESCAPE_REGEX_WITH_HTML_ENTITIES, + :ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, :EscapedString + + # Convert an object into a "JSON-ready" representation composed of + # primitives like Hash, Array, String, Numeric, + # and +true+/+false+/+nil+. + # Recursively calls #as_json to the object to recursively build a + # fully JSON-ready object. + # + # This allows developers to implement #as_json without having to + # worry about what base types of objects they are allowed to return + # or having to remember to call #as_json recursively. + # + # Note: the +options+ hash passed to +object.to_json+ is only passed + # to +object.as_json+, not any of this method's recursive +#as_json+ + # calls. + def jsonify(value) + case value + when String + EscapedString.new(value) + when Numeric, NilClass, TrueClass, FalseClass + value.as_json + when Hash + Hash[value.map { |k, v| [jsonify(k), jsonify(v)] }] + when Array + value.map { |v| jsonify(v) } + else + jsonify value.as_json + end + end + + # Encode a "jsonified" Ruby data structure using the JSON gem + def stringify(jsonified) + ::JSON.generate(jsonified, quirks_mode: true, max_nesting: false) + end + end + + class << self + # If true, use ISO 8601 format for dates and times. Otherwise, fall back + # to the Active Support legacy format. + attr_accessor :use_standard_json_time_format + + # If true, encode >, <, & as escaped unicode sequences (e.g. > as \u003e) + # as a safety measure. + attr_accessor :escape_html_entities_in_json + + # Sets the precision of encoded time values. + # Defaults to 3 (equivalent to millisecond precision) + attr_accessor :time_precision + + # Sets the encoder used by Rails to encode Ruby objects into JSON strings + # in +Object#to_json+ and +ActiveSupport::JSON.encode+. + attr_accessor :json_encoder + end + + self.use_standard_json_time_format = true + self.escape_html_entities_in_json = true + self.json_encoder = JSONGemEncoder + self.time_precision = 3 + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/key_generator.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/key_generator.rb new file mode 100644 index 00000000..78f7d7ca --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/key_generator.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require "concurrent/map" +require "openssl" + +module ActiveSupport + # KeyGenerator is a simple wrapper around OpenSSL's implementation of PBKDF2. + # It can be used to derive a number of keys for various purposes from a given secret. + # This lets Rails applications have a single secure secret, but avoid reusing that + # key in multiple incompatible contexts. + class KeyGenerator + def initialize(secret, options = {}) + @secret = secret + # The default iterations are higher than required for our key derivation uses + # on the off chance someone uses this for password storage + @iterations = options[:iterations] || 2**16 + end + + # Returns a derived key suitable for use. The default key_size is chosen + # to be compatible with the default settings of ActiveSupport::MessageVerifier. + # i.e. OpenSSL::Digest::SHA1#block_length + def generate_key(salt, key_size = 64) + OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size) + end + end + + # CachingKeyGenerator is a wrapper around KeyGenerator which allows users to avoid + # re-executing the key generation process when it's called using the same salt and + # key_size. + class CachingKeyGenerator + def initialize(key_generator) + @key_generator = key_generator + @cache_keys = Concurrent::Map.new + end + + # Returns a derived key suitable for use. + def generate_key(*args) + @cache_keys[args.join] ||= @key_generator.generate_key(*args) + end + end + + class LegacyKeyGenerator # :nodoc: + SECRET_MIN_LENGTH = 30 # Characters + + def initialize(secret) + ensure_secret_secure(secret) + @secret = secret + end + + def generate_key(salt) + @secret + end + + private + + # To prevent users from using something insecure like "Password" we make sure that the + # secret they've provided is at least 30 characters in length. + def ensure_secret_secure(secret) + if secret.blank? + raise ArgumentError, "A secret is required to generate an integrity hash " \ + "for cookie session data. Set a secret_key_base of at least " \ + "#{SECRET_MIN_LENGTH} characters in via `bin/rails credentials:edit`." + end + + if secret.length < SECRET_MIN_LENGTH + raise ArgumentError, "Secret should be something secure, " \ + "like \"#{SecureRandom.hex(16)}\". The value you " \ + "provided, \"#{secret}\", is shorter than the minimum length " \ + "of #{SECRET_MIN_LENGTH} characters." + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/lazy_load_hooks.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/lazy_load_hooks.rb new file mode 100644 index 00000000..dc8080c4 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/lazy_load_hooks.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +module ActiveSupport + # lazy_load_hooks allows Rails to lazily load a lot of components and thus + # making the app boot faster. Because of this feature now there is no need to + # require ActiveRecord::Base at boot time purely to apply + # configuration. Instead a hook is registered that applies configuration once + # ActiveRecord::Base is loaded. Here ActiveRecord::Base is + # used as example but this feature can be applied elsewhere too. + # + # Here is an example where +on_load+ method is called to register a hook. + # + # initializer 'active_record.initialize_timezone' do + # ActiveSupport.on_load(:active_record) do + # self.time_zone_aware_attributes = true + # self.default_timezone = :utc + # end + # end + # + # When the entirety of +ActiveRecord::Base+ has been + # evaluated then +run_load_hooks+ is invoked. The very last line of + # +ActiveRecord::Base+ is: + # + # ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base) + module LazyLoadHooks + def self.extended(base) # :nodoc: + base.class_eval do + @load_hooks = Hash.new { |h, k| h[k] = [] } + @loaded = Hash.new { |h, k| h[k] = [] } + @run_once = Hash.new { |h, k| h[k] = [] } + end + end + + # Declares a block that will be executed when a Rails component is fully + # loaded. + # + # Options: + # + # * :yield - Yields the object that run_load_hooks to +block+. + # * :run_once - Given +block+ will run only once. + def on_load(name, options = {}, &block) + @loaded[name].each do |base| + execute_hook(name, base, options, block) + end + + @load_hooks[name] << [block, options] + end + + def run_load_hooks(name, base = Object) + @loaded[name] << base + @load_hooks[name].each do |hook, options| + execute_hook(name, base, options, hook) + end + end + + private + + def with_execution_control(name, block, once) + unless @run_once[name].include?(block) + @run_once[name] << block if once + + yield + end + end + + def execute_hook(name, base, options, block) + with_execution_control(name, block, options[:run_once]) do + if options[:yield] + block.call(base) + else + base.instance_eval(&block) + end + end + end + end + + extend LazyLoadHooks +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/locale/en.yml b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/locale/en.yml new file mode 100644 index 00000000..c64b7598 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/locale/en.yml @@ -0,0 +1,135 @@ +en: + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%Y-%m-%d" + short: "%b %d" + long: "%B %d, %Y" + + day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday] + abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December] + abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec] + # Used in date_select and datetime_select. + order: + - year + - month + - day + + time: + formats: + default: "%a, %d %b %Y %H:%M:%S %z" + short: "%d %b %H:%M" + long: "%B %d, %Y %H:%M" + am: "am" + pm: "pm" + +# Used in array.to_sentence. + support: + array: + words_connector: ", " + two_words_connector: " and " + last_word_connector: ", and " + number: + # Used in NumberHelper.number_to_delimited() + # These are also the defaults for 'currency', 'percentage', 'precision', and 'human' + format: + # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5) + separator: "." + # Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three) + delimiter: "," + # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00) + precision: 3 + # If set to true, precision will mean the number of significant digits instead + # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2) + significant: false + # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2) + strip_insignificant_zeros: false + + # Used in NumberHelper.number_to_currency() + currency: + format: + # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00) + format: "%u%n" + unit: "$" + # These five are to override number.format and are optional + separator: "." + delimiter: "," + precision: 2 + significant: false + strip_insignificant_zeros: false + + # Used in NumberHelper.number_to_percentage() + percentage: + format: + # These five are to override number.format and are optional + # separator: + delimiter: "" + # precision: + # significant: false + # strip_insignificant_zeros: false + format: "%n%" + + # Used in NumberHelper.number_to_rounded() + precision: + format: + # These five are to override number.format and are optional + # separator: + delimiter: "" + # precision: + # significant: false + # strip_insignificant_zeros: false + + # Used in NumberHelper.number_to_human_size() and NumberHelper.number_to_human() + human: + format: + # These five are to override number.format and are optional + # separator: + delimiter: "" + precision: 3 + significant: true + strip_insignificant_zeros: true + # Used in number_to_human_size() + storage_units: + # Storage units output formatting. + # %u is the storage unit, %n is the number (default: 2 MB) + format: "%n %u" + units: + byte: + one: "Byte" + other: "Bytes" + kb: "KB" + mb: "MB" + gb: "GB" + tb: "TB" + pb: "PB" + eb: "EB" + # Used in NumberHelper.number_to_human() + decimal_units: + format: "%n %u" + # Decimal units output formatting + # By default we will only quantify some of the exponents + # but the commented ones might be defined or overridden + # by the user. + units: + # femto: Quadrillionth + # pico: Trillionth + # nano: Billionth + # micro: Millionth + # mili: Thousandth + # centi: Hundredth + # deci: Tenth + unit: "" + # ten: + # one: Ten + # other: Tens + # hundred: Hundred + thousand: Thousand + million: Million + billion: Billion + trillion: Trillion + quadrillion: Quadrillion diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/log_subscriber.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/log_subscriber.rb new file mode 100644 index 00000000..0f7be06c --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/log_subscriber.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/class/attribute" +require "active_support/subscriber" + +module ActiveSupport + # ActiveSupport::LogSubscriber is an object set to consume + # ActiveSupport::Notifications with the sole purpose of logging them. + # The log subscriber dispatches notifications to a registered object based + # on its given namespace. + # + # An example would be Active Record log subscriber responsible for logging + # queries: + # + # module ActiveRecord + # class LogSubscriber < ActiveSupport::LogSubscriber + # def sql(event) + # "#{event.payload[:name]} (#{event.duration}) #{event.payload[:sql]}" + # end + # end + # end + # + # And it's finally registered as: + # + # ActiveRecord::LogSubscriber.attach_to :active_record + # + # Since we need to know all instance methods before attaching the log + # subscriber, the line above should be called after your + # ActiveRecord::LogSubscriber definition. + # + # After configured, whenever a "sql.active_record" notification is published, + # it will properly dispatch the event (ActiveSupport::Notifications::Event) to + # the sql method. + # + # Log subscriber also has some helpers to deal with logging and automatically + # flushes all logs when the request finishes (via action_dispatch.callback + # notification) in a Rails environment. + class LogSubscriber < Subscriber + # Embed in a String to clear all previous ANSI sequences. + CLEAR = "\e[0m" + BOLD = "\e[1m" + + # Colors + BLACK = "\e[30m" + RED = "\e[31m" + GREEN = "\e[32m" + YELLOW = "\e[33m" + BLUE = "\e[34m" + MAGENTA = "\e[35m" + CYAN = "\e[36m" + WHITE = "\e[37m" + + mattr_accessor :colorize_logging, default: true + + class << self + def logger + @logger ||= if defined?(Rails) && Rails.respond_to?(:logger) + Rails.logger + end + end + + attr_writer :logger + + def log_subscribers + subscribers + end + + # Flush all log_subscribers' logger. + def flush_all! + logger.flush if logger.respond_to?(:flush) + end + end + + def logger + LogSubscriber.logger + end + + def start(name, id, payload) + super if logger + end + + def finish(name, id, payload) + super if logger + rescue => e + if logger + logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}" + end + end + + private + + %w(info debug warn error fatal unknown).each do |level| + class_eval <<-METHOD, __FILE__, __LINE__ + 1 + def #{level}(progname = nil, &block) + logger.#{level}(progname, &block) if logger + end + METHOD + end + + # Set color by using a symbol or one of the defined constants. If a third + # option is set to +true+, it also adds bold to the string. This is based + # on the Highline implementation and will automatically append CLEAR to the + # end of the returned String. + def color(text, color, bold = false) # :doc: + return text unless colorize_logging + color = self.class.const_get(color.upcase) if color.is_a?(Symbol) + bold = bold ? BOLD : "" + "#{bold}#{color}#{text}#{CLEAR}" + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/log_subscriber/test_helper.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/log_subscriber/test_helper.rb new file mode 100644 index 00000000..3f19ef50 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/log_subscriber/test_helper.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +require "active_support/log_subscriber" +require "active_support/logger" +require "active_support/notifications" + +module ActiveSupport + class LogSubscriber + # Provides some helpers to deal with testing log subscribers by setting up + # notifications. Take for instance Active Record subscriber tests: + # + # class SyncLogSubscriberTest < ActiveSupport::TestCase + # include ActiveSupport::LogSubscriber::TestHelper + # + # setup do + # ActiveRecord::LogSubscriber.attach_to(:active_record) + # end + # + # def test_basic_query_logging + # Developer.all.to_a + # wait + # assert_equal 1, @logger.logged(:debug).size + # assert_match(/Developer Load/, @logger.logged(:debug).last) + # assert_match(/SELECT \* FROM "developers"/, @logger.logged(:debug).last) + # end + # end + # + # All you need to do is to ensure that your log subscriber is added to + # Rails::Subscriber, as in the second line of the code above. The test + # helpers are responsible for setting up the queue, subscriptions and + # turning colors in logs off. + # + # The messages are available in the @logger instance, which is a logger with + # limited powers (it actually does not send anything to your output), and + # you can collect them doing @logger.logged(level), where level is the level + # used in logging, like info, debug, warn and so on. + module TestHelper + def setup # :nodoc: + @logger = MockLogger.new + @notifier = ActiveSupport::Notifications::Fanout.new + + ActiveSupport::LogSubscriber.colorize_logging = false + + @old_notifier = ActiveSupport::Notifications.notifier + set_logger(@logger) + ActiveSupport::Notifications.notifier = @notifier + end + + def teardown # :nodoc: + set_logger(nil) + ActiveSupport::Notifications.notifier = @old_notifier + end + + class MockLogger + include ActiveSupport::Logger::Severity + + attr_reader :flush_count + attr_accessor :level + + def initialize(level = DEBUG) + @flush_count = 0 + @level = level + @logged = Hash.new { |h, k| h[k] = [] } + end + + def method_missing(level, message = nil) + if block_given? + @logged[level] << yield + else + @logged[level] << message + end + end + + def logged(level) + @logged[level].compact.map { |l| l.to_s.strip } + end + + def flush + @flush_count += 1 + end + + ActiveSupport::Logger::Severity.constants.each do |severity| + class_eval <<-EOT, __FILE__, __LINE__ + 1 + def #{severity.downcase}? + #{severity} >= @level + end + EOT + end + end + + # Wait notifications to be published. + def wait + @notifier.wait + end + + # Overwrite if you use another logger in your log subscriber. + # + # def logger + # ActiveRecord::Base.logger = @logger + # end + def set_logger(logger) + ActiveSupport::LogSubscriber.logger = logger + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/logger.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/logger.rb new file mode 100644 index 00000000..8152a182 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/logger.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +require "active_support/logger_silence" +require "active_support/logger_thread_safe_level" +require "logger" + +module ActiveSupport + class Logger < ::Logger + include ActiveSupport::LoggerThreadSafeLevel + include LoggerSilence + + # Returns true if the logger destination matches one of the sources + # + # logger = Logger.new(STDOUT) + # ActiveSupport::Logger.logger_outputs_to?(logger, STDOUT) + # # => true + def self.logger_outputs_to?(logger, *sources) + logdev = logger.instance_variable_get("@logdev") + logger_source = logdev.dev if logdev.respond_to?(:dev) + sources.any? { |source| source == logger_source } + end + + # Broadcasts logs to multiple loggers. + def self.broadcast(logger) # :nodoc: + Module.new do + define_method(:add) do |*args, &block| + logger.add(*args, &block) + super(*args, &block) + end + + define_method(:<<) do |x| + logger << x + super(x) + end + + define_method(:close) do + logger.close + super() + end + + define_method(:progname=) do |name| + logger.progname = name + super(name) + end + + define_method(:formatter=) do |formatter| + logger.formatter = formatter + super(formatter) + end + + define_method(:level=) do |level| + logger.level = level + super(level) + end + + define_method(:local_level=) do |level| + logger.local_level = level if logger.respond_to?(:local_level=) + super(level) if respond_to?(:local_level=) + end + + define_method(:silence) do |level = Logger::ERROR, &block| + if logger.respond_to?(:silence) + logger.silence(level) do + if defined?(super) + super(level, &block) + else + block.call(self) + end + end + else + if defined?(super) + super(level, &block) + else + block.call(self) + end + end + end + end + end + + def initialize(*args) + super + @formatter = SimpleFormatter.new + after_initialize if respond_to? :after_initialize + end + + def add(severity, message = nil, progname = nil, &block) + return true if @logdev.nil? || (severity || UNKNOWN) < level + super + end + + Logger::Severity.constants.each do |severity| + class_eval(<<-EOT, __FILE__, __LINE__ + 1) + def #{severity.downcase}? # def debug? + Logger::#{severity} >= level # DEBUG >= level + end # end + EOT + end + + # Simple formatter which only displays the message. + class SimpleFormatter < ::Logger::Formatter + # This method is invoked when a log event occurs + def call(severity, timestamp, progname, msg) + "#{String === msg ? msg : msg.inspect}\n" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/logger_silence.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/logger_silence.rb new file mode 100644 index 00000000..89f32b67 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/logger_silence.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require "active_support/concern" +require "active_support/core_ext/module/attribute_accessors" +require "concurrent" + +module LoggerSilence + extend ActiveSupport::Concern + + included do + cattr_accessor :silencer, default: true + end + + # Silences the logger for the duration of the block. + def silence(temporary_level = Logger::ERROR) + if silencer + begin + old_local_level = local_level + self.local_level = temporary_level + + yield self + ensure + self.local_level = old_local_level + end + else + yield self + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/logger_thread_safe_level.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/logger_thread_safe_level.rb new file mode 100644 index 00000000..ba32813d --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/logger_thread_safe_level.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require "active_support/concern" + +module ActiveSupport + module LoggerThreadSafeLevel # :nodoc: + extend ActiveSupport::Concern + + def after_initialize + @local_levels = Concurrent::Map.new(initial_capacity: 2) + end + + def local_log_id + Thread.current.__id__ + end + + def local_level + @local_levels[local_log_id] + end + + def local_level=(level) + if level + @local_levels[local_log_id] = level + else + @local_levels.delete(local_log_id) + end + end + + def level + local_level || super + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/message_encryptor.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/message_encryptor.rb new file mode 100644 index 00000000..8b732708 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/message_encryptor.rb @@ -0,0 +1,229 @@ +# frozen_string_literal: true + +require "openssl" +require "base64" +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/message_verifier" +require "active_support/messages/metadata" + +module ActiveSupport + # MessageEncryptor is a simple way to encrypt values which get stored + # somewhere you don't trust. + # + # The cipher text and initialization vector are base64 encoded and returned + # to you. + # + # This can be used in situations similar to the MessageVerifier, but + # where you don't want users to be able to determine the value of the payload. + # + # len = ActiveSupport::MessageEncryptor.key_len + # salt = SecureRandom.random_bytes(len) + # key = ActiveSupport::KeyGenerator.new('password').generate_key(salt, len) # => "\x89\xE0\x156\xAC..." + # crypt = ActiveSupport::MessageEncryptor.new(key) # => # + # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..." + # crypt.decrypt_and_verify(encrypted_data) # => "my secret data" + # + # === Confining messages to a specific purpose + # + # By default any message can be used throughout your app. But they can also be + # confined to a specific +:purpose+. + # + # token = crypt.encrypt_and_sign("this is the chair", purpose: :login) + # + # Then that same purpose must be passed when verifying to get the data back out: + # + # crypt.decrypt_and_verify(token, purpose: :login) # => "this is the chair" + # crypt.decrypt_and_verify(token, purpose: :shipping) # => nil + # crypt.decrypt_and_verify(token) # => nil + # + # Likewise, if a message has no purpose it won't be returned when verifying with + # a specific purpose. + # + # token = crypt.encrypt_and_sign("the conversation is lively") + # crypt.decrypt_and_verify(token, purpose: :scare_tactics) # => nil + # crypt.decrypt_and_verify(token) # => "the conversation is lively" + # + # === Making messages expire + # + # By default messages last forever and verifying one year from now will still + # return the original value. But messages can be set to expire at a given + # time with +:expires_in+ or +:expires_at+. + # + # crypt.encrypt_and_sign(parcel, expires_in: 1.month) + # crypt.encrypt_and_sign(doowad, expires_at: Time.now.end_of_year) + # + # Then the messages can be verified and returned upto the expire time. + # Thereafter, verifying returns +nil+. + # + # === Rotating keys + # + # MessageEncryptor also supports rotating out old configurations by falling + # back to a stack of encryptors. Call +rotate+ to build and add an encryptor + # so +decrypt_and_verify+ will also try the fallback. + # + # By default any rotated encryptors use the values of the primary + # encryptor unless specified otherwise. + # + # You'd give your encryptor the new defaults: + # + # crypt = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm") + # + # Then gradually rotate the old values out by adding them as fallbacks. Any message + # generated with the old values will then work until the rotation is removed. + # + # crypt.rotate old_secret # Fallback to an old secret instead of @secret. + # crypt.rotate cipher: "aes-256-cbc" # Fallback to an old cipher instead of aes-256-gcm. + # + # Though if both the secret and the cipher was changed at the same time, + # the above should be combined into: + # + # crypt.rotate old_secret, cipher: "aes-256-cbc" + class MessageEncryptor + prepend Messages::Rotator::Encryptor + + cattr_accessor :use_authenticated_message_encryption, instance_accessor: false, default: false + + class << self + def default_cipher #:nodoc: + if use_authenticated_message_encryption + "aes-256-gcm" + else + "aes-256-cbc" + end + end + end + + module NullSerializer #:nodoc: + def self.load(value) + value + end + + def self.dump(value) + value + end + end + + module NullVerifier #:nodoc: + def self.verify(value) + value + end + + def self.generate(value) + value + end + end + + class InvalidMessage < StandardError; end + OpenSSLCipherError = OpenSSL::Cipher::CipherError + + # Initialize a new MessageEncryptor. +secret+ must be at least as long as + # the cipher key size. For the default 'aes-256-gcm' cipher, this is 256 + # bits. If you are using a user-entered secret, you can generate a suitable + # key by using ActiveSupport::KeyGenerator or a similar key + # derivation function. + # + # First additional parameter is used as the signature key for +MessageVerifier+. + # This allows you to specify keys to encrypt and sign data. + # + # ActiveSupport::MessageEncryptor.new('secret', 'signature_secret') + # + # Options: + # * :cipher - Cipher to use. Can be any cipher returned by + # OpenSSL::Cipher.ciphers. Default is 'aes-256-gcm'. + # * :digest - String of digest to use for signing. Default is + # +SHA1+. Ignored when using an AEAD cipher like 'aes-256-gcm'. + # * :serializer - Object serializer to use. Default is +Marshal+. + def initialize(secret, *signature_key_or_options) + options = signature_key_or_options.extract_options! + sign_secret = signature_key_or_options.first + @secret = secret + @sign_secret = sign_secret + @cipher = options[:cipher] || self.class.default_cipher + @digest = options[:digest] || "SHA1" unless aead_mode? + @verifier = resolve_verifier + @serializer = options[:serializer] || Marshal + end + + # Encrypt and sign a message. We need to sign the message in order to avoid + # padding attacks. Reference: https://www.limited-entropy.com/padding-oracle-attacks/. + def encrypt_and_sign(value, expires_at: nil, expires_in: nil, purpose: nil) + verifier.generate(_encrypt(value, expires_at: expires_at, expires_in: expires_in, purpose: purpose)) + end + + # Decrypt and verify a message. We need to verify the message in order to + # avoid padding attacks. Reference: https://www.limited-entropy.com/padding-oracle-attacks/. + def decrypt_and_verify(data, purpose: nil, **) + _decrypt(verifier.verify(data), purpose) + end + + # Given a cipher, returns the key length of the cipher to help generate the key of desired size + def self.key_len(cipher = default_cipher) + OpenSSL::Cipher.new(cipher).key_len + end + + private + def _encrypt(value, **metadata_options) + cipher = new_cipher + cipher.encrypt + cipher.key = @secret + + # Rely on OpenSSL for the initialization vector + iv = cipher.random_iv + cipher.auth_data = "" if aead_mode? + + encrypted_data = cipher.update(Messages::Metadata.wrap(@serializer.dump(value), metadata_options)) + encrypted_data << cipher.final + + blob = "#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}" + blob = "#{blob}--#{::Base64.strict_encode64 cipher.auth_tag}" if aead_mode? + blob + end + + def _decrypt(encrypted_message, purpose) + cipher = new_cipher + encrypted_data, iv, auth_tag = encrypted_message.split("--".freeze).map { |v| ::Base64.strict_decode64(v) } + + # Currently the OpenSSL bindings do not raise an error if auth_tag is + # truncated, which would allow an attacker to easily forge it. See + # https://github.com/ruby/openssl/issues/63 + raise InvalidMessage if aead_mode? && (auth_tag.nil? || auth_tag.bytes.length != 16) + + cipher.decrypt + cipher.key = @secret + cipher.iv = iv + if aead_mode? + cipher.auth_tag = auth_tag + cipher.auth_data = "" + end + + decrypted_data = cipher.update(encrypted_data) + decrypted_data << cipher.final + + message = Messages::Metadata.verify(decrypted_data, purpose) + @serializer.load(message) if message + rescue OpenSSLCipherError, TypeError, ArgumentError + raise InvalidMessage + end + + def new_cipher + OpenSSL::Cipher.new(@cipher) + end + + def verifier + @verifier + end + + def aead_mode? + @aead_mode ||= new_cipher.authenticated? + end + + def resolve_verifier + if aead_mode? + NullVerifier + else + MessageVerifier.new(@sign_secret || @secret, digest: @digest, serializer: NullSerializer) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/message_verifier.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/message_verifier.rb new file mode 100644 index 00000000..83c39c0a --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/message_verifier.rb @@ -0,0 +1,205 @@ +# frozen_string_literal: true + +require "base64" +require "active_support/core_ext/object/blank" +require "active_support/security_utils" +require "active_support/messages/metadata" +require "active_support/messages/rotator" + +module ActiveSupport + # +MessageVerifier+ makes it easy to generate and verify messages which are + # signed to prevent tampering. + # + # This is useful for cases like remember-me tokens and auto-unsubscribe links + # where the session store isn't suitable or available. + # + # Remember Me: + # cookies[:remember_me] = @verifier.generate([@user.id, 2.weeks.from_now]) + # + # In the authentication filter: + # + # id, time = @verifier.verify(cookies[:remember_me]) + # if Time.now < time + # self.current_user = User.find(id) + # end + # + # By default it uses Marshal to serialize the message. If you want to use + # another serialization method, you can set the serializer in the options + # hash upon initialization: + # + # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', serializer: YAML) + # + # +MessageVerifier+ creates HMAC signatures using SHA1 hash algorithm by default. + # If you want to use a different hash algorithm, you can change it by providing + # +:digest+ key as an option while initializing the verifier: + # + # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', digest: 'SHA256') + # + # === Confining messages to a specific purpose + # + # By default any message can be used throughout your app. But they can also be + # confined to a specific +:purpose+. + # + # token = @verifier.generate("this is the chair", purpose: :login) + # + # Then that same purpose must be passed when verifying to get the data back out: + # + # @verifier.verified(token, purpose: :login) # => "this is the chair" + # @verifier.verified(token, purpose: :shipping) # => nil + # @verifier.verified(token) # => nil + # + # @verifier.verify(token, purpose: :login) # => "this is the chair" + # @verifier.verify(token, purpose: :shipping) # => ActiveSupport::MessageVerifier::InvalidSignature + # @verifier.verify(token) # => ActiveSupport::MessageVerifier::InvalidSignature + # + # Likewise, if a message has no purpose it won't be returned when verifying with + # a specific purpose. + # + # token = @verifier.generate("the conversation is lively") + # @verifier.verified(token, purpose: :scare_tactics) # => nil + # @verifier.verified(token) # => "the conversation is lively" + # + # @verifier.verify(token, purpose: :scare_tactics) # => ActiveSupport::MessageVerifier::InvalidSignature + # @verifier.verify(token) # => "the conversation is lively" + # + # === Making messages expire + # + # By default messages last forever and verifying one year from now will still + # return the original value. But messages can be set to expire at a given + # time with +:expires_in+ or +:expires_at+. + # + # @verifier.generate(parcel, expires_in: 1.month) + # @verifier.generate(doowad, expires_at: Time.now.end_of_year) + # + # Then the messages can be verified and returned upto the expire time. + # Thereafter, the +verified+ method returns +nil+ while +verify+ raises + # ActiveSupport::MessageVerifier::InvalidSignature. + # + # === Rotating keys + # + # MessageVerifier also supports rotating out old configurations by falling + # back to a stack of verifiers. Call +rotate+ to build and add a verifier to + # so either +verified+ or +verify+ will also try verifying with the fallback. + # + # By default any rotated verifiers use the values of the primary + # verifier unless specified otherwise. + # + # You'd give your verifier the new defaults: + # + # verifier = ActiveSupport::MessageVerifier.new(@secret, digest: "SHA512", serializer: JSON) + # + # Then gradually rotate the old values out by adding them as fallbacks. Any message + # generated with the old values will then work until the rotation is removed. + # + # verifier.rotate old_secret # Fallback to an old secret instead of @secret. + # verifier.rotate digest: "SHA256" # Fallback to an old digest instead of SHA512. + # verifier.rotate serializer: Marshal # Fallback to an old serializer instead of JSON. + # + # Though the above would most likely be combined into one rotation: + # + # verifier.rotate old_secret, digest: "SHA256", serializer: Marshal + class MessageVerifier + prepend Messages::Rotator::Verifier + + class InvalidSignature < StandardError; end + + def initialize(secret, options = {}) + raise ArgumentError, "Secret should not be nil." unless secret + @secret = secret + @digest = options[:digest] || "SHA1" + @serializer = options[:serializer] || Marshal + end + + # Checks if a signed message could have been generated by signing an object + # with the +MessageVerifier+'s secret. + # + # verifier = ActiveSupport::MessageVerifier.new 's3Krit' + # signed_message = verifier.generate 'a private message' + # verifier.valid_message?(signed_message) # => true + # + # tampered_message = signed_message.chop # editing the message invalidates the signature + # verifier.valid_message?(tampered_message) # => false + def valid_message?(signed_message) + return if signed_message.nil? || !signed_message.valid_encoding? || signed_message.blank? + + data, digest = signed_message.split("--".freeze) + data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data)) + end + + # Decodes the signed message using the +MessageVerifier+'s secret. + # + # verifier = ActiveSupport::MessageVerifier.new 's3Krit' + # + # signed_message = verifier.generate 'a private message' + # verifier.verified(signed_message) # => 'a private message' + # + # Returns +nil+ if the message was not signed with the same secret. + # + # other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit' + # other_verifier.verified(signed_message) # => nil + # + # Returns +nil+ if the message is not Base64-encoded. + # + # invalid_message = "f--46a0120593880c733a53b6dad75b42ddc1c8996d" + # verifier.verified(invalid_message) # => nil + # + # Raises any error raised while decoding the signed message. + # + # incompatible_message = "test--dad7b06c94abba8d46a15fafaef56c327665d5ff" + # verifier.verified(incompatible_message) # => TypeError: incompatible marshal file format + def verified(signed_message, purpose: nil, **) + if valid_message?(signed_message) + begin + data = signed_message.split("--".freeze)[0] + message = Messages::Metadata.verify(decode(data), purpose) + @serializer.load(message) if message + rescue ArgumentError => argument_error + return if argument_error.message.include?("invalid base64") + raise + end + end + end + + # Decodes the signed message using the +MessageVerifier+'s secret. + # + # verifier = ActiveSupport::MessageVerifier.new 's3Krit' + # signed_message = verifier.generate 'a private message' + # + # verifier.verify(signed_message) # => 'a private message' + # + # Raises +InvalidSignature+ if the message was not signed with the same + # secret or was not Base64-encoded. + # + # other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit' + # other_verifier.verify(signed_message) # => ActiveSupport::MessageVerifier::InvalidSignature + def verify(*args) + verified(*args) || raise(InvalidSignature) + end + + # Generates a signed message for the provided value. + # + # The message is signed with the +MessageVerifier+'s secret. Without knowing + # the secret, the original value cannot be extracted from the message. + # + # verifier = ActiveSupport::MessageVerifier.new 's3Krit' + # verifier.generate 'a private message' # => "BAhJIhRwcml2YXRlLW1lc3NhZ2UGOgZFVA==--e2d724331ebdee96a10fb99b089508d1c72bd772" + def generate(value, expires_at: nil, expires_in: nil, purpose: nil) + data = encode(Messages::Metadata.wrap(@serializer.dump(value), expires_at: expires_at, expires_in: expires_in, purpose: purpose)) + "#{data}--#{generate_digest(data)}" + end + + private + def encode(data) + ::Base64.strict_encode64(data) + end + + def decode(data) + ::Base64.strict_decode64(data) + end + + def generate_digest(data) + require "openssl" unless defined?(OpenSSL) + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data) + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/messages/metadata.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/messages/metadata.rb new file mode 100644 index 00000000..e97caac7 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/messages/metadata.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require "time" + +module ActiveSupport + module Messages #:nodoc: + class Metadata #:nodoc: + def initialize(message, expires_at = nil, purpose = nil) + @message, @expires_at, @purpose = message, expires_at, purpose + end + + def as_json(options = {}) + { _rails: { message: @message, exp: @expires_at, pur: @purpose } } + end + + class << self + def wrap(message, expires_at: nil, expires_in: nil, purpose: nil) + if expires_at || expires_in || purpose + JSON.encode new(encode(message), pick_expiry(expires_at, expires_in), purpose) + else + message + end + end + + def verify(message, purpose) + extract_metadata(message).verify(purpose) + end + + private + def pick_expiry(expires_at, expires_in) + if expires_at + expires_at.utc.iso8601(3) + elsif expires_in + Time.now.utc.advance(seconds: expires_in).iso8601(3) + end + end + + def extract_metadata(message) + data = JSON.decode(message) rescue nil + + if data.is_a?(Hash) && data.key?("_rails") + new(decode(data["_rails"]["message"]), data["_rails"]["exp"], data["_rails"]["pur"]) + else + new(message) + end + end + + def encode(message) + ::Base64.strict_encode64(message) + end + + def decode(message) + ::Base64.strict_decode64(message) + end + end + + def verify(purpose) + @message if match?(purpose) && fresh? + end + + private + def match?(purpose) + @purpose.to_s == purpose.to_s + end + + def fresh? + @expires_at.nil? || Time.now.utc < Time.iso8601(@expires_at) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/messages/rotation_configuration.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/messages/rotation_configuration.rb new file mode 100644 index 00000000..bd50d6d3 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/messages/rotation_configuration.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module ActiveSupport + module Messages + class RotationConfiguration # :nodoc: + attr_reader :signed, :encrypted + + def initialize + @signed, @encrypted = [], [] + end + + def rotate(kind, *args) + case kind + when :signed + @signed << args + when :encrypted + @encrypted << args + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/messages/rotator.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/messages/rotator.rb new file mode 100644 index 00000000..823a399d --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/messages/rotator.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module ActiveSupport + module Messages + module Rotator # :nodoc: + def initialize(*, **options) + super + + @options = options + @rotations = [] + end + + def rotate(*secrets, **options) + @rotations << build_rotation(*secrets, @options.merge(options)) + end + + module Encryptor + include Rotator + + def decrypt_and_verify(*args, on_rotation: nil, **options) + super + rescue MessageEncryptor::InvalidMessage, MessageVerifier::InvalidSignature + run_rotations(on_rotation) { |encryptor| encryptor.decrypt_and_verify(*args, options) } || raise + end + + private + def build_rotation(secret = @secret, sign_secret = @sign_secret, options) + self.class.new(secret, sign_secret, options) + end + end + + module Verifier + include Rotator + + def verified(*args, on_rotation: nil, **options) + super || run_rotations(on_rotation) { |verifier| verifier.verified(*args, options) } + end + + private + def build_rotation(secret = @secret, options) + self.class.new(secret, options) + end + end + + private + def run_rotations(on_rotation) + @rotations.find do |rotation| + if message = yield(rotation) rescue next + on_rotation.call if on_rotation + return message + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/multibyte.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/multibyte.rb new file mode 100644 index 00000000..3fe3a05e --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/multibyte.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module ActiveSupport #:nodoc: + module Multibyte + autoload :Chars, "active_support/multibyte/chars" + autoload :Unicode, "active_support/multibyte/unicode" + + # The proxy class returned when calling mb_chars. You can use this accessor + # to configure your own proxy class so you can support other encodings. See + # the ActiveSupport::Multibyte::Chars implementation for an example how to + # do this. + # + # ActiveSupport::Multibyte.proxy_class = CharsForUTF32 + def self.proxy_class=(klass) + @proxy_class = klass + end + + # Returns the current proxy class. + def self.proxy_class + @proxy_class ||= ActiveSupport::Multibyte::Chars + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/multibyte/chars.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/multibyte/chars.rb new file mode 100644 index 00000000..8152b8fd --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/multibyte/chars.rb @@ -0,0 +1,235 @@ +# frozen_string_literal: true + +require "active_support/json" +require "active_support/core_ext/string/access" +require "active_support/core_ext/string/behavior" +require "active_support/core_ext/module/delegation" +require "active_support/core_ext/regexp" + +module ActiveSupport #:nodoc: + module Multibyte #:nodoc: + # Chars enables you to work transparently with UTF-8 encoding in the Ruby + # String class without having extensive knowledge about the encoding. A + # Chars object accepts a string upon initialization and proxies String + # methods in an encoding safe manner. All the normal String methods are also + # implemented on the proxy. + # + # String methods are proxied through the Chars object, and can be accessed + # through the +mb_chars+ method. Methods which would normally return a + # String object now return a Chars object so methods can be chained. + # + # 'The Perfect String '.mb_chars.downcase.strip.normalize + # # => # + # + # Chars objects are perfectly interchangeable with String objects as long as + # no explicit class checks are made. If certain methods do explicitly check + # the class, call +to_s+ before you pass chars objects to them. + # + # bad.explicit_checking_method 'T'.mb_chars.downcase.to_s + # + # The default Chars implementation assumes that the encoding of the string + # is UTF-8, if you want to handle different encodings you can write your own + # multibyte string handler and configure it through + # ActiveSupport::Multibyte.proxy_class. + # + # class CharsForUTF32 + # def size + # @wrapped_string.size / 4 + # end + # + # def self.accepts?(string) + # string.length % 4 == 0 + # end + # end + # + # ActiveSupport::Multibyte.proxy_class = CharsForUTF32 + class Chars + include Comparable + attr_reader :wrapped_string + alias to_s wrapped_string + alias to_str wrapped_string + + delegate :<=>, :=~, :acts_like_string?, to: :wrapped_string + + # Creates a new Chars instance by wrapping _string_. + def initialize(string) + @wrapped_string = string + @wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen? + end + + # Forward all undefined methods to the wrapped string. + def method_missing(method, *args, &block) + result = @wrapped_string.__send__(method, *args, &block) + if /!$/.match?(method) + self if result + else + result.kind_of?(String) ? chars(result) : result + end + end + + # Returns +true+ if _obj_ responds to the given method. Private methods + # are included in the search only if the optional second parameter + # evaluates to +true+. + def respond_to_missing?(method, include_private) + @wrapped_string.respond_to?(method, include_private) + end + + # Returns +true+ when the proxy class can handle the string. Returns + # +false+ otherwise. + def self.consumes?(string) + string.encoding == Encoding::UTF_8 + end + + # Works just like String#split, with the exception that the items + # in the resulting list are Chars instances instead of String. This makes + # chaining methods easier. + # + # 'Café périferôl'.mb_chars.split(/é/).map { |part| part.upcase.to_s } # => ["CAF", " P", "RIFERÔL"] + def split(*args) + @wrapped_string.split(*args).map { |i| self.class.new(i) } + end + + # Works like String#slice!, but returns an instance of + # Chars, or +nil+ if the string was not modified. The string will not be + # modified if the range given is out of bounds + # + # string = 'Welcome' + # string.mb_chars.slice!(3) # => # + # string # => 'Welome' + # string.mb_chars.slice!(0..3) # => # + # string # => 'me' + def slice!(*args) + string_sliced = @wrapped_string.slice!(*args) + if string_sliced + chars(string_sliced) + end + end + + # Reverses all characters in the string. + # + # 'Café'.mb_chars.reverse.to_s # => 'éfaC' + def reverse + chars(Unicode.unpack_graphemes(@wrapped_string).reverse.flatten.pack("U*")) + end + + # Limits the byte size of the string to a number of bytes without breaking + # characters. Usable when the storage for a string is limited for some + # reason. + # + # 'こんにちは'.mb_chars.limit(7).to_s # => "こん" + def limit(limit) + slice(0...translate_offset(limit)) + end + + # Converts characters in the string to uppercase. + # + # 'Laurent, où sont les tests ?'.mb_chars.upcase.to_s # => "LAURENT, OÙ SONT LES TESTS ?" + def upcase + chars Unicode.upcase(@wrapped_string) + end + + # Converts characters in the string to lowercase. + # + # 'VĚDA A VÝZKUM'.mb_chars.downcase.to_s # => "věda a výzkum" + def downcase + chars Unicode.downcase(@wrapped_string) + end + + # Converts characters in the string to the opposite case. + # + # 'El Cañón'.mb_chars.swapcase.to_s # => "eL cAÑÓN" + def swapcase + chars Unicode.swapcase(@wrapped_string) + end + + # Converts the first character to uppercase and the remainder to lowercase. + # + # 'über'.mb_chars.capitalize.to_s # => "Über" + def capitalize + (slice(0) || chars("")).upcase + (slice(1..-1) || chars("")).downcase + end + + # Capitalizes the first letter of every word, when possible. + # + # "ÉL QUE SE ENTERÓ".mb_chars.titleize.to_s # => "Él Que Se Enteró" + # "日本語".mb_chars.titleize.to_s # => "日本語" + def titleize + chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1) }) + end + alias_method :titlecase, :titleize + + # Returns the KC normalization of the string by default. NFKC is + # considered the best normalization form for passing strings to databases + # and validations. + # + # * form - The form you want to normalize in. Should be one of the following: + # :c, :kc, :d, or :kd. Default is + # ActiveSupport::Multibyte::Unicode.default_normalization_form + def normalize(form = nil) + chars(Unicode.normalize(@wrapped_string, form)) + end + + # Performs canonical decomposition on all the characters. + # + # 'é'.length # => 2 + # 'é'.mb_chars.decompose.to_s.length # => 3 + def decompose + chars(Unicode.decompose(:canonical, @wrapped_string.codepoints.to_a).pack("U*")) + end + + # Performs composition on all the characters. + # + # 'é'.length # => 3 + # 'é'.mb_chars.compose.to_s.length # => 2 + def compose + chars(Unicode.compose(@wrapped_string.codepoints.to_a).pack("U*")) + end + + # Returns the number of grapheme clusters in the string. + # + # 'क्षि'.mb_chars.length # => 4 + # 'क्षि'.mb_chars.grapheme_length # => 3 + def grapheme_length + Unicode.unpack_graphemes(@wrapped_string).length + end + + # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent + # resulting in a valid UTF-8 string. + # + # Passing +true+ will forcibly tidy all bytes, assuming that the string's + # encoding is entirely CP1252 or ISO-8859-1. + def tidy_bytes(force = false) + chars(Unicode.tidy_bytes(@wrapped_string, force)) + end + + def as_json(options = nil) #:nodoc: + to_s.as_json(options) + end + + %w(capitalize downcase reverse tidy_bytes upcase).each do |method| + define_method("#{method}!") do |*args| + @wrapped_string = send(method, *args).to_s + self + end + end + + private + + def translate_offset(byte_offset) + return nil if byte_offset.nil? + return 0 if @wrapped_string == "" + + begin + @wrapped_string.byteslice(0...byte_offset).unpack("U*").length + rescue ArgumentError + byte_offset -= 1 + retry + end + end + + def chars(string) + self.class.new(string) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/multibyte/unicode.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/multibyte/unicode.rb new file mode 100644 index 00000000..f923061f --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/multibyte/unicode.rb @@ -0,0 +1,394 @@ +# frozen_string_literal: true + +module ActiveSupport + module Multibyte + module Unicode + extend self + + # A list of all available normalization forms. + # See http://www.unicode.org/reports/tr15/tr15-29.html for more + # information about normalization. + NORMALIZATION_FORMS = [:c, :kc, :d, :kd] + + # The Unicode version that is supported by the implementation + UNICODE_VERSION = "9.0.0" + + # The default normalization used for operations that require + # normalization. It can be set to any of the normalizations + # in NORMALIZATION_FORMS. + # + # ActiveSupport::Multibyte::Unicode.default_normalization_form = :c + attr_accessor :default_normalization_form + @default_normalization_form = :kc + + # Hangul character boundaries and properties + HANGUL_SBASE = 0xAC00 + HANGUL_LBASE = 0x1100 + HANGUL_VBASE = 0x1161 + HANGUL_TBASE = 0x11A7 + HANGUL_LCOUNT = 19 + HANGUL_VCOUNT = 21 + HANGUL_TCOUNT = 28 + HANGUL_NCOUNT = HANGUL_VCOUNT * HANGUL_TCOUNT + HANGUL_SCOUNT = 11172 + HANGUL_SLAST = HANGUL_SBASE + HANGUL_SCOUNT + + # Detect whether the codepoint is in a certain character class. Returns + # +true+ when it's in the specified character class and +false+ otherwise. + # Valid character classes are: :cr, :lf, :l, + # :v, :lv, :lvt and :t. + # + # Primarily used by the grapheme cluster support. + def in_char_class?(codepoint, classes) + classes.detect { |c| database.boundary[c] === codepoint } ? true : false + end + + # Unpack the string at grapheme boundaries. Returns a list of character + # lists. + # + # Unicode.unpack_graphemes('क्षि') # => [[2325, 2381], [2359], [2367]] + # Unicode.unpack_graphemes('Café') # => [[67], [97], [102], [233]] + def unpack_graphemes(string) + codepoints = string.codepoints.to_a + unpacked = [] + pos = 0 + marker = 0 + eoc = codepoints.length + while (pos < eoc) + pos += 1 + previous = codepoints[pos - 1] + current = codepoints[pos] + + # See http://unicode.org/reports/tr29/#Grapheme_Cluster_Boundary_Rules + should_break = + if pos == eoc + true + # GB3. CR X LF + elsif previous == database.boundary[:cr] && current == database.boundary[:lf] + false + # GB4. (Control|CR|LF) ÷ + elsif previous && in_char_class?(previous, [:control, :cr, :lf]) + true + # GB5. ÷ (Control|CR|LF) + elsif in_char_class?(current, [:control, :cr, :lf]) + true + # GB6. L X (L|V|LV|LVT) + elsif database.boundary[:l] === previous && in_char_class?(current, [:l, :v, :lv, :lvt]) + false + # GB7. (LV|V) X (V|T) + elsif in_char_class?(previous, [:lv, :v]) && in_char_class?(current, [:v, :t]) + false + # GB8. (LVT|T) X (T) + elsif in_char_class?(previous, [:lvt, :t]) && database.boundary[:t] === current + false + # GB9. X (Extend | ZWJ) + elsif in_char_class?(current, [:extend, :zwj]) + false + # GB9a. X SpacingMark + elsif database.boundary[:spacingmark] === current + false + # GB9b. Prepend X + elsif database.boundary[:prepend] === previous + false + # GB10. (E_Base | EBG) Extend* X E_Modifier + elsif (marker...pos).any? { |i| in_char_class?(codepoints[i], [:e_base, :e_base_gaz]) && codepoints[i + 1...pos].all? { |c| database.boundary[:extend] === c } } && database.boundary[:e_modifier] === current + false + # GB11. ZWJ X (Glue_After_Zwj | EBG) + elsif database.boundary[:zwj] === previous && in_char_class?(current, [:glue_after_zwj, :e_base_gaz]) + false + # GB12. ^ (RI RI)* RI X RI + # GB13. [^RI] (RI RI)* RI X RI + elsif codepoints[marker..pos].all? { |c| database.boundary[:regional_indicator] === c } && codepoints[marker..pos].count { |c| database.boundary[:regional_indicator] === c }.even? + false + # GB999. Any ÷ Any + else + true + end + + if should_break + unpacked << codepoints[marker..pos - 1] + marker = pos + end + end + unpacked + end + + # Reverse operation of unpack_graphemes. + # + # Unicode.pack_graphemes(Unicode.unpack_graphemes('क्षि')) # => 'क्षि' + def pack_graphemes(unpacked) + unpacked.flatten.pack("U*") + end + + # Re-order codepoints so the string becomes canonical. + def reorder_characters(codepoints) + length = codepoints.length - 1 + pos = 0 + while pos < length do + cp1, cp2 = database.codepoints[codepoints[pos]], database.codepoints[codepoints[pos + 1]] + if (cp1.combining_class > cp2.combining_class) && (cp2.combining_class > 0) + codepoints[pos..pos + 1] = cp2.code, cp1.code + pos += (pos > 0 ? -1 : 1) + else + pos += 1 + end + end + codepoints + end + + # Decompose composed characters to the decomposed form. + def decompose(type, codepoints) + codepoints.inject([]) do |decomposed, cp| + # if it's a hangul syllable starter character + if HANGUL_SBASE <= cp && cp < HANGUL_SLAST + sindex = cp - HANGUL_SBASE + ncp = [] # new codepoints + ncp << HANGUL_LBASE + sindex / HANGUL_NCOUNT + ncp << HANGUL_VBASE + (sindex % HANGUL_NCOUNT) / HANGUL_TCOUNT + tindex = sindex % HANGUL_TCOUNT + ncp << (HANGUL_TBASE + tindex) unless tindex == 0 + decomposed.concat ncp + # if the codepoint is decomposable in with the current decomposition type + elsif (ncp = database.codepoints[cp].decomp_mapping) && (!database.codepoints[cp].decomp_type || type == :compatibility) + decomposed.concat decompose(type, ncp.dup) + else + decomposed << cp + end + end + end + + # Compose decomposed characters to the composed form. + def compose(codepoints) + pos = 0 + eoa = codepoints.length - 1 + starter_pos = 0 + starter_char = codepoints[0] + previous_combining_class = -1 + while pos < eoa + pos += 1 + lindex = starter_char - HANGUL_LBASE + # -- Hangul + if 0 <= lindex && lindex < HANGUL_LCOUNT + vindex = codepoints[starter_pos + 1] - HANGUL_VBASE rescue vindex = -1 + if 0 <= vindex && vindex < HANGUL_VCOUNT + tindex = codepoints[starter_pos + 2] - HANGUL_TBASE rescue tindex = -1 + if 0 <= tindex && tindex < HANGUL_TCOUNT + j = starter_pos + 2 + eoa -= 2 + else + tindex = 0 + j = starter_pos + 1 + eoa -= 1 + end + codepoints[starter_pos..j] = (lindex * HANGUL_VCOUNT + vindex) * HANGUL_TCOUNT + tindex + HANGUL_SBASE + end + starter_pos += 1 + starter_char = codepoints[starter_pos] + # -- Other characters + else + current_char = codepoints[pos] + current = database.codepoints[current_char] + if current.combining_class > previous_combining_class + if ref = database.composition_map[starter_char] + composition = ref[current_char] + else + composition = nil + end + unless composition.nil? + codepoints[starter_pos] = composition + starter_char = composition + codepoints.delete_at pos + eoa -= 1 + pos -= 1 + previous_combining_class = -1 + else + previous_combining_class = current.combining_class + end + else + previous_combining_class = current.combining_class + end + if current.combining_class == 0 + starter_pos = pos + starter_char = codepoints[pos] + end + end + end + codepoints + end + + # Rubinius' String#scrub, however, doesn't support ASCII-incompatible chars. + if !defined?(Rubinius) + # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent + # resulting in a valid UTF-8 string. + # + # Passing +true+ will forcibly tidy all bytes, assuming that the string's + # encoding is entirely CP1252 or ISO-8859-1. + def tidy_bytes(string, force = false) + return string if string.empty? + return recode_windows1252_chars(string) if force + string.scrub { |bad| recode_windows1252_chars(bad) } + end + else + def tidy_bytes(string, force = false) + return string if string.empty? + return recode_windows1252_chars(string) if force + + # We can't transcode to the same format, so we choose a nearly-identical encoding. + # We're going to 'transcode' bytes from UTF-8 when possible, then fall back to + # CP1252 when we get errors. The final string will be 'converted' back to UTF-8 + # before returning. + reader = Encoding::Converter.new(Encoding::UTF_8, Encoding::UTF_16LE) + + source = string.dup + out = "".force_encoding(Encoding::UTF_16LE) + + loop do + reader.primitive_convert(source, out) + _, _, _, error_bytes, _ = reader.primitive_errinfo + break if error_bytes.nil? + out << error_bytes.encode(Encoding::UTF_16LE, Encoding::Windows_1252, invalid: :replace, undef: :replace) + end + + reader.finish + + out.encode!(Encoding::UTF_8) + end + end + + # Returns the KC normalization of the string by default. NFKC is + # considered the best normalization form for passing strings to databases + # and validations. + # + # * string - The string to perform normalization on. + # * form - The form you want to normalize in. Should be one of + # the following: :c, :kc, :d, or :kd. + # Default is ActiveSupport::Multibyte::Unicode.default_normalization_form. + def normalize(string, form = nil) + form ||= @default_normalization_form + # See http://www.unicode.org/reports/tr15, Table 1 + codepoints = string.codepoints.to_a + case form + when :d + reorder_characters(decompose(:canonical, codepoints)) + when :c + compose(reorder_characters(decompose(:canonical, codepoints))) + when :kd + reorder_characters(decompose(:compatibility, codepoints)) + when :kc + compose(reorder_characters(decompose(:compatibility, codepoints))) + else + raise ArgumentError, "#{form} is not a valid normalization variant", caller + end.pack("U*".freeze) + end + + def downcase(string) + apply_mapping string, :lowercase_mapping + end + + def upcase(string) + apply_mapping string, :uppercase_mapping + end + + def swapcase(string) + apply_mapping string, :swapcase_mapping + end + + # Holds data about a codepoint in the Unicode database. + class Codepoint + attr_accessor :code, :combining_class, :decomp_type, :decomp_mapping, :uppercase_mapping, :lowercase_mapping + + # Initializing Codepoint object with default values + def initialize + @combining_class = 0 + @uppercase_mapping = 0 + @lowercase_mapping = 0 + end + + def swapcase_mapping + uppercase_mapping > 0 ? uppercase_mapping : lowercase_mapping + end + end + + # Holds static data from the Unicode database. + class UnicodeDatabase + ATTRIBUTES = :codepoints, :composition_exclusion, :composition_map, :boundary, :cp1252 + + attr_writer(*ATTRIBUTES) + + def initialize + @codepoints = Hash.new(Codepoint.new) + @composition_exclusion = [] + @composition_map = {} + @boundary = {} + @cp1252 = {} + end + + # Lazy load the Unicode database so it's only loaded when it's actually used + ATTRIBUTES.each do |attr_name| + class_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{attr_name} # def codepoints + load # load + @#{attr_name} # @codepoints + end # end + EOS + end + + # Loads the Unicode database and returns all the internal objects of + # UnicodeDatabase. + def load + begin + @codepoints, @composition_exclusion, @composition_map, @boundary, @cp1252 = File.open(self.class.filename, "rb") { |f| Marshal.load f.read } + rescue => e + raise IOError.new("Couldn't load the Unicode tables for UTF8Handler (#{e.message}), ActiveSupport::Multibyte is unusable") + end + + # Redefine the === method so we can write shorter rules for grapheme cluster breaks + @boundary.each_key do |k| + @boundary[k].instance_eval do + def ===(other) + detect { |i| i === other } ? true : false + end + end if @boundary[k].kind_of?(Array) + end + + # define attr_reader methods for the instance variables + class << self + attr_reader(*ATTRIBUTES) + end + end + + # Returns the directory in which the data files are stored. + def self.dirname + File.expand_path("../values", __dir__) + end + + # Returns the filename for the data file for this version. + def self.filename + File.expand_path File.join(dirname, "unicode_tables.dat") + end + end + + private + + def apply_mapping(string, mapping) + database.codepoints + string.each_codepoint.map do |codepoint| + cp = database.codepoints[codepoint] + if cp && (ncp = cp.send(mapping)) && ncp > 0 + ncp + else + codepoint + end + end.pack("U*") + end + + def recode_windows1252_chars(string) + string.encode(Encoding::UTF_8, Encoding::Windows_1252, invalid: :replace, undef: :replace) + end + + def database + @database ||= UnicodeDatabase.new + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/notifications.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/notifications.rb new file mode 100644 index 00000000..6207de80 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/notifications.rb @@ -0,0 +1,216 @@ +# frozen_string_literal: true + +require "active_support/notifications/instrumenter" +require "active_support/notifications/fanout" +require "active_support/per_thread_registry" + +module ActiveSupport + # = Notifications + # + # ActiveSupport::Notifications provides an instrumentation API for + # Ruby. + # + # == Instrumenters + # + # To instrument an event you just need to do: + # + # ActiveSupport::Notifications.instrument('render', extra: :information) do + # render plain: 'Foo' + # end + # + # That first executes the block and then notifies all subscribers once done. + # + # In the example above +render+ is the name of the event, and the rest is called + # the _payload_. The payload is a mechanism that allows instrumenters to pass + # extra information to subscribers. Payloads consist of a hash whose contents + # are arbitrary and generally depend on the event. + # + # == Subscribers + # + # You can consume those events and the information they provide by registering + # a subscriber. + # + # ActiveSupport::Notifications.subscribe('render') do |name, start, finish, id, payload| + # name # => String, name of the event (such as 'render' from above) + # start # => Time, when the instrumented block started execution + # finish # => Time, when the instrumented block ended execution + # id # => String, unique ID for this notification + # payload # => Hash, the payload + # end + # + # For instance, let's store all "render" events in an array: + # + # events = [] + # + # ActiveSupport::Notifications.subscribe('render') do |*args| + # events << ActiveSupport::Notifications::Event.new(*args) + # end + # + # That code returns right away, you are just subscribing to "render" events. + # The block is saved and will be called whenever someone instruments "render": + # + # ActiveSupport::Notifications.instrument('render', extra: :information) do + # render plain: 'Foo' + # end + # + # event = events.first + # event.name # => "render" + # event.duration # => 10 (in milliseconds) + # event.payload # => { extra: :information } + # + # The block in the subscribe call gets the name of the event, start + # timestamp, end timestamp, a string with a unique identifier for that event + # (something like "535801666f04d0298cd6"), and a hash with the payload, in + # that order. + # + # If an exception happens during that particular instrumentation the payload will + # have a key :exception with an array of two elements as value: a string with + # the name of the exception class, and the exception message. + # The :exception_object key of the payload will have the exception + # itself as the value. + # + # As the previous example depicts, the class ActiveSupport::Notifications::Event + # is able to take the arguments as they come and provide an object-oriented + # interface to that data. + # + # It is also possible to pass an object which responds to call method + # as the second parameter to the subscribe method instead of a block: + # + # module ActionController + # class PageRequest + # def call(name, started, finished, unique_id, payload) + # Rails.logger.debug ['notification:', name, started, finished, unique_id, payload].join(' ') + # end + # end + # end + # + # ActiveSupport::Notifications.subscribe('process_action.action_controller', ActionController::PageRequest.new) + # + # resulting in the following output within the logs including a hash with the payload: + # + # notification: process_action.action_controller 2012-04-13 01:08:35 +0300 2012-04-13 01:08:35 +0300 af358ed7fab884532ec7 { + # controller: "Devise::SessionsController", + # action: "new", + # params: {"action"=>"new", "controller"=>"devise/sessions"}, + # format: :html, + # method: "GET", + # path: "/login/sign_in", + # status: 200, + # view_runtime: 279.3080806732178, + # db_runtime: 40.053 + # } + # + # You can also subscribe to all events whose name matches a certain regexp: + # + # ActiveSupport::Notifications.subscribe(/render/) do |*args| + # ... + # end + # + # and even pass no argument to subscribe, in which case you are subscribing + # to all events. + # + # == Temporary Subscriptions + # + # Sometimes you do not want to subscribe to an event for the entire life of + # the application. There are two ways to unsubscribe. + # + # WARNING: The instrumentation framework is designed for long-running subscribers, + # use this feature sparingly because it wipes some internal caches and that has + # a negative impact on performance. + # + # === Subscribe While a Block Runs + # + # You can subscribe to some event temporarily while some block runs. For + # example, in + # + # callback = lambda {|*args| ... } + # ActiveSupport::Notifications.subscribed(callback, "sql.active_record") do + # ... + # end + # + # the callback will be called for all "sql.active_record" events instrumented + # during the execution of the block. The callback is unsubscribed automatically + # after that. + # + # === Manual Unsubscription + # + # The +subscribe+ method returns a subscriber object: + # + # subscriber = ActiveSupport::Notifications.subscribe("render") do |*args| + # ... + # end + # + # To prevent that block from being called anymore, just unsubscribe passing + # that reference: + # + # ActiveSupport::Notifications.unsubscribe(subscriber) + # + # You can also unsubscribe by passing the name of the subscriber object. Note + # that this will unsubscribe all subscriptions with the given name: + # + # ActiveSupport::Notifications.unsubscribe("render") + # + # == Default Queue + # + # Notifications ships with a queue implementation that consumes and publishes events + # to all log subscribers. You can use any queue implementation you want. + # + module Notifications + class << self + attr_accessor :notifier + + def publish(name, *args) + notifier.publish(name, *args) + end + + def instrument(name, payload = {}) + if notifier.listening?(name) + instrumenter.instrument(name, payload) { yield payload if block_given? } + else + yield payload if block_given? + end + end + + def subscribe(*args, &block) + notifier.subscribe(*args, &block) + end + + def subscribed(callback, *args, &block) + subscriber = subscribe(*args, &callback) + yield + ensure + unsubscribe(subscriber) + end + + def unsubscribe(subscriber_or_name) + notifier.unsubscribe(subscriber_or_name) + end + + def instrumenter + InstrumentationRegistry.instance.instrumenter_for(notifier) + end + end + + # This class is a registry which holds all of the +Instrumenter+ objects + # in a particular thread local. To access the +Instrumenter+ object for a + # particular +notifier+, you can call the following method: + # + # InstrumentationRegistry.instrumenter_for(notifier) + # + # The instrumenters for multiple notifiers are held in a single instance of + # this class. + class InstrumentationRegistry # :nodoc: + extend ActiveSupport::PerThreadRegistry + + def initialize + @registry = {} + end + + def instrumenter_for(notifier) + @registry[notifier] ||= Instrumenter.new(notifier) + end + end + + self.notifier = Fanout.new + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/notifications/fanout.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/notifications/fanout.rb new file mode 100644 index 00000000..25aab175 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/notifications/fanout.rb @@ -0,0 +1,159 @@ +# frozen_string_literal: true + +require "mutex_m" +require "concurrent/map" + +module ActiveSupport + module Notifications + # This is a default queue implementation that ships with Notifications. + # It just pushes events to all registered log subscribers. + # + # This class is thread safe. All methods are reentrant. + class Fanout + include Mutex_m + + def initialize + @subscribers = [] + @listeners_for = Concurrent::Map.new + super + end + + def subscribe(pattern = nil, block = Proc.new) + subscriber = Subscribers.new pattern, block + synchronize do + @subscribers << subscriber + @listeners_for.clear + end + subscriber + end + + def unsubscribe(subscriber_or_name) + synchronize do + case subscriber_or_name + when String + @subscribers.reject! { |s| s.matches?(subscriber_or_name) } + else + @subscribers.delete(subscriber_or_name) + end + + @listeners_for.clear + end + end + + def start(name, id, payload) + listeners_for(name).each { |s| s.start(name, id, payload) } + end + + def finish(name, id, payload, listeners = listeners_for(name)) + listeners.each { |s| s.finish(name, id, payload) } + end + + def publish(name, *args) + listeners_for(name).each { |s| s.publish(name, *args) } + end + + def listeners_for(name) + # this is correctly done double-checked locking (Concurrent::Map's lookups have volatile semantics) + @listeners_for[name] || synchronize do + # use synchronisation when accessing @subscribers + @listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) } + end + end + + def listening?(name) + listeners_for(name).any? + end + + # This is a sync queue, so there is no waiting. + def wait + end + + module Subscribers # :nodoc: + def self.new(pattern, listener) + if listener.respond_to?(:start) && listener.respond_to?(:finish) + subscriber = Evented.new pattern, listener + else + subscriber = Timed.new pattern, listener + end + + unless pattern + AllMessages.new(subscriber) + else + subscriber + end + end + + class Evented #:nodoc: + def initialize(pattern, delegate) + @pattern = pattern + @delegate = delegate + @can_publish = delegate.respond_to?(:publish) + end + + def publish(name, *args) + if @can_publish + @delegate.publish name, *args + end + end + + def start(name, id, payload) + @delegate.start name, id, payload + end + + def finish(name, id, payload) + @delegate.finish name, id, payload + end + + def subscribed_to?(name) + @pattern === name + end + + def matches?(name) + @pattern && @pattern === name + end + end + + class Timed < Evented # :nodoc: + def publish(name, *args) + @delegate.call name, *args + end + + def start(name, id, payload) + timestack = Thread.current[:_timestack] ||= [] + timestack.push Time.now + end + + def finish(name, id, payload) + timestack = Thread.current[:_timestack] + started = timestack.pop + @delegate.call(name, started, Time.now, id, payload) + end + end + + class AllMessages # :nodoc: + def initialize(delegate) + @delegate = delegate + end + + def start(name, id, payload) + @delegate.start name, id, payload + end + + def finish(name, id, payload) + @delegate.finish name, id, payload + end + + def publish(name, *args) + @delegate.publish name, *args + end + + def subscribed_to?(name) + true + end + + alias :matches? :=== + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/notifications/instrumenter.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/notifications/instrumenter.rb new file mode 100644 index 00000000..e99f5ee6 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/notifications/instrumenter.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require "securerandom" + +module ActiveSupport + module Notifications + # Instrumenters are stored in a thread local. + class Instrumenter + attr_reader :id + + def initialize(notifier) + @id = unique_id + @notifier = notifier + end + + # Instrument the given block by measuring the time taken to execute it + # and publish it. Notice that events get sent even if an error occurs + # in the passed-in block. + def instrument(name, payload = {}) + # some of the listeners might have state + listeners_state = start name, payload + begin + yield payload + rescue Exception => e + payload[:exception] = [e.class.name, e.message] + payload[:exception_object] = e + raise e + ensure + finish_with_state listeners_state, name, payload + end + end + + # Send a start notification with +name+ and +payload+. + def start(name, payload) + @notifier.start name, @id, payload + end + + # Send a finish notification with +name+ and +payload+. + def finish(name, payload) + @notifier.finish name, @id, payload + end + + def finish_with_state(listeners_state, name, payload) + @notifier.finish name, @id, payload, listeners_state + end + + private + + def unique_id + SecureRandom.hex(10) + end + end + + class Event + attr_reader :name, :time, :transaction_id, :payload, :children + attr_accessor :end + + def initialize(name, start, ending, transaction_id, payload) + @name = name + @payload = payload.dup + @time = start + @transaction_id = transaction_id + @end = ending + @children = [] + @duration = nil + end + + # Returns the difference in milliseconds between when the execution of the + # event started and when it ended. + # + # ActiveSupport::Notifications.subscribe('wait') do |*args| + # @event = ActiveSupport::Notifications::Event.new(*args) + # end + # + # ActiveSupport::Notifications.instrument('wait') do + # sleep 1 + # end + # + # @event.duration # => 1000.138 + def duration + @duration ||= 1000.0 * (self.end - time) + end + + def <<(event) + @children << event + end + + def parent_of?(event) + @children.include? event + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper.rb new file mode 100644 index 00000000..8fd6e932 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper.rb @@ -0,0 +1,371 @@ +# frozen_string_literal: true + +module ActiveSupport + module NumberHelper + extend ActiveSupport::Autoload + + eager_autoload do + autoload :NumberConverter + autoload :RoundingHelper + autoload :NumberToRoundedConverter + autoload :NumberToDelimitedConverter + autoload :NumberToHumanConverter + autoload :NumberToHumanSizeConverter + autoload :NumberToPhoneConverter + autoload :NumberToCurrencyConverter + autoload :NumberToPercentageConverter + end + + extend self + + # Formats a +number+ into a phone number (US by default e.g., (555) + # 123-9876). You can customize the format in the +options+ hash. + # + # ==== Options + # + # * :area_code - Adds parentheses around the area code. + # * :delimiter - Specifies the delimiter to use + # (defaults to "-"). + # * :extension - Specifies an extension to add to the + # end of the generated number. + # * :country_code - Sets the country code for the phone + # number. + # * :pattern - Specifies how the number is divided into three + # groups with the custom regexp to override the default format. + # ==== Examples + # + # number_to_phone(5551234) # => "555-1234" + # number_to_phone('5551234') # => "555-1234" + # number_to_phone(1235551234) # => "123-555-1234" + # number_to_phone(1235551234, area_code: true) # => "(123) 555-1234" + # number_to_phone(1235551234, delimiter: ' ') # => "123 555 1234" + # number_to_phone(1235551234, area_code: true, extension: 555) # => "(123) 555-1234 x 555" + # number_to_phone(1235551234, country_code: 1) # => "+1-123-555-1234" + # number_to_phone('123a456') # => "123a456" + # + # number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: '.') + # # => "+1.123.555.1234 x 1343" + # + # number_to_phone(75561234567, pattern: /(\d{1,4})(\d{4})(\d{4})$/, area_code: true) + # # => "(755) 6123-4567" + # number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/) + # # => "133-1234-5678" + def number_to_phone(number, options = {}) + NumberToPhoneConverter.convert(number, options) + end + + # Formats a +number+ into a currency string (e.g., $13.65). You + # can customize the format in the +options+ hash. + # + # The currency unit and number formatting of the current locale will be used + # unless otherwise specified in the provided options. No currency conversion + # is performed. If the user is given a way to change their locale, they will + # also be able to change the relative value of the currency displayed with + # this helper. If your application will ever support multiple locales, you + # may want to specify a constant :locale option or consider + # using a library capable of currency conversion. + # + # ==== Options + # + # * :locale - Sets the locale to be used for formatting + # (defaults to current locale). + # * :precision - Sets the level of precision (defaults + # to 2). + # * :unit - Sets the denomination of the currency + # (defaults to "$"). + # * :separator - Sets the separator between the units + # (defaults to "."). + # * :delimiter - Sets the thousands delimiter (defaults + # to ","). + # * :format - Sets the format for non-negative numbers + # (defaults to "%u%n"). Fields are %u for the + # currency, and %n for the number. + # * :negative_format - Sets the format for negative + # numbers (defaults to prepending a hyphen to the formatted + # number given by :format). Accepts the same fields + # than :format, except %n is here the + # absolute value of the number. + # + # ==== Examples + # + # number_to_currency(1234567890.50) # => "$1,234,567,890.50" + # number_to_currency(1234567890.506) # => "$1,234,567,890.51" + # number_to_currency(1234567890.506, precision: 3) # => "$1,234,567,890.506" + # number_to_currency(1234567890.506, locale: :fr) # => "1 234 567 890,51 €" + # number_to_currency('123a456') # => "$123a456" + # + # number_to_currency(-1234567890.50, negative_format: '(%u%n)') + # # => "($1,234,567,890.50)" + # number_to_currency(1234567890.50, unit: '£', separator: ',', delimiter: '') + # # => "£1234567890,50" + # number_to_currency(1234567890.50, unit: '£', separator: ',', delimiter: '', format: '%n %u') + # # => "1234567890,50 £" + def number_to_currency(number, options = {}) + NumberToCurrencyConverter.convert(number, options) + end + + # Formats a +number+ as a percentage string (e.g., 65%). You can + # customize the format in the +options+ hash. + # + # ==== Options + # + # * :locale - Sets the locale to be used for formatting + # (defaults to current locale). + # * :precision - Sets the precision of the number + # (defaults to 3). Keeps the number's precision if +nil+. + # * :significant - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional + # digits (defaults to +false+). + # * :separator - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * :delimiter - Sets the thousands delimiter (defaults + # to ""). + # * :strip_insignificant_zeros - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +false+). + # * :format - Specifies the format of the percentage + # string The number field is %n (defaults to "%n%"). + # + # ==== Examples + # + # number_to_percentage(100) # => "100.000%" + # number_to_percentage('98') # => "98.000%" + # number_to_percentage(100, precision: 0) # => "100%" + # number_to_percentage(1000, delimiter: '.', separator: ',') # => "1.000,000%" + # number_to_percentage(302.24398923423, precision: 5) # => "302.24399%" + # number_to_percentage(1000, locale: :fr) # => "1000,000%" + # number_to_percentage(1000, precision: nil) # => "1000%" + # number_to_percentage('98a') # => "98a%" + # number_to_percentage(100, format: '%n %') # => "100.000 %" + def number_to_percentage(number, options = {}) + NumberToPercentageConverter.convert(number, options) + end + + # Formats a +number+ with grouped thousands using +delimiter+ + # (e.g., 12,324). You can customize the format in the +options+ + # hash. + # + # ==== Options + # + # * :locale - Sets the locale to be used for formatting + # (defaults to current locale). + # * :delimiter - Sets the thousands delimiter (defaults + # to ","). + # * :separator - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * :delimiter_pattern - Sets a custom regular expression used for + # deriving the placement of delimiter. Helpful when using currency formats + # like INR. + # + # ==== Examples + # + # number_to_delimited(12345678) # => "12,345,678" + # number_to_delimited('123456') # => "123,456" + # number_to_delimited(12345678.05) # => "12,345,678.05" + # number_to_delimited(12345678, delimiter: '.') # => "12.345.678" + # number_to_delimited(12345678, delimiter: ',') # => "12,345,678" + # number_to_delimited(12345678.05, separator: ' ') # => "12,345,678 05" + # number_to_delimited(12345678.05, locale: :fr) # => "12 345 678,05" + # number_to_delimited('112a') # => "112a" + # number_to_delimited(98765432.98, delimiter: ' ', separator: ',') + # # => "98 765 432,98" + # number_to_delimited("123456.78", + # delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/) + # # => "1,23,456.78" + def number_to_delimited(number, options = {}) + NumberToDelimitedConverter.convert(number, options) + end + + # Formats a +number+ with the specified level of + # :precision (e.g., 112.32 has a precision of 2 if + # +:significant+ is +false+, and 5 if +:significant+ is +true+). + # You can customize the format in the +options+ hash. + # + # ==== Options + # + # * :locale - Sets the locale to be used for formatting + # (defaults to current locale). + # * :precision - Sets the precision of the number + # (defaults to 3). Keeps the number's precision if +nil+. + # * :significant - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional + # digits (defaults to +false+). + # * :separator - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * :delimiter - Sets the thousands delimiter (defaults + # to ""). + # * :strip_insignificant_zeros - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +false+). + # + # ==== Examples + # + # number_to_rounded(111.2345) # => "111.235" + # number_to_rounded(111.2345, precision: 2) # => "111.23" + # number_to_rounded(13, precision: 5) # => "13.00000" + # number_to_rounded(389.32314, precision: 0) # => "389" + # number_to_rounded(111.2345, significant: true) # => "111" + # number_to_rounded(111.2345, precision: 1, significant: true) # => "100" + # number_to_rounded(13, precision: 5, significant: true) # => "13.000" + # number_to_rounded(13, precision: nil) # => "13" + # number_to_rounded(111.234, locale: :fr) # => "111,234" + # + # number_to_rounded(13, precision: 5, significant: true, strip_insignificant_zeros: true) + # # => "13" + # + # number_to_rounded(389.32314, precision: 4, significant: true) # => "389.3" + # number_to_rounded(1111.2345, precision: 2, separator: ',', delimiter: '.') + # # => "1.111,23" + def number_to_rounded(number, options = {}) + NumberToRoundedConverter.convert(number, options) + end + + # Formats the bytes in +number+ into a more understandable + # representation (e.g., giving it 1500 yields 1.5 KB). This + # method is useful for reporting file sizes to users. You can + # customize the format in the +options+ hash. + # + # See number_to_human if you want to pretty-print a + # generic number. + # + # ==== Options + # + # * :locale - Sets the locale to be used for formatting + # (defaults to current locale). + # * :precision - Sets the precision of the number + # (defaults to 3). + # * :significant - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional + # digits (defaults to +true+) + # * :separator - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * :delimiter - Sets the thousands delimiter (defaults + # to ""). + # * :strip_insignificant_zeros - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +true+) + # + # ==== Examples + # + # number_to_human_size(123) # => "123 Bytes" + # number_to_human_size(1234) # => "1.21 KB" + # number_to_human_size(12345) # => "12.1 KB" + # number_to_human_size(1234567) # => "1.18 MB" + # number_to_human_size(1234567890) # => "1.15 GB" + # number_to_human_size(1234567890123) # => "1.12 TB" + # number_to_human_size(1234567890123456) # => "1.1 PB" + # number_to_human_size(1234567890123456789) # => "1.07 EB" + # number_to_human_size(1234567, precision: 2) # => "1.2 MB" + # number_to_human_size(483989, precision: 2) # => "470 KB" + # number_to_human_size(1234567, precision: 2, separator: ',') # => "1,2 MB" + # number_to_human_size(1234567890123, precision: 5) # => "1.1228 TB" + # number_to_human_size(524288000, precision: 5) # => "500 MB" + def number_to_human_size(number, options = {}) + NumberToHumanSizeConverter.convert(number, options) + end + + # Pretty prints (formats and approximates) a number in a way it + # is more readable by humans (eg.: 1200000000 becomes "1.2 + # Billion"). This is useful for numbers that can get very large + # (and too hard to read). + # + # See number_to_human_size if you want to print a file + # size. + # + # You can also define your own unit-quantifier names if you want + # to use other decimal units (eg.: 1500 becomes "1.5 + # kilometers", 0.150 becomes "150 milliliters", etc). You may + # define a wide range of unit quantifiers, even fractional ones + # (centi, deci, mili, etc). + # + # ==== Options + # + # * :locale - Sets the locale to be used for formatting + # (defaults to current locale). + # * :precision - Sets the precision of the number + # (defaults to 3). + # * :significant - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional + # digits (defaults to +true+) + # * :separator - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * :delimiter - Sets the thousands delimiter (defaults + # to ""). + # * :strip_insignificant_zeros - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +true+) + # * :units - A Hash of unit quantifier names. Or a + # string containing an i18n scope where to find this hash. It + # might have the following keys: + # * *integers*: :unit, :ten, + # :hundred, :thousand, :million, + # :billion, :trillion, + # :quadrillion + # * *fractionals*: :deci, :centi, + # :mili, :micro, :nano, + # :pico, :femto + # * :format - Sets the format of the output string + # (defaults to "%n %u"). The field types are: + # * %u - The quantifier (ex.: 'thousand') + # * %n - The number + # + # ==== Examples + # + # number_to_human(123) # => "123" + # number_to_human(1234) # => "1.23 Thousand" + # number_to_human(12345) # => "12.3 Thousand" + # number_to_human(1234567) # => "1.23 Million" + # number_to_human(1234567890) # => "1.23 Billion" + # number_to_human(1234567890123) # => "1.23 Trillion" + # number_to_human(1234567890123456) # => "1.23 Quadrillion" + # number_to_human(1234567890123456789) # => "1230 Quadrillion" + # number_to_human(489939, precision: 2) # => "490 Thousand" + # number_to_human(489939, precision: 4) # => "489.9 Thousand" + # number_to_human(1234567, precision: 4, + # significant: false) # => "1.2346 Million" + # number_to_human(1234567, precision: 1, + # separator: ',', + # significant: false) # => "1,2 Million" + # + # number_to_human(500000000, precision: 5) # => "500 Million" + # number_to_human(12345012345, significant: false) # => "12.345 Billion" + # + # Non-significant zeros after the decimal separator are stripped + # out by default (set :strip_insignificant_zeros to + # +false+ to change that): + # + # number_to_human(12.00001) # => "12" + # number_to_human(12.00001, strip_insignificant_zeros: false) # => "12.0" + # + # ==== Custom Unit Quantifiers + # + # You can also use your own custom unit quantifiers: + # number_to_human(500000, units: { unit: 'ml', thousand: 'lt' }) # => "500 lt" + # + # If in your I18n locale you have: + # + # distance: + # centi: + # one: "centimeter" + # other: "centimeters" + # unit: + # one: "meter" + # other: "meters" + # thousand: + # one: "kilometer" + # other: "kilometers" + # billion: "gazillion-distance" + # + # Then you could do: + # + # number_to_human(543934, units: :distance) # => "544 kilometers" + # number_to_human(54393498, units: :distance) # => "54400 kilometers" + # number_to_human(54393498000, units: :distance) # => "54.4 gazillion-distance" + # number_to_human(343, units: :distance, precision: 1) # => "300 meters" + # number_to_human(1, units: :distance) # => "1 meter" + # number_to_human(0.34, units: :distance) # => "34 centimeters" + def number_to_human(number, options = {}) + NumberToHumanConverter.convert(number, options) + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper/number_converter.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper/number_converter.rb new file mode 100644 index 00000000..06ba797a --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper/number_converter.rb @@ -0,0 +1,184 @@ +# frozen_string_literal: true + +require "active_support/core_ext/big_decimal/conversions" +require "active_support/core_ext/object/blank" +require "active_support/core_ext/hash/keys" +require "active_support/i18n" +require "active_support/core_ext/class/attribute" + +module ActiveSupport + module NumberHelper + class NumberConverter # :nodoc: + # Default and i18n option namespace per class + class_attribute :namespace + + # Does the object need a number that is a valid float? + class_attribute :validate_float + + attr_reader :number, :opts + + DEFAULTS = { + # Used in number_to_delimited + # These are also the defaults for 'currency', 'percentage', 'precision', and 'human' + format: { + # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5) + separator: ".", + # Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three) + delimiter: ",", + # Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00) + precision: 3, + # If set to true, precision will mean the number of significant digits instead + # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2) + significant: false, + # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2) + strip_insignificant_zeros: false + }, + + # Used in number_to_currency + currency: { + format: { + format: "%u%n", + negative_format: "-%u%n", + unit: "$", + # These five are to override number.format and are optional + separator: ".", + delimiter: ",", + precision: 2, + significant: false, + strip_insignificant_zeros: false + } + }, + + # Used in number_to_percentage + percentage: { + format: { + delimiter: "", + format: "%n%" + } + }, + + # Used in number_to_rounded + precision: { + format: { + delimiter: "" + } + }, + + # Used in number_to_human_size and number_to_human + human: { + format: { + # These five are to override number.format and are optional + delimiter: "", + precision: 3, + significant: true, + strip_insignificant_zeros: true + }, + # Used in number_to_human_size + storage_units: { + # Storage units output formatting. + # %u is the storage unit, %n is the number (default: 2 MB) + format: "%n %u", + units: { + byte: "Bytes", + kb: "KB", + mb: "MB", + gb: "GB", + tb: "TB" + } + }, + # Used in number_to_human + decimal_units: { + format: "%n %u", + # Decimal units output formatting + # By default we will only quantify some of the exponents + # but the commented ones might be defined or overridden + # by the user. + units: { + # femto: Quadrillionth + # pico: Trillionth + # nano: Billionth + # micro: Millionth + # mili: Thousandth + # centi: Hundredth + # deci: Tenth + unit: "", + # ten: + # one: Ten + # other: Tens + # hundred: Hundred + thousand: "Thousand", + million: "Million", + billion: "Billion", + trillion: "Trillion", + quadrillion: "Quadrillion" + } + } + } + } + + def self.convert(number, options) + new(number, options).execute + end + + def initialize(number, options) + @number = number + @opts = options.symbolize_keys + end + + def execute + if !number + nil + elsif validate_float? && !valid_float? + number + else + convert + end + end + + private + + def options + @options ||= format_options.merge(opts) + end + + def format_options + default_format_options.merge!(i18n_format_options) + end + + def default_format_options + options = DEFAULTS[:format].dup + options.merge!(DEFAULTS[namespace][:format]) if namespace + options + end + + def i18n_format_options + locale = opts[:locale] + options = I18n.translate(:'number.format', locale: locale, default: {}).dup + + if namespace + options.merge!(I18n.translate(:"number.#{namespace}.format", locale: locale, default: {})) + end + + options + end + + def translate_number_value_with_default(key, i18n_options = {}) + I18n.translate(key, { default: default_value(key), scope: :number }.merge!(i18n_options)) + end + + def translate_in_locale(key, i18n_options = {}) + translate_number_value_with_default(key, { locale: options[:locale] }.merge(i18n_options)) + end + + def default_value(key) + key.split(".").reduce(DEFAULTS) { |defaults, k| defaults[k.to_sym] } + end + + def valid_float? + Float(number) + rescue ArgumentError, TypeError + false + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper/number_to_currency_converter.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper/number_to_currency_converter.rb new file mode 100644 index 00000000..3f037c73 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper/number_to_currency_converter.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require "active_support/core_ext/numeric/inquiry" + +module ActiveSupport + module NumberHelper + class NumberToCurrencyConverter < NumberConverter # :nodoc: + self.namespace = :currency + + def convert + number = self.number.to_s.strip + format = options[:format] + + if number.to_f.negative? + format = options[:negative_format] + number = absolute_value(number) + end + + rounded_number = NumberToRoundedConverter.convert(number, options) + format.gsub("%n".freeze, rounded_number).gsub("%u".freeze, options[:unit]) + end + + private + + def absolute_value(number) + number.respond_to?(:abs) ? number.abs : number.sub(/\A-/, "") + end + + def options + @options ||= begin + defaults = default_format_options.merge(i18n_opts) + # Override negative format if format options are given + defaults[:negative_format] = "-#{opts[:format]}" if opts[:format] + defaults.merge!(opts) + end + end + + def i18n_opts + # Set International negative format if it does not exist + i18n = i18n_format_options + i18n[:negative_format] ||= "-#{i18n[:format]}" if i18n[:format] + i18n + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper/number_to_delimited_converter.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper/number_to_delimited_converter.rb new file mode 100644 index 00000000..d5b57067 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper/number_to_delimited_converter.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module ActiveSupport + module NumberHelper + class NumberToDelimitedConverter < NumberConverter #:nodoc: + self.validate_float = true + + DEFAULT_DELIMITER_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/ + + def convert + parts.join(options[:separator]) + end + + private + + def parts + left, right = number.to_s.split(".".freeze) + left.gsub!(delimiter_pattern) do |digit_to_delimit| + "#{digit_to_delimit}#{options[:delimiter]}" + end + [left, right].compact + end + + def delimiter_pattern + options.fetch(:delimiter_pattern, DEFAULT_DELIMITER_REGEX) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper/number_to_human_converter.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper/number_to_human_converter.rb new file mode 100644 index 00000000..03eb6671 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper/number_to_human_converter.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +module ActiveSupport + module NumberHelper + class NumberToHumanConverter < NumberConverter # :nodoc: + DECIMAL_UNITS = { 0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion, + -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto } + INVERTED_DECIMAL_UNITS = DECIMAL_UNITS.invert + + self.namespace = :human + self.validate_float = true + + def convert # :nodoc: + @number = RoundingHelper.new(options).round(number) + @number = Float(number) + + # for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files + unless options.key?(:strip_insignificant_zeros) + options[:strip_insignificant_zeros] = true + end + + units = opts[:units] + exponent = calculate_exponent(units) + @number = number / (10**exponent) + + rounded_number = NumberToRoundedConverter.convert(number, options) + unit = determine_unit(units, exponent) + format.gsub("%n".freeze, rounded_number).gsub("%u".freeze, unit).strip + end + + private + + def format + options[:format] || translate_in_locale("human.decimal_units.format") + end + + def determine_unit(units, exponent) + exp = DECIMAL_UNITS[exponent] + case units + when Hash + units[exp] || "" + when String, Symbol + I18n.translate("#{units}.#{exp}", locale: options[:locale], count: number.to_i) + else + translate_in_locale("human.decimal_units.units.#{exp}", count: number.to_i) + end + end + + def calculate_exponent(units) + exponent = number != 0 ? Math.log10(number.abs).floor : 0 + unit_exponents(units).find { |e| exponent >= e } || 0 + end + + def unit_exponents(units) + case units + when Hash + units + when String, Symbol + I18n.translate(units.to_s, locale: options[:locale], raise: true) + when nil + translate_in_locale("human.decimal_units.units", raise: true) + else + raise ArgumentError, ":units must be a Hash or String translation scope." + end.keys.map { |e_name| INVERTED_DECIMAL_UNITS[e_name] }.sort_by(&:-@) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper/number_to_human_size_converter.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper/number_to_human_size_converter.rb new file mode 100644 index 00000000..842f2fc8 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper/number_to_human_size_converter.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module ActiveSupport + module NumberHelper + class NumberToHumanSizeConverter < NumberConverter #:nodoc: + STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb, :pb, :eb] + + self.namespace = :human + self.validate_float = true + + def convert + @number = Float(number) + + # for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files + unless options.key?(:strip_insignificant_zeros) + options[:strip_insignificant_zeros] = true + end + + if smaller_than_base? + number_to_format = number.to_i.to_s + else + human_size = number / (base**exponent) + number_to_format = NumberToRoundedConverter.convert(human_size, options) + end + conversion_format.gsub("%n".freeze, number_to_format).gsub("%u".freeze, unit) + end + + private + + def conversion_format + translate_number_value_with_default("human.storage_units.format", locale: options[:locale], raise: true) + end + + def unit + translate_number_value_with_default(storage_unit_key, locale: options[:locale], count: number.to_i, raise: true) + end + + def storage_unit_key + key_end = smaller_than_base? ? "byte" : STORAGE_UNITS[exponent] + "human.storage_units.units.#{key_end}" + end + + def exponent + max = STORAGE_UNITS.size - 1 + exp = (Math.log(number) / Math.log(base)).to_i + exp = max if exp > max # avoid overflow for the highest unit + exp + end + + def smaller_than_base? + number.to_i < base + end + + def base + 1024 + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper/number_to_percentage_converter.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper/number_to_percentage_converter.rb new file mode 100644 index 00000000..4dcdad2e --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper/number_to_percentage_converter.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module ActiveSupport + module NumberHelper + class NumberToPercentageConverter < NumberConverter # :nodoc: + self.namespace = :percentage + + def convert + rounded_number = NumberToRoundedConverter.convert(number, options) + options[:format].gsub("%n".freeze, rounded_number) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper/number_to_phone_converter.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper/number_to_phone_converter.rb new file mode 100644 index 00000000..96410f49 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper/number_to_phone_converter.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module ActiveSupport + module NumberHelper + class NumberToPhoneConverter < NumberConverter #:nodoc: + def convert + str = country_code(opts[:country_code]).dup + str << convert_to_phone_number(number.to_s.strip) + str << phone_ext(opts[:extension]) + end + + private + + def convert_to_phone_number(number) + if opts[:area_code] + convert_with_area_code(number) + else + convert_without_area_code(number) + end + end + + def convert_with_area_code(number) + default_pattern = /(\d{1,3})(\d{3})(\d{4}$)/ + number.gsub!(regexp_pattern(default_pattern), + "(\\1) \\2#{delimiter}\\3") + number + end + + def convert_without_area_code(number) + default_pattern = /(\d{0,3})(\d{3})(\d{4})$/ + number.gsub!(regexp_pattern(default_pattern), + "\\1#{delimiter}\\2#{delimiter}\\3") + number.slice!(0, 1) if start_with_delimiter?(number) + number + end + + def start_with_delimiter?(number) + delimiter.present? && number.start_with?(delimiter) + end + + def delimiter + opts[:delimiter] || "-" + end + + def country_code(code) + code.blank? ? "" : "+#{code}#{delimiter}" + end + + def phone_ext(ext) + ext.blank? ? "" : " x #{ext}" + end + + def regexp_pattern(default_pattern) + opts.fetch :pattern, default_pattern + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper/number_to_rounded_converter.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper/number_to_rounded_converter.rb new file mode 100644 index 00000000..eb528a05 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper/number_to_rounded_converter.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module ActiveSupport + module NumberHelper + class NumberToRoundedConverter < NumberConverter # :nodoc: + self.namespace = :precision + self.validate_float = true + + def convert + helper = RoundingHelper.new(options) + rounded_number = helper.round(number) + + if precision = options[:precision] + if options[:significant] && precision > 0 + digits = helper.digit_count(rounded_number) + precision -= digits + precision = 0 if precision < 0 # don't let it be negative + end + + formatted_string = + if BigDecimal === rounded_number && rounded_number.finite? + s = rounded_number.to_s("F") + s << "0".freeze * precision + a, b = s.split(".".freeze, 2) + a << ".".freeze + a << b[0, precision] + else + "%00.#{precision}f" % rounded_number + end + else + formatted_string = rounded_number + end + + delimited_number = NumberToDelimitedConverter.convert(formatted_string, options) + format_number(delimited_number) + end + + private + + def strip_insignificant_zeros + options[:strip_insignificant_zeros] + end + + def format_number(number) + if strip_insignificant_zeros + escaped_separator = Regexp.escape(options[:separator]) + number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, "") + else + number + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper/rounding_helper.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper/rounding_helper.rb new file mode 100644 index 00000000..2ad8d49c --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/number_helper/rounding_helper.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +module ActiveSupport + module NumberHelper + class RoundingHelper # :nodoc: + attr_reader :options + + def initialize(options) + @options = options + end + + def round(number) + return number unless precision + number = convert_to_decimal(number) + if significant && precision > 0 + round_significant(number) + else + round_without_significant(number) + end + end + + def digit_count(number) + return 1 if number.zero? + (Math.log10(absolute_number(number)) + 1).floor + end + + private + def round_without_significant(number) + number = number.round(precision) + number = number.to_i if precision == 0 && number.finite? + number = number.abs if number.zero? # prevent showing negative zeros + number + end + + def round_significant(number) + return 0 if number.zero? + digits = digit_count(number) + multiplier = 10**(digits - precision) + (number / BigDecimal(multiplier.to_f.to_s)).round * multiplier + end + + def convert_to_decimal(number) + case number + when Float, String + BigDecimal(number.to_s) + when Rational + BigDecimal(number, digit_count(number.to_i) + precision) + else + number.to_d + end + end + + def precision + options[:precision] + end + + def significant + options[:significant] + end + + def absolute_number(number) + number.respond_to?(:abs) ? number.abs : number.to_d.abs + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/option_merger.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/option_merger.rb new file mode 100644 index 00000000..ab9ca727 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/option_merger.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require "active_support/core_ext/hash/deep_merge" + +module ActiveSupport + class OptionMerger #:nodoc: + instance_methods.each do |method| + undef_method(method) if method !~ /^(__|instance_eval|class|object_id)/ + end + + def initialize(context, options) + @context, @options = context, options + end + + private + def method_missing(method, *arguments, &block) + if arguments.first.is_a?(Proc) + proc = arguments.pop + arguments << lambda { |*args| @options.deep_merge(proc.call(*args)) } + else + arguments << (arguments.last.respond_to?(:to_hash) ? @options.deep_merge(arguments.pop) : @options.dup) + end + + @context.__send__(method, *arguments, &block) + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/ordered_hash.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/ordered_hash.rb new file mode 100644 index 00000000..57585130 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/ordered_hash.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "yaml" + +YAML.add_builtin_type("omap") do |type, val| + ActiveSupport::OrderedHash[val.map { |v| v.to_a.first }] +end + +module ActiveSupport + # DEPRECATED: ActiveSupport::OrderedHash implements a hash that preserves + # insertion order. + # + # oh = ActiveSupport::OrderedHash.new + # oh[:a] = 1 + # oh[:b] = 2 + # oh.keys # => [:a, :b], this order is guaranteed + # + # Also, maps the +omap+ feature for YAML files + # (See http://yaml.org/type/omap.html) to support ordered items + # when loading from yaml. + # + # ActiveSupport::OrderedHash is namespaced to prevent conflicts + # with other implementations. + class OrderedHash < ::Hash + def to_yaml_type + "!tag:yaml.org,2002:omap" + end + + def encode_with(coder) + coder.represent_seq "!omap", map { |k, v| { k => v } } + end + + def select(*args, &block) + dup.tap { |hash| hash.select!(*args, &block) } + end + + def reject(*args, &block) + dup.tap { |hash| hash.reject!(*args, &block) } + end + + def nested_under_indifferent_access + self + end + + # Returns true to make sure that this hash is extractable via Array#extract_options! + def extractable_options? + true + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/ordered_options.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/ordered_options.rb new file mode 100644 index 00000000..c4e419f5 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/ordered_options.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require "active_support/core_ext/object/blank" + +module ActiveSupport + # Usually key value pairs are handled something like this: + # + # h = {} + # h[:boy] = 'John' + # h[:girl] = 'Mary' + # h[:boy] # => 'John' + # h[:girl] # => 'Mary' + # h[:dog] # => nil + # + # Using +OrderedOptions+, the above code could be reduced to: + # + # h = ActiveSupport::OrderedOptions.new + # h.boy = 'John' + # h.girl = 'Mary' + # h.boy # => 'John' + # h.girl # => 'Mary' + # h.dog # => nil + # + # To raise an exception when the value is blank, append a + # bang to the key name, like: + # + # h.dog! # => raises KeyError: :dog is blank + # + class OrderedOptions < Hash + alias_method :_get, :[] # preserve the original #[] method + protected :_get # make it protected + + def []=(key, value) + super(key.to_sym, value) + end + + def [](key) + super(key.to_sym) + end + + def method_missing(name, *args) + name_string = name.to_s + if name_string.chomp!("=") + self[name_string] = args.first + else + bangs = name_string.chomp!("!") + + if bangs + self[name_string].presence || raise(KeyError.new(":#{name_string} is blank")) + else + self[name_string] + end + end + end + + def respond_to_missing?(name, include_private) + true + end + end + + # +InheritableOptions+ provides a constructor to build an +OrderedOptions+ + # hash inherited from another hash. + # + # Use this if you already have some hash and you want to create a new one based on it. + # + # h = ActiveSupport::InheritableOptions.new({ girl: 'Mary', boy: 'John' }) + # h.girl # => 'Mary' + # h.boy # => 'John' + class InheritableOptions < OrderedOptions + def initialize(parent = nil) + if parent.kind_of?(OrderedOptions) + # use the faster _get when dealing with OrderedOptions + super() { |h, k| parent._get(k) } + elsif parent + super() { |h, k| parent[k] } + else + super() + end + end + + def inheritable_copy + self.class.new(self) + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/per_thread_registry.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/per_thread_registry.rb new file mode 100644 index 00000000..eb92fb43 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/per_thread_registry.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/delegation" + +module ActiveSupport + # NOTE: This approach has been deprecated for end-user code in favor of {thread_mattr_accessor}[rdoc-ref:Module#thread_mattr_accessor] and friends. + # Please use that approach instead. + # + # This module is used to encapsulate access to thread local variables. + # + # Instead of polluting the thread locals namespace: + # + # Thread.current[:connection_handler] + # + # you define a class that extends this module: + # + # module ActiveRecord + # class RuntimeRegistry + # extend ActiveSupport::PerThreadRegistry + # + # attr_accessor :connection_handler + # end + # end + # + # and invoke the declared instance accessors as class methods. So + # + # ActiveRecord::RuntimeRegistry.connection_handler = connection_handler + # + # sets a connection handler local to the current thread, and + # + # ActiveRecord::RuntimeRegistry.connection_handler + # + # returns a connection handler local to the current thread. + # + # This feature is accomplished by instantiating the class and storing the + # instance as a thread local keyed by the class name. In the example above + # a key "ActiveRecord::RuntimeRegistry" is stored in Thread.current. + # The class methods proxy to said thread local instance. + # + # If the class has an initializer, it must accept no arguments. + module PerThreadRegistry + def self.extended(object) + object.instance_variable_set "@per_thread_registry_key", object.name.freeze + end + + def instance + Thread.current[@per_thread_registry_key] ||= new + end + + private + def method_missing(name, *args, &block) + # Caches the method definition as a singleton method of the receiver. + # + # By letting #delegate handle it, we avoid an enclosure that'll capture args. + singleton_class.delegate name, to: :instance + + send(name, *args, &block) + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/proxy_object.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/proxy_object.rb new file mode 100644 index 00000000..0965fcd2 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/proxy_object.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module ActiveSupport + # A class with no predefined methods that behaves similarly to Builder's + # BlankSlate. Used for proxy classes. + class ProxyObject < ::BasicObject + undef_method :== + undef_method :equal? + + # Let ActiveSupport::ProxyObject at least raise exceptions. + def raise(*args) + ::Object.send(:raise, *args) + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/rails.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/rails.rb new file mode 100644 index 00000000..5c34a0ab --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/rails.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +# This is private interface. +# +# Rails components cherry pick from Active Support as needed, but there are a +# few features that are used for sure in some way or another and it is not worth +# putting individual requires absolutely everywhere. Think blank? for example. +# +# This file is loaded by every Rails component except Active Support itself, +# but it does not belong to the Rails public interface. It is internal to +# Rails and can change anytime. + +# Defines Object#blank? and Object#present?. +require "active_support/core_ext/object/blank" + +# Rails own autoload, eager_load, etc. +require "active_support/dependencies/autoload" + +# Support for ClassMethods and the included macro. +require "active_support/concern" + +# Defines Class#class_attribute. +require "active_support/core_ext/class/attribute" + +# Defines Module#delegate. +require "active_support/core_ext/module/delegation" + +# Defines ActiveSupport::Deprecation. +require "active_support/deprecation" + +# Defines Regexp#match?. +# +# This should be removed when Rails needs Ruby 2.4 or later, and the require +# added where other Regexp extensions are being used (easy to grep). +require "active_support/core_ext/regexp" diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/railtie.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/railtie.rb new file mode 100644 index 00000000..605b50d3 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/railtie.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require "active_support" +require "active_support/i18n_railtie" + +module ActiveSupport + class Railtie < Rails::Railtie # :nodoc: + config.active_support = ActiveSupport::OrderedOptions.new + + config.eager_load_namespaces << ActiveSupport + + initializer "active_support.set_authenticated_message_encryption" do |app| + config.after_initialize do + unless app.config.active_support.use_authenticated_message_encryption.nil? + ActiveSupport::MessageEncryptor.use_authenticated_message_encryption = + app.config.active_support.use_authenticated_message_encryption + end + end + end + + initializer "active_support.reset_all_current_attributes_instances" do |app| + app.reloader.before_class_unload { ActiveSupport::CurrentAttributes.clear_all } + app.executor.to_run { ActiveSupport::CurrentAttributes.reset_all } + app.executor.to_complete { ActiveSupport::CurrentAttributes.reset_all } + end + + initializer "active_support.deprecation_behavior" do |app| + if deprecation = app.config.active_support.deprecation + ActiveSupport::Deprecation.behavior = deprecation + end + end + + # Sets the default value for Time.zone + # If assigned value cannot be matched to a TimeZone, an exception will be raised. + initializer "active_support.initialize_time_zone" do |app| + begin + TZInfo::DataSource.get + rescue TZInfo::DataSourceNotFound => e + raise e.exception "tzinfo-data is not present. Please add gem 'tzinfo-data' to your Gemfile and run bundle install" + end + require "active_support/core_ext/time/zones" + Time.zone_default = Time.find_zone!(app.config.time_zone) + end + + # Sets the default week start + # If assigned value is not a valid day symbol (e.g. :sunday, :monday, ...), an exception will be raised. + initializer "active_support.initialize_beginning_of_week" do |app| + require "active_support/core_ext/date/calculations" + beginning_of_week_default = Date.find_beginning_of_week!(app.config.beginning_of_week) + + Date.beginning_of_week_default = beginning_of_week_default + end + + initializer "active_support.require_master_key" do |app| + if app.config.respond_to?(:require_master_key) && app.config.require_master_key + begin + app.credentials.key + rescue ActiveSupport::EncryptedFile::MissingKeyError => error + $stderr.puts error.message + exit 1 + end + end + end + + initializer "active_support.set_configs" do |app| + app.config.active_support.each do |k, v| + k = "#{k}=" + ActiveSupport.send(k, v) if ActiveSupport.respond_to? k + end + end + + initializer "active_support.set_hash_digest_class" do |app| + config.after_initialize do + if app.config.active_support.use_sha1_digests + ActiveSupport::Digest.hash_digest_class = ::Digest::SHA1 + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/reloader.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/reloader.rb new file mode 100644 index 00000000..b26d9c36 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/reloader.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true + +require "active_support/execution_wrapper" + +module ActiveSupport + #-- + # This class defines several callbacks: + # + # to_prepare -- Run once at application startup, and also from + # +to_run+. + # + # to_run -- Run before a work run that is reloading. If + # +reload_classes_only_on_change+ is true (the default), the class + # unload will have already occurred. + # + # to_complete -- Run after a work run that has reloaded. If + # +reload_classes_only_on_change+ is false, the class unload will + # have occurred after the work run, but before this callback. + # + # before_class_unload -- Run immediately before the classes are + # unloaded. + # + # after_class_unload -- Run immediately after the classes are + # unloaded. + # + class Reloader < ExecutionWrapper + define_callbacks :prepare + + define_callbacks :class_unload + + # Registers a callback that will run once at application startup and every time the code is reloaded. + def self.to_prepare(*args, &block) + set_callback(:prepare, *args, &block) + end + + # Registers a callback that will run immediately before the classes are unloaded. + def self.before_class_unload(*args, &block) + set_callback(:class_unload, *args, &block) + end + + # Registers a callback that will run immediately after the classes are unloaded. + def self.after_class_unload(*args, &block) + set_callback(:class_unload, :after, *args, &block) + end + + to_run(:after) { self.class.prepare! } + + # Initiate a manual reload + def self.reload! + executor.wrap do + new.tap do |instance| + begin + instance.run! + ensure + instance.complete! + end + end + end + prepare! + end + + def self.run! # :nodoc: + if check! + super + else + Null + end + end + + # Run the supplied block as a work unit, reloading code as needed + def self.wrap + executor.wrap do + super + end + end + + class_attribute :executor, default: Executor + class_attribute :check, default: lambda { false } + + def self.check! # :nodoc: + @should_reload ||= check.call + end + + def self.reloaded! # :nodoc: + @should_reload = false + end + + def self.prepare! # :nodoc: + new.run_callbacks(:prepare) + end + + def initialize + super + @locked = false + end + + # Acquire the ActiveSupport::Dependencies::Interlock unload lock, + # ensuring it will be released automatically + def require_unload_lock! + unless @locked + ActiveSupport::Dependencies.interlock.start_unloading + @locked = true + end + end + + # Release the unload lock if it has been previously obtained + def release_unload_lock! + if @locked + @locked = false + ActiveSupport::Dependencies.interlock.done_unloading + end + end + + def run! # :nodoc: + super + release_unload_lock! + end + + def class_unload!(&block) # :nodoc: + require_unload_lock! + run_callbacks(:class_unload, &block) + end + + def complete! # :nodoc: + super + self.class.reloaded! + ensure + release_unload_lock! + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/rescuable.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/rescuable.rb new file mode 100644 index 00000000..e0fa29ca --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/rescuable.rb @@ -0,0 +1,174 @@ +# frozen_string_literal: true + +require "active_support/concern" +require "active_support/core_ext/class/attribute" +require "active_support/core_ext/string/inflections" + +module ActiveSupport + # Rescuable module adds support for easier exception handling. + module Rescuable + extend Concern + + included do + class_attribute :rescue_handlers, default: [] + end + + module ClassMethods + # Rescue exceptions raised in controller actions. + # + # rescue_from receives a series of exception classes or class + # names, and a trailing :with option with the name of a method + # or a Proc object to be called to handle them. Alternatively a block can + # be given. + # + # Handlers that take one argument will be called with the exception, so + # that the exception can be inspected when dealing with it. + # + # Handlers are inherited. They are searched from right to left, from + # bottom to top, and up the hierarchy. The handler of the first class for + # which exception.is_a?(klass) holds true is the one invoked, if + # any. + # + # class ApplicationController < ActionController::Base + # rescue_from User::NotAuthorized, with: :deny_access # self defined exception + # rescue_from ActiveRecord::RecordInvalid, with: :show_errors + # + # rescue_from 'MyAppError::Base' do |exception| + # render xml: exception, status: 500 + # end + # + # private + # def deny_access + # ... + # end + # + # def show_errors(exception) + # exception.record.new_record? ? ... + # end + # end + # + # Exceptions raised inside exception handlers are not propagated up. + def rescue_from(*klasses, with: nil, &block) + unless with + if block_given? + with = block + else + raise ArgumentError, "Need a handler. Pass the with: keyword argument or provide a block." + end + end + + klasses.each do |klass| + key = if klass.is_a?(Module) && klass.respond_to?(:===) + klass.name + elsif klass.is_a?(String) + klass + else + raise ArgumentError, "#{klass.inspect} must be an Exception class or a String referencing an Exception class" + end + + # Put the new handler at the end because the list is read in reverse. + self.rescue_handlers += [[key, with]] + end + end + + # Matches an exception to a handler based on the exception class. + # + # If no handler matches the exception, check for a handler matching the + # (optional) exception.cause. If no handler matches the exception or its + # cause, this returns +nil+, so you can deal with unhandled exceptions. + # Be sure to re-raise unhandled exceptions if this is what you expect. + # + # begin + # … + # rescue => exception + # rescue_with_handler(exception) || raise + # end + # + # Returns the exception if it was handled and +nil+ if it was not. + def rescue_with_handler(exception, object: self, visited_exceptions: []) + visited_exceptions << exception + + if handler = handler_for_rescue(exception, object: object) + handler.call exception + exception + elsif exception + if visited_exceptions.include?(exception.cause) + nil + else + rescue_with_handler(exception.cause, object: object, visited_exceptions: visited_exceptions) + end + end + end + + def handler_for_rescue(exception, object: self) #:nodoc: + case rescuer = find_rescue_handler(exception) + when Symbol + method = object.method(rescuer) + if method.arity == 0 + -> e { method.call } + else + method + end + when Proc + if rescuer.arity == 0 + -> e { object.instance_exec(&rescuer) } + else + -> e { object.instance_exec(e, &rescuer) } + end + end + end + + private + def find_rescue_handler(exception) + if exception + # Handlers are in order of declaration but the most recently declared + # is the highest priority match, so we search for matching handlers + # in reverse. + _, handler = rescue_handlers.reverse_each.detect do |class_or_name, _| + if klass = constantize_rescue_handler_class(class_or_name) + klass === exception + end + end + + handler + end + end + + def constantize_rescue_handler_class(class_or_name) + case class_or_name + when String, Symbol + begin + # Try a lexical lookup first since we support + # + # class Super + # rescue_from 'Error', with: … + # end + # + # class Sub + # class Error < StandardError; end + # end + # + # so an Error raised in Sub will hit the 'Error' handler. + const_get class_or_name + rescue NameError + class_or_name.safe_constantize + end + else + class_or_name + end + end + end + + # Delegates to the class method, but uses the instance as the subject for + # rescue_from handlers (method calls, instance_exec blocks). + def rescue_with_handler(exception) + self.class.rescue_with_handler exception, object: self + end + + # Internal handler lookup. Delegates to class method. Some libraries call + # this directly, so keeping it around for compatibility. + def handler_for_rescue(exception) #:nodoc: + self.class.handler_for_rescue exception, object: self + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/security_utils.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/security_utils.rb new file mode 100644 index 00000000..20b6b9cd --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/security_utils.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require "digest/sha2" + +module ActiveSupport + module SecurityUtils + # Constant time string comparison, for fixed length strings. + # + # The values compared should be of fixed length, such as strings + # that have already been processed by HMAC. Raises in case of length mismatch. + def fixed_length_secure_compare(a, b) + raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize + + l = a.unpack "C#{a.bytesize}" + + res = 0 + b.each_byte { |byte| res |= byte ^ l.shift } + res == 0 + end + module_function :fixed_length_secure_compare + + # Constant time string comparison, for variable length strings. + # + # The values are first processed by SHA256, so that we don't leak length info + # via timing attacks. + def secure_compare(a, b) + fixed_length_secure_compare(::Digest::SHA256.hexdigest(a), ::Digest::SHA256.hexdigest(b)) && a == b + end + module_function :secure_compare + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/string_inquirer.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/string_inquirer.rb new file mode 100644 index 00000000..a3af3672 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/string_inquirer.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module ActiveSupport + # Wrapping a string in this class gives you a prettier way to test + # for equality. The value returned by Rails.env is wrapped + # in a StringInquirer object, so instead of calling this: + # + # Rails.env == 'production' + # + # you can call this: + # + # Rails.env.production? + # + # == Instantiating a new StringInquirer + # + # vehicle = ActiveSupport::StringInquirer.new('car') + # vehicle.car? # => true + # vehicle.bike? # => false + class StringInquirer < String + private + + def respond_to_missing?(method_name, include_private = false) + (method_name[-1] == "?") || super + end + + def method_missing(method_name, *arguments) + if method_name[-1] == "?" + self == method_name[0..-2] + else + super + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/subscriber.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/subscriber.rb new file mode 100644 index 00000000..9291b48c --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/subscriber.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +require "active_support/per_thread_registry" +require "active_support/notifications" + +module ActiveSupport + # ActiveSupport::Subscriber is an object set to consume + # ActiveSupport::Notifications. The subscriber dispatches notifications to + # a registered object based on its given namespace. + # + # An example would be an Active Record subscriber responsible for collecting + # statistics about queries: + # + # module ActiveRecord + # class StatsSubscriber < ActiveSupport::Subscriber + # attach_to :active_record + # + # def sql(event) + # Statsd.timing("sql.#{event.payload[:name]}", event.duration) + # end + # end + # end + # + # After configured, whenever a "sql.active_record" notification is published, + # it will properly dispatch the event (ActiveSupport::Notifications::Event) to + # the +sql+ method. + class Subscriber + class << self + # Attach the subscriber to a namespace. + def attach_to(namespace, subscriber = new, notifier = ActiveSupport::Notifications) + @namespace = namespace + @subscriber = subscriber + @notifier = notifier + + subscribers << subscriber + + # Add event subscribers for all existing methods on the class. + subscriber.public_methods(false).each do |event| + add_event_subscriber(event) + end + end + + # Adds event subscribers for all new methods added to the class. + def method_added(event) + # Only public methods are added as subscribers, and only if a notifier + # has been set up. This means that subscribers will only be set up for + # classes that call #attach_to. + if public_method_defined?(event) && notifier + add_event_subscriber(event) + end + end + + def subscribers + @@subscribers ||= [] + end + + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. + protected + + attr_reader :subscriber, :notifier, :namespace + + private + + def add_event_subscriber(event) # :doc: + return if %w{ start finish }.include?(event.to_s) + + pattern = "#{event}.#{namespace}" + + # Don't add multiple subscribers (eg. if methods are redefined). + return if subscriber.patterns.include?(pattern) + + subscriber.patterns << pattern + notifier.subscribe(pattern, subscriber) + end + end + + attr_reader :patterns # :nodoc: + + def initialize + @queue_key = [self.class.name, object_id].join "-" + @patterns = [] + super + end + + def start(name, id, payload) + e = ActiveSupport::Notifications::Event.new(name, now, nil, id, payload) + parent = event_stack.last + parent << e if parent + + event_stack.push e + end + + def finish(name, id, payload) + finished = now + event = event_stack.pop + event.end = finished + event.payload.merge!(payload) + + method = name.split(".".freeze).first + send(method, event) + end + + private + + def event_stack + SubscriberQueueRegistry.instance.get_queue(@queue_key) + end + + def now + Process.clock_gettime(Process::CLOCK_MONOTONIC) + end + end + + # This is a registry for all the event stacks kept for subscribers. + # + # See the documentation of ActiveSupport::PerThreadRegistry + # for further details. + class SubscriberQueueRegistry # :nodoc: + extend PerThreadRegistry + + def initialize + @registry = {} + end + + def get_queue(queue_key) + @registry[queue_key] ||= [] + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/tagged_logging.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/tagged_logging.rb new file mode 100644 index 00000000..8561cba9 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/tagged_logging.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/delegation" +require "active_support/core_ext/object/blank" +require "logger" +require "active_support/logger" + +module ActiveSupport + # Wraps any standard Logger object to provide tagging capabilities. + # + # logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT)) + # logger.tagged('BCX') { logger.info 'Stuff' } # Logs "[BCX] Stuff" + # logger.tagged('BCX', "Jason") { logger.info 'Stuff' } # Logs "[BCX] [Jason] Stuff" + # logger.tagged('BCX') { logger.tagged('Jason') { logger.info 'Stuff' } } # Logs "[BCX] [Jason] Stuff" + # + # This is used by the default Rails.logger as configured by Railties to make + # it easy to stamp log lines with subdomains, request ids, and anything else + # to aid debugging of multi-user production applications. + module TaggedLogging + module Formatter # :nodoc: + # This method is invoked when a log event occurs. + def call(severity, timestamp, progname, msg) + super(severity, timestamp, progname, "#{tags_text}#{msg}") + end + + def tagged(*tags) + new_tags = push_tags(*tags) + yield self + ensure + pop_tags(new_tags.size) + end + + def push_tags(*tags) + tags.flatten.reject(&:blank?).tap do |new_tags| + current_tags.concat new_tags + end + end + + def pop_tags(size = 1) + current_tags.pop size + end + + def clear_tags! + current_tags.clear + end + + def current_tags + # We use our object ID here to avoid conflicting with other instances + thread_key = @thread_key ||= "activesupport_tagged_logging_tags:#{object_id}".freeze + Thread.current[thread_key] ||= [] + end + + def tags_text + tags = current_tags + if tags.any? + tags.collect { |tag| "[#{tag}] " }.join + end + end + end + + def self.new(logger) + # Ensure we set a default formatter so we aren't extending nil! + logger.formatter ||= ActiveSupport::Logger::SimpleFormatter.new + logger.formatter.extend Formatter + logger.extend(self) + end + + delegate :push_tags, :pop_tags, :clear_tags!, to: :formatter + + def tagged(*tags) + formatter.tagged(*tags) { yield self } + end + + def flush + clear_tags! + super if defined?(super) + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/test_case.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/test_case.rb new file mode 100644 index 00000000..69d69c84 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/test_case.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +gem "minitest" # make sure we get the gem, not stdlib +require "minitest" +require "active_support/testing/tagged_logging" +require "active_support/testing/setup_and_teardown" +require "active_support/testing/assertions" +require "active_support/testing/deprecation" +require "active_support/testing/declarative" +require "active_support/testing/isolation" +require "active_support/testing/constant_lookup" +require "active_support/testing/time_helpers" +require "active_support/testing/file_fixtures" + +module ActiveSupport + class TestCase < ::Minitest::Test + Assertion = Minitest::Assertion + + class << self + # Sets the order in which test cases are run. + # + # ActiveSupport::TestCase.test_order = :random # => :random + # + # Valid values are: + # * +:random+ (to run tests in random order) + # * +:parallel+ (to run tests in parallel) + # * +:sorted+ (to run tests alphabetically by method name) + # * +:alpha+ (equivalent to +:sorted+) + def test_order=(new_order) + ActiveSupport.test_order = new_order + end + + # Returns the order in which test cases are run. + # + # ActiveSupport::TestCase.test_order # => :random + # + # Possible values are +:random+, +:parallel+, +:alpha+, +:sorted+. + # Defaults to +:random+. + def test_order + ActiveSupport.test_order ||= :random + end + end + + alias_method :method_name, :name + + include ActiveSupport::Testing::TaggedLogging + prepend ActiveSupport::Testing::SetupAndTeardown + include ActiveSupport::Testing::Assertions + include ActiveSupport::Testing::Deprecation + include ActiveSupport::Testing::TimeHelpers + include ActiveSupport::Testing::FileFixtures + extend ActiveSupport::Testing::Declarative + + # test/unit backwards compatibility methods + alias :assert_raise :assert_raises + alias :assert_not_empty :refute_empty + alias :assert_not_equal :refute_equal + alias :assert_not_in_delta :refute_in_delta + alias :assert_not_in_epsilon :refute_in_epsilon + alias :assert_not_includes :refute_includes + alias :assert_not_instance_of :refute_instance_of + alias :assert_not_kind_of :refute_kind_of + alias :assert_no_match :refute_match + alias :assert_not_nil :refute_nil + alias :assert_not_operator :refute_operator + alias :assert_not_predicate :refute_predicate + alias :assert_not_respond_to :refute_respond_to + alias :assert_not_same :refute_same + + ActiveSupport.run_load_hooks(:active_support_test_case, self) + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/assertions.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/assertions.rb new file mode 100644 index 00000000..a891ff61 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/assertions.rb @@ -0,0 +1,214 @@ +# frozen_string_literal: true + +module ActiveSupport + module Testing + module Assertions + UNTRACKED = Object.new # :nodoc: + + # Asserts that an expression is not truthy. Passes if object is + # +nil+ or +false+. "Truthy" means "considered true in a conditional" + # like if foo. + # + # assert_not nil # => true + # assert_not false # => true + # assert_not 'foo' # => Expected "foo" to be nil or false + # + # An error message can be specified. + # + # assert_not foo, 'foo should be false' + def assert_not(object, message = nil) + message ||= "Expected #{mu_pp(object)} to be nil or false" + assert !object, message + end + + # Assertion that the block should not raise an exception. + # + # Passes if evaluated code in the yielded block raises no exception. + # + # assert_nothing_raised do + # perform_service(param: 'no_exception') + # end + def assert_nothing_raised + yield + end + + # Test numeric difference between the return value of an expression as a + # result of what is evaluated in the yielded block. + # + # assert_difference 'Article.count' do + # post :create, params: { article: {...} } + # end + # + # An arbitrary expression is passed in and evaluated. + # + # assert_difference 'Article.last.comments(:reload).size' do + # post :create, params: { comment: {...} } + # end + # + # An arbitrary positive or negative difference can be specified. + # The default is 1. + # + # assert_difference 'Article.count', -1 do + # post :delete, params: { id: ... } + # end + # + # An array of expressions can also be passed in and evaluated. + # + # assert_difference [ 'Article.count', 'Post.count' ], 2 do + # post :create, params: { article: {...} } + # end + # + # A hash of expressions/numeric differences can also be passed in and evaluated. + # + # assert_difference ->{ Article.count } => 1, ->{ Notification.count } => 2 do + # post :create, params: { article: {...} } + # end + # + # A lambda or a list of lambdas can be passed in and evaluated: + # + # assert_difference ->{ Article.count }, 2 do + # post :create, params: { article: {...} } + # end + # + # assert_difference [->{ Article.count }, ->{ Post.count }], 2 do + # post :create, params: { article: {...} } + # end + # + # An error message can be specified. + # + # assert_difference 'Article.count', -1, 'An Article should be destroyed' do + # post :delete, params: { id: ... } + # end + def assert_difference(expression, *args, &block) + expressions = + if expression.is_a?(Hash) + message = args[0] + expression + else + difference = args[0] || 1 + message = args[1] + Hash[Array(expression).map { |e| [e, difference] }] + end + + exps = expressions.keys.map { |e| + e.respond_to?(:call) ? e : lambda { eval(e, block.binding) } + } + before = exps.map(&:call) + + retval = yield + + expressions.zip(exps, before) do |(code, diff), exp, before_value| + error = "#{code.inspect} didn't change by #{diff}" + error = "#{message}.\n#{error}" if message + assert_equal(before_value + diff, exp.call, error) + end + + retval + end + + # Assertion that the numeric result of evaluating an expression is not + # changed before and after invoking the passed in block. + # + # assert_no_difference 'Article.count' do + # post :create, params: { article: invalid_attributes } + # end + # + # An error message can be specified. + # + # assert_no_difference 'Article.count', 'An Article should not be created' do + # post :create, params: { article: invalid_attributes } + # end + def assert_no_difference(expression, message = nil, &block) + assert_difference expression, 0, message, &block + end + + # Assertion that the result of evaluating an expression is changed before + # and after invoking the passed in block. + # + # assert_changes 'Status.all_good?' do + # post :create, params: { status: { ok: false } } + # end + # + # You can pass the block as a string to be evaluated in the context of + # the block. A lambda can be passed for the block as well. + # + # assert_changes -> { Status.all_good? } do + # post :create, params: { status: { ok: false } } + # end + # + # The assertion is useful to test side effects. The passed block can be + # anything that can be converted to string with #to_s. + # + # assert_changes :@object do + # @object = 42 + # end + # + # The keyword arguments :from and :to can be given to specify the + # expected initial value and the expected value after the block was + # executed. + # + # assert_changes :@object, from: nil, to: :foo do + # @object = :foo + # end + # + # An error message can be specified. + # + # assert_changes -> { Status.all_good? }, 'Expected the status to be bad' do + # post :create, params: { status: { incident: true } } + # end + def assert_changes(expression, message = nil, from: UNTRACKED, to: UNTRACKED, &block) + exp = expression.respond_to?(:call) ? expression : -> { eval(expression.to_s, block.binding) } + + before = exp.call + retval = yield + + unless from == UNTRACKED + error = "#{expression.inspect} isn't #{from.inspect}" + error = "#{message}.\n#{error}" if message + assert from === before, error + end + + after = exp.call + + error = "#{expression.inspect} didn't change" + error = "#{error}. It was already #{to}" if before == to + error = "#{message}.\n#{error}" if message + assert before != after, error + + unless to == UNTRACKED + error = "#{expression.inspect} didn't change to #{to}" + error = "#{message}.\n#{error}" if message + assert to === after, error + end + + retval + end + + # Assertion that the result of evaluating an expression is not changed before + # and after invoking the passed in block. + # + # assert_no_changes 'Status.all_good?' do + # post :create, params: { status: { ok: true } } + # end + # + # An error message can be specified. + # + # assert_no_changes -> { Status.all_good? }, 'Expected the status to be good' do + # post :create, params: { status: { ok: false } } + # end + def assert_no_changes(expression, message = nil, &block) + exp = expression.respond_to?(:call) ? expression : -> { eval(expression.to_s, block.binding) } + + before = exp.call + retval = yield + after = exp.call + + error = "#{expression.inspect} did change to #{after}" + error = "#{message}.\n#{error}" if message + assert before == after, error + + retval + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/autorun.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/autorun.rb new file mode 100644 index 00000000..889b4165 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/autorun.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +gem "minitest" + +require "minitest" + +Minitest.autorun diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/constant_lookup.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/constant_lookup.rb new file mode 100644 index 00000000..51167e92 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/constant_lookup.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require "active_support/concern" +require "active_support/inflector" + +module ActiveSupport + module Testing + # Resolves a constant from a minitest spec name. + # + # Given the following spec-style test: + # + # describe WidgetsController, :index do + # describe "authenticated user" do + # describe "returns widgets" do + # it "has a controller that exists" do + # assert_kind_of WidgetsController, @controller + # end + # end + # end + # end + # + # The test will have the following name: + # + # "WidgetsController::index::authenticated user::returns widgets" + # + # The constant WidgetsController can be resolved from the name. + # The following code will resolve the constant: + # + # controller = determine_constant_from_test_name(name) do |constant| + # Class === constant && constant < ::ActionController::Metal + # end + module ConstantLookup + extend ::ActiveSupport::Concern + + module ClassMethods # :nodoc: + def determine_constant_from_test_name(test_name) + names = test_name.split "::" + while names.size > 0 do + names.last.sub!(/Test$/, "") + begin + constant = names.join("::").safe_constantize + break(constant) if yield(constant) + ensure + names.pop + end + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/declarative.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/declarative.rb new file mode 100644 index 00000000..7c340368 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/declarative.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module ActiveSupport + module Testing + module Declarative + unless defined?(Spec) + # Helper to define a test method using a String. Under the hood, it replaces + # spaces with underscores and defines the test method. + # + # test "verify something" do + # ... + # end + def test(name, &block) + test_name = "test_#{name.gsub(/\s+/, '_')}".to_sym + defined = method_defined? test_name + raise "#{test_name} is already defined in #{self}" if defined + if block_given? + define_method(test_name, &block) + else + define_method(test_name) do + flunk "No implementation provided for #{name}" + end + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/deprecation.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/deprecation.rb new file mode 100644 index 00000000..f6554357 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/deprecation.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require "active_support/deprecation" +require "active_support/core_ext/regexp" + +module ActiveSupport + module Testing + module Deprecation #:nodoc: + def assert_deprecated(match = nil, deprecator = nil, &block) + result, warnings = collect_deprecations(deprecator, &block) + assert !warnings.empty?, "Expected a deprecation warning within the block but received none" + if match + match = Regexp.new(Regexp.escape(match)) unless match.is_a?(Regexp) + assert warnings.any? { |w| match.match?(w) }, "No deprecation warning matched #{match}: #{warnings.join(', ')}" + end + result + end + + def assert_not_deprecated(deprecator = nil, &block) + result, deprecations = collect_deprecations(deprecator, &block) + assert deprecations.empty?, "Expected no deprecation warning within the block but received #{deprecations.size}: \n #{deprecations * "\n "}" + result + end + + def collect_deprecations(deprecator = nil) + deprecator ||= ActiveSupport::Deprecation + old_behavior = deprecator.behavior + deprecations = [] + deprecator.behavior = Proc.new do |message, callstack| + deprecations << message + end + result = yield + [result, deprecations] + ensure + deprecator.behavior = old_behavior + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/file_fixtures.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/file_fixtures.rb new file mode 100644 index 00000000..ad923d1a --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/file_fixtures.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module ActiveSupport + module Testing + # Adds simple access to sample files called file fixtures. + # File fixtures are normal files stored in + # ActiveSupport::TestCase.file_fixture_path. + # + # File fixtures are represented as +Pathname+ objects. + # This makes it easy to extract specific information: + # + # file_fixture("example.txt").read # get the file's content + # file_fixture("example.mp3").size # get the file size + module FileFixtures + extend ActiveSupport::Concern + + included do + class_attribute :file_fixture_path, instance_writer: false + end + + # Returns a +Pathname+ to the fixture file named +fixture_name+. + # + # Raises +ArgumentError+ if +fixture_name+ can't be found. + def file_fixture(fixture_name) + path = Pathname.new(File.join(file_fixture_path, fixture_name)) + + if path.exist? + path + else + msg = "the directory '%s' does not contain a file named '%s'" + raise ArgumentError, msg % [file_fixture_path, fixture_name] + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/isolation.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/isolation.rb new file mode 100644 index 00000000..562f985f --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/isolation.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +module ActiveSupport + module Testing + module Isolation + require "thread" + + def self.included(klass) #:nodoc: + klass.class_eval do + parallelize_me! + end + end + + def self.forking_env? + !ENV["NO_FORK"] && Process.respond_to?(:fork) + end + + def run + serialized = run_in_isolation do + super + end + + Marshal.load(serialized) + end + + module Forking + def run_in_isolation(&blk) + read, write = IO.pipe + read.binmode + write.binmode + + pid = fork do + read.close + yield + begin + if error? + failures.map! { |e| + begin + Marshal.dump e + e + rescue TypeError + ex = Exception.new e.message + ex.set_backtrace e.backtrace + Minitest::UnexpectedError.new ex + end + } + end + test_result = defined?(Minitest::Result) ? Minitest::Result.from(self) : dup + result = Marshal.dump(test_result) + end + + write.puts [result].pack("m") + exit! + end + + write.close + result = read.read + Process.wait2(pid) + result.unpack("m")[0] + end + end + + module Subprocess + ORIG_ARGV = ARGV.dup unless defined?(ORIG_ARGV) + + # Crazy H4X to get this working in windows / jruby with + # no forking. + def run_in_isolation(&blk) + require "tempfile" + + if ENV["ISOLATION_TEST"] + yield + test_result = defined?(Minitest::Result) ? Minitest::Result.from(self) : dup + File.open(ENV["ISOLATION_OUTPUT"], "w") do |file| + file.puts [Marshal.dump(test_result)].pack("m") + end + exit! + else + Tempfile.open("isolation") do |tmpfile| + env = { + "ISOLATION_TEST" => self.class.name, + "ISOLATION_OUTPUT" => tmpfile.path + } + + test_opts = "-n#{self.class.name}##{name}" + + load_path_args = [] + $-I.each do |p| + load_path_args << "-I" + load_path_args << File.expand_path(p) + end + + child = IO.popen([env, Gem.ruby, *load_path_args, $0, *ORIG_ARGV, test_opts]) + + begin + Process.wait(child.pid) + rescue Errno::ECHILD # The child process may exit before we wait + nil + end + + return tmpfile.read.unpack("m")[0] + end + end + end + end + + include forking_env? ? Forking : Subprocess + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/method_call_assertions.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/method_call_assertions.rb new file mode 100644 index 00000000..c6358002 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/method_call_assertions.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require "minitest/mock" + +module ActiveSupport + module Testing + module MethodCallAssertions # :nodoc: + private + def assert_called(object, method_name, message = nil, times: 1, returns: nil) + times_called = 0 + + object.stub(method_name, proc { times_called += 1; returns }) { yield } + + error = "Expected #{method_name} to be called #{times} times, " \ + "but was called #{times_called} times" + error = "#{message}.\n#{error}" if message + assert_equal times, times_called, error + end + + def assert_called_with(object, method_name, args = [], returns: nil) + mock = Minitest::Mock.new + + if args.all? { |arg| arg.is_a?(Array) } + args.each { |arg| mock.expect(:call, returns, arg) } + else + mock.expect(:call, returns, args) + end + + object.stub(method_name, mock) { yield } + + mock.verify + end + + def assert_not_called(object, method_name, message = nil, &block) + assert_called(object, method_name, message, times: 0, &block) + end + + def stub_any_instance(klass, instance: klass.new) + klass.stub(:new, instance) { yield instance } + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/setup_and_teardown.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/setup_and_teardown.rb new file mode 100644 index 00000000..35321cd1 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/setup_and_teardown.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require "active_support/callbacks" + +module ActiveSupport + module Testing + # Adds support for +setup+ and +teardown+ callbacks. + # These callbacks serve as a replacement to overwriting the + # #setup and #teardown methods of your TestCase. + # + # class ExampleTest < ActiveSupport::TestCase + # setup do + # # ... + # end + # + # teardown do + # # ... + # end + # end + module SetupAndTeardown + def self.prepended(klass) + klass.include ActiveSupport::Callbacks + klass.define_callbacks :setup, :teardown + klass.extend ClassMethods + end + + module ClassMethods + # Add a callback, which runs before TestCase#setup. + def setup(*args, &block) + set_callback(:setup, :before, *args, &block) + end + + # Add a callback, which runs after TestCase#teardown. + def teardown(*args, &block) + set_callback(:teardown, :after, *args, &block) + end + end + + def before_setup # :nodoc: + super + run_callbacks :setup + end + + def after_teardown # :nodoc: + begin + run_callbacks :teardown + rescue => e + self.failures << Minitest::UnexpectedError.new(e) + end + + super + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/stream.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/stream.rb new file mode 100644 index 00000000..d070a179 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/stream.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module ActiveSupport + module Testing + module Stream #:nodoc: + private + + def silence_stream(stream) + old_stream = stream.dup + stream.reopen(IO::NULL) + stream.sync = true + yield + ensure + stream.reopen(old_stream) + old_stream.close + end + + def quietly + silence_stream(STDOUT) do + silence_stream(STDERR) do + yield + end + end + end + + def capture(stream) + stream = stream.to_s + captured_stream = Tempfile.new(stream) + stream_io = eval("$#{stream}") + origin_stream = stream_io.dup + stream_io.reopen(captured_stream) + + yield + + stream_io.rewind + return captured_stream.read + ensure + captured_stream.close + captured_stream.unlink + stream_io.reopen(origin_stream) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/tagged_logging.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/tagged_logging.rb new file mode 100644 index 00000000..9ca50c79 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/tagged_logging.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module ActiveSupport + module Testing + # Logs a "PostsControllerTest: test name" heading before each test to + # make test.log easier to search and follow along with. + module TaggedLogging #:nodoc: + attr_writer :tagged_logger + + def before_setup + if tagged_logger && tagged_logger.info? + heading = "#{self.class}: #{name}" + divider = "-" * heading.size + tagged_logger.info divider + tagged_logger.info heading + tagged_logger.info divider + end + super + end + + private + def tagged_logger + @tagged_logger ||= (defined?(Rails.logger) && Rails.logger) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/time_helpers.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/time_helpers.rb new file mode 100644 index 00000000..998a51a3 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/testing/time_helpers.rb @@ -0,0 +1,200 @@ +# frozen_string_literal: true + +require "active_support/core_ext/module/redefine_method" +require "active_support/core_ext/string/strip" # for strip_heredoc +require "active_support/core_ext/time/calculations" +require "concurrent/map" + +module ActiveSupport + module Testing + class SimpleStubs # :nodoc: + Stub = Struct.new(:object, :method_name, :original_method) + + def initialize + @stubs = Concurrent::Map.new { |h, k| h[k] = {} } + end + + def stub_object(object, method_name, &block) + if stub = stubbing(object, method_name) + unstub_object(stub) + end + + new_name = "__simple_stub__#{method_name}" + + @stubs[object.object_id][method_name] = Stub.new(object, method_name, new_name) + + object.singleton_class.send :alias_method, new_name, method_name + object.define_singleton_method(method_name, &block) + end + + def unstub_all! + @stubs.each_value do |object_stubs| + object_stubs.each_value do |stub| + unstub_object(stub) + end + end + @stubs.clear + end + + def stubbing(object, method_name) + @stubs[object.object_id][method_name] + end + + private + + def unstub_object(stub) + singleton_class = stub.object.singleton_class + singleton_class.send :silence_redefinition_of_method, stub.method_name + singleton_class.send :alias_method, stub.method_name, stub.original_method + singleton_class.send :undef_method, stub.original_method + end + end + + # Contains helpers that help you test passage of time. + module TimeHelpers + def after_teardown + travel_back + super + end + + # Changes current time to the time in the future or in the past by a given time difference by + # stubbing +Time.now+, +Date.today+, and +DateTime.now+. The stubs are automatically removed + # at the end of the test. + # + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # travel 1.day + # Time.current # => Sun, 10 Nov 2013 15:34:49 EST -05:00 + # Date.current # => Sun, 10 Nov 2013 + # DateTime.current # => Sun, 10 Nov 2013 15:34:49 -0500 + # + # This method also accepts a block, which will return the current time back to its original + # state at the end of the block: + # + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # travel 1.day do + # User.create.created_at # => Sun, 10 Nov 2013 15:34:49 EST -05:00 + # end + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + def travel(duration, &block) + travel_to Time.now + duration, &block + end + + # Changes current time to the given time by stubbing +Time.now+, + # +Date.today+, and +DateTime.now+ to return the time or date passed into this method. + # The stubs are automatically removed at the end of the test. + # + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # travel_to Time.zone.local(2004, 11, 24, 01, 04, 44) + # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 + # Date.current # => Wed, 24 Nov 2004 + # DateTime.current # => Wed, 24 Nov 2004 01:04:44 -0500 + # + # Dates are taken as their timestamp at the beginning of the day in the + # application time zone. Time.current returns said timestamp, + # and Time.now its equivalent in the system time zone. Similarly, + # Date.current returns a date equal to the argument, and + # Date.today the date according to Time.now, which may + # be different. (Note that you rarely want to deal with Time.now, + # or Date.today, in order to honor the application time zone + # please always use Time.current and Date.current.) + # + # Note that the usec for the time passed will be set to 0 to prevent rounding + # errors with external services, like MySQL (which will round instead of floor, + # leading to off-by-one-second errors). + # + # This method also accepts a block, which will return the current time back to its original + # state at the end of the block: + # + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # travel_to Time.zone.local(2004, 11, 24, 01, 04, 44) do + # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 + # end + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + def travel_to(date_or_time) + if block_given? && simple_stubs.stubbing(Time, :now) + travel_to_nested_block_call = <<-MSG.strip_heredoc + + Calling `travel_to` with a block, when we have previously already made a call to `travel_to`, can lead to confusing time stubbing. + + Instead of: + + travel_to 2.days.from_now do + # 2 days from today + travel_to 3.days.from_now do + # 5 days from today + end + end + + preferred way to achieve above is: + + travel 2.days do + # 2 days from today + end + + travel 5.days do + # 5 days from today + end + + MSG + raise travel_to_nested_block_call + end + + if date_or_time.is_a?(Date) && !date_or_time.is_a?(DateTime) + now = date_or_time.midnight.to_time + else + now = date_or_time.to_time.change(usec: 0) + end + + simple_stubs.stub_object(Time, :now) { at(now.to_i) } + simple_stubs.stub_object(Date, :today) { jd(now.to_date.jd) } + simple_stubs.stub_object(DateTime, :now) { jd(now.to_date.jd, now.hour, now.min, now.sec, Rational(now.utc_offset, 86400)) } + + if block_given? + begin + yield + ensure + travel_back + end + end + end + + # Returns the current time back to its original state, by removing the stubs added by + # +travel+ and +travel_to+. + # + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # travel_to Time.zone.local(2004, 11, 24, 01, 04, 44) + # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 + # travel_back + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + def travel_back + simple_stubs.unstub_all! + end + + # Calls +travel_to+ with +Time.now+. + # + # Time.current # => Sun, 09 Jul 2017 15:34:49 EST -05:00 + # freeze_time + # sleep(1) + # Time.current # => Sun, 09 Jul 2017 15:34:49 EST -05:00 + # + # This method also accepts a block, which will return the current time back to its original + # state at the end of the block: + # + # Time.current # => Sun, 09 Jul 2017 15:34:49 EST -05:00 + # freeze_time do + # sleep(1) + # User.create.created_at # => Sun, 09 Jul 2017 15:34:49 EST -05:00 + # end + # Time.current # => Sun, 09 Jul 2017 15:34:50 EST -05:00 + def freeze_time(&block) + travel_to Time.now, &block + end + + private + + def simple_stubs + @simple_stubs ||= SimpleStubs.new + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/time.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/time.rb new file mode 100644 index 00000000..51854675 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/time.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module ActiveSupport + autoload :Duration, "active_support/duration" + autoload :TimeWithZone, "active_support/time_with_zone" + autoload :TimeZone, "active_support/values/time_zone" +end + +require "date" +require "time" + +require "active_support/core_ext/time" +require "active_support/core_ext/date" +require "active_support/core_ext/date_time" + +require "active_support/core_ext/integer/time" +require "active_support/core_ext/numeric/time" + +require "active_support/core_ext/string/conversions" +require "active_support/core_ext/string/zones" diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/time_with_zone.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/time_with_zone.rb new file mode 100644 index 00000000..20650ce7 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/time_with_zone.rb @@ -0,0 +1,551 @@ +# frozen_string_literal: true + +require "active_support/duration" +require "active_support/values/time_zone" +require "active_support/core_ext/object/acts_like" +require "active_support/core_ext/date_and_time/compatibility" + +module ActiveSupport + # A Time-like class that can represent a time in any time zone. Necessary + # because standard Ruby Time instances are limited to UTC and the + # system's ENV['TZ'] zone. + # + # You shouldn't ever need to create a TimeWithZone instance directly via +new+. + # Instead use methods +local+, +parse+, +at+ and +now+ on TimeZone instances, + # and +in_time_zone+ on Time and DateTime instances. + # + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # Time.zone.local(2007, 2, 10, 15, 30, 45) # => Sat, 10 Feb 2007 15:30:45 EST -05:00 + # Time.zone.parse('2007-02-10 15:30:45') # => Sat, 10 Feb 2007 15:30:45 EST -05:00 + # Time.zone.at(1171139445) # => Sat, 10 Feb 2007 15:30:45 EST -05:00 + # Time.zone.now # => Sun, 18 May 2008 13:07:55 EDT -04:00 + # Time.utc(2007, 2, 10, 20, 30, 45).in_time_zone # => Sat, 10 Feb 2007 15:30:45 EST -05:00 + # + # See Time and TimeZone for further documentation of these methods. + # + # TimeWithZone instances implement the same API as Ruby Time instances, so + # that Time and TimeWithZone instances are interchangeable. + # + # t = Time.zone.now # => Sun, 18 May 2008 13:27:25 EDT -04:00 + # t.hour # => 13 + # t.dst? # => true + # t.utc_offset # => -14400 + # t.zone # => "EDT" + # t.to_s(:rfc822) # => "Sun, 18 May 2008 13:27:25 -0400" + # t + 1.day # => Mon, 19 May 2008 13:27:25 EDT -04:00 + # t.beginning_of_year # => Tue, 01 Jan 2008 00:00:00 EST -05:00 + # t > Time.utc(1999) # => true + # t.is_a?(Time) # => true + # t.is_a?(ActiveSupport::TimeWithZone) # => true + class TimeWithZone + # Report class name as 'Time' to thwart type checking. + def self.name + "Time" + end + + PRECISIONS = Hash.new { |h, n| h[n] = "%FT%T.%#{n}N".freeze } + PRECISIONS[0] = "%FT%T".freeze + + include Comparable, DateAndTime::Compatibility + attr_reader :time_zone + + def initialize(utc_time, time_zone, local_time = nil, period = nil) + @utc = utc_time ? transfer_time_values_to_utc_constructor(utc_time) : nil + @time_zone, @time = time_zone, local_time + @period = @utc ? period : get_period_and_ensure_valid_local_time(period) + end + + # Returns a Time instance that represents the time in +time_zone+. + def time + @time ||= period.to_local(@utc) + end + + # Returns a Time instance of the simultaneous time in the UTC timezone. + def utc + @utc ||= period.to_utc(@time) + end + alias_method :comparable_time, :utc + alias_method :getgm, :utc + alias_method :getutc, :utc + alias_method :gmtime, :utc + + # Returns the underlying TZInfo::TimezonePeriod. + def period + @period ||= time_zone.period_for_utc(@utc) + end + + # Returns the simultaneous time in Time.zone, or the specified zone. + def in_time_zone(new_zone = ::Time.zone) + return self if time_zone == new_zone + utc.in_time_zone(new_zone) + end + + # Returns a Time instance of the simultaneous time in the system timezone. + def localtime(utc_offset = nil) + utc.getlocal(utc_offset) + end + alias_method :getlocal, :localtime + + # Returns true if the current time is within Daylight Savings Time for the + # specified time zone. + # + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # Time.zone.parse("2012-5-30").dst? # => true + # Time.zone.parse("2012-11-30").dst? # => false + def dst? + period.dst? + end + alias_method :isdst, :dst? + + # Returns true if the current time zone is set to UTC. + # + # Time.zone = 'UTC' # => 'UTC' + # Time.zone.now.utc? # => true + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # Time.zone.now.utc? # => false + def utc? + period.offset.abbreviation == :UTC || period.offset.abbreviation == :UCT + end + alias_method :gmt?, :utc? + + # Returns the offset from current time to UTC time in seconds. + def utc_offset + period.utc_total_offset + end + alias_method :gmt_offset, :utc_offset + alias_method :gmtoff, :utc_offset + + # Returns a formatted string of the offset from UTC, or an alternative + # string if the time zone is already UTC. + # + # Time.zone = 'Eastern Time (US & Canada)' # => "Eastern Time (US & Canada)" + # Time.zone.now.formatted_offset(true) # => "-05:00" + # Time.zone.now.formatted_offset(false) # => "-0500" + # Time.zone = 'UTC' # => "UTC" + # Time.zone.now.formatted_offset(true, "0") # => "0" + def formatted_offset(colon = true, alternate_utc_string = nil) + utc? && alternate_utc_string || TimeZone.seconds_to_utc_offset(utc_offset, colon) + end + + # Returns the time zone abbreviation. + # + # Time.zone = 'Eastern Time (US & Canada)' # => "Eastern Time (US & Canada)" + # Time.zone.now.zone # => "EST" + def zone + period.zone_identifier.to_s + end + + # Returns a string of the object's date, time, zone, and offset from UTC. + # + # Time.zone.now.inspect # => "Thu, 04 Dec 2014 11:00:25 EST -05:00" + def inspect + "#{time.strftime('%a, %d %b %Y %H:%M:%S')} #{zone} #{formatted_offset}" + end + + # Returns a string of the object's date and time in the ISO 8601 standard + # format. + # + # Time.zone.now.xmlschema # => "2014-12-04T11:02:37-05:00" + def xmlschema(fraction_digits = 0) + "#{time.strftime(PRECISIONS[fraction_digits.to_i])}#{formatted_offset(true, 'Z'.freeze)}" + end + alias_method :iso8601, :xmlschema + alias_method :rfc3339, :xmlschema + + # Coerces time to a string for JSON encoding. The default format is ISO 8601. + # You can get %Y/%m/%d %H:%M:%S +offset style by setting + # ActiveSupport::JSON::Encoding.use_standard_json_time_format + # to +false+. + # + # # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = true + # Time.utc(2005,2,1,15,15,10).in_time_zone("Hawaii").to_json + # # => "2005-02-01T05:15:10.000-10:00" + # + # # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = false + # Time.utc(2005,2,1,15,15,10).in_time_zone("Hawaii").to_json + # # => "2005/02/01 05:15:10 -1000" + def as_json(options = nil) + if ActiveSupport::JSON::Encoding.use_standard_json_time_format + xmlschema(ActiveSupport::JSON::Encoding.time_precision) + else + %(#{time.strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}) + end + end + + def init_with(coder) #:nodoc: + initialize(coder["utc"], coder["zone"], coder["time"]) + end + + def encode_with(coder) #:nodoc: + coder.tag = "!ruby/object:ActiveSupport::TimeWithZone" + coder.map = { "utc" => utc, "zone" => time_zone, "time" => time } + end + + # Returns a string of the object's date and time in the format used by + # HTTP requests. + # + # Time.zone.now.httpdate # => "Tue, 01 Jan 2013 04:39:43 GMT" + def httpdate + utc.httpdate + end + + # Returns a string of the object's date and time in the RFC 2822 standard + # format. + # + # Time.zone.now.rfc2822 # => "Tue, 01 Jan 2013 04:51:39 +0000" + def rfc2822 + to_s(:rfc822) + end + alias_method :rfc822, :rfc2822 + + # Returns a string of the object's date and time. + # Accepts an optional format: + # * :default - default value, mimics Ruby Time#to_s format. + # * :db - format outputs time in UTC :db time. See Time#to_formatted_s(:db). + # * Any key in Time::DATE_FORMATS can be used. See active_support/core_ext/time/conversions.rb. + def to_s(format = :default) + if format == :db + utc.to_s(format) + elsif formatter = ::Time::DATE_FORMATS[format] + formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter) + else + "#{time.strftime("%Y-%m-%d %H:%M:%S")} #{formatted_offset(false, 'UTC')}" # mimicking Ruby Time#to_s format + end + end + alias_method :to_formatted_s, :to_s + + # Replaces %Z directive with +zone before passing to Time#strftime, + # so that zone information is correct. + def strftime(format) + format = format.gsub(/((?:\A|[^%])(?:%%)*)%Z/, "\\1#{zone}") + getlocal(utc_offset).strftime(format) + end + + # Use the time in UTC for comparisons. + def <=>(other) + utc <=> other + end + + # Returns true if the current object's time is within the specified + # +min+ and +max+ time. + def between?(min, max) + utc.between?(min, max) + end + + # Returns true if the current object's time is in the past. + def past? + utc.past? + end + + # Returns true if the current object's time falls within + # the current day. + def today? + time.today? + end + + # Returns true if the current object's time is in the future. + def future? + utc.future? + end + + # Returns +true+ if +other+ is equal to current object. + def eql?(other) + other.eql?(utc) + end + + def hash + utc.hash + end + + # Adds an interval of time to the current object's time and returns that + # value as a new TimeWithZone object. + # + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # now = Time.zone.now # => Sun, 02 Nov 2014 01:26:28 EDT -04:00 + # now + 1000 # => Sun, 02 Nov 2014 01:43:08 EDT -04:00 + # + # If we're adding a Duration of variable length (i.e., years, months, days), + # move forward from #time, otherwise move forward from #utc, for accuracy + # when moving across DST boundaries. + # + # For instance, a time + 24.hours will advance exactly 24 hours, while a + # time + 1.day will advance 23-25 hours, depending on the day. + # + # now + 24.hours # => Mon, 03 Nov 2014 00:26:28 EST -05:00 + # now + 1.day # => Mon, 03 Nov 2014 01:26:28 EST -05:00 + def +(other) + if duration_of_variable_length?(other) + method_missing(:+, other) + else + result = utc.acts_like?(:date) ? utc.since(other) : utc + other rescue utc.since(other) + result.in_time_zone(time_zone) + end + end + alias_method :since, :+ + alias_method :in, :+ + + # Returns a new TimeWithZone object that represents the difference between + # the current object's time and the +other+ time. + # + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # now = Time.zone.now # => Mon, 03 Nov 2014 00:26:28 EST -05:00 + # now - 1000 # => Mon, 03 Nov 2014 00:09:48 EST -05:00 + # + # If subtracting a Duration of variable length (i.e., years, months, days), + # move backward from #time, otherwise move backward from #utc, for accuracy + # when moving across DST boundaries. + # + # For instance, a time - 24.hours will go subtract exactly 24 hours, while a + # time - 1.day will subtract 23-25 hours, depending on the day. + # + # now - 24.hours # => Sun, 02 Nov 2014 01:26:28 EDT -04:00 + # now - 1.day # => Sun, 02 Nov 2014 00:26:28 EDT -04:00 + def -(other) + if other.acts_like?(:time) + to_time - other.to_time + elsif duration_of_variable_length?(other) + method_missing(:-, other) + else + result = utc.acts_like?(:date) ? utc.ago(other) : utc - other rescue utc.ago(other) + result.in_time_zone(time_zone) + end + end + + # Subtracts an interval of time from the current object's time and returns + # the result as a new TimeWithZone object. + # + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # now = Time.zone.now # => Mon, 03 Nov 2014 00:26:28 EST -05:00 + # now.ago(1000) # => Mon, 03 Nov 2014 00:09:48 EST -05:00 + # + # If we're subtracting a Duration of variable length (i.e., years, months, + # days), move backward from #time, otherwise move backward from #utc, for + # accuracy when moving across DST boundaries. + # + # For instance, time.ago(24.hours) will move back exactly 24 hours, + # while time.ago(1.day) will move back 23-25 hours, depending on + # the day. + # + # now.ago(24.hours) # => Sun, 02 Nov 2014 01:26:28 EDT -04:00 + # now.ago(1.day) # => Sun, 02 Nov 2014 00:26:28 EDT -04:00 + def ago(other) + since(-other) + end + + # Returns a new +ActiveSupport::TimeWithZone+ where one or more of the elements have + # been changed according to the +options+ parameter. The time options (:hour, + # :min, :sec, :usec, :nsec) reset cascadingly, + # so if only the hour is passed, then minute, sec, usec and nsec is set to 0. If the + # hour and minute is passed, then sec, usec and nsec is set to 0. The +options+ + # parameter takes a hash with any of these keys: :year, :month, + # :day, :hour, :min, :sec, :usec, + # :nsec, :offset, :zone. Pass either :usec + # or :nsec, not both. Similarly, pass either :zone or + # :offset, not both. + # + # t = Time.zone.now # => Fri, 14 Apr 2017 11:45:15 EST -05:00 + # t.change(year: 2020) # => Tue, 14 Apr 2020 11:45:15 EST -05:00 + # t.change(hour: 12) # => Fri, 14 Apr 2017 12:00:00 EST -05:00 + # t.change(min: 30) # => Fri, 14 Apr 2017 11:30:00 EST -05:00 + # t.change(offset: "-10:00") # => Fri, 14 Apr 2017 11:45:15 HST -10:00 + # t.change(zone: "Hawaii") # => Fri, 14 Apr 2017 11:45:15 HST -10:00 + def change(options) + if options[:zone] && options[:offset] + raise ArgumentError, "Can't change both :offset and :zone at the same time: #{options.inspect}" + end + + new_time = time.change(options) + + if options[:zone] + new_zone = ::Time.find_zone(options[:zone]) + elsif options[:offset] + new_zone = ::Time.find_zone(new_time.utc_offset) + end + + new_zone ||= time_zone + periods = new_zone.periods_for_local(new_time) + + self.class.new(nil, new_zone, new_time, periods.include?(period) ? period : nil) + end + + # Uses Date to provide precise Time calculations for years, months, and days + # according to the proleptic Gregorian calendar. The result is returned as a + # new TimeWithZone object. + # + # The +options+ parameter takes a hash with any of these keys: + # :years, :months, :weeks, :days, + # :hours, :minutes, :seconds. + # + # If advancing by a value of variable length (i.e., years, weeks, months, + # days), move forward from #time, otherwise move forward from #utc, for + # accuracy when moving across DST boundaries. + # + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # now = Time.zone.now # => Sun, 02 Nov 2014 01:26:28 EDT -04:00 + # now.advance(seconds: 1) # => Sun, 02 Nov 2014 01:26:29 EDT -04:00 + # now.advance(minutes: 1) # => Sun, 02 Nov 2014 01:27:28 EDT -04:00 + # now.advance(hours: 1) # => Sun, 02 Nov 2014 01:26:28 EST -05:00 + # now.advance(days: 1) # => Mon, 03 Nov 2014 01:26:28 EST -05:00 + # now.advance(weeks: 1) # => Sun, 09 Nov 2014 01:26:28 EST -05:00 + # now.advance(months: 1) # => Tue, 02 Dec 2014 01:26:28 EST -05:00 + # now.advance(years: 1) # => Mon, 02 Nov 2015 01:26:28 EST -05:00 + def advance(options) + # If we're advancing a value of variable length (i.e., years, weeks, months, days), advance from #time, + # otherwise advance from #utc, for accuracy when moving across DST boundaries + if options.values_at(:years, :weeks, :months, :days).any? + method_missing(:advance, options) + else + utc.advance(options).in_time_zone(time_zone) + end + end + + %w(year mon month day mday wday yday hour min sec usec nsec to_date).each do |method_name| + class_eval <<-EOV, __FILE__, __LINE__ + 1 + def #{method_name} # def month + time.#{method_name} # time.month + end # end + EOV + end + + # Returns Array of parts of Time in sequence of + # [seconds, minutes, hours, day, month, year, weekday, yearday, dst?, zone]. + # + # now = Time.zone.now # => Tue, 18 Aug 2015 02:29:27 UTC +00:00 + # now.to_a # => [27, 29, 2, 18, 8, 2015, 2, 230, false, "UTC"] + def to_a + [time.sec, time.min, time.hour, time.day, time.mon, time.year, time.wday, time.yday, dst?, zone] + end + + # Returns the object's date and time as a floating point number of seconds + # since the Epoch (January 1, 1970 00:00 UTC). + # + # Time.zone.now.to_f # => 1417709320.285418 + def to_f + utc.to_f + end + + # Returns the object's date and time as an integer number of seconds + # since the Epoch (January 1, 1970 00:00 UTC). + # + # Time.zone.now.to_i # => 1417709320 + def to_i + utc.to_i + end + alias_method :tv_sec, :to_i + + # Returns the object's date and time as a rational number of seconds + # since the Epoch (January 1, 1970 00:00 UTC). + # + # Time.zone.now.to_r # => (708854548642709/500000) + def to_r + utc.to_r + end + + # Returns an instance of DateTime with the timezone's UTC offset + # + # Time.zone.now.to_datetime # => Tue, 18 Aug 2015 02:32:20 +0000 + # Time.current.in_time_zone('Hawaii').to_datetime # => Mon, 17 Aug 2015 16:32:20 -1000 + def to_datetime + @to_datetime ||= utc.to_datetime.new_offset(Rational(utc_offset, 86_400)) + end + + # Returns an instance of +Time+, either with the same UTC offset + # as +self+ or in the local system timezone depending on the setting + # of +ActiveSupport.to_time_preserves_timezone+. + def to_time + if preserve_timezone + @to_time_with_instance_offset ||= getlocal(utc_offset) + else + @to_time_with_system_offset ||= getlocal + end + end + + # So that +self+ acts_like?(:time). + def acts_like_time? + true + end + + # Say we're a Time to thwart type checking. + def is_a?(klass) + klass == ::Time || super + end + alias_method :kind_of?, :is_a? + + # An instance of ActiveSupport::TimeWithZone is never blank + def blank? + false + end + + def freeze + # preload instance variables before freezing + period; utc; time; to_datetime; to_time + super + end + + def marshal_dump + [utc, time_zone.name, time] + end + + def marshal_load(variables) + initialize(variables[0].utc, ::Time.find_zone(variables[1]), variables[2].utc) + end + + # respond_to_missing? is not called in some cases, such as when type conversion is + # performed with Kernel#String + def respond_to?(sym, include_priv = false) + # ensure that we're not going to throw and rescue from NoMethodError in method_missing which is slow + return false if sym.to_sym == :to_str + super + end + + # Ensure proxy class responds to all methods that underlying time instance + # responds to. + def respond_to_missing?(sym, include_priv) + return false if sym.to_sym == :acts_like_date? + time.respond_to?(sym, include_priv) + end + + # Send the missing method to +time+ instance, and wrap result in a new + # TimeWithZone with the existing +time_zone+. + def method_missing(sym, *args, &block) + wrap_with_time_zone time.__send__(sym, *args, &block) + rescue NoMethodError => e + raise e, e.message.sub(time.inspect, inspect), e.backtrace + end + + private + def get_period_and_ensure_valid_local_time(period) + # we don't want a Time.local instance enforcing its own DST rules as well, + # so transfer time values to a utc constructor if necessary + @time = transfer_time_values_to_utc_constructor(@time) unless @time.utc? + begin + period || @time_zone.period_for_local(@time) + rescue ::TZInfo::PeriodNotFound + # time is in the "spring forward" hour gap, so we're moving the time forward one hour and trying again + @time += 1.hour + retry + end + end + + def transfer_time_values_to_utc_constructor(time) + # avoid creating another Time object if possible + return time if time.instance_of?(::Time) && time.utc? + ::Time.utc(time.year, time.month, time.day, time.hour, time.min, time.sec + time.subsec) + end + + def duration_of_variable_length?(obj) + ActiveSupport::Duration === obj && obj.parts.any? { |p| [:years, :months, :weeks, :days].include?(p[0]) } + end + + def wrap_with_time_zone(time) + if time.acts_like?(:time) + periods = time_zone.periods_for_local(time) + self.class.new(nil, time_zone, time, periods.include?(period) ? period : nil) + elsif time.is_a?(Range) + wrap_with_time_zone(time.begin)..wrap_with_time_zone(time.end) + else + time + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/values/time_zone.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/values/time_zone.rb new file mode 100644 index 00000000..90501bc5 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/values/time_zone.rb @@ -0,0 +1,565 @@ +# frozen_string_literal: true + +require "tzinfo" +require "concurrent/map" +require "active_support/core_ext/object/blank" + +module ActiveSupport + # The TimeZone class serves as a wrapper around TZInfo::Timezone instances. + # It allows us to do the following: + # + # * Limit the set of zones provided by TZInfo to a meaningful subset of 134 + # zones. + # * Retrieve and display zones with a friendlier name + # (e.g., "Eastern Time (US & Canada)" instead of "America/New_York"). + # * Lazily load TZInfo::Timezone instances only when they're needed. + # * Create ActiveSupport::TimeWithZone instances via TimeZone's +local+, + # +parse+, +at+ and +now+ methods. + # + # If you set config.time_zone in the Rails Application, you can + # access this TimeZone object via Time.zone: + # + # # application.rb: + # class Application < Rails::Application + # config.time_zone = 'Eastern Time (US & Canada)' + # end + # + # Time.zone # => # + # Time.zone.name # => "Eastern Time (US & Canada)" + # Time.zone.now # => Sun, 18 May 2008 14:30:44 EDT -04:00 + class TimeZone + # Keys are Rails TimeZone names, values are TZInfo identifiers. + MAPPING = { + "International Date Line West" => "Etc/GMT+12", + "Midway Island" => "Pacific/Midway", + "American Samoa" => "Pacific/Pago_Pago", + "Hawaii" => "Pacific/Honolulu", + "Alaska" => "America/Juneau", + "Pacific Time (US & Canada)" => "America/Los_Angeles", + "Tijuana" => "America/Tijuana", + "Mountain Time (US & Canada)" => "America/Denver", + "Arizona" => "America/Phoenix", + "Chihuahua" => "America/Chihuahua", + "Mazatlan" => "America/Mazatlan", + "Central Time (US & Canada)" => "America/Chicago", + "Saskatchewan" => "America/Regina", + "Guadalajara" => "America/Mexico_City", + "Mexico City" => "America/Mexico_City", + "Monterrey" => "America/Monterrey", + "Central America" => "America/Guatemala", + "Eastern Time (US & Canada)" => "America/New_York", + "Indiana (East)" => "America/Indiana/Indianapolis", + "Bogota" => "America/Bogota", + "Lima" => "America/Lima", + "Quito" => "America/Lima", + "Atlantic Time (Canada)" => "America/Halifax", + "Caracas" => "America/Caracas", + "La Paz" => "America/La_Paz", + "Santiago" => "America/Santiago", + "Newfoundland" => "America/St_Johns", + "Brasilia" => "America/Sao_Paulo", + "Buenos Aires" => "America/Argentina/Buenos_Aires", + "Montevideo" => "America/Montevideo", + "Georgetown" => "America/Guyana", + "Puerto Rico" => "America/Puerto_Rico", + "Greenland" => "America/Godthab", + "Mid-Atlantic" => "Atlantic/South_Georgia", + "Azores" => "Atlantic/Azores", + "Cape Verde Is." => "Atlantic/Cape_Verde", + "Dublin" => "Europe/Dublin", + "Edinburgh" => "Europe/London", + "Lisbon" => "Europe/Lisbon", + "London" => "Europe/London", + "Casablanca" => "Africa/Casablanca", + "Monrovia" => "Africa/Monrovia", + "UTC" => "Etc/UTC", + "Belgrade" => "Europe/Belgrade", + "Bratislava" => "Europe/Bratislava", + "Budapest" => "Europe/Budapest", + "Ljubljana" => "Europe/Ljubljana", + "Prague" => "Europe/Prague", + "Sarajevo" => "Europe/Sarajevo", + "Skopje" => "Europe/Skopje", + "Warsaw" => "Europe/Warsaw", + "Zagreb" => "Europe/Zagreb", + "Brussels" => "Europe/Brussels", + "Copenhagen" => "Europe/Copenhagen", + "Madrid" => "Europe/Madrid", + "Paris" => "Europe/Paris", + "Amsterdam" => "Europe/Amsterdam", + "Berlin" => "Europe/Berlin", + "Bern" => "Europe/Zurich", + "Zurich" => "Europe/Zurich", + "Rome" => "Europe/Rome", + "Stockholm" => "Europe/Stockholm", + "Vienna" => "Europe/Vienna", + "West Central Africa" => "Africa/Algiers", + "Bucharest" => "Europe/Bucharest", + "Cairo" => "Africa/Cairo", + "Helsinki" => "Europe/Helsinki", + "Kyiv" => "Europe/Kiev", + "Riga" => "Europe/Riga", + "Sofia" => "Europe/Sofia", + "Tallinn" => "Europe/Tallinn", + "Vilnius" => "Europe/Vilnius", + "Athens" => "Europe/Athens", + "Istanbul" => "Europe/Istanbul", + "Minsk" => "Europe/Minsk", + "Jerusalem" => "Asia/Jerusalem", + "Harare" => "Africa/Harare", + "Pretoria" => "Africa/Johannesburg", + "Kaliningrad" => "Europe/Kaliningrad", + "Moscow" => "Europe/Moscow", + "St. Petersburg" => "Europe/Moscow", + "Volgograd" => "Europe/Volgograd", + "Samara" => "Europe/Samara", + "Kuwait" => "Asia/Kuwait", + "Riyadh" => "Asia/Riyadh", + "Nairobi" => "Africa/Nairobi", + "Baghdad" => "Asia/Baghdad", + "Tehran" => "Asia/Tehran", + "Abu Dhabi" => "Asia/Muscat", + "Muscat" => "Asia/Muscat", + "Baku" => "Asia/Baku", + "Tbilisi" => "Asia/Tbilisi", + "Yerevan" => "Asia/Yerevan", + "Kabul" => "Asia/Kabul", + "Ekaterinburg" => "Asia/Yekaterinburg", + "Islamabad" => "Asia/Karachi", + "Karachi" => "Asia/Karachi", + "Tashkent" => "Asia/Tashkent", + "Chennai" => "Asia/Kolkata", + "Kolkata" => "Asia/Kolkata", + "Mumbai" => "Asia/Kolkata", + "New Delhi" => "Asia/Kolkata", + "Kathmandu" => "Asia/Kathmandu", + "Astana" => "Asia/Dhaka", + "Dhaka" => "Asia/Dhaka", + "Sri Jayawardenepura" => "Asia/Colombo", + "Almaty" => "Asia/Almaty", + "Novosibirsk" => "Asia/Novosibirsk", + "Rangoon" => "Asia/Rangoon", + "Bangkok" => "Asia/Bangkok", + "Hanoi" => "Asia/Bangkok", + "Jakarta" => "Asia/Jakarta", + "Krasnoyarsk" => "Asia/Krasnoyarsk", + "Beijing" => "Asia/Shanghai", + "Chongqing" => "Asia/Chongqing", + "Hong Kong" => "Asia/Hong_Kong", + "Urumqi" => "Asia/Urumqi", + "Kuala Lumpur" => "Asia/Kuala_Lumpur", + "Singapore" => "Asia/Singapore", + "Taipei" => "Asia/Taipei", + "Perth" => "Australia/Perth", + "Irkutsk" => "Asia/Irkutsk", + "Ulaanbaatar" => "Asia/Ulaanbaatar", + "Seoul" => "Asia/Seoul", + "Osaka" => "Asia/Tokyo", + "Sapporo" => "Asia/Tokyo", + "Tokyo" => "Asia/Tokyo", + "Yakutsk" => "Asia/Yakutsk", + "Darwin" => "Australia/Darwin", + "Adelaide" => "Australia/Adelaide", + "Canberra" => "Australia/Melbourne", + "Melbourne" => "Australia/Melbourne", + "Sydney" => "Australia/Sydney", + "Brisbane" => "Australia/Brisbane", + "Hobart" => "Australia/Hobart", + "Vladivostok" => "Asia/Vladivostok", + "Guam" => "Pacific/Guam", + "Port Moresby" => "Pacific/Port_Moresby", + "Magadan" => "Asia/Magadan", + "Srednekolymsk" => "Asia/Srednekolymsk", + "Solomon Is." => "Pacific/Guadalcanal", + "New Caledonia" => "Pacific/Noumea", + "Fiji" => "Pacific/Fiji", + "Kamchatka" => "Asia/Kamchatka", + "Marshall Is." => "Pacific/Majuro", + "Auckland" => "Pacific/Auckland", + "Wellington" => "Pacific/Auckland", + "Nuku'alofa" => "Pacific/Tongatapu", + "Tokelau Is." => "Pacific/Fakaofo", + "Chatham Is." => "Pacific/Chatham", + "Samoa" => "Pacific/Apia" + } + + UTC_OFFSET_WITH_COLON = "%s%02d:%02d" + UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.tr(":", "") + + @lazy_zones_map = Concurrent::Map.new + @country_zones = Concurrent::Map.new + + class << self + # Assumes self represents an offset from UTC in seconds (as returned from + # Time#utc_offset) and turns this into an +HH:MM formatted string. + # + # ActiveSupport::TimeZone.seconds_to_utc_offset(-21_600) # => "-06:00" + def seconds_to_utc_offset(seconds, colon = true) + format = colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON + sign = (seconds < 0 ? "-" : "+") + hours = seconds.abs / 3600 + minutes = (seconds.abs % 3600) / 60 + format % [sign, hours, minutes] + end + + def find_tzinfo(name) + TZInfo::Timezone.new(MAPPING[name] || name) + end + + alias_method :create, :new + + # Returns a TimeZone instance with the given name, or +nil+ if no + # such TimeZone instance exists. (This exists to support the use of + # this class with the +composed_of+ macro.) + def new(name) + self[name] + end + + # Returns an array of all TimeZone objects. There are multiple + # TimeZone objects per time zone, in many cases, to make it easier + # for users to find their own time zone. + def all + @zones ||= zones_map.values.sort + end + + # Locate a specific time zone object. If the argument is a string, it + # is interpreted to mean the name of the timezone to locate. If it is a + # numeric value it is either the hour offset, or the second offset, of the + # timezone to find. (The first one with that offset will be returned.) + # Returns +nil+ if no such time zone is known to the system. + def [](arg) + case arg + when String + begin + @lazy_zones_map[arg] ||= create(arg) + rescue TZInfo::InvalidTimezoneIdentifier + nil + end + when Numeric, ActiveSupport::Duration + arg *= 3600 if arg.abs <= 13 + all.find { |z| z.utc_offset == arg.to_i } + else + raise ArgumentError, "invalid argument to TimeZone[]: #{arg.inspect}" + end + end + + # A convenience method for returning a collection of TimeZone objects + # for time zones in the USA. + def us_zones + country_zones(:us) + end + + # A convenience method for returning a collection of TimeZone objects + # for time zones in the country specified by its ISO 3166-1 Alpha2 code. + def country_zones(country_code) + code = country_code.to_s.upcase + @country_zones[code] ||= load_country_zones(code) + end + + def clear #:nodoc: + @lazy_zones_map = Concurrent::Map.new + @country_zones = Concurrent::Map.new + @zones = nil + @zones_map = nil + end + + private + def load_country_zones(code) + country = TZInfo::Country.get(code) + country.zone_identifiers.map do |tz_id| + if MAPPING.value?(tz_id) + MAPPING.inject([]) do |memo, (key, value)| + memo << self[key] if value == tz_id + memo + end + else + create(tz_id, nil, TZInfo::Timezone.new(tz_id)) + end + end.flatten(1).sort! + end + + def zones_map + @zones_map ||= MAPPING.each_with_object({}) do |(name, _), zones| + timezone = self[name] + zones[name] = timezone if timezone + end + end + end + + include Comparable + attr_reader :name + attr_reader :tzinfo + + # Create a new TimeZone object with the given name and offset. The + # offset is the number of seconds that this time zone is offset from UTC + # (GMT). Seconds were chosen as the offset unit because that is the unit + # that Ruby uses to represent time zone offsets (see Time#utc_offset). + def initialize(name, utc_offset = nil, tzinfo = nil) + @name = name + @utc_offset = utc_offset + @tzinfo = tzinfo || TimeZone.find_tzinfo(name) + end + + # Returns the offset of this time zone from UTC in seconds. + def utc_offset + if @utc_offset + @utc_offset + else + tzinfo.current_period.utc_offset if tzinfo && tzinfo.current_period + end + end + + # Returns a formatted string of the offset from UTC, or an alternative + # string if the time zone is already UTC. + # + # zone = ActiveSupport::TimeZone['Central Time (US & Canada)'] + # zone.formatted_offset # => "-06:00" + # zone.formatted_offset(false) # => "-0600" + def formatted_offset(colon = true, alternate_utc_string = nil) + utc_offset == 0 && alternate_utc_string || self.class.seconds_to_utc_offset(utc_offset, colon) + end + + # Compare this time zone to the parameter. The two are compared first on + # their offsets, and then by name. + def <=>(zone) + return unless zone.respond_to? :utc_offset + result = (utc_offset <=> zone.utc_offset) + result = (name <=> zone.name) if result == 0 + result + end + + # Compare #name and TZInfo identifier to a supplied regexp, returning +true+ + # if a match is found. + def =~(re) + re === name || re === MAPPING[name] + end + + # Returns a textual representation of this time zone. + def to_s + "(GMT#{formatted_offset}) #{name}" + end + + # Method for creating new ActiveSupport::TimeWithZone instance in time zone + # of +self+ from given values. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.local(2007, 2, 1, 15, 30, 45) # => Thu, 01 Feb 2007 15:30:45 HST -10:00 + def local(*args) + time = Time.utc(*args) + ActiveSupport::TimeWithZone.new(nil, self, time) + end + + # Method for creating new ActiveSupport::TimeWithZone instance in time zone + # of +self+ from number of seconds since the Unix epoch. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.utc(2000).to_f # => 946684800.0 + # Time.zone.at(946684800.0) # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + def at(secs) + Time.at(secs).utc.in_time_zone(self) + end + + # Method for creating new ActiveSupport::TimeWithZone instance in time zone + # of +self+ from an ISO 8601 string. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.iso8601('1999-12-31T14:00:00') # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # + # If the time components are missing then they will be set to zero. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.iso8601('1999-12-31') # => Fri, 31 Dec 1999 00:00:00 HST -10:00 + # + # If the string is invalid then an +ArgumentError+ will be raised unlike +parse+ + # which usually returns +nil+ when given an invalid date string. + def iso8601(str) + parts = Date._iso8601(str) + + raise ArgumentError, "invalid date" if parts.empty? + + time = Time.new( + parts.fetch(:year), + parts.fetch(:mon), + parts.fetch(:mday), + parts.fetch(:hour, 0), + parts.fetch(:min, 0), + parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0), + parts.fetch(:offset, 0) + ) + + if parts[:offset] + TimeWithZone.new(time.utc, self) + else + TimeWithZone.new(nil, self, time) + end + end + + # Method for creating new ActiveSupport::TimeWithZone instance in time zone + # of +self+ from parsed string. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.parse('1999-12-31 14:00:00') # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # + # If upper components are missing from the string, they are supplied from + # TimeZone#now: + # + # Time.zone.now # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # Time.zone.parse('22:30:00') # => Fri, 31 Dec 1999 22:30:00 HST -10:00 + # + # However, if the date component is not provided, but any other upper + # components are supplied, then the day of the month defaults to 1: + # + # Time.zone.parse('Mar 2000') # => Wed, 01 Mar 2000 00:00:00 HST -10:00 + # + # If the string is invalid then an +ArgumentError+ could be raised. + def parse(str, now = now()) + parts_to_time(Date._parse(str, false), now) + end + + # Method for creating new ActiveSupport::TimeWithZone instance in time zone + # of +self+ from an RFC 3339 string. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.rfc3339('2000-01-01T00:00:00Z') # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # + # If the time or zone components are missing then an +ArgumentError+ will + # be raised. This is much stricter than either +parse+ or +iso8601+ which + # allow for missing components. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.rfc3339('1999-12-31') # => ArgumentError: invalid date + def rfc3339(str) + parts = Date._rfc3339(str) + + raise ArgumentError, "invalid date" if parts.empty? + + time = Time.new( + parts.fetch(:year), + parts.fetch(:mon), + parts.fetch(:mday), + parts.fetch(:hour), + parts.fetch(:min), + parts.fetch(:sec) + parts.fetch(:sec_fraction, 0), + parts.fetch(:offset) + ) + + TimeWithZone.new(time.utc, self) + end + + # Parses +str+ according to +format+ and returns an ActiveSupport::TimeWithZone. + # + # Assumes that +str+ is a time in the time zone +self+, + # unless +format+ includes an explicit time zone. + # (This is the same behavior as +parse+.) + # In either case, the returned TimeWithZone has the timezone of +self+. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.strptime('1999-12-31 14:00:00', '%Y-%m-%d %H:%M:%S') # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # + # If upper components are missing from the string, they are supplied from + # TimeZone#now: + # + # Time.zone.now # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # Time.zone.strptime('22:30:00', '%H:%M:%S') # => Fri, 31 Dec 1999 22:30:00 HST -10:00 + # + # However, if the date component is not provided, but any other upper + # components are supplied, then the day of the month defaults to 1: + # + # Time.zone.strptime('Mar 2000', '%b %Y') # => Wed, 01 Mar 2000 00:00:00 HST -10:00 + def strptime(str, format, now = now()) + parts_to_time(DateTime._strptime(str, format), now) + end + + # Returns an ActiveSupport::TimeWithZone instance representing the current + # time in the time zone represented by +self+. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.now # => Wed, 23 Jan 2008 20:24:27 HST -10:00 + def now + time_now.utc.in_time_zone(self) + end + + # Returns the current date in this time zone. + def today + tzinfo.now.to_date + end + + # Returns the next date in this time zone. + def tomorrow + today + 1 + end + + # Returns the previous date in this time zone. + def yesterday + today - 1 + end + + # Adjust the given time to the simultaneous time in the time zone + # represented by +self+. Returns a Time.utc() instance -- if you want an + # ActiveSupport::TimeWithZone instance, use Time#in_time_zone() instead. + def utc_to_local(time) + tzinfo.utc_to_local(time) + end + + # Adjust the given time to the simultaneous time in UTC. Returns a + # Time.utc() instance. + def local_to_utc(time, dst = true) + tzinfo.local_to_utc(time, dst) + end + + # Available so that TimeZone instances respond like TZInfo::Timezone + # instances. + def period_for_utc(time) + tzinfo.period_for_utc(time) + end + + # Available so that TimeZone instances respond like TZInfo::Timezone + # instances. + def period_for_local(time, dst = true) + tzinfo.period_for_local(time, dst) { |periods| periods.last } + end + + def periods_for_local(time) #:nodoc: + tzinfo.periods_for_local(time) + end + + def init_with(coder) #:nodoc: + initialize(coder["name"]) + end + + def encode_with(coder) #:nodoc: + coder.tag = "!ruby/object:#{self.class}" + coder.map = { "name" => tzinfo.name } + end + + private + def parts_to_time(parts, now) + raise ArgumentError, "invalid date" if parts.nil? + return if parts.empty? + + if parts[:seconds] + time = Time.at(parts[:seconds]) + else + time = Time.new( + parts.fetch(:year, now.year), + parts.fetch(:mon, now.month), + parts.fetch(:mday, parts[:year] || parts[:mon] ? 1 : now.day), + parts.fetch(:hour, 0), + parts.fetch(:min, 0), + parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0), + parts.fetch(:offset, 0) + ) + end + + if parts[:offset] || parts[:seconds] + TimeWithZone.new(time.utc, self) + else + TimeWithZone.new(nil, self, time) + end + end + + def time_now + Time.now + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/values/unicode_tables.dat b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/values/unicode_tables.dat new file mode 100644 index 00000000..f7d9c48b Binary files /dev/null and b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/values/unicode_tables.dat differ diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/version.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/version.rb new file mode 100644 index 00000000..928838c8 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/version.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require_relative "gem_version" + +module ActiveSupport + # Returns the version of the currently loaded ActiveSupport as a Gem::Version + def self.version + gem_version + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/xml_mini.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/xml_mini.rb new file mode 100644 index 00000000..f087e2ce --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/xml_mini.rb @@ -0,0 +1,209 @@ +# frozen_string_literal: true + +require "time" +require "base64" +require "bigdecimal" +require "active_support/core_ext/module/delegation" +require "active_support/core_ext/string/inflections" +require "active_support/core_ext/date_time/calculations" + +module ActiveSupport + # = XmlMini + # + # To use the much faster libxml parser: + # gem 'libxml-ruby', '=0.9.7' + # XmlMini.backend = 'LibXML' + module XmlMini + extend self + + # This module decorates files deserialized using Hash.from_xml with + # the original_filename and content_type methods. + module FileLike #:nodoc: + attr_writer :original_filename, :content_type + + def original_filename + @original_filename || "untitled" + end + + def content_type + @content_type || "application/octet-stream" + end + end + + DEFAULT_ENCODINGS = { + "binary" => "base64" + } unless defined?(DEFAULT_ENCODINGS) + + unless defined?(TYPE_NAMES) + TYPE_NAMES = { + "Symbol" => "symbol", + "Integer" => "integer", + "BigDecimal" => "decimal", + "Float" => "float", + "TrueClass" => "boolean", + "FalseClass" => "boolean", + "Date" => "date", + "DateTime" => "dateTime", + "Time" => "dateTime", + "Array" => "array", + "Hash" => "hash" + } + + # No need to map these on Ruby 2.4+ + TYPE_NAMES["Fixnum"] = "integer" unless 0.class == Integer + TYPE_NAMES["Bignum"] = "integer" unless 0.class == Integer + end + + FORMATTING = { + "symbol" => Proc.new { |symbol| symbol.to_s }, + "date" => Proc.new { |date| date.to_s(:db) }, + "dateTime" => Proc.new { |time| time.xmlschema }, + "binary" => Proc.new { |binary| ::Base64.encode64(binary) }, + "yaml" => Proc.new { |yaml| yaml.to_yaml } + } unless defined?(FORMATTING) + + # TODO use regexp instead of Date.parse + unless defined?(PARSING) + PARSING = { + "symbol" => Proc.new { |symbol| symbol.to_s.to_sym }, + "date" => Proc.new { |date| ::Date.parse(date) }, + "datetime" => Proc.new { |time| Time.xmlschema(time).utc rescue ::DateTime.parse(time).utc }, + "integer" => Proc.new { |integer| integer.to_i }, + "float" => Proc.new { |float| float.to_f }, + "decimal" => Proc.new do |number| + if String === number + begin + BigDecimal(number) + rescue ArgumentError + BigDecimal(number.to_f.to_s) + end + else + BigDecimal(number) + end + end, + "boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.to_s.strip) }, + "string" => Proc.new { |string| string.to_s }, + "yaml" => Proc.new { |yaml| YAML.load(yaml) rescue yaml }, + "base64Binary" => Proc.new { |bin| ::Base64.decode64(bin) }, + "binary" => Proc.new { |bin, entity| _parse_binary(bin, entity) }, + "file" => Proc.new { |file, entity| _parse_file(file, entity) } + } + + PARSING.update( + "double" => PARSING["float"], + "dateTime" => PARSING["datetime"] + ) + end + + attr_accessor :depth + self.depth = 100 + + delegate :parse, to: :backend + + def backend + current_thread_backend || @backend + end + + def backend=(name) + backend = name && cast_backend_name_to_module(name) + self.current_thread_backend = backend if current_thread_backend + @backend = backend + end + + def with_backend(name) + old_backend = current_thread_backend + self.current_thread_backend = name && cast_backend_name_to_module(name) + yield + ensure + self.current_thread_backend = old_backend + end + + def to_tag(key, value, options) + type_name = options.delete(:type) + merged_options = options.merge(root: key, skip_instruct: true) + + if value.is_a?(::Method) || value.is_a?(::Proc) + if value.arity == 1 + value.call(merged_options) + else + value.call(merged_options, key.to_s.singularize) + end + elsif value.respond_to?(:to_xml) + value.to_xml(merged_options) + else + type_name ||= TYPE_NAMES[value.class.name] + type_name ||= value.class.name if value && !value.respond_to?(:to_str) + type_name = type_name.to_s if type_name + type_name = "dateTime" if type_name == "datetime" + + key = rename_key(key.to_s, options) + + attributes = options[:skip_types] || type_name.nil? ? {} : { type: type_name } + attributes[:nil] = true if value.nil? + + encoding = options[:encoding] || DEFAULT_ENCODINGS[type_name] + attributes[:encoding] = encoding if encoding + + formatted_value = FORMATTING[type_name] && !value.nil? ? + FORMATTING[type_name].call(value) : value + + options[:builder].tag!(key, formatted_value, attributes) + end + end + + def rename_key(key, options = {}) + camelize = options[:camelize] + dasherize = !options.has_key?(:dasherize) || options[:dasherize] + if camelize + key = true == camelize ? key.camelize : key.camelize(camelize) + end + key = _dasherize(key) if dasherize + key + end + + private + + def _dasherize(key) + # $2 must be a non-greedy regex for this to work + left, middle, right = /\A(_*)(.*?)(_*)\Z/.match(key.strip)[1, 3] + "#{left}#{middle.tr('_ ', '--')}#{right}" + end + + # TODO: Add support for other encodings + def _parse_binary(bin, entity) + case entity["encoding"] + when "base64" + ::Base64.decode64(bin) + else + bin + end + end + + def _parse_file(file, entity) + f = StringIO.new(::Base64.decode64(file)) + f.extend(FileLike) + f.original_filename = entity["name"] + f.content_type = entity["content_type"] + f + end + + def current_thread_backend + Thread.current[:xml_mini_backend] + end + + def current_thread_backend=(name) + Thread.current[:xml_mini_backend] = name && cast_backend_name_to_module(name) + end + + def cast_backend_name_to_module(name) + if name.is_a?(Module) + name + else + require "active_support/xml_mini/#{name.downcase}" + ActiveSupport.const_get("XmlMini_#{name}") + end + end + end + + XmlMini.backend = "REXML" +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/xml_mini/jdom.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/xml_mini/jdom.rb new file mode 100644 index 00000000..7f94a640 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/xml_mini/jdom.rb @@ -0,0 +1,183 @@ +# frozen_string_literal: true + +raise "JRuby is required to use the JDOM backend for XmlMini" unless RUBY_PLATFORM.include?("java") + +require "jruby" +include Java + +require "active_support/core_ext/object/blank" + +java_import javax.xml.parsers.DocumentBuilder unless defined? DocumentBuilder +java_import javax.xml.parsers.DocumentBuilderFactory unless defined? DocumentBuilderFactory +java_import java.io.StringReader unless defined? StringReader +java_import org.xml.sax.InputSource unless defined? InputSource +java_import org.xml.sax.Attributes unless defined? Attributes +java_import org.w3c.dom.Node unless defined? Node + +module ActiveSupport + module XmlMini_JDOM #:nodoc: + extend self + + CONTENT_KEY = "__content__".freeze + + NODE_TYPE_NAMES = %w{ATTRIBUTE_NODE CDATA_SECTION_NODE COMMENT_NODE DOCUMENT_FRAGMENT_NODE + DOCUMENT_NODE DOCUMENT_TYPE_NODE ELEMENT_NODE ENTITY_NODE ENTITY_REFERENCE_NODE NOTATION_NODE + PROCESSING_INSTRUCTION_NODE TEXT_NODE} + + node_type_map = {} + NODE_TYPE_NAMES.each { |type| node_type_map[Node.send(type)] = type } + + # Parse an XML Document string or IO into a simple hash using Java's jdom. + # data:: + # XML Document string or IO to parse + def parse(data) + if data.respond_to?(:read) + data = data.read + end + + if data.blank? + {} + else + @dbf = DocumentBuilderFactory.new_instance + # secure processing of java xml + # https://archive.is/9xcQQ + @dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false) + @dbf.setFeature("http://xml.org/sax/features/external-general-entities", false) + @dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false) + @dbf.setFeature(javax.xml.XMLConstants::FEATURE_SECURE_PROCESSING, true) + xml_string_reader = StringReader.new(data) + xml_input_source = InputSource.new(xml_string_reader) + doc = @dbf.new_document_builder.parse(xml_input_source) + merge_element!({ CONTENT_KEY => "" }, doc.document_element, XmlMini.depth) + end + end + + private + + # Convert an XML element and merge into the hash + # + # hash:: + # Hash to merge the converted element into. + # element:: + # XML element to merge into hash + def merge_element!(hash, element, depth) + raise "Document too deep!" if depth == 0 + delete_empty(hash) + merge!(hash, element.tag_name, collapse(element, depth)) + end + + def delete_empty(hash) + hash.delete(CONTENT_KEY) if hash[CONTENT_KEY] == "" + end + + # Actually converts an XML document element into a data structure. + # + # element:: + # The document element to be collapsed. + def collapse(element, depth) + hash = get_attributes(element) + + child_nodes = element.child_nodes + if child_nodes.length > 0 + (0...child_nodes.length).each do |i| + child = child_nodes.item(i) + merge_element!(hash, child, depth - 1) unless child.node_type == Node.TEXT_NODE + end + merge_texts!(hash, element) unless empty_content?(element) + hash + else + merge_texts!(hash, element) + end + end + + # Merge all the texts of an element into the hash + # + # hash:: + # Hash to add the converted element to. + # element:: + # XML element whose texts are to me merged into the hash + def merge_texts!(hash, element) + delete_empty(hash) + text_children = texts(element) + if text_children.join.empty? + hash + else + # must use value to prevent double-escaping + merge!(hash, CONTENT_KEY, text_children.join) + end + end + + # Adds a new key/value pair to an existing Hash. If the key to be added + # already exists and the existing value associated with key is not + # an Array, it will be wrapped in an Array. Then the new value is + # appended to that Array. + # + # hash:: + # Hash to add key/value pair to. + # key:: + # Key to be added. + # value:: + # Value to be associated with key. + def merge!(hash, key, value) + if hash.has_key?(key) + if hash[key].instance_of?(Array) + hash[key] << value + else + hash[key] = [hash[key], value] + end + elsif value.instance_of?(Array) + hash[key] = [value] + else + hash[key] = value + end + hash + end + + # Converts the attributes array of an XML element into a hash. + # Returns an empty Hash if node has no attributes. + # + # element:: + # XML element to extract attributes from. + def get_attributes(element) + attribute_hash = {} + attributes = element.attributes + (0...attributes.length).each do |i| + attribute_hash[CONTENT_KEY] ||= "" + attribute_hash[attributes.item(i).name] = attributes.item(i).value + end + attribute_hash + end + + # Determines if a document element has text content + # + # element:: + # XML element to be checked. + def texts(element) + texts = [] + child_nodes = element.child_nodes + (0...child_nodes.length).each do |i| + item = child_nodes.item(i) + if item.node_type == Node.TEXT_NODE + texts << item.get_data + end + end + texts + end + + # Determines if a document element has text content + # + # element:: + # XML element to be checked. + def empty_content?(element) + text = "".dup + child_nodes = element.child_nodes + (0...child_nodes.length).each do |i| + item = child_nodes.item(i) + if item.node_type == Node.TEXT_NODE + text << item.get_data.strip + end + end + text.strip.length == 0 + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/xml_mini/libxml.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/xml_mini/libxml.rb new file mode 100644 index 00000000..0b000fea --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/xml_mini/libxml.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require "libxml" +require "active_support/core_ext/object/blank" +require "stringio" + +module ActiveSupport + module XmlMini_LibXML #:nodoc: + extend self + + # Parse an XML Document string or IO into a simple hash using libxml. + # data:: + # XML Document string or IO to parse + def parse(data) + if !data.respond_to?(:read) + data = StringIO.new(data || "") + end + + if data.eof? + {} + else + LibXML::XML::Parser.io(data).parse.to_hash + end + end + end +end + +module LibXML #:nodoc: + module Conversions #:nodoc: + module Document #:nodoc: + def to_hash + root.to_hash + end + end + + module Node #:nodoc: + CONTENT_ROOT = "__content__".freeze + + # Convert XML document to hash. + # + # hash:: + # Hash to merge the converted element into. + def to_hash(hash = {}) + node_hash = {} + + # Insert node hash into parent hash correctly. + case hash[name] + when Array then hash[name] << node_hash + when Hash then hash[name] = [hash[name], node_hash] + when nil then hash[name] = node_hash + end + + # Handle child elements + each_child do |c| + if c.element? + c.to_hash(node_hash) + elsif c.text? || c.cdata? + node_hash[CONTENT_ROOT] ||= "".dup + node_hash[CONTENT_ROOT] << c.content + end + end + + # Remove content node if it is blank + if node_hash.length > 1 && node_hash[CONTENT_ROOT].blank? + node_hash.delete(CONTENT_ROOT) + end + + # Handle attributes + each_attr { |a| node_hash[a.name] = a.value } + + hash + end + end + end +end + +# :enddoc: + +LibXML::XML::Document.include(LibXML::Conversions::Document) +LibXML::XML::Node.include(LibXML::Conversions::Node) diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/xml_mini/libxmlsax.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/xml_mini/libxmlsax.rb new file mode 100644 index 00000000..dcf16e60 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/xml_mini/libxmlsax.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require "libxml" +require "active_support/core_ext/object/blank" +require "stringio" + +module ActiveSupport + module XmlMini_LibXMLSAX #:nodoc: + extend self + + # Class that will build the hash while the XML document + # is being parsed using SAX events. + class HashBuilder + include LibXML::XML::SaxParser::Callbacks + + CONTENT_KEY = "__content__".freeze + HASH_SIZE_KEY = "__hash_size__".freeze + + attr_reader :hash + + def current_hash + @hash_stack.last + end + + def on_start_document + @hash = { CONTENT_KEY => "".dup } + @hash_stack = [@hash] + end + + def on_end_document + @hash = @hash_stack.pop + @hash.delete(CONTENT_KEY) + end + + def on_start_element(name, attrs = {}) + new_hash = { CONTENT_KEY => "".dup }.merge!(attrs) + new_hash[HASH_SIZE_KEY] = new_hash.size + 1 + + case current_hash[name] + when Array then current_hash[name] << new_hash + when Hash then current_hash[name] = [current_hash[name], new_hash] + when nil then current_hash[name] = new_hash + end + + @hash_stack.push(new_hash) + end + + def on_end_element(name) + if current_hash.length > current_hash.delete(HASH_SIZE_KEY) && current_hash[CONTENT_KEY].blank? || current_hash[CONTENT_KEY] == "" + current_hash.delete(CONTENT_KEY) + end + @hash_stack.pop + end + + def on_characters(string) + current_hash[CONTENT_KEY] << string + end + + alias_method :on_cdata_block, :on_characters + end + + attr_accessor :document_class + self.document_class = HashBuilder + + def parse(data) + if !data.respond_to?(:read) + data = StringIO.new(data || "") + end + + if data.eof? + {} + else + LibXML::XML::Error.set_handler(&LibXML::XML::Error::QUIET_HANDLER) + parser = LibXML::XML::SaxParser.io(data) + document = document_class.new + + parser.callbacks = document + parser.parse + document.hash + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/xml_mini/nokogiri.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/xml_mini/nokogiri.rb new file mode 100644 index 00000000..5ee6fc81 --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/xml_mini/nokogiri.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +begin + require "nokogiri" +rescue LoadError => e + $stderr.puts "You don't have nokogiri installed in your application. Please add it to your Gemfile and run bundle install" + raise e +end +require "active_support/core_ext/object/blank" +require "stringio" + +module ActiveSupport + module XmlMini_Nokogiri #:nodoc: + extend self + + # Parse an XML Document string or IO into a simple hash using libxml / nokogiri. + # data:: + # XML Document string or IO to parse + def parse(data) + if !data.respond_to?(:read) + data = StringIO.new(data || "") + end + + if data.eof? + {} + else + doc = Nokogiri::XML(data) + raise doc.errors.first if doc.errors.length > 0 + doc.to_hash + end + end + + module Conversions #:nodoc: + module Document #:nodoc: + def to_hash + root.to_hash + end + end + + module Node #:nodoc: + CONTENT_ROOT = "__content__".freeze + + # Convert XML document to hash. + # + # hash:: + # Hash to merge the converted element into. + def to_hash(hash = {}) + node_hash = {} + + # Insert node hash into parent hash correctly. + case hash[name] + when Array then hash[name] << node_hash + when Hash then hash[name] = [hash[name], node_hash] + when nil then hash[name] = node_hash + end + + # Handle child elements + children.each do |c| + if c.element? + c.to_hash(node_hash) + elsif c.text? || c.cdata? + node_hash[CONTENT_ROOT] ||= "".dup + node_hash[CONTENT_ROOT] << c.content + end + end + + # Remove content node if it is blank and there are child tags + if node_hash.length > 1 && node_hash[CONTENT_ROOT].blank? + node_hash.delete(CONTENT_ROOT) + end + + # Handle attributes + attribute_nodes.each { |a| node_hash[a.node_name] = a.value } + + hash + end + end + end + + Nokogiri::XML::Document.include(Conversions::Document) + Nokogiri::XML::Node.include(Conversions::Node) + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/xml_mini/nokogirisax.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/xml_mini/nokogirisax.rb new file mode 100644 index 00000000..b01ed00a --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/xml_mini/nokogirisax.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +begin + require "nokogiri" +rescue LoadError => e + $stderr.puts "You don't have nokogiri installed in your application. Please add it to your Gemfile and run bundle install" + raise e +end +require "active_support/core_ext/object/blank" +require "stringio" + +module ActiveSupport + module XmlMini_NokogiriSAX #:nodoc: + extend self + + # Class that will build the hash while the XML document + # is being parsed using SAX events. + class HashBuilder < Nokogiri::XML::SAX::Document + CONTENT_KEY = "__content__".freeze + HASH_SIZE_KEY = "__hash_size__".freeze + + attr_reader :hash + + def current_hash + @hash_stack.last + end + + def start_document + @hash = {} + @hash_stack = [@hash] + end + + def end_document + raise "Parse stack not empty!" if @hash_stack.size > 1 + end + + def error(error_message) + raise error_message + end + + def start_element(name, attrs = []) + new_hash = { CONTENT_KEY => "".dup }.merge!(Hash[attrs]) + new_hash[HASH_SIZE_KEY] = new_hash.size + 1 + + case current_hash[name] + when Array then current_hash[name] << new_hash + when Hash then current_hash[name] = [current_hash[name], new_hash] + when nil then current_hash[name] = new_hash + end + + @hash_stack.push(new_hash) + end + + def end_element(name) + if current_hash.length > current_hash.delete(HASH_SIZE_KEY) && current_hash[CONTENT_KEY].blank? || current_hash[CONTENT_KEY] == "" + current_hash.delete(CONTENT_KEY) + end + @hash_stack.pop + end + + def characters(string) + current_hash[CONTENT_KEY] << string + end + + alias_method :cdata_block, :characters + end + + attr_accessor :document_class + self.document_class = HashBuilder + + def parse(data) + if !data.respond_to?(:read) + data = StringIO.new(data || "") + end + + if data.eof? + {} + else + document = document_class.new + parser = Nokogiri::XML::SAX::Parser.new(document) + parser.parse(data) + document.hash + end + end + end +end diff --git a/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/xml_mini/rexml.rb b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/xml_mini/rexml.rb new file mode 100644 index 00000000..32458d5b --- /dev/null +++ b/path/ruby/2.6.0/gems/activesupport-5.2.3/lib/active_support/xml_mini/rexml.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +require "active_support/core_ext/kernel/reporting" +require "active_support/core_ext/object/blank" +require "stringio" + +module ActiveSupport + module XmlMini_REXML #:nodoc: + extend self + + CONTENT_KEY = "__content__".freeze + + # Parse an XML Document string or IO into a simple hash. + # + # Same as XmlSimple::xml_in but doesn't shoot itself in the foot, + # and uses the defaults from Active Support. + # + # data:: + # XML Document string or IO to parse + def parse(data) + if !data.respond_to?(:read) + data = StringIO.new(data || "") + end + + if data.eof? + {} + else + silence_warnings { require "rexml/document" } unless defined?(REXML::Document) + doc = REXML::Document.new(data) + + if doc.root + merge_element!({}, doc.root, XmlMini.depth) + else + raise REXML::ParseException, + "The document #{doc.to_s.inspect} does not have a valid root" + end + end + end + + private + # Convert an XML element and merge into the hash + # + # hash:: + # Hash to merge the converted element into. + # element:: + # XML element to merge into hash + def merge_element!(hash, element, depth) + raise REXML::ParseException, "The document is too deep" if depth == 0 + merge!(hash, element.name, collapse(element, depth)) + end + + # Actually converts an XML document element into a data structure. + # + # element:: + # The document element to be collapsed. + def collapse(element, depth) + hash = get_attributes(element) + + if element.has_elements? + element.each_element { |child| merge_element!(hash, child, depth - 1) } + merge_texts!(hash, element) unless empty_content?(element) + hash + else + merge_texts!(hash, element) + end + end + + # Merge all the texts of an element into the hash + # + # hash:: + # Hash to add the converted element to. + # element:: + # XML element whose texts are to me merged into the hash + def merge_texts!(hash, element) + unless element.has_text? + hash + else + # must use value to prevent double-escaping + texts = "".dup + element.texts.each { |t| texts << t.value } + merge!(hash, CONTENT_KEY, texts) + end + end + + # Adds a new key/value pair to an existing Hash. If the key to be added + # already exists and the existing value associated with key is not + # an Array, it will be wrapped in an Array. Then the new value is + # appended to that Array. + # + # hash:: + # Hash to add key/value pair to. + # key:: + # Key to be added. + # value:: + # Value to be associated with key. + def merge!(hash, key, value) + if hash.has_key?(key) + if hash[key].instance_of?(Array) + hash[key] << value + else + hash[key] = [hash[key], value] + end + elsif value.instance_of?(Array) + hash[key] = [value] + else + hash[key] = value + end + hash + end + + # Converts the attributes array of an XML element into a hash. + # Returns an empty Hash if node has no attributes. + # + # element:: + # XML element to extract attributes from. + def get_attributes(element) + attributes = {} + element.attributes.each { |n, v| attributes[n] = v } + attributes + end + + # Determines if a document element has text content + # + # element:: + # XML element to be checked. + def empty_content?(element) + element.texts.join.blank? + end + end +end diff --git a/path/ruby/2.6.0/gems/addressable-2.7.0/CHANGELOG.md b/path/ruby/2.6.0/gems/addressable-2.7.0/CHANGELOG.md new file mode 100644 index 00000000..a7786213 --- /dev/null +++ b/path/ruby/2.6.0/gems/addressable-2.7.0/CHANGELOG.md @@ -0,0 +1,235 @@ +# Addressable 2.7.0 +- added `:compacted` flag to `normalized_query` +- `heuristic_parse` handles `mailto:` more intuitively +- refactored validation to use a prepended module +- dropped explicit support for JRuby 9.0.5.0 +- compatibility w/ public_suffix 4.x +- performance improvements + +# Addressable 2.6.0 +- added `tld=` method to allow assignment to the public suffix +- most `heuristic_parse` patterns are now case-insensitive +- `heuristic_parse` handles more `file://` URI variations +- fixes bug in `heuristic_parse` when uri starts with digit +- fixes bug in `request_uri=` with query strings +- fixes template issues with `nil` and `?` operator +- `frozen_string_literal` pragmas added +- minor performance improvements in regexps +- fixes to eliminate warnings + +# Addressable 2.5.2 +- better support for frozen string literals +- fixed bug w/ uppercase characters in scheme +- IDNA errors w/ emoji URLs +- compatibility w/ public_suffix 3.x + +# Addressable 2.5.1 +- allow unicode normalization to be disabled for URI Template expansion +- removed duplicate test + +# Addressable 2.5.0 +- dropping support for Ruby 1.9 +- adding support for Ruby 2.4 preview +- add support for public suffixes and tld; first runtime dependency +- hostname escaping should match RFC; underscores in hostnames no longer escaped +- paths beginning with // and missing an authority are now considered invalid +- validation now also takes place after setting a path +- handle backslashes in authority more like a browser for `heuristic_parse` +- unescaped backslashes in host now raise an `InvalidURIError` +- `merge!`, `join!`, `omit!` and `normalize!` don't disable deferred validation +- `heuristic_parse` now trims whitespace before parsing +- host parts longer than 63 bytes will be ignored and not passed to libidn +- normalized values always encoded as UTF-8 + +# Addressable 2.4.0 +- support for 1.8.x dropped +- double quotes in a host now raises an error +- newlines in host will no longer get unescaped during normalization +- stricter handling of bogus scheme values +- stricter handling of encoded port values +- calling `require 'addressable'` will now load both the URI and Template files +- assigning to the `hostname` component with an `IPAddr` object is now supported +- assigning to the `origin` component is now supported +- fixed minor bug where an exception would be thrown for a missing ACE suffix +- better partial expansion of URI templates + +# Addressable 2.3.8 +- fix warnings +- update dependency gems +- support for 1.8.x officially deprecated + +# Addressable 2.3.7 +- fix scenario in which invalid URIs don't get an exception until inspected +- handle hostnames with two adjacent periods correctly +- upgrade of RSpec + +# Addressable 2.3.6 +- normalization drops empty query string +- better handling in template extract for missing values +- template modifier for `'?'` now treated as optional +- fixed issue where character class parameters were modified +- templates can now be tested for equality +- added `:sorted` option to normalization of query strings +- fixed issue with normalization of hosts given in `'example.com.'` form + +# Addressable 2.3.5 +- added Addressable::URI#empty? method +- Addressable::URI#hostname methods now strip square brackets from IPv6 hosts +- compatibility with Net::HTTP in Ruby 2.0.0 +- Addressable::URI#route_from should always give relative URIs + +# Addressable 2.3.4 +- fixed issue with encoding altering its inputs +- query string normalization now leaves ';' characters alone +- FakeFS is detected before attempting to load unicode tables +- additional testing to ensure frozen objects don't cause problems + +# Addressable 2.3.3 +- fixed issue with converting common primitives during template expansion +- fixed port encoding issue +- removed a few warnings +- normalize should now ignore %2B in query strings +- the IDNA logic should now be handled by libidn in Ruby 1.9 +- no template match should now result in nil instead of an empty MatchData +- added license information to gemspec + +# Addressable 2.3.2 +- added Addressable::URI#default_port method +- fixed issue with Marshalling Unicode data on Windows +- improved heuristic parsing to better handle IPv4 addresses + +# Addressable 2.3.1 +- fixed missing unicode data file + +# Addressable 2.3.0 +- updated Addressable::Template to use RFC 6570, level 4 +- fixed compatibility problems with some versions of Ruby +- moved unicode tables into a data file for performance reasons +- removing support for multiple query value notations + +# Addressable 2.2.8 +- fixed issues with dot segment removal code +- form encoding can now handle multiple values per key +- updated development environment + +# Addressable 2.2.7 +- fixed issues related to Addressable::URI#query_values= +- the Addressable::URI.parse method is now polymorphic + +# Addressable 2.2.6 +- changed the way ambiguous paths are handled +- fixed bug with frozen URIs +- https supported in heuristic parsing + +# Addressable 2.2.5 +- 'parsing' a pre-parsed URI object is now a dup operation +- introduced conditional support for libidn +- fixed normalization issue on ampersands in query strings +- added additional tests around handling of query strings + +# Addressable 2.2.4 +- added origin support from draft-ietf-websec-origin-00 +- resolved issue with attempting to navigate below root +- fixed bug with string splitting in query strings + +# Addressable 2.2.3 +- added :flat_array notation for query strings + +# Addressable 2.2.2 +- fixed issue with percent escaping of '+' character in query strings + +# Addressable 2.2.1 +- added support for application/x-www-form-urlencoded. + +# Addressable 2.2.0 +- added site methods +- improved documentation + +# Addressable 2.1.2 +- added HTTP request URI methods +- better handling of Windows file paths +- validation_deferred boolean replaced with defer_validation block +- normalization of percent-encoded paths should now be correct +- fixed issue with constructing URIs with relative paths +- fixed warnings + +# Addressable 2.1.1 +- more type checking changes +- fixed issue with unicode normalization +- added method to find template defaults +- symbolic keys are now allowed in template mappings +- numeric values and symbolic values are now allowed in template mappings + +# Addressable 2.1.0 +- refactored URI template support out into its own class +- removed extract method due to being useless and unreliable +- removed Addressable::URI.expand_template +- removed Addressable::URI#extract_mapping +- added partial template expansion +- fixed minor bugs in the parse and heuristic_parse methods +- fixed incompatibility with Ruby 1.9.1 +- fixed bottleneck in Addressable::URI#hash and Addressable::URI#to_s +- fixed unicode normalization exception +- updated query_values methods to better handle subscript notation +- worked around issue with freezing URIs +- improved specs + +# Addressable 2.0.2 +- fixed issue with URI template expansion +- fixed issue with percent escaping characters 0-15 + +# Addressable 2.0.1 +- fixed issue with query string assignment +- fixed issue with improperly encoded components + +# Addressable 2.0.0 +- the initialize method now takes an options hash as its only parameter +- added query_values method to URI class +- completely replaced IDNA implementation with pure Ruby +- renamed Addressable::ADDRESSABLE_VERSION to Addressable::VERSION +- completely reworked the Rakefile +- changed the behavior of the port method significantly +- Addressable::URI.encode_segment, Addressable::URI.unencode_segment renamed +- documentation is now in YARD format +- more rigorous type checking +- to_str method implemented, implicit conversion to Strings now allowed +- Addressable::URI#omit method added, Addressable::URI#merge method replaced +- updated URI Template code to match v 03 of the draft spec +- added a bunch of new specifications + +# Addressable 1.0.4 +- switched to using RSpec's pending system for specs that rely on IDN +- fixed issue with creating URIs with paths that are not prefixed with '/' + +# Addressable 1.0.3 +- implemented a hash method + +# Addressable 1.0.2 +- fixed minor bug with the extract_mapping method + +# Addressable 1.0.1 +- fixed minor bug with the extract_mapping method + +# Addressable 1.0.0 +- heuristic parse method added +- parsing is slightly more strict +- replaced to_h with to_hash +- fixed routing methods +- improved specifications +- improved heckle rake task +- no surviving heckle mutations + +# Addressable 0.1.2 +- improved normalization +- fixed bug in joining algorithm +- updated specifications + +# Addressable 0.1.1 +- updated documentation +- added URI Template variable extraction + +# Addressable 0.1.0 +- initial release +- implementation based on RFC 3986, 3987 +- support for IRIs via libidn +- support for the URI Template draft spec diff --git a/path/ruby/2.6.0/gems/addressable-2.7.0/Gemfile b/path/ruby/2.6.0/gems/addressable-2.7.0/Gemfile new file mode 100644 index 00000000..33ab6fa4 --- /dev/null +++ b/path/ruby/2.6.0/gems/addressable-2.7.0/Gemfile @@ -0,0 +1,32 @@ +source 'https://rubygems.org' + +gemspec + +group :test do + gem 'rspec', '~> 3.8' + gem 'rspec-its', '~> 1.3' +end + +group :development do + gem 'launchy', '~> 2.4', '>= 2.4.3' + gem 'redcarpet', :platform => :mri_19 + gem 'yard' +end + +group :test, :development do + gem 'rake', '> 10.0', '< 12' + gem 'simplecov', :require => false + gem 'coveralls', :require => false, :platforms => [ + :ruby_20, :ruby_21, :ruby_22, :ruby_23 + ] + # Used to test compatibility. + gem 'rack-mount', git: 'https://github.com/sporkmonger/rack-mount.git', require: 'rack/mount' + + if RUBY_VERSION.start_with?('2.0', '2.1') + gem 'rack', '< 2', :require => false + else + gem 'rack', :require => false + end +end + +gem 'idn-ruby', :platform => [:mri_20, :mri_21, :mri_22, :mri_23, :mri_24] diff --git a/path/ruby/2.6.0/gems/addressable-2.7.0/LICENSE.txt b/path/ruby/2.6.0/gems/addressable-2.7.0/LICENSE.txt new file mode 100644 index 00000000..ef51da2b --- /dev/null +++ b/path/ruby/2.6.0/gems/addressable-2.7.0/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/path/ruby/2.6.0/gems/addressable-2.7.0/README.md b/path/ruby/2.6.0/gems/addressable-2.7.0/README.md new file mode 100644 index 00000000..48a53fb7 --- /dev/null +++ b/path/ruby/2.6.0/gems/addressable-2.7.0/README.md @@ -0,0 +1,121 @@ +# Addressable + +
    +
    Homepage
    github.com/sporkmonger/addressable
    +
    Author
    Bob Aman
    +
    Copyright
    Copyright © Bob Aman
    +
    License
    Apache 2.0
    +
    + +[![Gem Version](http://img.shields.io/gem/dt/addressable.svg)][gem] +[![Build Status](https://secure.travis-ci.org/sporkmonger/addressable.svg?branch=master)][travis] +[![Test Coverage Status](https://img.shields.io/coveralls/sporkmonger/addressable.svg)][coveralls] +[![Documentation Coverage Status](http://inch-ci.org/github/sporkmonger/addressable.svg?branch=master)][inch] + +[gem]: https://rubygems.org/gems/addressable +[travis]: http://travis-ci.org/sporkmonger/addressable +[coveralls]: https://coveralls.io/r/sporkmonger/addressable +[inch]: http://inch-ci.org/github/sporkmonger/addressable + +# Description + +Addressable is an alternative implementation to the URI implementation +that is part of Ruby's standard library. It is flexible, offers heuristic +parsing, and additionally provides extensive support for IRIs and URI templates. + +Addressable closely conforms to RFC 3986, RFC 3987, and RFC 6570 (level 4). + +# Reference + +- {Addressable::URI} +- {Addressable::Template} + +# Example usage + +```ruby +require "addressable/uri" + +uri = Addressable::URI.parse("http://example.com/path/to/resource/") +uri.scheme +#=> "http" +uri.host +#=> "example.com" +uri.path +#=> "/path/to/resource/" + +uri = Addressable::URI.parse("http://www.詹姆斯.com/") +uri.normalize +#=> # +``` + + +# URI Templates + +For more details, see [RFC 6570](https://www.rfc-editor.org/rfc/rfc6570.txt). + + +```ruby + +require "addressable/template" + +template = Addressable::Template.new("http://example.com/{?query*}") +template.expand({ + "query" => { + 'foo' => 'bar', + 'color' => 'red' + } +}) +#=> # + +template = Addressable::Template.new("http://example.com/{?one,two,three}") +template.partial_expand({"one" => "1", "three" => 3}).pattern +#=> "http://example.com/?one=1{&two}&three=3" + +template = Addressable::Template.new( + "http://{host}{/segments*}/{?one,two,bogus}{#fragment}" +) +uri = Addressable::URI.parse( + "http://example.com/a/b/c/?one=1&two=2#foo" +) +template.extract(uri) +#=> +# { +# "host" => "example.com", +# "segments" => ["a", "b", "c"], +# "one" => "1", +# "two" => "2", +# "fragment" => "foo" +# } +``` + +# Install + +```console +$ gem install addressable +``` + +You may optionally turn on native IDN support by installing libidn and the +idn gem: + +```console +$ sudo apt-get install idn # Debian/Ubuntu +$ brew install libidn # OS X +$ gem install idn-ruby +``` + +# Semantic Versioning + +This project uses [Semantic Versioning](https://semver.org/). You can (and should) specify your +dependency using a pessimistic version constraint covering the major and minor +values: + +```ruby +spec.add_dependency 'addressable', '~> 2.5' +``` + +If you need a specific bug fix, you can also specify minimum tiny versions +without preventing updates to the latest minor release: + +```ruby +spec.add_dependency 'addressable', '~> 2.3', '>= 2.3.7' +``` diff --git a/path/ruby/2.6.0/gems/addressable-2.7.0/Rakefile b/path/ruby/2.6.0/gems/addressable-2.7.0/Rakefile new file mode 100644 index 00000000..b7e0ff31 --- /dev/null +++ b/path/ruby/2.6.0/gems/addressable-2.7.0/Rakefile @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'rubygems' +require 'rake' + +require File.join(File.dirname(__FILE__), 'lib', 'addressable', 'version') + +PKG_DISPLAY_NAME = 'Addressable' +PKG_NAME = PKG_DISPLAY_NAME.downcase +PKG_VERSION = Addressable::VERSION::STRING +PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}" + +RELEASE_NAME = "REL #{PKG_VERSION}" + +PKG_SUMMARY = "URI Implementation" +PKG_DESCRIPTION = <<-TEXT +Addressable is an alternative implementation to the URI implementation that is +part of Ruby's standard library. It is flexible, offers heuristic parsing, and +additionally provides extensive support for IRIs and URI templates. +TEXT + +PKG_FILES = FileList[ + "lib/**/*", "spec/**/*", "vendor/**/*", "data/**/*", + "tasks/**/*", + "[A-Z]*", "Rakefile" +].exclude(/pkg/).exclude(/database\.yml/). + exclude(/Gemfile\.lock/).exclude(/[_\.]git$/) + +task :default => "spec" + +WINDOWS = (RUBY_PLATFORM =~ /mswin|win32|mingw|bccwin|cygwin/) rescue false +SUDO = WINDOWS ? '' : ('sudo' unless ENV['SUDOLESS']) + +Dir['tasks/**/*.rake'].each { |rake| load rake } diff --git a/path/ruby/2.6.0/gems/addressable-2.7.0/data/unicode.data b/path/ruby/2.6.0/gems/addressable-2.7.0/data/unicode.data new file mode 100644 index 00000000..cdfc2241 Binary files /dev/null and b/path/ruby/2.6.0/gems/addressable-2.7.0/data/unicode.data differ diff --git a/path/ruby/2.6.0/gems/addressable-2.7.0/lib/addressable.rb b/path/ruby/2.6.0/gems/addressable-2.7.0/lib/addressable.rb new file mode 100644 index 00000000..b4e98b69 --- /dev/null +++ b/path/ruby/2.6.0/gems/addressable-2.7.0/lib/addressable.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +require 'addressable/uri' +require 'addressable/template' diff --git a/path/ruby/2.6.0/gems/addressable-2.7.0/lib/addressable/idna.rb b/path/ruby/2.6.0/gems/addressable-2.7.0/lib/addressable/idna.rb new file mode 100644 index 00000000..e41c1f5d --- /dev/null +++ b/path/ruby/2.6.0/gems/addressable-2.7.0/lib/addressable/idna.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +# encoding:utf-8 +#-- +# Copyright (C) Bob Aman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + + +begin + require "addressable/idna/native" +rescue LoadError + # libidn or the idn gem was not available, fall back on a pure-Ruby + # implementation... + require "addressable/idna/pure" +end diff --git a/path/ruby/2.6.0/gems/addressable-2.7.0/lib/addressable/idna/native.rb b/path/ruby/2.6.0/gems/addressable-2.7.0/lib/addressable/idna/native.rb new file mode 100644 index 00000000..84de8e8c --- /dev/null +++ b/path/ruby/2.6.0/gems/addressable-2.7.0/lib/addressable/idna/native.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +# encoding:utf-8 +#-- +# Copyright (C) Bob Aman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + + +require "idn" + +module Addressable + module IDNA + def self.punycode_encode(value) + IDN::Punycode.encode(value.to_s) + end + + def self.punycode_decode(value) + IDN::Punycode.decode(value.to_s) + end + + def self.unicode_normalize_kc(value) + IDN::Stringprep.nfkc_normalize(value.to_s) + end + + def self.to_ascii(value) + value.to_s.split('.', -1).map do |segment| + if segment.size > 0 && segment.size < 64 + IDN::Idna.toASCII(segment, IDN::Idna::ALLOW_UNASSIGNED) + elsif segment.size >= 64 + segment + else + '' + end + end.join('.') + end + + def self.to_unicode(value) + value.to_s.split('.', -1).map do |segment| + if segment.size > 0 && segment.size < 64 + IDN::Idna.toUnicode(segment, IDN::Idna::ALLOW_UNASSIGNED) + elsif segment.size >= 64 + segment + else + '' + end + end.join('.') + end + end +end diff --git a/path/ruby/2.6.0/gems/addressable-2.7.0/lib/addressable/idna/pure.rb b/path/ruby/2.6.0/gems/addressable-2.7.0/lib/addressable/idna/pure.rb new file mode 100644 index 00000000..f7c553c6 --- /dev/null +++ b/path/ruby/2.6.0/gems/addressable-2.7.0/lib/addressable/idna/pure.rb @@ -0,0 +1,676 @@ +# frozen_string_literal: true + +# encoding:utf-8 +#-- +# Copyright (C) Bob Aman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + + +module Addressable + module IDNA + # This module is loosely based on idn_actionmailer by Mick Staugaard, + # the unicode library by Yoshida Masato, and the punycode implementation + # by Kazuhiro Nishiyama. Most of the code was copied verbatim, but + # some reformatting was done, and some translation from C was done. + # + # Without their code to work from as a base, we'd all still be relying + # on the presence of libidn. Which nobody ever seems to have installed. + # + # Original sources: + # http://github.com/staugaard/idn_actionmailer + # http://www.yoshidam.net/Ruby.html#unicode + # http://rubyforge.org/frs/?group_id=2550 + + + UNICODE_TABLE = File.expand_path( + File.join(File.dirname(__FILE__), '../../..', 'data/unicode.data') + ) + + ACE_PREFIX = "xn--" + + UTF8_REGEX = /\A(?: + [\x09\x0A\x0D\x20-\x7E] # ASCII + | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte + | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs + | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte + | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates + | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 + | [\xF1-\xF3][\x80-\xBF]{3} # planes 4nil5 + | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 + )*\z/mnx + + UTF8_REGEX_MULTIBYTE = /(?: + [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte + | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs + | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte + | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates + | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 + | [\xF1-\xF3][\x80-\xBF]{3} # planes 4nil5 + | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 + )/mnx + + # :startdoc: + + # Converts from a Unicode internationalized domain name to an ASCII + # domain name as described in RFC 3490. + def self.to_ascii(input) + input = input.to_s unless input.is_a?(String) + input = input.dup + if input.respond_to?(:force_encoding) + input.force_encoding(Encoding::ASCII_8BIT) + end + if input =~ UTF8_REGEX && input =~ UTF8_REGEX_MULTIBYTE + parts = unicode_downcase(input).split('.') + parts.map! do |part| + if part.respond_to?(:force_encoding) + part.force_encoding(Encoding::ASCII_8BIT) + end + if part =~ UTF8_REGEX && part =~ UTF8_REGEX_MULTIBYTE + ACE_PREFIX + punycode_encode(unicode_normalize_kc(part)) + else + part + end + end + parts.join('.') + else + input + end + end + + # Converts from an ASCII domain name to a Unicode internationalized + # domain name as described in RFC 3490. + def self.to_unicode(input) + input = input.to_s unless input.is_a?(String) + parts = input.split('.') + parts.map! do |part| + if part =~ /^#{ACE_PREFIX}(.+)/ + begin + punycode_decode(part[/^#{ACE_PREFIX}(.+)/, 1]) + rescue Addressable::IDNA::PunycodeBadInput + # toUnicode is explicitly defined as never-fails by the spec + part + end + else + part + end + end + output = parts.join('.') + if output.respond_to?(:force_encoding) + output.force_encoding(Encoding::UTF_8) + end + output + end + + # Unicode normalization form KC. + def self.unicode_normalize_kc(input) + input = input.to_s unless input.is_a?(String) + unpacked = input.unpack("U*") + unpacked = + unicode_compose(unicode_sort_canonical(unicode_decompose(unpacked))) + return unpacked.pack("U*") + end + + ## + # Unicode aware downcase method. + # + # @api private + # @param [String] input + # The input string. + # @return [String] The downcased result. + def self.unicode_downcase(input) + input = input.to_s unless input.is_a?(String) + unpacked = input.unpack("U*") + unpacked.map! { |codepoint| lookup_unicode_lowercase(codepoint) } + return unpacked.pack("U*") + end + private_class_method :unicode_downcase + + def self.unicode_compose(unpacked) + unpacked_result = [] + length = unpacked.length + + return unpacked if length == 0 + + starter = unpacked[0] + starter_cc = lookup_unicode_combining_class(starter) + starter_cc = 256 if starter_cc != 0 + for i in 1...length + ch = unpacked[i] + + if (starter_cc == 0 && + (composite = unicode_compose_pair(starter, ch)) != nil) + starter = composite + else + unpacked_result << starter + starter = ch + end + end + unpacked_result << starter + return unpacked_result + end + private_class_method :unicode_compose + + def self.unicode_compose_pair(ch_one, ch_two) + if ch_one >= HANGUL_LBASE && ch_one < HANGUL_LBASE + HANGUL_LCOUNT && + ch_two >= HANGUL_VBASE && ch_two < HANGUL_VBASE + HANGUL_VCOUNT + # Hangul L + V + return HANGUL_SBASE + ( + (ch_one - HANGUL_LBASE) * HANGUL_VCOUNT + (ch_two - HANGUL_VBASE) + ) * HANGUL_TCOUNT + elsif ch_one >= HANGUL_SBASE && + ch_one < HANGUL_SBASE + HANGUL_SCOUNT && + (ch_one - HANGUL_SBASE) % HANGUL_TCOUNT == 0 && + ch_two >= HANGUL_TBASE && ch_two < HANGUL_TBASE + HANGUL_TCOUNT + # Hangul LV + T + return ch_one + (ch_two - HANGUL_TBASE) + end + + p = [] + ucs4_to_utf8 = lambda do |ch| + if ch < 128 + p << ch + elsif ch < 2048 + p << (ch >> 6 | 192) + p << (ch & 63 | 128) + elsif ch < 0x10000 + p << (ch >> 12 | 224) + p << (ch >> 6 & 63 | 128) + p << (ch & 63 | 128) + elsif ch < 0x200000 + p << (ch >> 18 | 240) + p << (ch >> 12 & 63 | 128) + p << (ch >> 6 & 63 | 128) + p << (ch & 63 | 128) + elsif ch < 0x4000000 + p << (ch >> 24 | 248) + p << (ch >> 18 & 63 | 128) + p << (ch >> 12 & 63 | 128) + p << (ch >> 6 & 63 | 128) + p << (ch & 63 | 128) + elsif ch < 0x80000000 + p << (ch >> 30 | 252) + p << (ch >> 24 & 63 | 128) + p << (ch >> 18 & 63 | 128) + p << (ch >> 12 & 63 | 128) + p << (ch >> 6 & 63 | 128) + p << (ch & 63 | 128) + end + end + + ucs4_to_utf8.call(ch_one) + ucs4_to_utf8.call(ch_two) + + return lookup_unicode_composition(p) + end + private_class_method :unicode_compose_pair + + def self.unicode_sort_canonical(unpacked) + unpacked = unpacked.dup + i = 1 + length = unpacked.length + + return unpacked if length < 2 + + while i < length + last = unpacked[i-1] + ch = unpacked[i] + last_cc = lookup_unicode_combining_class(last) + cc = lookup_unicode_combining_class(ch) + if cc != 0 && last_cc != 0 && last_cc > cc + unpacked[i] = last + unpacked[i-1] = ch + i -= 1 if i > 1 + else + i += 1 + end + end + return unpacked + end + private_class_method :unicode_sort_canonical + + def self.unicode_decompose(unpacked) + unpacked_result = [] + for cp in unpacked + if cp >= HANGUL_SBASE && cp < HANGUL_SBASE + HANGUL_SCOUNT + l, v, t = unicode_decompose_hangul(cp) + unpacked_result << l + unpacked_result << v if v + unpacked_result << t if t + else + dc = lookup_unicode_compatibility(cp) + unless dc + unpacked_result << cp + else + unpacked_result.concat(unicode_decompose(dc.unpack("U*"))) + end + end + end + return unpacked_result + end + private_class_method :unicode_decompose + + def self.unicode_decompose_hangul(codepoint) + sindex = codepoint - HANGUL_SBASE; + if sindex < 0 || sindex >= HANGUL_SCOUNT + l = codepoint + v = t = nil + return l, v, t + end + l = HANGUL_LBASE + sindex / HANGUL_NCOUNT + v = HANGUL_VBASE + (sindex % HANGUL_NCOUNT) / HANGUL_TCOUNT + t = HANGUL_TBASE + sindex % HANGUL_TCOUNT + if t == HANGUL_TBASE + t = nil + end + return l, v, t + end + private_class_method :unicode_decompose_hangul + + def self.lookup_unicode_combining_class(codepoint) + codepoint_data = UNICODE_DATA[codepoint] + (codepoint_data ? + (codepoint_data[UNICODE_DATA_COMBINING_CLASS] || 0) : + 0) + end + private_class_method :lookup_unicode_combining_class + + def self.lookup_unicode_compatibility(codepoint) + codepoint_data = UNICODE_DATA[codepoint] + (codepoint_data ? + codepoint_data[UNICODE_DATA_COMPATIBILITY] : nil) + end + private_class_method :lookup_unicode_compatibility + + def self.lookup_unicode_lowercase(codepoint) + codepoint_data = UNICODE_DATA[codepoint] + (codepoint_data ? + (codepoint_data[UNICODE_DATA_LOWERCASE] || codepoint) : + codepoint) + end + private_class_method :lookup_unicode_lowercase + + def self.lookup_unicode_composition(unpacked) + return COMPOSITION_TABLE[unpacked] + end + private_class_method :lookup_unicode_composition + + HANGUL_SBASE = 0xac00 + HANGUL_LBASE = 0x1100 + HANGUL_LCOUNT = 19 + HANGUL_VBASE = 0x1161 + HANGUL_VCOUNT = 21 + HANGUL_TBASE = 0x11a7 + HANGUL_TCOUNT = 28 + HANGUL_NCOUNT = HANGUL_VCOUNT * HANGUL_TCOUNT # 588 + HANGUL_SCOUNT = HANGUL_LCOUNT * HANGUL_NCOUNT # 11172 + + UNICODE_DATA_COMBINING_CLASS = 0 + UNICODE_DATA_EXCLUSION = 1 + UNICODE_DATA_CANONICAL = 2 + UNICODE_DATA_COMPATIBILITY = 3 + UNICODE_DATA_UPPERCASE = 4 + UNICODE_DATA_LOWERCASE = 5 + UNICODE_DATA_TITLECASE = 6 + + begin + if defined?(FakeFS) + fakefs_state = FakeFS.activated? + FakeFS.deactivate! + end + # This is a sparse Unicode table. Codepoints without entries are + # assumed to have the value: [0, 0, nil, nil, nil, nil, nil] + UNICODE_DATA = File.open(UNICODE_TABLE, "rb") do |file| + Marshal.load(file.read) + end + ensure + if defined?(FakeFS) + FakeFS.activate! if fakefs_state + end + end + + COMPOSITION_TABLE = {} + UNICODE_DATA.each do |codepoint, data| + canonical = data[UNICODE_DATA_CANONICAL] + exclusion = data[UNICODE_DATA_EXCLUSION] + + if canonical && exclusion == 0 + COMPOSITION_TABLE[canonical.unpack("C*")] = codepoint + end + end + + UNICODE_MAX_LENGTH = 256 + ACE_MAX_LENGTH = 256 + + PUNYCODE_BASE = 36 + PUNYCODE_TMIN = 1 + PUNYCODE_TMAX = 26 + PUNYCODE_SKEW = 38 + PUNYCODE_DAMP = 700 + PUNYCODE_INITIAL_BIAS = 72 + PUNYCODE_INITIAL_N = 0x80 + PUNYCODE_DELIMITER = 0x2D + + PUNYCODE_MAXINT = 1 << 64 + + PUNYCODE_PRINT_ASCII = + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + + "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" + + " !\"\#$%&'()*+,-./" + + "0123456789:;<=>?" + + "@ABCDEFGHIJKLMNO" + + "PQRSTUVWXYZ[\\]^_" + + "`abcdefghijklmno" + + "pqrstuvwxyz{|}~\n" + + # Input is invalid. + class PunycodeBadInput < StandardError; end + # Output would exceed the space provided. + class PunycodeBigOutput < StandardError; end + # Input needs wider integers to process. + class PunycodeOverflow < StandardError; end + + def self.punycode_encode(unicode) + unicode = unicode.to_s unless unicode.is_a?(String) + input = unicode.unpack("U*") + output = [0] * (ACE_MAX_LENGTH + 1) + input_length = input.size + output_length = [ACE_MAX_LENGTH] + + # Initialize the state + n = PUNYCODE_INITIAL_N + delta = out = 0 + max_out = output_length[0] + bias = PUNYCODE_INITIAL_BIAS + + # Handle the basic code points: + input_length.times do |j| + if punycode_basic?(input[j]) + if max_out - out < 2 + raise PunycodeBigOutput, + "Output would exceed the space provided." + end + output[out] = input[j] + out += 1 + end + end + + h = b = out + + # h is the number of code points that have been handled, b is the + # number of basic code points, and out is the number of characters + # that have been output. + + if b > 0 + output[out] = PUNYCODE_DELIMITER + out += 1 + end + + # Main encoding loop: + + while h < input_length + # All non-basic code points < n have been + # handled already. Find the next larger one: + + m = PUNYCODE_MAXINT + input_length.times do |j| + m = input[j] if (n...m) === input[j] + end + + # Increase delta enough to advance the decoder's + # state to , but guard against overflow: + + if m - n > (PUNYCODE_MAXINT - delta) / (h + 1) + raise PunycodeOverflow, "Input needs wider integers to process." + end + delta += (m - n) * (h + 1) + n = m + + input_length.times do |j| + # Punycode does not need to check whether input[j] is basic: + if input[j] < n + delta += 1 + if delta == 0 + raise PunycodeOverflow, + "Input needs wider integers to process." + end + end + + if input[j] == n + # Represent delta as a generalized variable-length integer: + + q = delta; k = PUNYCODE_BASE + while true + if out >= max_out + raise PunycodeBigOutput, + "Output would exceed the space provided." + end + t = ( + if k <= bias + PUNYCODE_TMIN + elsif k >= bias + PUNYCODE_TMAX + PUNYCODE_TMAX + else + k - bias + end + ) + break if q < t + output[out] = + punycode_encode_digit(t + (q - t) % (PUNYCODE_BASE - t)) + out += 1 + q = (q - t) / (PUNYCODE_BASE - t) + k += PUNYCODE_BASE + end + + output[out] = punycode_encode_digit(q) + out += 1 + bias = punycode_adapt(delta, h + 1, h == b) + delta = 0 + h += 1 + end + end + + delta += 1 + n += 1 + end + + output_length[0] = out + + outlen = out + outlen.times do |j| + c = output[j] + unless c >= 0 && c <= 127 + raise StandardError, "Invalid output char." + end + unless PUNYCODE_PRINT_ASCII[c] + raise PunycodeBadInput, "Input is invalid." + end + end + + output[0..outlen].map { |x| x.chr }.join("").sub(/\0+\z/, "") + end + private_class_method :punycode_encode + + def self.punycode_decode(punycode) + input = [] + output = [] + + if ACE_MAX_LENGTH * 2 < punycode.size + raise PunycodeBigOutput, "Output would exceed the space provided." + end + punycode.each_byte do |c| + unless c >= 0 && c <= 127 + raise PunycodeBadInput, "Input is invalid." + end + input.push(c) + end + + input_length = input.length + output_length = [UNICODE_MAX_LENGTH] + + # Initialize the state + n = PUNYCODE_INITIAL_N + + out = i = 0 + max_out = output_length[0] + bias = PUNYCODE_INITIAL_BIAS + + # Handle the basic code points: Let b be the number of input code + # points before the last delimiter, or 0 if there is none, then + # copy the first b code points to the output. + + b = 0 + input_length.times do |j| + b = j if punycode_delimiter?(input[j]) + end + if b > max_out + raise PunycodeBigOutput, "Output would exceed the space provided." + end + + b.times do |j| + unless punycode_basic?(input[j]) + raise PunycodeBadInput, "Input is invalid." + end + output[out] = input[j] + out+=1 + end + + # Main decoding loop: Start just after the last delimiter if any + # basic code points were copied; start at the beginning otherwise. + + in_ = b > 0 ? b + 1 : 0 + while in_ < input_length + + # in_ is the index of the next character to be consumed, and + # out is the number of code points in the output array. + + # Decode a generalized variable-length integer into delta, + # which gets added to i. The overflow checking is easier + # if we increase i as we go, then subtract off its starting + # value at the end to obtain delta. + + oldi = i; w = 1; k = PUNYCODE_BASE + while true + if in_ >= input_length + raise PunycodeBadInput, "Input is invalid." + end + digit = punycode_decode_digit(input[in_]) + in_+=1 + if digit >= PUNYCODE_BASE + raise PunycodeBadInput, "Input is invalid." + end + if digit > (PUNYCODE_MAXINT - i) / w + raise PunycodeOverflow, "Input needs wider integers to process." + end + i += digit * w + t = ( + if k <= bias + PUNYCODE_TMIN + elsif k >= bias + PUNYCODE_TMAX + PUNYCODE_TMAX + else + k - bias + end + ) + break if digit < t + if w > PUNYCODE_MAXINT / (PUNYCODE_BASE - t) + raise PunycodeOverflow, "Input needs wider integers to process." + end + w *= PUNYCODE_BASE - t + k += PUNYCODE_BASE + end + + bias = punycode_adapt(i - oldi, out + 1, oldi == 0) + + # I was supposed to wrap around from out + 1 to 0, + # incrementing n each time, so we'll fix that now: + + if i / (out + 1) > PUNYCODE_MAXINT - n + raise PunycodeOverflow, "Input needs wider integers to process." + end + n += i / (out + 1) + i %= out + 1 + + # Insert n at position i of the output: + + # not needed for Punycode: + # raise PUNYCODE_INVALID_INPUT if decode_digit(n) <= base + if out >= max_out + raise PunycodeBigOutput, "Output would exceed the space provided." + end + + #memmove(output + i + 1, output + i, (out - i) * sizeof *output) + output[i + 1, out - i] = output[i, out - i] + output[i] = n + i += 1 + + out += 1 + end + + output_length[0] = out + + output.pack("U*") + end + private_class_method :punycode_decode + + def self.punycode_basic?(codepoint) + codepoint < 0x80 + end + private_class_method :punycode_basic? + + def self.punycode_delimiter?(codepoint) + codepoint == PUNYCODE_DELIMITER + end + private_class_method :punycode_delimiter? + + def self.punycode_encode_digit(d) + d + 22 + 75 * ((d < 26) ? 1 : 0) + end + private_class_method :punycode_encode_digit + + # Returns the numeric value of a basic codepoint + # (for use in representing integers) in the range 0 to + # base - 1, or PUNYCODE_BASE if codepoint does not represent a value. + def self.punycode_decode_digit(codepoint) + if codepoint - 48 < 10 + codepoint - 22 + elsif codepoint - 65 < 26 + codepoint - 65 + elsif codepoint - 97 < 26 + codepoint - 97 + else + PUNYCODE_BASE + end + end + private_class_method :punycode_decode_digit + + # Bias adaptation method + def self.punycode_adapt(delta, numpoints, firsttime) + delta = firsttime ? delta / PUNYCODE_DAMP : delta >> 1 + # delta >> 1 is a faster way of doing delta / 2 + delta += delta / numpoints + difference = PUNYCODE_BASE - PUNYCODE_TMIN + + k = 0 + while delta > (difference * PUNYCODE_TMAX) / 2 + delta /= difference + k += PUNYCODE_BASE + end + + k + (difference + 1) * delta / (delta + PUNYCODE_SKEW) + end + private_class_method :punycode_adapt + end + # :startdoc: +end diff --git a/path/ruby/2.6.0/gems/addressable-2.7.0/lib/addressable/template.rb b/path/ruby/2.6.0/gems/addressable-2.7.0/lib/addressable/template.rb new file mode 100644 index 00000000..26966953 --- /dev/null +++ b/path/ruby/2.6.0/gems/addressable-2.7.0/lib/addressable/template.rb @@ -0,0 +1,1045 @@ +# frozen_string_literal: true + +# encoding:utf-8 +#-- +# Copyright (C) Bob Aman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + + +require "addressable/version" +require "addressable/uri" + +module Addressable + ## + # This is an implementation of a URI template based on + # RFC 6570 (http://tools.ietf.org/html/rfc6570). + class Template + # Constants used throughout the template code. + anything = + Addressable::URI::CharacterClasses::RESERVED + + Addressable::URI::CharacterClasses::UNRESERVED + + + variable_char_class = + Addressable::URI::CharacterClasses::ALPHA + + Addressable::URI::CharacterClasses::DIGIT + '_' + + var_char = + "(?:(?:[#{variable_char_class}]|%[a-fA-F0-9][a-fA-F0-9])+)" + RESERVED = + "(?:[#{anything}]|%[a-fA-F0-9][a-fA-F0-9])" + UNRESERVED = + "(?:[#{ + Addressable::URI::CharacterClasses::UNRESERVED + }]|%[a-fA-F0-9][a-fA-F0-9])" + variable = + "(?:#{var_char}(?:\\.?#{var_char})*)" + varspec = + "(?:(#{variable})(\\*|:\\d+)?)" + VARNAME = + /^#{variable}$/ + VARSPEC = + /^#{varspec}$/ + VARIABLE_LIST = + /^#{varspec}(?:,#{varspec})*$/ + operator = + "+#./;?&=,!@|" + EXPRESSION = + /\{([#{operator}])?(#{varspec}(?:,#{varspec})*)\}/ + + + LEADERS = { + '?' => '?', + '/' => '/', + '#' => '#', + '.' => '.', + ';' => ';', + '&' => '&' + } + JOINERS = { + '?' => '&', + '.' => '.', + ';' => ';', + '&' => '&', + '/' => '/' + } + + ## + # Raised if an invalid template value is supplied. + class InvalidTemplateValueError < StandardError + end + + ## + # Raised if an invalid template operator is used in a pattern. + class InvalidTemplateOperatorError < StandardError + end + + ## + # Raised if an invalid template operator is used in a pattern. + class TemplateOperatorAbortedError < StandardError + end + + ## + # This class represents the data that is extracted when a Template + # is matched against a URI. + class MatchData + ## + # Creates a new MatchData object. + # MatchData objects should never be instantiated directly. + # + # @param [Addressable::URI] uri + # The URI that the template was matched against. + def initialize(uri, template, mapping) + @uri = uri.dup.freeze + @template = template + @mapping = mapping.dup.freeze + end + + ## + # @return [Addressable::URI] + # The URI that the Template was matched against. + attr_reader :uri + + ## + # @return [Addressable::Template] + # The Template used for the match. + attr_reader :template + + ## + # @return [Hash] + # The mapping that resulted from the match. + # Note that this mapping does not include keys or values for + # variables that appear in the Template, but are not present + # in the URI. + attr_reader :mapping + + ## + # @return [Array] + # The list of variables that were present in the Template. + # Note that this list will include variables which do not appear + # in the mapping because they were not present in URI. + def variables + self.template.variables + end + alias_method :keys, :variables + alias_method :names, :variables + + ## + # @return [Array] + # The list of values that were captured by the Template. + # Note that this list will include nils for any variables which + # were in the Template, but did not appear in the URI. + def values + @values ||= self.variables.inject([]) do |accu, key| + accu << self.mapping[key] + accu + end + end + alias_method :captures, :values + + ## + # Accesses captured values by name or by index. + # + # @param [String, Symbol, Fixnum] key + # Capture index or name. Note that when accessing by with index + # of 0, the full URI will be returned. The intention is to mimic + # the ::MatchData#[] behavior. + # + # @param [#to_int, nil] len + # If provided, an array of values will be returend with the given + # parameter used as length. + # + # @return [Array, String, nil] + # The captured value corresponding to the index or name. If the + # value was not provided or the key is unknown, nil will be + # returned. + # + # If the second parameter is provided, an array of that length will + # be returned instead. + def [](key, len = nil) + if len + to_a[key, len] + elsif String === key or Symbol === key + mapping[key.to_s] + else + to_a[key] + end + end + + ## + # @return [Array] + # Array with the matched URI as first element followed by the captured + # values. + def to_a + [to_s, *values] + end + + ## + # @return [String] + # The matched URI as String. + def to_s + uri.to_s + end + alias_method :string, :to_s + + # Returns multiple captured values at once. + # + # @param [String, Symbol, Fixnum] *indexes + # Indices of the captures to be returned + # + # @return [Array] + # Values corresponding to given indices. + # + # @see Addressable::Template::MatchData#[] + def values_at(*indexes) + indexes.map { |i| self[i] } + end + + ## + # Returns a String representation of the MatchData's state. + # + # @return [String] The MatchData's state, as a String. + def inspect + sprintf("#<%s:%#0x RESULT:%s>", + self.class.to_s, self.object_id, self.mapping.inspect) + end + + ## + # Dummy method for code expecting a ::MatchData instance + # + # @return [String] An empty string. + def pre_match + "" + end + alias_method :post_match, :pre_match + end + + ## + # Creates a new Addressable::Template object. + # + # @param [#to_str] pattern The URI Template pattern. + # + # @return [Addressable::Template] The initialized Template object. + def initialize(pattern) + if !pattern.respond_to?(:to_str) + raise TypeError, "Can't convert #{pattern.class} into String." + end + @pattern = pattern.to_str.dup.freeze + end + + ## + # Freeze URI, initializing instance variables. + # + # @return [Addressable::URI] The frozen URI object. + def freeze + self.variables + self.variable_defaults + self.named_captures + super + end + + ## + # @return [String] The Template object's pattern. + attr_reader :pattern + + ## + # Returns a String representation of the Template object's state. + # + # @return [String] The Template object's state, as a String. + def inspect + sprintf("#<%s:%#0x PATTERN:%s>", + self.class.to_s, self.object_id, self.pattern) + end + + ## + # Returns true if the Template objects are equal. This method + # does NOT normalize either Template before doing the comparison. + # + # @param [Object] template The Template to compare. + # + # @return [TrueClass, FalseClass] + # true if the Templates are equivalent, false + # otherwise. + def ==(template) + return false unless template.kind_of?(Template) + return self.pattern == template.pattern + end + + ## + # Addressable::Template makes no distinction between `==` and `eql?`. + # + # @see #== + alias_method :eql?, :== + + ## + # Extracts a mapping from the URI using a URI Template pattern. + # + # @param [Addressable::URI, #to_str] uri + # The URI to extract from. + # + # @param [#restore, #match] processor + # A template processor object may optionally be supplied. + # + # The object should respond to either the restore or + # match messages or both. The restore method should + # take two parameters: `[String] name` and `[String] value`. + # The restore method should reverse any transformations that + # have been performed on the value to ensure a valid URI. + # The match method should take a single + # parameter: `[String] name`. The match method should return + # a String containing a regular expression capture group for + # matching on that particular variable. The default value is `".*?"`. + # The match method has no effect on multivariate operator + # expansions. + # + # @return [Hash, NilClass] + # The Hash mapping that was extracted from the URI, or + # nil if the URI didn't match the template. + # + # @example + # class ExampleProcessor + # def self.restore(name, value) + # return value.gsub(/\+/, " ") if name == "query" + # return value + # end + # + # def self.match(name) + # return ".*?" if name == "first" + # return ".*" + # end + # end + # + # uri = Addressable::URI.parse( + # "http://example.com/search/an+example+search+query/" + # ) + # Addressable::Template.new( + # "http://example.com/search/{query}/" + # ).extract(uri, ExampleProcessor) + # #=> {"query" => "an example search query"} + # + # uri = Addressable::URI.parse("http://example.com/a/b/c/") + # Addressable::Template.new( + # "http://example.com/{first}/{second}/" + # ).extract(uri, ExampleProcessor) + # #=> {"first" => "a", "second" => "b/c"} + # + # uri = Addressable::URI.parse("http://example.com/a/b/c/") + # Addressable::Template.new( + # "http://example.com/{first}/{-list|/|second}/" + # ).extract(uri) + # #=> {"first" => "a", "second" => ["b", "c"]} + def extract(uri, processor=nil) + match_data = self.match(uri, processor) + return (match_data ? match_data.mapping : nil) + end + + ## + # Extracts match data from the URI using a URI Template pattern. + # + # @param [Addressable::URI, #to_str] uri + # The URI to extract from. + # + # @param [#restore, #match] processor + # A template processor object may optionally be supplied. + # + # The object should respond to either the restore or + # match messages or both. The restore method should + # take two parameters: `[String] name` and `[String] value`. + # The restore method should reverse any transformations that + # have been performed on the value to ensure a valid URI. + # The match method should take a single + # parameter: `[String] name`. The match method should return + # a String containing a regular expression capture group for + # matching on that particular variable. The default value is `".*?"`. + # The match method has no effect on multivariate operator + # expansions. + # + # @return [Hash, NilClass] + # The Hash mapping that was extracted from the URI, or + # nil if the URI didn't match the template. + # + # @example + # class ExampleProcessor + # def self.restore(name, value) + # return value.gsub(/\+/, " ") if name == "query" + # return value + # end + # + # def self.match(name) + # return ".*?" if name == "first" + # return ".*" + # end + # end + # + # uri = Addressable::URI.parse( + # "http://example.com/search/an+example+search+query/" + # ) + # match = Addressable::Template.new( + # "http://example.com/search/{query}/" + # ).match(uri, ExampleProcessor) + # match.variables + # #=> ["query"] + # match.captures + # #=> ["an example search query"] + # + # uri = Addressable::URI.parse("http://example.com/a/b/c/") + # match = Addressable::Template.new( + # "http://example.com/{first}/{+second}/" + # ).match(uri, ExampleProcessor) + # match.variables + # #=> ["first", "second"] + # match.captures + # #=> ["a", "b/c"] + # + # uri = Addressable::URI.parse("http://example.com/a/b/c/") + # match = Addressable::Template.new( + # "http://example.com/{first}{/second*}/" + # ).match(uri) + # match.variables + # #=> ["first", "second"] + # match.captures + # #=> ["a", ["b", "c"]] + def match(uri, processor=nil) + uri = Addressable::URI.parse(uri) + mapping = {} + + # First, we need to process the pattern, and extract the values. + expansions, expansion_regexp = + parse_template_pattern(pattern, processor) + + return nil unless uri.to_str.match(expansion_regexp) + unparsed_values = uri.to_str.scan(expansion_regexp).flatten + + if uri.to_str == pattern + return Addressable::Template::MatchData.new(uri, self, mapping) + elsif expansions.size > 0 + index = 0 + expansions.each do |expansion| + _, operator, varlist = *expansion.match(EXPRESSION) + varlist.split(',').each do |varspec| + _, name, modifier = *varspec.match(VARSPEC) + mapping[name] ||= nil + case operator + when nil, '+', '#', '/', '.' + unparsed_value = unparsed_values[index] + name = varspec[VARSPEC, 1] + value = unparsed_value + value = value.split(JOINERS[operator]) if value && modifier == '*' + when ';', '?', '&' + if modifier == '*' + if unparsed_values[index] + value = unparsed_values[index].split(JOINERS[operator]) + value = value.inject({}) do |acc, v| + key, val = v.split('=') + val = "" if val.nil? + acc[key] = val + acc + end + end + else + if (unparsed_values[index]) + name, value = unparsed_values[index].split('=') + value = "" if value.nil? + end + end + end + if processor != nil && processor.respond_to?(:restore) + value = processor.restore(name, value) + end + if processor == nil + if value.is_a?(Hash) + value = value.inject({}){|acc, (k, v)| + acc[Addressable::URI.unencode_component(k)] = + Addressable::URI.unencode_component(v) + acc + } + elsif value.is_a?(Array) + value = value.map{|v| Addressable::URI.unencode_component(v) } + else + value = Addressable::URI.unencode_component(value) + end + end + if !mapping.has_key?(name) || mapping[name].nil? + # Doesn't exist, set to value (even if value is nil) + mapping[name] = value + end + index = index + 1 + end + end + return Addressable::Template::MatchData.new(uri, self, mapping) + else + return nil + end + end + + ## + # Expands a URI template into another URI template. + # + # @param [Hash] mapping The mapping that corresponds to the pattern. + # @param [#validate, #transform] processor + # An optional processor object may be supplied. + # @param [Boolean] normalize_values + # Optional flag to enable/disable unicode normalization. Default: true + # + # The object should respond to either the validate or + # transform messages or both. Both the validate and + # transform methods should take two parameters: name and + # value. The validate method should return true + # or false; true if the value of the variable is valid, + # false otherwise. An InvalidTemplateValueError + # exception will be raised if the value is invalid. The transform + # method should return the transformed variable value as a String. + # If a transform method is used, the value will not be percent + # encoded automatically. Unicode normalization will be performed both + # before and after sending the value to the transform method. + # + # @return [Addressable::Template] The partially expanded URI template. + # + # @example + # Addressable::Template.new( + # "http://example.com/{one}/{two}/" + # ).partial_expand({"one" => "1"}).pattern + # #=> "http://example.com/1/{two}/" + # + # Addressable::Template.new( + # "http://example.com/{?one,two}/" + # ).partial_expand({"one" => "1"}).pattern + # #=> "http://example.com/?one=1{&two}/" + # + # Addressable::Template.new( + # "http://example.com/{?one,two,three}/" + # ).partial_expand({"one" => "1", "three" => 3}).pattern + # #=> "http://example.com/?one=1{&two}&three=3" + def partial_expand(mapping, processor=nil, normalize_values=true) + result = self.pattern.dup + mapping = normalize_keys(mapping) + result.gsub!( EXPRESSION ) do |capture| + transform_partial_capture(mapping, capture, processor, normalize_values) + end + return Addressable::Template.new(result) + end + + ## + # Expands a URI template into a full URI. + # + # @param [Hash] mapping The mapping that corresponds to the pattern. + # @param [#validate, #transform] processor + # An optional processor object may be supplied. + # @param [Boolean] normalize_values + # Optional flag to enable/disable unicode normalization. Default: true + # + # The object should respond to either the validate or + # transform messages or both. Both the validate and + # transform methods should take two parameters: name and + # value. The validate method should return true + # or false; true if the value of the variable is valid, + # false otherwise. An InvalidTemplateValueError + # exception will be raised if the value is invalid. The transform + # method should return the transformed variable value as a String. + # If a transform method is used, the value will not be percent + # encoded automatically. Unicode normalization will be performed both + # before and after sending the value to the transform method. + # + # @return [Addressable::URI] The expanded URI template. + # + # @example + # class ExampleProcessor + # def self.validate(name, value) + # return !!(value =~ /^[\w ]+$/) if name == "query" + # return true + # end + # + # def self.transform(name, value) + # return value.gsub(/ /, "+") if name == "query" + # return value + # end + # end + # + # Addressable::Template.new( + # "http://example.com/search/{query}/" + # ).expand( + # {"query" => "an example search query"}, + # ExampleProcessor + # ).to_str + # #=> "http://example.com/search/an+example+search+query/" + # + # Addressable::Template.new( + # "http://example.com/search/{query}/" + # ).expand( + # {"query" => "an example search query"} + # ).to_str + # #=> "http://example.com/search/an%20example%20search%20query/" + # + # Addressable::Template.new( + # "http://example.com/search/{query}/" + # ).expand( + # {"query" => "bogus!"}, + # ExampleProcessor + # ).to_str + # #=> Addressable::Template::InvalidTemplateValueError + def expand(mapping, processor=nil, normalize_values=true) + result = self.pattern.dup + mapping = normalize_keys(mapping) + result.gsub!( EXPRESSION ) do |capture| + transform_capture(mapping, capture, processor, normalize_values) + end + return Addressable::URI.parse(result) + end + + ## + # Returns an Array of variables used within the template pattern. + # The variables are listed in the Array in the order they appear within + # the pattern. Multiple occurrences of a variable within a pattern are + # not represented in this Array. + # + # @return [Array] The variables present in the template's pattern. + def variables + @variables ||= ordered_variable_defaults.map { |var, val| var }.uniq + end + alias_method :keys, :variables + alias_method :names, :variables + + ## + # Returns a mapping of variables to their default values specified + # in the template. Variables without defaults are not returned. + # + # @return [Hash] Mapping of template variables to their defaults + def variable_defaults + @variable_defaults ||= + Hash[*ordered_variable_defaults.reject { |k, v| v.nil? }.flatten] + end + + ## + # Coerces a template into a `Regexp` object. This regular expression will + # behave very similarly to the actual template, and should match the same + # URI values, but it cannot fully handle, for example, values that would + # extract to an `Array`. + # + # @return [Regexp] A regular expression which should match the template. + def to_regexp + _, source = parse_template_pattern(pattern) + Regexp.new(source) + end + + ## + # Returns the source of the coerced `Regexp`. + # + # @return [String] The source of the `Regexp` given by {#to_regexp}. + # + # @api private + def source + self.to_regexp.source + end + + ## + # Returns the named captures of the coerced `Regexp`. + # + # @return [Hash] The named captures of the `Regexp` given by {#to_regexp}. + # + # @api private + def named_captures + self.to_regexp.named_captures + end + + ## + # Generates a route result for a given set of parameters. + # Should only be used by rack-mount. + # + # @param params [Hash] The set of parameters used to expand the template. + # @param recall [Hash] Default parameters used to expand the template. + # @param options [Hash] Either a `:processor` or a `:parameterize` block. + # + # @api private + def generate(params={}, recall={}, options={}) + merged = recall.merge(params) + if options[:processor] + processor = options[:processor] + elsif options[:parameterize] + # TODO: This is sending me into fits trying to shoe-horn this into + # the existing API. I think I've got this backwards and processors + # should be a set of 4 optional blocks named :validate, :transform, + # :match, and :restore. Having to use a singleton here is a huge + # code smell. + processor = Object.new + class <validate
    or + # transform messages or both. Both the validate and + # transform methods should take two parameters: name and + # value. The validate method should return true + # or false; true if the value of the variable is valid, + # false otherwise. An InvalidTemplateValueError exception + # will be raised if the value is invalid. The transform method + # should return the transformed variable value as a String. If a + # transform method is used, the value will not be percent encoded + # automatically. Unicode normalization will be performed both before and + # after sending the value to the transform method. + # + # @return [String] The expanded expression + def transform_partial_capture(mapping, capture, processor = nil, + normalize_values = true) + _, operator, varlist = *capture.match(EXPRESSION) + + vars = varlist.split(",") + + if operator == "?" + # partial expansion of form style query variables sometimes requires a + # slight reordering of the variables to produce a valid url. + first_to_expand = vars.find { |varspec| + _, name, _ = *varspec.match(VARSPEC) + mapping.key?(name) && !mapping[name].nil? + } + + vars = [first_to_expand] + vars.reject {|varspec| varspec == first_to_expand} if first_to_expand + end + + vars. + inject("".dup) do |acc, varspec| + _, name, _ = *varspec.match(VARSPEC) + next_val = if mapping.key? name + transform_capture(mapping, "{#{operator}#{varspec}}", + processor, normalize_values) + else + "{#{operator}#{varspec}}" + end + # If we've already expanded at least one '?' operator with non-empty + # value, change to '&' + operator = "&" if (operator == "?") && (next_val != "") + acc << next_val + end + end + + ## + # Transforms a mapped value so that values can be substituted into the + # template. + # + # @param [Hash] mapping The mapping to replace captures + # @param [String] capture + # The expression to replace + # @param [#validate, #transform] processor + # An optional processor object may be supplied. + # @param [Boolean] normalize_values + # Optional flag to enable/disable unicode normalization. Default: true + # + # + # The object should respond to either the validate or + # transform messages or both. Both the validate and + # transform methods should take two parameters: name and + # value. The validate method should return true + # or false; true if the value of the variable is valid, + # false otherwise. An InvalidTemplateValueError exception + # will be raised if the value is invalid. The transform method + # should return the transformed variable value as a String. If a + # transform method is used, the value will not be percent encoded + # automatically. Unicode normalization will be performed both before and + # after sending the value to the transform method. + # + # @return [String] The expanded expression + def transform_capture(mapping, capture, processor=nil, + normalize_values=true) + _, operator, varlist = *capture.match(EXPRESSION) + return_value = varlist.split(',').inject([]) do |acc, varspec| + _, name, modifier = *varspec.match(VARSPEC) + value = mapping[name] + unless value == nil || value == {} + allow_reserved = %w(+ #).include?(operator) + # Common primitives where the .to_s output is well-defined + if Numeric === value || Symbol === value || + value == true || value == false + value = value.to_s + end + length = modifier.gsub(':', '').to_i if modifier =~ /^:\d+/ + + unless (Hash === value) || + value.respond_to?(:to_ary) || value.respond_to?(:to_str) + raise TypeError, + "Can't convert #{value.class} into String or Array." + end + + value = normalize_value(value) if normalize_values + + if processor == nil || !processor.respond_to?(:transform) + # Handle percent escaping + if allow_reserved + encode_map = + Addressable::URI::CharacterClasses::RESERVED + + Addressable::URI::CharacterClasses::UNRESERVED + else + encode_map = Addressable::URI::CharacterClasses::UNRESERVED + end + if value.kind_of?(Array) + transformed_value = value.map do |val| + if length + Addressable::URI.encode_component(val[0...length], encode_map) + else + Addressable::URI.encode_component(val, encode_map) + end + end + unless modifier == "*" + transformed_value = transformed_value.join(',') + end + elsif value.kind_of?(Hash) + transformed_value = value.map do |key, val| + if modifier == "*" + "#{ + Addressable::URI.encode_component( key, encode_map) + }=#{ + Addressable::URI.encode_component( val, encode_map) + }" + else + "#{ + Addressable::URI.encode_component( key, encode_map) + },#{ + Addressable::URI.encode_component( val, encode_map) + }" + end + end + unless modifier == "*" + transformed_value = transformed_value.join(',') + end + else + if length + transformed_value = Addressable::URI.encode_component( + value[0...length], encode_map) + else + transformed_value = Addressable::URI.encode_component( + value, encode_map) + end + end + end + + # Process, if we've got a processor + if processor != nil + if processor.respond_to?(:validate) + if !processor.validate(name, value) + display_value = value.kind_of?(Array) ? value.inspect : value + raise InvalidTemplateValueError, + "#{name}=#{display_value} is an invalid template value." + end + end + if processor.respond_to?(:transform) + transformed_value = processor.transform(name, value) + if normalize_values + transformed_value = normalize_value(transformed_value) + end + end + end + acc << [name, transformed_value] + end + acc + end + return "" if return_value.empty? + join_values(operator, return_value) + end + + ## + # Takes a set of values, and joins them together based on the + # operator. + # + # @param [String, Nil] operator One of the operators from the set + # (?,&,+,#,;,/,.), or nil if there wasn't one. + # @param [Array] return_value + # The set of return values (as [variable_name, value] tuples) that will + # be joined together. + # + # @return [String] The transformed mapped value + def join_values(operator, return_value) + leader = LEADERS.fetch(operator, '') + joiner = JOINERS.fetch(operator, ',') + case operator + when '&', '?' + leader + return_value.map{|k,v| + if v.is_a?(Array) && v.first =~ /=/ + v.join(joiner) + elsif v.is_a?(Array) + v.map{|inner_value| "#{k}=#{inner_value}"}.join(joiner) + else + "#{k}=#{v}" + end + }.join(joiner) + when ';' + return_value.map{|k,v| + if v.is_a?(Array) && v.first =~ /=/ + ';' + v.join(";") + elsif v.is_a?(Array) + ';' + v.map{|inner_value| "#{k}=#{inner_value}"}.join(";") + else + v && v != '' ? ";#{k}=#{v}" : ";#{k}" + end + }.join + else + leader + return_value.map{|k,v| v}.join(joiner) + end + end + + ## + # Takes a set of values, and joins them together based on the + # operator. + # + # @param [Hash, Array, String] value + # Normalizes keys and values with IDNA#unicode_normalize_kc + # + # @return [Hash, Array, String] The normalized values + def normalize_value(value) + unless value.is_a?(Hash) + value = value.respond_to?(:to_ary) ? value.to_ary : value.to_str + end + + # Handle unicode normalization + if value.kind_of?(Array) + value.map! { |val| Addressable::IDNA.unicode_normalize_kc(val) } + elsif value.kind_of?(Hash) + value = value.inject({}) { |acc, (k, v)| + acc[Addressable::IDNA.unicode_normalize_kc(k)] = + Addressable::IDNA.unicode_normalize_kc(v) + acc + } + else + value = Addressable::IDNA.unicode_normalize_kc(value) + end + value + end + + ## + # Generates a hash with string keys + # + # @param [Hash] mapping A mapping hash to normalize + # + # @return [Hash] + # A hash with stringified keys + def normalize_keys(mapping) + return mapping.inject({}) do |accu, pair| + name, value = pair + if Symbol === name + name = name.to_s + elsif name.respond_to?(:to_str) + name = name.to_str + else + raise TypeError, + "Can't convert #{name.class} into String." + end + accu[name] = value + accu + end + end + + ## + # Generates the Regexp that parses a template pattern. + # + # @param [String] pattern The URI template pattern. + # @param [#match] processor The template processor to use. + # + # @return [Regexp] + # A regular expression which may be used to parse a template pattern. + def parse_template_pattern(pattern, processor=nil) + # Escape the pattern. The two gsubs restore the escaped curly braces + # back to their original form. Basically, escape everything that isn't + # within an expansion. + escaped_pattern = Regexp.escape( + pattern + ).gsub(/\\\{(.*?)\\\}/) do |escaped| + escaped.gsub(/\\(.)/, "\\1") + end + + expansions = [] + + # Create a regular expression that captures the values of the + # variables in the URI. + regexp_string = escaped_pattern.gsub( EXPRESSION ) do |expansion| + + expansions << expansion + _, operator, varlist = *expansion.match(EXPRESSION) + leader = Regexp.escape(LEADERS.fetch(operator, '')) + joiner = Regexp.escape(JOINERS.fetch(operator, ',')) + combined = varlist.split(',').map do |varspec| + _, name, modifier = *varspec.match(VARSPEC) + + result = processor && processor.respond_to?(:match) ? processor.match(name) : nil + if result + "(?<#{name}>#{ result })" + else + group = case operator + when '+' + "#{ RESERVED }*?" + when '#' + "#{ RESERVED }*?" + when '/' + "#{ UNRESERVED }*?" + when '.' + "#{ UNRESERVED.gsub('\.', '') }*?" + when ';' + "#{ UNRESERVED }*=?#{ UNRESERVED }*?" + when '?' + "#{ UNRESERVED }*=#{ UNRESERVED }*?" + when '&' + "#{ UNRESERVED }*=#{ UNRESERVED }*?" + else + "#{ UNRESERVED }*?" + end + if modifier == '*' + "(?<#{name}>#{group}(?:#{joiner}?#{group})*)?" + else + "(?<#{name}>#{group})?" + end + end + end.join("#{joiner}?") + "(?:|#{leader}#{combined})" + end + + # Ensure that the regular expression matches the whole URI. + regexp_string = "^#{regexp_string}$" + return expansions, Regexp.new(regexp_string) + end + + end +end diff --git a/path/ruby/2.6.0/gems/addressable-2.7.0/lib/addressable/uri.rb b/path/ruby/2.6.0/gems/addressable-2.7.0/lib/addressable/uri.rb new file mode 100644 index 00000000..71a806bf --- /dev/null +++ b/path/ruby/2.6.0/gems/addressable-2.7.0/lib/addressable/uri.rb @@ -0,0 +1,2529 @@ +# frozen_string_literal: true + +# encoding:utf-8 +#-- +# Copyright (C) Bob Aman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + + +require "addressable/version" +require "addressable/idna" +require "public_suffix" + +## +# Addressable is a library for processing links and URIs. +module Addressable + ## + # This is an implementation of a URI parser based on + # RFC 3986, + # RFC 3987. + class URI + ## + # Raised if something other than a uri is supplied. + class InvalidURIError < StandardError + end + + ## + # Container for the character classes specified in + # RFC 3986. + module CharacterClasses + ALPHA = "a-zA-Z" + DIGIT = "0-9" + GEN_DELIMS = "\\:\\/\\?\\#\\[\\]\\@" + SUB_DELIMS = "\\!\\$\\&\\'\\(\\)\\*\\+\\,\\;\\=" + RESERVED = GEN_DELIMS + SUB_DELIMS + UNRESERVED = ALPHA + DIGIT + "\\-\\.\\_\\~" + PCHAR = UNRESERVED + SUB_DELIMS + "\\:\\@" + SCHEME = ALPHA + DIGIT + "\\-\\+\\." + HOST = UNRESERVED + SUB_DELIMS + "\\[\\:\\]" + AUTHORITY = PCHAR + PATH = PCHAR + "\\/" + QUERY = PCHAR + "\\/\\?" + FRAGMENT = PCHAR + "\\/\\?" + end + + SLASH = '/' + EMPTY_STR = '' + + URIREGEX = /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/ + + PORT_MAPPING = { + "http" => 80, + "https" => 443, + "ftp" => 21, + "tftp" => 69, + "sftp" => 22, + "ssh" => 22, + "svn+ssh" => 22, + "telnet" => 23, + "nntp" => 119, + "gopher" => 70, + "wais" => 210, + "ldap" => 389, + "prospero" => 1525 + } + + ## + # Returns a URI object based on the parsed string. + # + # @param [String, Addressable::URI, #to_str] uri + # The URI string to parse. + # No parsing is performed if the object is already an + # Addressable::URI. + # + # @return [Addressable::URI] The parsed URI. + def self.parse(uri) + # If we were given nil, return nil. + return nil unless uri + # If a URI object is passed, just return itself. + return uri.dup if uri.kind_of?(self) + + # If a URI object of the Ruby standard library variety is passed, + # convert it to a string, then parse the string. + # We do the check this way because we don't want to accidentally + # cause a missing constant exception to be thrown. + if uri.class.name =~ /^URI\b/ + uri = uri.to_s + end + + # Otherwise, convert to a String + begin + uri = uri.to_str + rescue TypeError, NoMethodError + raise TypeError, "Can't convert #{uri.class} into String." + end if not uri.is_a? String + + # This Regexp supplied as an example in RFC 3986, and it works great. + scan = uri.scan(URIREGEX) + fragments = scan[0] + scheme = fragments[1] + authority = fragments[3] + path = fragments[4] + query = fragments[6] + fragment = fragments[8] + user = nil + password = nil + host = nil + port = nil + if authority != nil + # The Regexp above doesn't split apart the authority. + userinfo = authority[/^([^\[\]]*)@/, 1] + if userinfo != nil + user = userinfo.strip[/^([^:]*):?/, 1] + password = userinfo.strip[/:(.*)$/, 1] + end + host = authority.sub( + /^([^\[\]]*)@/, EMPTY_STR + ).sub( + /:([^:@\[\]]*?)$/, EMPTY_STR + ) + port = authority[/:([^:@\[\]]*?)$/, 1] + end + if port == EMPTY_STR + port = nil + end + + return new( + :scheme => scheme, + :user => user, + :password => password, + :host => host, + :port => port, + :path => path, + :query => query, + :fragment => fragment + ) + end + + ## + # Converts an input to a URI. The input does not have to be a valid + # URI — the method will use heuristics to guess what URI was intended. + # This is not standards-compliant, merely user-friendly. + # + # @param [String, Addressable::URI, #to_str] uri + # The URI string to parse. + # No parsing is performed if the object is already an + # Addressable::URI. + # @param [Hash] hints + # A Hash of hints to the heuristic parser. + # Defaults to {:scheme => "http"}. + # + # @return [Addressable::URI] The parsed URI. + def self.heuristic_parse(uri, hints={}) + # If we were given nil, return nil. + return nil unless uri + # If a URI object is passed, just return itself. + return uri.dup if uri.kind_of?(self) + + # If a URI object of the Ruby standard library variety is passed, + # convert it to a string, then parse the string. + # We do the check this way because we don't want to accidentally + # cause a missing constant exception to be thrown. + if uri.class.name =~ /^URI\b/ + uri = uri.to_s + end + + if !uri.respond_to?(:to_str) + raise TypeError, "Can't convert #{uri.class} into String." + end + # Otherwise, convert to a String + uri = uri.to_str.dup.strip + hints = { + :scheme => "http" + }.merge(hints) + case uri + when /^http:\//i + uri.sub!(/^http:\/+/i, "http://") + when /^https:\//i + uri.sub!(/^https:\/+/i, "https://") + when /^feed:\/+http:\//i + uri.sub!(/^feed:\/+http:\/+/i, "feed:http://") + when /^feed:\//i + uri.sub!(/^feed:\/+/i, "feed://") + when %r[^file:/{4}]i + uri.sub!(%r[^file:/+]i, "file:////") + when %r[^file://localhost/]i + uri.sub!(%r[^file://localhost/+]i, "file:///") + when %r[^file:/+]i + uri.sub!(%r[^file:/+]i, "file:///") + when /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/ + uri.sub!(/^/, hints[:scheme] + "://") + when /\A\d+\..*:\d+\z/ + uri = "#{hints[:scheme]}://#{uri}" + end + match = uri.match(URIREGEX) + fragments = match.captures + authority = fragments[3] + if authority && authority.length > 0 + new_authority = authority.tr("\\", "/").gsub(" ", "%20") + # NOTE: We want offset 4, not 3! + offset = match.offset(4) + uri = uri.dup + uri[offset[0]...offset[1]] = new_authority + end + parsed = self.parse(uri) + if parsed.scheme =~ /^[^\/?#\.]+\.[^\/?#]+$/ + parsed = self.parse(hints[:scheme] + "://" + uri) + end + if parsed.path.include?(".") + if parsed.path[/\b@\b/] + parsed.scheme = "mailto" unless parsed.scheme + elsif new_host = parsed.path[/^([^\/]+\.[^\/]*)/, 1] + parsed.defer_validation do + new_path = parsed.path.sub( + Regexp.new("^" + Regexp.escape(new_host)), EMPTY_STR) + parsed.host = new_host + parsed.path = new_path + parsed.scheme = hints[:scheme] unless parsed.scheme + end + end + end + return parsed + end + + ## + # Converts a path to a file scheme URI. If the path supplied is + # relative, it will be returned as a relative URI. If the path supplied + # is actually a non-file URI, it will parse the URI as if it had been + # parsed with Addressable::URI.parse. Handles all of the + # various Microsoft-specific formats for specifying paths. + # + # @param [String, Addressable::URI, #to_str] path + # Typically a String path to a file or directory, but + # will return a sensible return value if an absolute URI is supplied + # instead. + # + # @return [Addressable::URI] + # The parsed file scheme URI or the original URI if some other URI + # scheme was provided. + # + # @example + # base = Addressable::URI.convert_path("/absolute/path/") + # uri = Addressable::URI.convert_path("relative/path") + # (base + uri).to_s + # #=> "file:///absolute/path/relative/path" + # + # Addressable::URI.convert_path( + # "c:\\windows\\My Documents 100%20\\foo.txt" + # ).to_s + # #=> "file:///c:/windows/My%20Documents%20100%20/foo.txt" + # + # Addressable::URI.convert_path("http://example.com/").to_s + # #=> "http://example.com/" + def self.convert_path(path) + # If we were given nil, return nil. + return nil unless path + # If a URI object is passed, just return itself. + return path if path.kind_of?(self) + if !path.respond_to?(:to_str) + raise TypeError, "Can't convert #{path.class} into String." + end + # Otherwise, convert to a String + path = path.to_str.strip + + path.sub!(/^file:\/?\/?/, EMPTY_STR) if path =~ /^file:\/?\/?/ + path = SLASH + path if path =~ /^([a-zA-Z])[\|:]/ + uri = self.parse(path) + + if uri.scheme == nil + # Adjust windows-style uris + uri.path.sub!(/^\/?([a-zA-Z])[\|:][\\\/]/) do + "/#{$1.downcase}:/" + end + uri.path.tr!("\\", SLASH) + if File.exist?(uri.path) && + File.stat(uri.path).directory? + uri.path.chomp!(SLASH) + uri.path = uri.path + '/' + end + + # If the path is absolute, set the scheme and host. + if uri.path.start_with?(SLASH) + uri.scheme = "file" + uri.host = EMPTY_STR + end + uri.normalize! + end + + return uri + end + + ## + # Joins several URIs together. + # + # @param [String, Addressable::URI, #to_str] *uris + # The URIs to join. + # + # @return [Addressable::URI] The joined URI. + # + # @example + # base = "http://example.com/" + # uri = Addressable::URI.parse("relative/path") + # Addressable::URI.join(base, uri) + # #=> # + def self.join(*uris) + uri_objects = uris.collect do |uri| + if !uri.respond_to?(:to_str) + raise TypeError, "Can't convert #{uri.class} into String." + end + uri.kind_of?(self) ? uri : self.parse(uri.to_str) + end + result = uri_objects.shift.dup + for uri in uri_objects + result.join!(uri) + end + return result + end + + ## + # Tables used to optimize encoding operations in `self.encode_component` + # and `self.normalize_component` + SEQUENCE_ENCODING_TABLE = Hash.new do |hash, sequence| + hash[sequence] = sequence.unpack("C*").map do |c| + format("%02x", c) + end.join + end + + SEQUENCE_UPCASED_PERCENT_ENCODING_TABLE = Hash.new do |hash, sequence| + hash[sequence] = sequence.unpack("C*").map do |c| + format("%%%02X", c) + end.join + end + + ## + # Percent encodes a URI component. + # + # @param [String, #to_str] component The URI component to encode. + # + # @param [String, Regexp] character_class + # The characters which are not percent encoded. If a String + # is passed, the String must be formatted as a regular + # expression character class. (Do not include the surrounding square + # brackets.) For example, "b-zB-Z0-9" would cause + # everything but the letters 'b' through 'z' and the numbers '0' through + # '9' to be percent encoded. If a Regexp is passed, the + # value /[^b-zB-Z0-9]/ would have the same effect. A set of + # useful String values may be found in the + # Addressable::URI::CharacterClasses module. The default + # value is the reserved plus unreserved character classes specified in + # RFC 3986. + # + # @param [Regexp] upcase_encoded + # A string of characters that may already be percent encoded, and whose + # encodings should be upcased. This allows normalization of percent + # encodings for characters not included in the + # character_class. + # + # @return [String] The encoded component. + # + # @example + # Addressable::URI.encode_component("simple/example", "b-zB-Z0-9") + # => "simple%2Fex%61mple" + # Addressable::URI.encode_component("simple/example", /[^b-zB-Z0-9]/) + # => "simple%2Fex%61mple" + # Addressable::URI.encode_component( + # "simple/example", Addressable::URI::CharacterClasses::UNRESERVED + # ) + # => "simple%2Fexample" + def self.encode_component(component, character_class= + CharacterClasses::RESERVED + CharacterClasses::UNRESERVED, + upcase_encoded='') + return nil if component.nil? + + begin + if component.kind_of?(Symbol) || + component.kind_of?(Numeric) || + component.kind_of?(TrueClass) || + component.kind_of?(FalseClass) + component = component.to_s + else + component = component.to_str + end + rescue TypeError, NoMethodError + raise TypeError, "Can't convert #{component.class} into String." + end if !component.is_a? String + + if ![String, Regexp].include?(character_class.class) + raise TypeError, + "Expected String or Regexp, got #{character_class.inspect}" + end + if character_class.kind_of?(String) + character_class = /[^#{character_class}]/ + end + # We can't perform regexps on invalid UTF sequences, but + # here we need to, so switch to ASCII. + component = component.dup + component.force_encoding(Encoding::ASCII_8BIT) + # Avoiding gsub! because there are edge cases with frozen strings + component = component.gsub(character_class) do |sequence| + SEQUENCE_UPCASED_PERCENT_ENCODING_TABLE[sequence] + end + if upcase_encoded.length > 0 + upcase_encoded_chars = upcase_encoded.chars.map do |char| + SEQUENCE_ENCODING_TABLE[char] + end + component = component.gsub(/%(#{upcase_encoded_chars.join('|')})/, + &:upcase) + end + return component + end + + class << self + alias_method :encode_component, :encode_component + end + + ## + # Unencodes any percent encoded characters within a URI component. + # This method may be used for unencoding either components or full URIs, + # however, it is recommended to use the unencode_component + # alias when unencoding components. + # + # @param [String, Addressable::URI, #to_str] uri + # The URI or component to unencode. + # + # @param [Class] return_type + # The type of object to return. + # This value may only be set to String or + # Addressable::URI. All other values are invalid. Defaults + # to String. + # + # @param [String] leave_encoded + # A string of characters to leave encoded. If a percent encoded character + # in this list is encountered then it will remain percent encoded. + # + # @return [String, Addressable::URI] + # The unencoded component or URI. + # The return type is determined by the return_type + # parameter. + def self.unencode(uri, return_type=String, leave_encoded='') + return nil if uri.nil? + + begin + uri = uri.to_str + rescue NoMethodError, TypeError + raise TypeError, "Can't convert #{uri.class} into String." + end if !uri.is_a? String + if ![String, ::Addressable::URI].include?(return_type) + raise TypeError, + "Expected Class (String or Addressable::URI), " + + "got #{return_type.inspect}" + end + uri = uri.dup + # Seriously, only use UTF-8. I'm really not kidding! + uri.force_encoding("utf-8") + leave_encoded = leave_encoded.dup.force_encoding("utf-8") + result = uri.gsub(/%[0-9a-f]{2}/iu) do |sequence| + c = sequence[1..3].to_i(16).chr + c.force_encoding("utf-8") + leave_encoded.include?(c) ? sequence : c + end + result.force_encoding("utf-8") + if return_type == String + return result + elsif return_type == ::Addressable::URI + return ::Addressable::URI.parse(result) + end + end + + class << self + alias_method :unescape, :unencode + alias_method :unencode_component, :unencode + alias_method :unescape_component, :unencode + end + + + ## + # Normalizes the encoding of a URI component. + # + # @param [String, #to_str] component The URI component to encode. + # + # @param [String, Regexp] character_class + # The characters which are not percent encoded. If a String + # is passed, the String must be formatted as a regular + # expression character class. (Do not include the surrounding square + # brackets.) For example, "b-zB-Z0-9" would cause + # everything but the letters 'b' through 'z' and the numbers '0' + # through '9' to be percent encoded. If a Regexp is passed, + # the value /[^b-zB-Z0-9]/ would have the same effect. A + # set of useful String values may be found in the + # Addressable::URI::CharacterClasses module. The default + # value is the reserved plus unreserved character classes specified in + # RFC 3986. + # + # @param [String] leave_encoded + # When character_class is a String then + # leave_encoded is a string of characters that should remain + # percent encoded while normalizing the component; if they appear percent + # encoded in the original component, then they will be upcased ("%2f" + # normalized to "%2F") but otherwise left alone. + # + # @return [String] The normalized component. + # + # @example + # Addressable::URI.normalize_component("simpl%65/%65xampl%65", "b-zB-Z") + # => "simple%2Fex%61mple" + # Addressable::URI.normalize_component( + # "simpl%65/%65xampl%65", /[^b-zB-Z]/ + # ) + # => "simple%2Fex%61mple" + # Addressable::URI.normalize_component( + # "simpl%65/%65xampl%65", + # Addressable::URI::CharacterClasses::UNRESERVED + # ) + # => "simple%2Fexample" + # Addressable::URI.normalize_component( + # "one%20two%2fthree%26four", + # "0-9a-zA-Z &/", + # "/" + # ) + # => "one two%2Fthree&four" + def self.normalize_component(component, character_class= + CharacterClasses::RESERVED + CharacterClasses::UNRESERVED, + leave_encoded='') + return nil if component.nil? + + begin + component = component.to_str + rescue NoMethodError, TypeError + raise TypeError, "Can't convert #{component.class} into String." + end if !component.is_a? String + + if ![String, Regexp].include?(character_class.class) + raise TypeError, + "Expected String or Regexp, got #{character_class.inspect}" + end + if character_class.kind_of?(String) + leave_re = if leave_encoded.length > 0 + character_class = "#{character_class}%" unless character_class.include?('%') + + "|%(?!#{leave_encoded.chars.map do |char| + seq = SEQUENCE_ENCODING_TABLE[char] + [seq.upcase, seq.downcase] + end.flatten.join('|')})" + end + + character_class = /[^#{character_class}]#{leave_re}/ + end + # We can't perform regexps on invalid UTF sequences, but + # here we need to, so switch to ASCII. + component = component.dup + component.force_encoding(Encoding::ASCII_8BIT) + unencoded = self.unencode_component(component, String, leave_encoded) + begin + encoded = self.encode_component( + Addressable::IDNA.unicode_normalize_kc(unencoded), + character_class, + leave_encoded + ) + rescue ArgumentError + encoded = self.encode_component(unencoded) + end + encoded.force_encoding(Encoding::UTF_8) + return encoded + end + + ## + # Percent encodes any special characters in the URI. + # + # @param [String, Addressable::URI, #to_str] uri + # The URI to encode. + # + # @param [Class] return_type + # The type of object to return. + # This value may only be set to String or + # Addressable::URI. All other values are invalid. Defaults + # to String. + # + # @return [String, Addressable::URI] + # The encoded URI. + # The return type is determined by the return_type + # parameter. + def self.encode(uri, return_type=String) + return nil if uri.nil? + + begin + uri = uri.to_str + rescue NoMethodError, TypeError + raise TypeError, "Can't convert #{uri.class} into String." + end if !uri.is_a? String + + if ![String, ::Addressable::URI].include?(return_type) + raise TypeError, + "Expected Class (String or Addressable::URI), " + + "got #{return_type.inspect}" + end + uri_object = uri.kind_of?(self) ? uri : self.parse(uri) + encoded_uri = Addressable::URI.new( + :scheme => self.encode_component(uri_object.scheme, + Addressable::URI::CharacterClasses::SCHEME), + :authority => self.encode_component(uri_object.authority, + Addressable::URI::CharacterClasses::AUTHORITY), + :path => self.encode_component(uri_object.path, + Addressable::URI::CharacterClasses::PATH), + :query => self.encode_component(uri_object.query, + Addressable::URI::CharacterClasses::QUERY), + :fragment => self.encode_component(uri_object.fragment, + Addressable::URI::CharacterClasses::FRAGMENT) + ) + if return_type == String + return encoded_uri.to_s + elsif return_type == ::Addressable::URI + return encoded_uri + end + end + + class << self + alias_method :escape, :encode + end + + ## + # Normalizes the encoding of a URI. Characters within a hostname are + # not percent encoded to allow for internationalized domain names. + # + # @param [String, Addressable::URI, #to_str] uri + # The URI to encode. + # + # @param [Class] return_type + # The type of object to return. + # This value may only be set to String or + # Addressable::URI. All other values are invalid. Defaults + # to String. + # + # @return [String, Addressable::URI] + # The encoded URI. + # The return type is determined by the return_type + # parameter. + def self.normalized_encode(uri, return_type=String) + begin + uri = uri.to_str + rescue NoMethodError, TypeError + raise TypeError, "Can't convert #{uri.class} into String." + end if !uri.is_a? String + + if ![String, ::Addressable::URI].include?(return_type) + raise TypeError, + "Expected Class (String or Addressable::URI), " + + "got #{return_type.inspect}" + end + uri_object = uri.kind_of?(self) ? uri : self.parse(uri) + components = { + :scheme => self.unencode_component(uri_object.scheme), + :user => self.unencode_component(uri_object.user), + :password => self.unencode_component(uri_object.password), + :host => self.unencode_component(uri_object.host), + :port => (uri_object.port.nil? ? nil : uri_object.port.to_s), + :path => self.unencode_component(uri_object.path), + :query => self.unencode_component(uri_object.query), + :fragment => self.unencode_component(uri_object.fragment) + } + components.each do |key, value| + if value != nil + begin + components[key] = + Addressable::IDNA.unicode_normalize_kc(value.to_str) + rescue ArgumentError + # Likely a malformed UTF-8 character, skip unicode normalization + components[key] = value.to_str + end + end + end + encoded_uri = Addressable::URI.new( + :scheme => self.encode_component(components[:scheme], + Addressable::URI::CharacterClasses::SCHEME), + :user => self.encode_component(components[:user], + Addressable::URI::CharacterClasses::UNRESERVED), + :password => self.encode_component(components[:password], + Addressable::URI::CharacterClasses::UNRESERVED), + :host => components[:host], + :port => components[:port], + :path => self.encode_component(components[:path], + Addressable::URI::CharacterClasses::PATH), + :query => self.encode_component(components[:query], + Addressable::URI::CharacterClasses::QUERY), + :fragment => self.encode_component(components[:fragment], + Addressable::URI::CharacterClasses::FRAGMENT) + ) + if return_type == String + return encoded_uri.to_s + elsif return_type == ::Addressable::URI + return encoded_uri + end + end + + ## + # Encodes a set of key/value pairs according to the rules for the + # application/x-www-form-urlencoded MIME type. + # + # @param [#to_hash, #to_ary] form_values + # The form values to encode. + # + # @param [TrueClass, FalseClass] sort + # Sort the key/value pairs prior to encoding. + # Defaults to false. + # + # @return [String] + # The encoded value. + def self.form_encode(form_values, sort=false) + if form_values.respond_to?(:to_hash) + form_values = form_values.to_hash.to_a + elsif form_values.respond_to?(:to_ary) + form_values = form_values.to_ary + else + raise TypeError, "Can't convert #{form_values.class} into Array." + end + + form_values = form_values.inject([]) do |accu, (key, value)| + if value.kind_of?(Array) + value.each do |v| + accu << [key.to_s, v.to_s] + end + else + accu << [key.to_s, value.to_s] + end + accu + end + + if sort + # Useful for OAuth and optimizing caching systems + form_values = form_values.sort + end + escaped_form_values = form_values.map do |(key, value)| + # Line breaks are CRLF pairs + [ + self.encode_component( + key.gsub(/(\r\n|\n|\r)/, "\r\n"), + CharacterClasses::UNRESERVED + ).gsub("%20", "+"), + self.encode_component( + value.gsub(/(\r\n|\n|\r)/, "\r\n"), + CharacterClasses::UNRESERVED + ).gsub("%20", "+") + ] + end + return escaped_form_values.map do |(key, value)| + "#{key}=#{value}" + end.join("&") + end + + ## + # Decodes a String according to the rules for the + # application/x-www-form-urlencoded MIME type. + # + # @param [String, #to_str] encoded_value + # The form values to decode. + # + # @return [Array] + # The decoded values. + # This is not a Hash because of the possibility for + # duplicate keys. + def self.form_unencode(encoded_value) + if !encoded_value.respond_to?(:to_str) + raise TypeError, "Can't convert #{encoded_value.class} into String." + end + encoded_value = encoded_value.to_str + split_values = encoded_value.split("&").map do |pair| + pair.split("=", 2) + end + return split_values.map do |(key, value)| + [ + key ? self.unencode_component( + key.gsub("+", "%20")).gsub(/(\r\n|\n|\r)/, "\n") : nil, + value ? (self.unencode_component( + value.gsub("+", "%20")).gsub(/(\r\n|\n|\r)/, "\n")) : nil + ] + end + end + + ## + # Creates a new uri object from component parts. + # + # @option [String, #to_str] scheme The scheme component. + # @option [String, #to_str] user The user component. + # @option [String, #to_str] password The password component. + # @option [String, #to_str] userinfo + # The userinfo component. If this is supplied, the user and password + # components must be omitted. + # @option [String, #to_str] host The host component. + # @option [String, #to_str] port The port component. + # @option [String, #to_str] authority + # The authority component. If this is supplied, the user, password, + # userinfo, host, and port components must be omitted. + # @option [String, #to_str] path The path component. + # @option [String, #to_str] query The query component. + # @option [String, #to_str] fragment The fragment component. + # + # @return [Addressable::URI] The constructed URI object. + def initialize(options={}) + if options.has_key?(:authority) + if (options.keys & [:userinfo, :user, :password, :host, :port]).any? + raise ArgumentError, + "Cannot specify both an authority and any of the components " + + "within the authority." + end + end + if options.has_key?(:userinfo) + if (options.keys & [:user, :password]).any? + raise ArgumentError, + "Cannot specify both a userinfo and either the user or password." + end + end + + self.defer_validation do + # Bunch of crazy logic required because of the composite components + # like userinfo and authority. + self.scheme = options[:scheme] if options[:scheme] + self.user = options[:user] if options[:user] + self.password = options[:password] if options[:password] + self.userinfo = options[:userinfo] if options[:userinfo] + self.host = options[:host] if options[:host] + self.port = options[:port] if options[:port] + self.authority = options[:authority] if options[:authority] + self.path = options[:path] if options[:path] + self.query = options[:query] if options[:query] + self.query_values = options[:query_values] if options[:query_values] + self.fragment = options[:fragment] if options[:fragment] + end + self.to_s + end + + ## + # Freeze URI, initializing instance variables. + # + # @return [Addressable::URI] The frozen URI object. + def freeze + self.normalized_scheme + self.normalized_user + self.normalized_password + self.normalized_userinfo + self.normalized_host + self.normalized_port + self.normalized_authority + self.normalized_site + self.normalized_path + self.normalized_query + self.normalized_fragment + self.hash + super + end + + ## + # The scheme component for this URI. + # + # @return [String] The scheme component. + def scheme + return defined?(@scheme) ? @scheme : nil + end + + ## + # The scheme component for this URI, normalized. + # + # @return [String] The scheme component, normalized. + def normalized_scheme + return nil unless self.scheme + @normalized_scheme ||= begin + if self.scheme =~ /^\s*ssh\+svn\s*$/i + "svn+ssh".dup + else + Addressable::URI.normalize_component( + self.scheme.strip.downcase, + Addressable::URI::CharacterClasses::SCHEME + ) + end + end + # All normalized values should be UTF-8 + @normalized_scheme.force_encoding(Encoding::UTF_8) if @normalized_scheme + @normalized_scheme + end + + ## + # Sets the scheme component for this URI. + # + # @param [String, #to_str] new_scheme The new scheme component. + def scheme=(new_scheme) + if new_scheme && !new_scheme.respond_to?(:to_str) + raise TypeError, "Can't convert #{new_scheme.class} into String." + elsif new_scheme + new_scheme = new_scheme.to_str + end + if new_scheme && new_scheme !~ /\A[a-z][a-z0-9\.\+\-]*\z/i + raise InvalidURIError, "Invalid scheme format: #{new_scheme}" + end + @scheme = new_scheme + @scheme = nil if @scheme.to_s.strip.empty? + + # Reset dependent values + remove_instance_variable(:@normalized_scheme) if defined?(@normalized_scheme) + remove_composite_values + + # Ensure we haven't created an invalid URI + validate() + end + + ## + # The user component for this URI. + # + # @return [String] The user component. + def user + return defined?(@user) ? @user : nil + end + + ## + # The user component for this URI, normalized. + # + # @return [String] The user component, normalized. + def normalized_user + return nil unless self.user + return @normalized_user if defined?(@normalized_user) + @normalized_user ||= begin + if normalized_scheme =~ /https?/ && self.user.strip.empty? && + (!self.password || self.password.strip.empty?) + nil + else + Addressable::URI.normalize_component( + self.user.strip, + Addressable::URI::CharacterClasses::UNRESERVED + ) + end + end + # All normalized values should be UTF-8 + @normalized_user.force_encoding(Encoding::UTF_8) if @normalized_user + @normalized_user + end + + ## + # Sets the user component for this URI. + # + # @param [String, #to_str] new_user The new user component. + def user=(new_user) + if new_user && !new_user.respond_to?(:to_str) + raise TypeError, "Can't convert #{new_user.class} into String." + end + @user = new_user ? new_user.to_str : nil + + # You can't have a nil user with a non-nil password + if password != nil + @user = EMPTY_STR if @user.nil? + end + + # Reset dependent values + remove_instance_variable(:@userinfo) if defined?(@userinfo) + remove_instance_variable(:@normalized_userinfo) if defined?(@normalized_userinfo) + remove_instance_variable(:@authority) if defined?(@authority) + remove_instance_variable(:@normalized_user) if defined?(@normalized_user) + remove_composite_values + + # Ensure we haven't created an invalid URI + validate() + end + + ## + # The password component for this URI. + # + # @return [String] The password component. + def password + return defined?(@password) ? @password : nil + end + + ## + # The password component for this URI, normalized. + # + # @return [String] The password component, normalized. + def normalized_password + return nil unless self.password + return @normalized_password if defined?(@normalized_password) + @normalized_password ||= begin + if self.normalized_scheme =~ /https?/ && self.password.strip.empty? && + (!self.user || self.user.strip.empty?) + nil + else + Addressable::URI.normalize_component( + self.password.strip, + Addressable::URI::CharacterClasses::UNRESERVED + ) + end + end + # All normalized values should be UTF-8 + if @normalized_password + @normalized_password.force_encoding(Encoding::UTF_8) + end + @normalized_password + end + + ## + # Sets the password component for this URI. + # + # @param [String, #to_str] new_password The new password component. + def password=(new_password) + if new_password && !new_password.respond_to?(:to_str) + raise TypeError, "Can't convert #{new_password.class} into String." + end + @password = new_password ? new_password.to_str : nil + + # You can't have a nil user with a non-nil password + @password ||= nil + @user ||= nil + if @password != nil + @user = EMPTY_STR if @user.nil? + end + + # Reset dependent values + remove_instance_variable(:@userinfo) if defined?(@userinfo) + remove_instance_variable(:@normalized_userinfo) if defined?(@normalized_userinfo) + remove_instance_variable(:@authority) if defined?(@authority) + remove_instance_variable(:@normalized_password) if defined?(@normalized_password) + remove_composite_values + + # Ensure we haven't created an invalid URI + validate() + end + + ## + # The userinfo component for this URI. + # Combines the user and password components. + # + # @return [String] The userinfo component. + def userinfo + current_user = self.user + current_password = self.password + (current_user || current_password) && @userinfo ||= begin + if current_user && current_password + "#{current_user}:#{current_password}" + elsif current_user && !current_password + "#{current_user}" + end + end + end + + ## + # The userinfo component for this URI, normalized. + # + # @return [String] The userinfo component, normalized. + def normalized_userinfo + return nil unless self.userinfo + return @normalized_userinfo if defined?(@normalized_userinfo) + @normalized_userinfo ||= begin + current_user = self.normalized_user + current_password = self.normalized_password + if !current_user && !current_password + nil + elsif current_user && current_password + "#{current_user}:#{current_password}".dup + elsif current_user && !current_password + "#{current_user}".dup + end + end + # All normalized values should be UTF-8 + if @normalized_userinfo + @normalized_userinfo.force_encoding(Encoding::UTF_8) + end + @normalized_userinfo + end + + ## + # Sets the userinfo component for this URI. + # + # @param [String, #to_str] new_userinfo The new userinfo component. + def userinfo=(new_userinfo) + if new_userinfo && !new_userinfo.respond_to?(:to_str) + raise TypeError, "Can't convert #{new_userinfo.class} into String." + end + new_user, new_password = if new_userinfo + [ + new_userinfo.to_str.strip[/^(.*):/, 1], + new_userinfo.to_str.strip[/:(.*)$/, 1] + ] + else + [nil, nil] + end + + # Password assigned first to ensure validity in case of nil + self.password = new_password + self.user = new_user + + # Reset dependent values + remove_instance_variable(:@authority) if defined?(@authority) + remove_composite_values + + # Ensure we haven't created an invalid URI + validate() + end + + ## + # The host component for this URI. + # + # @return [String] The host component. + def host + return defined?(@host) ? @host : nil + end + + ## + # The host component for this URI, normalized. + # + # @return [String] The host component, normalized. + def normalized_host + return nil unless self.host + @normalized_host ||= begin + if !self.host.strip.empty? + result = ::Addressable::IDNA.to_ascii( + URI.unencode_component(self.host.strip.downcase) + ) + if result =~ /[^\.]\.$/ + # Single trailing dots are unnecessary. + result = result[0...-1] + end + result = Addressable::URI.normalize_component( + result, + CharacterClasses::HOST) + result + else + EMPTY_STR.dup + end + end + # All normalized values should be UTF-8 + @normalized_host.force_encoding(Encoding::UTF_8) if @normalized_host + @normalized_host + end + + ## + # Sets the host component for this URI. + # + # @param [String, #to_str] new_host The new host component. + def host=(new_host) + if new_host && !new_host.respond_to?(:to_str) + raise TypeError, "Can't convert #{new_host.class} into String." + end + @host = new_host ? new_host.to_str : nil + + # Reset dependent values + remove_instance_variable(:@authority) if defined?(@authority) + remove_instance_variable(:@normalized_host) if defined?(@normalized_host) + remove_composite_values + + # Ensure we haven't created an invalid URI + validate() + end + + ## + # This method is same as URI::Generic#host except + # brackets for IPv6 (and 'IPvFuture') addresses are removed. + # + # @see Addressable::URI#host + # + # @return [String] The hostname for this URI. + def hostname + v = self.host + /\A\[(.*)\]\z/ =~ v ? $1 : v + end + + ## + # This method is same as URI::Generic#host= except + # the argument can be a bare IPv6 address (or 'IPvFuture'). + # + # @see Addressable::URI#host= + # + # @param [String, #to_str] new_hostname The new hostname for this URI. + def hostname=(new_hostname) + if new_hostname && + (new_hostname.respond_to?(:ipv4?) || new_hostname.respond_to?(:ipv6?)) + new_hostname = new_hostname.to_s + elsif new_hostname && !new_hostname.respond_to?(:to_str) + raise TypeError, "Can't convert #{new_hostname.class} into String." + end + v = new_hostname ? new_hostname.to_str : nil + v = "[#{v}]" if /\A\[.*\]\z/ !~ v && /:/ =~ v + self.host = v + end + + ## + # Returns the top-level domain for this host. + # + # @example + # Addressable::URI.parse("http://www.example.co.uk").tld # => "co.uk" + def tld + PublicSuffix.parse(self.host, ignore_private: true).tld + end + + ## + # Sets the top-level domain for this URI. + # + # @param [String, #to_str] new_tld The new top-level domain. + def tld=(new_tld) + replaced_tld = host.sub(/#{tld}\z/, new_tld) + self.host = PublicSuffix::Domain.new(replaced_tld).to_s + end + + ## + # Returns the public suffix domain for this host. + # + # @example + # Addressable::URI.parse("http://www.example.co.uk").domain # => "example.co.uk" + def domain + PublicSuffix.domain(self.host, ignore_private: true) + end + + ## + # The authority component for this URI. + # Combines the user, password, host, and port components. + # + # @return [String] The authority component. + def authority + self.host && @authority ||= begin + authority = String.new + if self.userinfo != nil + authority << "#{self.userinfo}@" + end + authority << self.host + if self.port != nil + authority << ":#{self.port}" + end + authority + end + end + + ## + # The authority component for this URI, normalized. + # + # @return [String] The authority component, normalized. + def normalized_authority + return nil unless self.authority + @normalized_authority ||= begin + authority = String.new + if self.normalized_userinfo != nil + authority << "#{self.normalized_userinfo}@" + end + authority << self.normalized_host + if self.normalized_port != nil + authority << ":#{self.normalized_port}" + end + authority + end + # All normalized values should be UTF-8 + if @normalized_authority + @normalized_authority.force_encoding(Encoding::UTF_8) + end + @normalized_authority + end + + ## + # Sets the authority component for this URI. + # + # @param [String, #to_str] new_authority The new authority component. + def authority=(new_authority) + if new_authority + if !new_authority.respond_to?(:to_str) + raise TypeError, "Can't convert #{new_authority.class} into String." + end + new_authority = new_authority.to_str + new_userinfo = new_authority[/^([^\[\]]*)@/, 1] + if new_userinfo + new_user = new_userinfo.strip[/^([^:]*):?/, 1] + new_password = new_userinfo.strip[/:(.*)$/, 1] + end + new_host = new_authority.sub( + /^([^\[\]]*)@/, EMPTY_STR + ).sub( + /:([^:@\[\]]*?)$/, EMPTY_STR + ) + new_port = + new_authority[/:([^:@\[\]]*?)$/, 1] + end + + # Password assigned first to ensure validity in case of nil + self.password = defined?(new_password) ? new_password : nil + self.user = defined?(new_user) ? new_user : nil + self.host = defined?(new_host) ? new_host : nil + self.port = defined?(new_port) ? new_port : nil + + # Reset dependent values + remove_instance_variable(:@userinfo) if defined?(@userinfo) + remove_instance_variable(:@normalized_userinfo) if defined?(@normalized_userinfo) + remove_composite_values + + # Ensure we haven't created an invalid URI + validate() + end + + ## + # The origin for this URI, serialized to ASCII, as per + # RFC 6454, section 6.2. + # + # @return [String] The serialized origin. + def origin + if self.scheme && self.authority + if self.normalized_port + "#{self.normalized_scheme}://#{self.normalized_host}" + + ":#{self.normalized_port}" + else + "#{self.normalized_scheme}://#{self.normalized_host}" + end + else + "null" + end + end + + ## + # Sets the origin for this URI, serialized to ASCII, as per + # RFC 6454, section 6.2. This assignment will reset the `userinfo` + # component. + # + # @param [String, #to_str] new_origin The new origin component. + def origin=(new_origin) + if new_origin + if !new_origin.respond_to?(:to_str) + raise TypeError, "Can't convert #{new_origin.class} into String." + end + new_origin = new_origin.to_str + new_scheme = new_origin[/^([^:\/?#]+):\/\//, 1] + unless new_scheme + raise InvalidURIError, 'An origin cannot omit the scheme.' + end + new_host = new_origin[/:\/\/([^\/?#:]+)/, 1] + unless new_host + raise InvalidURIError, 'An origin cannot omit the host.' + end + new_port = new_origin[/:([^:@\[\]\/]*?)$/, 1] + end + + self.scheme = defined?(new_scheme) ? new_scheme : nil + self.host = defined?(new_host) ? new_host : nil + self.port = defined?(new_port) ? new_port : nil + self.userinfo = nil + + # Reset dependent values + remove_instance_variable(:@userinfo) if defined?(@userinfo) + remove_instance_variable(:@normalized_userinfo) if defined?(@normalized_userinfo) + remove_instance_variable(:@authority) if defined?(@authority) + remove_instance_variable(:@normalized_authority) if defined?(@normalized_authority) + remove_composite_values + + # Ensure we haven't created an invalid URI + validate() + end + + # Returns an array of known ip-based schemes. These schemes typically + # use a similar URI form: + # //:@:/ + def self.ip_based_schemes + return self.port_mapping.keys + end + + # Returns a hash of common IP-based schemes and their default port + # numbers. Adding new schemes to this hash, as necessary, will allow + # for better URI normalization. + def self.port_mapping + PORT_MAPPING + end + + ## + # The port component for this URI. + # This is the port number actually given in the URI. This does not + # infer port numbers from default values. + # + # @return [Integer] The port component. + def port + return defined?(@port) ? @port : nil + end + + ## + # The port component for this URI, normalized. + # + # @return [Integer] The port component, normalized. + def normalized_port + return nil unless self.port + return @normalized_port if defined?(@normalized_port) + @normalized_port ||= begin + if URI.port_mapping[self.normalized_scheme] == self.port + nil + else + self.port + end + end + end + + ## + # Sets the port component for this URI. + # + # @param [String, Integer, #to_s] new_port The new port component. + def port=(new_port) + if new_port != nil && new_port.respond_to?(:to_str) + new_port = Addressable::URI.unencode_component(new_port.to_str) + end + + if new_port.respond_to?(:valid_encoding?) && !new_port.valid_encoding? + raise InvalidURIError, "Invalid encoding in port" + end + + if new_port != nil && !(new_port.to_s =~ /^\d+$/) + raise InvalidURIError, + "Invalid port number: #{new_port.inspect}" + end + + @port = new_port.to_s.to_i + @port = nil if @port == 0 + + # Reset dependent values + remove_instance_variable(:@authority) if defined?(@authority) + remove_instance_variable(:@normalized_port) if defined?(@normalized_port) + remove_composite_values + + # Ensure we haven't created an invalid URI + validate() + end + + ## + # The inferred port component for this URI. + # This method will normalize to the default port for the URI's scheme if + # the port isn't explicitly specified in the URI. + # + # @return [Integer] The inferred port component. + def inferred_port + if self.port.to_i == 0 + self.default_port + else + self.port.to_i + end + end + + ## + # The default port for this URI's scheme. + # This method will always returns the default port for the URI's scheme + # regardless of the presence of an explicit port in the URI. + # + # @return [Integer] The default port. + def default_port + URI.port_mapping[self.scheme.strip.downcase] if self.scheme + end + + ## + # The combination of components that represent a site. + # Combines the scheme, user, password, host, and port components. + # Primarily useful for HTTP and HTTPS. + # + # For example, "http://example.com/path?query" would have a + # site value of "http://example.com". + # + # @return [String] The components that identify a site. + def site + (self.scheme || self.authority) && @site ||= begin + site_string = "".dup + site_string << "#{self.scheme}:" if self.scheme != nil + site_string << "//#{self.authority}" if self.authority != nil + site_string + end + end + + ## + # The normalized combination of components that represent a site. + # Combines the scheme, user, password, host, and port components. + # Primarily useful for HTTP and HTTPS. + # + # For example, "http://example.com/path?query" would have a + # site value of "http://example.com". + # + # @return [String] The normalized components that identify a site. + def normalized_site + return nil unless self.site + @normalized_site ||= begin + site_string = "".dup + if self.normalized_scheme != nil + site_string << "#{self.normalized_scheme}:" + end + if self.normalized_authority != nil + site_string << "//#{self.normalized_authority}" + end + site_string + end + # All normalized values should be UTF-8 + @normalized_site.force_encoding(Encoding::UTF_8) if @normalized_site + @normalized_site + end + + ## + # Sets the site value for this URI. + # + # @param [String, #to_str] new_site The new site value. + def site=(new_site) + if new_site + if !new_site.respond_to?(:to_str) + raise TypeError, "Can't convert #{new_site.class} into String." + end + new_site = new_site.to_str + # These two regular expressions derived from the primary parsing + # expression + self.scheme = new_site[/^(?:([^:\/?#]+):)?(?:\/\/(?:[^\/?#]*))?$/, 1] + self.authority = new_site[ + /^(?:(?:[^:\/?#]+):)?(?:\/\/([^\/?#]*))?$/, 1 + ] + else + self.scheme = nil + self.authority = nil + end + end + + ## + # The path component for this URI. + # + # @return [String] The path component. + def path + return defined?(@path) ? @path : EMPTY_STR + end + + NORMPATH = /^(?!\/)[^\/:]*:.*$/ + ## + # The path component for this URI, normalized. + # + # @return [String] The path component, normalized. + def normalized_path + @normalized_path ||= begin + path = self.path.to_s + if self.scheme == nil && path =~ NORMPATH + # Relative paths with colons in the first segment are ambiguous. + path = path.sub(":", "%2F") + end + # String#split(delimeter, -1) uses the more strict splitting behavior + # found by default in Python. + result = path.strip.split(SLASH, -1).map do |segment| + Addressable::URI.normalize_component( + segment, + Addressable::URI::CharacterClasses::PCHAR + ) + end.join(SLASH) + + result = URI.normalize_path(result) + if result.empty? && + ["http", "https", "ftp", "tftp"].include?(self.normalized_scheme) + result = SLASH.dup + end + result + end + # All normalized values should be UTF-8 + @normalized_path.force_encoding(Encoding::UTF_8) if @normalized_path + @normalized_path + end + + ## + # Sets the path component for this URI. + # + # @param [String, #to_str] new_path The new path component. + def path=(new_path) + if new_path && !new_path.respond_to?(:to_str) + raise TypeError, "Can't convert #{new_path.class} into String." + end + @path = (new_path || EMPTY_STR).to_str + if !@path.empty? && @path[0..0] != SLASH && host != nil + @path = "/#{@path}" + end + + # Reset dependent values + remove_instance_variable(:@normalized_path) if defined?(@normalized_path) + remove_composite_values + + # Ensure we haven't created an invalid URI + validate() + end + + ## + # The basename, if any, of the file in the path component. + # + # @return [String] The path's basename. + def basename + # Path cannot be nil + return File.basename(self.path).sub(/;[^\/]*$/, EMPTY_STR) + end + + ## + # The extname, if any, of the file in the path component. + # Empty string if there is no extension. + # + # @return [String] The path's extname. + def extname + return nil unless self.path + return File.extname(self.basename) + end + + ## + # The query component for this URI. + # + # @return [String] The query component. + def query + return defined?(@query) ? @query : nil + end + + ## + # The query component for this URI, normalized. + # + # @return [String] The query component, normalized. + def normalized_query(*flags) + return nil unless self.query + return @normalized_query if defined?(@normalized_query) + @normalized_query ||= begin + modified_query_class = Addressable::URI::CharacterClasses::QUERY.dup + # Make sure possible key-value pair delimiters are escaped. + modified_query_class.sub!("\\&", "").sub!("\\;", "") + pairs = (self.query || "").split("&", -1) + pairs.delete_if(&:empty?) if flags.include?(:compacted) + pairs.sort! if flags.include?(:sorted) + component = pairs.map do |pair| + Addressable::URI.normalize_component(pair, modified_query_class, "+") + end.join("&") + component == "" ? nil : component + end + # All normalized values should be UTF-8 + @normalized_query.force_encoding(Encoding::UTF_8) if @normalized_query + @normalized_query + end + + ## + # Sets the query component for this URI. + # + # @param [String, #to_str] new_query The new query component. + def query=(new_query) + if new_query && !new_query.respond_to?(:to_str) + raise TypeError, "Can't convert #{new_query.class} into String." + end + @query = new_query ? new_query.to_str : nil + + # Reset dependent values + remove_instance_variable(:@normalized_query) if defined?(@normalized_query) + remove_composite_values + end + + ## + # Converts the query component to a Hash value. + # + # @param [Class] return_type The return type desired. Value must be either + # `Hash` or `Array`. + # + # @return [Hash, Array, nil] The query string parsed as a Hash or Array + # or nil if the query string is blank. + # + # @example + # Addressable::URI.parse("?one=1&two=2&three=3").query_values + # #=> {"one" => "1", "two" => "2", "three" => "3"} + # Addressable::URI.parse("?one=two&one=three").query_values(Array) + # #=> [["one", "two"], ["one", "three"]] + # Addressable::URI.parse("?one=two&one=three").query_values(Hash) + # #=> {"one" => "three"} + # Addressable::URI.parse("?").query_values + # #=> {} + # Addressable::URI.parse("").query_values + # #=> nil + def query_values(return_type=Hash) + empty_accumulator = Array == return_type ? [] : {} + if return_type != Hash && return_type != Array + raise ArgumentError, "Invalid return type. Must be Hash or Array." + end + return nil if self.query == nil + split_query = self.query.split("&").map do |pair| + pair.split("=", 2) if pair && !pair.empty? + end.compact + return split_query.inject(empty_accumulator.dup) do |accu, pair| + # I'd rather use key/value identifiers instead of array lookups, + # but in this case I really want to maintain the exact pair structure, + # so it's best to make all changes in-place. + pair[0] = URI.unencode_component(pair[0]) + if pair[1].respond_to?(:to_str) + # I loathe the fact that I have to do this. Stupid HTML 4.01. + # Treating '+' as a space was just an unbelievably bad idea. + # There was nothing wrong with '%20'! + # If it ain't broke, don't fix it! + pair[1] = URI.unencode_component(pair[1].to_str.tr("+", " ")) + end + if return_type == Hash + accu[pair[0]] = pair[1] + else + accu << pair + end + accu + end + end + + ## + # Sets the query component for this URI from a Hash object. + # An empty Hash or Array will result in an empty query string. + # + # @param [Hash, #to_hash, Array] new_query_values The new query values. + # + # @example + # uri.query_values = {:a => "a", :b => ["c", "d", "e"]} + # uri.query + # # => "a=a&b=c&b=d&b=e" + # uri.query_values = [['a', 'a'], ['b', 'c'], ['b', 'd'], ['b', 'e']] + # uri.query + # # => "a=a&b=c&b=d&b=e" + # uri.query_values = [['a', 'a'], ['b', ['c', 'd', 'e']]] + # uri.query + # # => "a=a&b=c&b=d&b=e" + # uri.query_values = [['flag'], ['key', 'value']] + # uri.query + # # => "flag&key=value" + def query_values=(new_query_values) + if new_query_values == nil + self.query = nil + return nil + end + + if !new_query_values.is_a?(Array) + if !new_query_values.respond_to?(:to_hash) + raise TypeError, + "Can't convert #{new_query_values.class} into Hash." + end + new_query_values = new_query_values.to_hash + new_query_values = new_query_values.map do |key, value| + key = key.to_s if key.kind_of?(Symbol) + [key, value] + end + # Useful default for OAuth and caching. + # Only to be used for non-Array inputs. Arrays should preserve order. + new_query_values.sort! + end + + # new_query_values have form [['key1', 'value1'], ['key2', 'value2']] + buffer = "".dup + new_query_values.each do |key, value| + encoded_key = URI.encode_component( + key, CharacterClasses::UNRESERVED + ) + if value == nil + buffer << "#{encoded_key}&" + elsif value.kind_of?(Array) + value.each do |sub_value| + encoded_value = URI.encode_component( + sub_value, CharacterClasses::UNRESERVED + ) + buffer << "#{encoded_key}=#{encoded_value}&" + end + else + encoded_value = URI.encode_component( + value, CharacterClasses::UNRESERVED + ) + buffer << "#{encoded_key}=#{encoded_value}&" + end + end + self.query = buffer.chop + end + + ## + # The HTTP request URI for this URI. This is the path and the + # query string. + # + # @return [String] The request URI required for an HTTP request. + def request_uri + return nil if self.absolute? && self.scheme !~ /^https?$/i + return ( + (!self.path.empty? ? self.path : SLASH) + + (self.query ? "?#{self.query}" : EMPTY_STR) + ) + end + + ## + # Sets the HTTP request URI for this URI. + # + # @param [String, #to_str] new_request_uri The new HTTP request URI. + def request_uri=(new_request_uri) + if !new_request_uri.respond_to?(:to_str) + raise TypeError, "Can't convert #{new_request_uri.class} into String." + end + if self.absolute? && self.scheme !~ /^https?$/i + raise InvalidURIError, + "Cannot set an HTTP request URI for a non-HTTP URI." + end + new_request_uri = new_request_uri.to_str + path_component = new_request_uri[/^([^\?]*)\??(?:.*)$/, 1] + query_component = new_request_uri[/^(?:[^\?]*)\?(.*)$/, 1] + path_component = path_component.to_s + path_component = (!path_component.empty? ? path_component : SLASH) + self.path = path_component + self.query = query_component + + # Reset dependent values + remove_composite_values + end + + ## + # The fragment component for this URI. + # + # @return [String] The fragment component. + def fragment + return defined?(@fragment) ? @fragment : nil + end + + ## + # The fragment component for this URI, normalized. + # + # @return [String] The fragment component, normalized. + def normalized_fragment + return nil unless self.fragment + return @normalized_fragment if defined?(@normalized_fragment) + @normalized_fragment ||= begin + component = Addressable::URI.normalize_component( + self.fragment, + Addressable::URI::CharacterClasses::FRAGMENT + ) + component == "" ? nil : component + end + # All normalized values should be UTF-8 + if @normalized_fragment + @normalized_fragment.force_encoding(Encoding::UTF_8) + end + @normalized_fragment + end + + ## + # Sets the fragment component for this URI. + # + # @param [String, #to_str] new_fragment The new fragment component. + def fragment=(new_fragment) + if new_fragment && !new_fragment.respond_to?(:to_str) + raise TypeError, "Can't convert #{new_fragment.class} into String." + end + @fragment = new_fragment ? new_fragment.to_str : nil + + # Reset dependent values + remove_instance_variable(:@normalized_fragment) if defined?(@normalized_fragment) + remove_composite_values + + # Ensure we haven't created an invalid URI + validate() + end + + ## + # Determines if the scheme indicates an IP-based protocol. + # + # @return [TrueClass, FalseClass] + # true if the scheme indicates an IP-based protocol. + # false otherwise. + def ip_based? + if self.scheme + return URI.ip_based_schemes.include?( + self.scheme.strip.downcase) + end + return false + end + + ## + # Determines if the URI is relative. + # + # @return [TrueClass, FalseClass] + # true if the URI is relative. false + # otherwise. + def relative? + return self.scheme.nil? + end + + ## + # Determines if the URI is absolute. + # + # @return [TrueClass, FalseClass] + # true if the URI is absolute. false + # otherwise. + def absolute? + return !relative? + end + + ## + # Joins two URIs together. + # + # @param [String, Addressable::URI, #to_str] The URI to join with. + # + # @return [Addressable::URI] The joined URI. + def join(uri) + if !uri.respond_to?(:to_str) + raise TypeError, "Can't convert #{uri.class} into String." + end + if !uri.kind_of?(URI) + # Otherwise, convert to a String, then parse. + uri = URI.parse(uri.to_str) + end + if uri.to_s.empty? + return self.dup + end + + joined_scheme = nil + joined_user = nil + joined_password = nil + joined_host = nil + joined_port = nil + joined_path = nil + joined_query = nil + joined_fragment = nil + + # Section 5.2.2 of RFC 3986 + if uri.scheme != nil + joined_scheme = uri.scheme + joined_user = uri.user + joined_password = uri.password + joined_host = uri.host + joined_port = uri.port + joined_path = URI.normalize_path(uri.path) + joined_query = uri.query + else + if uri.authority != nil + joined_user = uri.user + joined_password = uri.password + joined_host = uri.host + joined_port = uri.port + joined_path = URI.normalize_path(uri.path) + joined_query = uri.query + else + if uri.path == nil || uri.path.empty? + joined_path = self.path + if uri.query != nil + joined_query = uri.query + else + joined_query = self.query + end + else + if uri.path[0..0] == SLASH + joined_path = URI.normalize_path(uri.path) + else + base_path = self.path.dup + base_path = EMPTY_STR if base_path == nil + base_path = URI.normalize_path(base_path) + + # Section 5.2.3 of RFC 3986 + # + # Removes the right-most path segment from the base path. + if base_path.include?(SLASH) + base_path.sub!(/\/[^\/]+$/, SLASH) + else + base_path = EMPTY_STR + end + + # If the base path is empty and an authority segment has been + # defined, use a base path of SLASH + if base_path.empty? && self.authority != nil + base_path = SLASH + end + + joined_path = URI.normalize_path(base_path + uri.path) + end + joined_query = uri.query + end + joined_user = self.user + joined_password = self.password + joined_host = self.host + joined_port = self.port + end + joined_scheme = self.scheme + end + joined_fragment = uri.fragment + + return self.class.new( + :scheme => joined_scheme, + :user => joined_user, + :password => joined_password, + :host => joined_host, + :port => joined_port, + :path => joined_path, + :query => joined_query, + :fragment => joined_fragment + ) + end + alias_method :+, :join + + ## + # Destructive form of join. + # + # @param [String, Addressable::URI, #to_str] The URI to join with. + # + # @return [Addressable::URI] The joined URI. + # + # @see Addressable::URI#join + def join!(uri) + replace_self(self.join(uri)) + end + + ## + # Merges a URI with a Hash of components. + # This method has different behavior from join. Any + # components present in the hash parameter will override the + # original components. The path component is not treated specially. + # + # @param [Hash, Addressable::URI, #to_hash] The components to merge with. + # + # @return [Addressable::URI] The merged URI. + # + # @see Hash#merge + def merge(hash) + if !hash.respond_to?(:to_hash) + raise TypeError, "Can't convert #{hash.class} into Hash." + end + hash = hash.to_hash + + if hash.has_key?(:authority) + if (hash.keys & [:userinfo, :user, :password, :host, :port]).any? + raise ArgumentError, + "Cannot specify both an authority and any of the components " + + "within the authority." + end + end + if hash.has_key?(:userinfo) + if (hash.keys & [:user, :password]).any? + raise ArgumentError, + "Cannot specify both a userinfo and either the user or password." + end + end + + uri = self.class.new + uri.defer_validation do + # Bunch of crazy logic required because of the composite components + # like userinfo and authority. + uri.scheme = + hash.has_key?(:scheme) ? hash[:scheme] : self.scheme + if hash.has_key?(:authority) + uri.authority = + hash.has_key?(:authority) ? hash[:authority] : self.authority + end + if hash.has_key?(:userinfo) + uri.userinfo = + hash.has_key?(:userinfo) ? hash[:userinfo] : self.userinfo + end + if !hash.has_key?(:userinfo) && !hash.has_key?(:authority) + uri.user = + hash.has_key?(:user) ? hash[:user] : self.user + uri.password = + hash.has_key?(:password) ? hash[:password] : self.password + end + if !hash.has_key?(:authority) + uri.host = + hash.has_key?(:host) ? hash[:host] : self.host + uri.port = + hash.has_key?(:port) ? hash[:port] : self.port + end + uri.path = + hash.has_key?(:path) ? hash[:path] : self.path + uri.query = + hash.has_key?(:query) ? hash[:query] : self.query + uri.fragment = + hash.has_key?(:fragment) ? hash[:fragment] : self.fragment + end + + return uri + end + + ## + # Destructive form of merge. + # + # @param [Hash, Addressable::URI, #to_hash] The components to merge with. + # + # @return [Addressable::URI] The merged URI. + # + # @see Addressable::URI#merge + def merge!(uri) + replace_self(self.merge(uri)) + end + + ## + # Returns the shortest normalized relative form of this URI that uses the + # supplied URI as a base for resolution. Returns an absolute URI if + # necessary. This is effectively the opposite of route_to. + # + # @param [String, Addressable::URI, #to_str] uri The URI to route from. + # + # @return [Addressable::URI] + # The normalized relative URI that is equivalent to the original URI. + def route_from(uri) + uri = URI.parse(uri).normalize + normalized_self = self.normalize + if normalized_self.relative? + raise ArgumentError, "Expected absolute URI, got: #{self.to_s}" + end + if uri.relative? + raise ArgumentError, "Expected absolute URI, got: #{uri.to_s}" + end + if normalized_self == uri + return Addressable::URI.parse("##{normalized_self.fragment}") + end + components = normalized_self.to_hash + if normalized_self.scheme == uri.scheme + components[:scheme] = nil + if normalized_self.authority == uri.authority + components[:user] = nil + components[:password] = nil + components[:host] = nil + components[:port] = nil + if normalized_self.path == uri.path + components[:path] = nil + if normalized_self.query == uri.query + components[:query] = nil + end + else + if uri.path != SLASH and components[:path] + self_splitted_path = split_path(components[:path]) + uri_splitted_path = split_path(uri.path) + self_dir = self_splitted_path.shift + uri_dir = uri_splitted_path.shift + while !self_splitted_path.empty? && !uri_splitted_path.empty? and self_dir == uri_dir + self_dir = self_splitted_path.shift + uri_dir = uri_splitted_path.shift + end + components[:path] = (uri_splitted_path.fill('..') + [self_dir] + self_splitted_path).join(SLASH) + end + end + end + end + # Avoid network-path references. + if components[:host] != nil + components[:scheme] = normalized_self.scheme + end + return Addressable::URI.new( + :scheme => components[:scheme], + :user => components[:user], + :password => components[:password], + :host => components[:host], + :port => components[:port], + :path => components[:path], + :query => components[:query], + :fragment => components[:fragment] + ) + end + + ## + # Returns the shortest normalized relative form of the supplied URI that + # uses this URI as a base for resolution. Returns an absolute URI if + # necessary. This is effectively the opposite of route_from. + # + # @param [String, Addressable::URI, #to_str] uri The URI to route to. + # + # @return [Addressable::URI] + # The normalized relative URI that is equivalent to the supplied URI. + def route_to(uri) + return URI.parse(uri).route_from(self) + end + + ## + # Returns a normalized URI object. + # + # NOTE: This method does not attempt to fully conform to specifications. + # It exists largely to correct other people's failures to read the + # specifications, and also to deal with caching issues since several + # different URIs may represent the same resource and should not be + # cached multiple times. + # + # @return [Addressable::URI] The normalized URI. + def normalize + # This is a special exception for the frequently misused feed + # URI scheme. + if normalized_scheme == "feed" + if self.to_s =~ /^feed:\/*http:\/*/ + return URI.parse( + self.to_s[/^feed:\/*(http:\/*.*)/, 1] + ).normalize + end + end + + return self.class.new( + :scheme => normalized_scheme, + :authority => normalized_authority, + :path => normalized_path, + :query => normalized_query, + :fragment => normalized_fragment + ) + end + + ## + # Destructively normalizes this URI object. + # + # @return [Addressable::URI] The normalized URI. + # + # @see Addressable::URI#normalize + def normalize! + replace_self(self.normalize) + end + + ## + # Creates a URI suitable for display to users. If semantic attacks are + # likely, the application should try to detect these and warn the user. + # See RFC 3986, + # section 7.6 for more information. + # + # @return [Addressable::URI] A URI suitable for display purposes. + def display_uri + display_uri = self.normalize + display_uri.host = ::Addressable::IDNA.to_unicode(display_uri.host) + return display_uri + end + + ## + # Returns true if the URI objects are equal. This method + # normalizes both URIs before doing the comparison, and allows comparison + # against Strings. + # + # @param [Object] uri The URI to compare. + # + # @return [TrueClass, FalseClass] + # true if the URIs are equivalent, false + # otherwise. + def ===(uri) + if uri.respond_to?(:normalize) + uri_string = uri.normalize.to_s + else + begin + uri_string = ::Addressable::URI.parse(uri).normalize.to_s + rescue InvalidURIError, TypeError + return false + end + end + return self.normalize.to_s == uri_string + end + + ## + # Returns true if the URI objects are equal. This method + # normalizes both URIs before doing the comparison. + # + # @param [Object] uri The URI to compare. + # + # @return [TrueClass, FalseClass] + # true if the URIs are equivalent, false + # otherwise. + def ==(uri) + return false unless uri.kind_of?(URI) + return self.normalize.to_s == uri.normalize.to_s + end + + ## + # Returns true if the URI objects are equal. This method + # does NOT normalize either URI before doing the comparison. + # + # @param [Object] uri The URI to compare. + # + # @return [TrueClass, FalseClass] + # true if the URIs are equivalent, false + # otherwise. + def eql?(uri) + return false unless uri.kind_of?(URI) + return self.to_s == uri.to_s + end + + ## + # A hash value that will make a URI equivalent to its normalized + # form. + # + # @return [Integer] A hash of the URI. + def hash + @hash ||= self.to_s.hash * -1 + end + + ## + # Clones the URI object. + # + # @return [Addressable::URI] The cloned URI. + def dup + duplicated_uri = self.class.new( + :scheme => self.scheme ? self.scheme.dup : nil, + :user => self.user ? self.user.dup : nil, + :password => self.password ? self.password.dup : nil, + :host => self.host ? self.host.dup : nil, + :port => self.port, + :path => self.path ? self.path.dup : nil, + :query => self.query ? self.query.dup : nil, + :fragment => self.fragment ? self.fragment.dup : nil + ) + return duplicated_uri + end + + ## + # Omits components from a URI. + # + # @param [Symbol] *components The components to be omitted. + # + # @return [Addressable::URI] The URI with components omitted. + # + # @example + # uri = Addressable::URI.parse("http://example.com/path?query") + # #=> # + # uri.omit(:scheme, :authority) + # #=> # + def omit(*components) + invalid_components = components - [ + :scheme, :user, :password, :userinfo, :host, :port, :authority, + :path, :query, :fragment + ] + unless invalid_components.empty? + raise ArgumentError, + "Invalid component names: #{invalid_components.inspect}." + end + duplicated_uri = self.dup + duplicated_uri.defer_validation do + components.each do |component| + duplicated_uri.send((component.to_s + "=").to_sym, nil) + end + duplicated_uri.user = duplicated_uri.normalized_user + end + duplicated_uri + end + + ## + # Destructive form of omit. + # + # @param [Symbol] *components The components to be omitted. + # + # @return [Addressable::URI] The URI with components omitted. + # + # @see Addressable::URI#omit + def omit!(*components) + replace_self(self.omit(*components)) + end + + ## + # Determines if the URI is an empty string. + # + # @return [TrueClass, FalseClass] + # Returns true if empty, false otherwise. + def empty? + return self.to_s.empty? + end + + ## + # Converts the URI to a String. + # + # @return [String] The URI's String representation. + def to_s + if self.scheme == nil && self.path != nil && !self.path.empty? && + self.path =~ NORMPATH + raise InvalidURIError, + "Cannot assemble URI string with ambiguous path: '#{self.path}'" + end + @uri_string ||= begin + uri_string = String.new + uri_string << "#{self.scheme}:" if self.scheme != nil + uri_string << "//#{self.authority}" if self.authority != nil + uri_string << self.path.to_s + uri_string << "?#{self.query}" if self.query != nil + uri_string << "##{self.fragment}" if self.fragment != nil + uri_string.force_encoding(Encoding::UTF_8) + uri_string + end + end + + ## + # URI's are glorified Strings. Allow implicit conversion. + alias_method :to_str, :to_s + + ## + # Returns a Hash of the URI components. + # + # @return [Hash] The URI as a Hash of components. + def to_hash + return { + :scheme => self.scheme, + :user => self.user, + :password => self.password, + :host => self.host, + :port => self.port, + :path => self.path, + :query => self.query, + :fragment => self.fragment + } + end + + ## + # Returns a String representation of the URI object's state. + # + # @return [String] The URI object's state, as a String. + def inspect + sprintf("#<%s:%#0x URI:%s>", URI.to_s, self.object_id, self.to_s) + end + + ## + # This method allows you to make several changes to a URI simultaneously, + # which separately would cause validation errors, but in conjunction, + # are valid. The URI will be revalidated as soon as the entire block has + # been executed. + # + # @param [Proc] block + # A set of operations to perform on a given URI. + def defer_validation + raise LocalJumpError, "No block given." unless block_given? + @validation_deferred = true + yield + @validation_deferred = false + validate + return nil + end + + protected + SELF_REF = '.' + PARENT = '..' + + RULE_2A = /\/\.\/|\/\.$/ + RULE_2B_2C = /\/([^\/]*)\/\.\.\/|\/([^\/]*)\/\.\.$/ + RULE_2D = /^\.\.?\/?/ + RULE_PREFIXED_PARENT = /^\/\.\.?\/|^(\/\.\.?)+\/?$/ + + ## + # Resolves paths to their simplest form. + # + # @param [String] path The path to normalize. + # + # @return [String] The normalized path. + def self.normalize_path(path) + # Section 5.2.4 of RFC 3986 + + return nil if path.nil? + normalized_path = path.dup + begin + mod = nil + mod ||= normalized_path.gsub!(RULE_2A, SLASH) + + pair = normalized_path.match(RULE_2B_2C) + parent, current = pair[1], pair[2] if pair + if pair && ((parent != SELF_REF && parent != PARENT) || + (current != SELF_REF && current != PARENT)) + mod ||= normalized_path.gsub!( + Regexp.new( + "/#{Regexp.escape(parent.to_s)}/\\.\\./|" + + "(/#{Regexp.escape(current.to_s)}/\\.\\.$)" + ), SLASH + ) + end + + mod ||= normalized_path.gsub!(RULE_2D, EMPTY_STR) + # Non-standard, removes prefixed dotted segments from path. + mod ||= normalized_path.gsub!(RULE_PREFIXED_PARENT, SLASH) + end until mod.nil? + + return normalized_path + end + + ## + # Ensures that the URI is valid. + def validate + return if !!@validation_deferred + if self.scheme != nil && self.ip_based? && + (self.host == nil || self.host.empty?) && + (self.path == nil || self.path.empty?) + raise InvalidURIError, + "Absolute URI missing hierarchical segment: '#{self.to_s}'" + end + if self.host == nil + if self.port != nil || + self.user != nil || + self.password != nil + raise InvalidURIError, "Hostname not supplied: '#{self.to_s}'" + end + end + if self.path != nil && !self.path.empty? && self.path[0..0] != SLASH && + self.authority != nil + raise InvalidURIError, + "Cannot have a relative path with an authority set: '#{self.to_s}'" + end + if self.path != nil && !self.path.empty? && + self.path[0..1] == SLASH + SLASH && self.authority == nil + raise InvalidURIError, + "Cannot have a path with two leading slashes " + + "without an authority set: '#{self.to_s}'" + end + unreserved = CharacterClasses::UNRESERVED + sub_delims = CharacterClasses::SUB_DELIMS + if !self.host.nil? && (self.host =~ /[<>{}\/\\\?\#\@"[[:space:]]]/ || + (self.host[/^\[(.*)\]$/, 1] != nil && self.host[/^\[(.*)\]$/, 1] !~ + Regexp.new("^[#{unreserved}#{sub_delims}:]*$"))) + raise InvalidURIError, "Invalid character in host: '#{self.host.to_s}'" + end + return nil + end + + ## + # Replaces the internal state of self with the specified URI's state. + # Used in destructive operations to avoid massive code repetition. + # + # @param [Addressable::URI] uri The URI to replace self with. + # + # @return [Addressable::URI] self. + def replace_self(uri) + # Reset dependent values + instance_variables.each do |var| + if instance_variable_defined?(var) && var != :@validation_deferred + remove_instance_variable(var) + end + end + + @scheme = uri.scheme + @user = uri.user + @password = uri.password + @host = uri.host + @port = uri.port + @path = uri.path + @query = uri.query + @fragment = uri.fragment + return self + end + + ## + # Splits path string with "/" (slash). + # It is considered that there is empty string after last slash when + # path ends with slash. + # + # @param [String] path The path to split. + # + # @return [Array] An array of parts of path. + def split_path(path) + splitted = path.split(SLASH) + splitted << EMPTY_STR if path.end_with? SLASH + splitted + end + + ## + # Resets composite values for the entire URI + # + # @api private + def remove_composite_values + remove_instance_variable(:@uri_string) if defined?(@uri_string) + remove_instance_variable(:@hash) if defined?(@hash) + end + end +end diff --git a/path/ruby/2.6.0/gems/addressable-2.7.0/lib/addressable/version.rb b/path/ruby/2.6.0/gems/addressable-2.7.0/lib/addressable/version.rb new file mode 100644 index 00000000..5be5ff09 --- /dev/null +++ b/path/ruby/2.6.0/gems/addressable-2.7.0/lib/addressable/version.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +# encoding:utf-8 +#-- +# Copyright (C) Bob Aman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#++ + + +# Used to prevent the class/module from being loaded more than once +if !defined?(Addressable::VERSION) + module Addressable + module VERSION + MAJOR = 2 + MINOR = 7 + TINY = 0 + + STRING = [MAJOR, MINOR, TINY].join('.') + end + end +end diff --git a/path/ruby/2.6.0/gems/addressable-2.7.0/spec/addressable/idna_spec.rb b/path/ruby/2.6.0/gems/addressable-2.7.0/spec/addressable/idna_spec.rb new file mode 100644 index 00000000..651f75af --- /dev/null +++ b/path/ruby/2.6.0/gems/addressable-2.7.0/spec/addressable/idna_spec.rb @@ -0,0 +1,300 @@ +# frozen_string_literal: true + +# coding: utf-8 +# Copyright (C) Bob Aman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +require "spec_helper" + +# Have to use RubyGems to load the idn gem. +require "rubygems" + +require "addressable/idna" + +shared_examples_for "converting from unicode to ASCII" do + it "should convert 'www.google.com' correctly" do + expect(Addressable::IDNA.to_ascii("www.google.com")).to eq("www.google.com") + end + + long = 'AcinusFallumTrompetumNullunCreditumVisumEstAtCuadLongumEtCefallum.com' + it "should convert '#{long}' correctly" do + expect(Addressable::IDNA.to_ascii(long)).to eq(long) + end + + it "should convert 'www.詹姆斯.com' correctly" do + expect(Addressable::IDNA.to_ascii( + "www.詹姆斯.com" + )).to eq("www.xn--8ws00zhy3a.com") + end + + it "should convert 'www.Iñtërnâtiônàlizætiøn.com' correctly" do + "www.Iñtërnâtiônàlizætiøn.com" + expect(Addressable::IDNA.to_ascii( + "www.I\xC3\xB1t\xC3\xABrn\xC3\xA2ti\xC3\xB4" + + "n\xC3\xA0liz\xC3\xA6ti\xC3\xB8n.com" + )).to eq("www.xn--itrntinliztin-vdb0a5exd8ewcye.com") + end + + it "should convert 'www.Iñtërnâtiônàlizætiøn.com' correctly" do + expect(Addressable::IDNA.to_ascii( + "www.In\xCC\x83te\xCC\x88rna\xCC\x82tio\xCC\x82n" + + "a\xCC\x80liz\xC3\xA6ti\xC3\xB8n.com" + )).to eq("www.xn--itrntinliztin-vdb0a5exd8ewcye.com") + end + + it "should convert " + + "'www.ほんとうにながいわけのわからないどめいんめいのらべるまだながくしないとたりない.w3.mag.keio.ac.jp' " + + "correctly" do + expect(Addressable::IDNA.to_ascii( + "www.\343\201\273\343\202\223\343\201\250\343\201\206\343\201\253\343" + + "\201\252\343\201\214\343\201\204\343\202\217\343\201\221\343\201\256" + + "\343\202\217\343\201\213\343\202\211\343\201\252\343\201\204\343\201" + + "\251\343\202\201\343\201\204\343\202\223\343\202\201\343\201\204\343" + + "\201\256\343\202\211\343\201\271\343\202\213\343\201\276\343\201\240" + + "\343\201\252\343\201\214\343\201\217\343\201\227\343\201\252\343\201" + + "\204\343\201\250\343\201\237\343\202\212\343\201\252\343\201\204." + + "w3.mag.keio.ac.jp" + )).to eq( + "www.xn--n8jaaaaai5bhf7as8fsfk3jnknefdde3" + + "fg11amb5gzdb4wi9bya3kc6lra.w3.mag.keio.ac.jp" + ) + end + + it "should convert " + + "'www.ほんとうにながいわけのわからないどめいんめいのらべるまだながくしないとたりない.w3.mag.keio.ac.jp' " + + "correctly" do + expect(Addressable::IDNA.to_ascii( + "www.\343\201\273\343\202\223\343\201\250\343\201\206\343\201\253\343" + + "\201\252\343\201\213\343\202\231\343\201\204\343\202\217\343\201\221" + + "\343\201\256\343\202\217\343\201\213\343\202\211\343\201\252\343\201" + + "\204\343\201\250\343\202\231\343\202\201\343\201\204\343\202\223\343" + + "\202\201\343\201\204\343\201\256\343\202\211\343\201\270\343\202\231" + + "\343\202\213\343\201\276\343\201\237\343\202\231\343\201\252\343\201" + + "\213\343\202\231\343\201\217\343\201\227\343\201\252\343\201\204\343" + + "\201\250\343\201\237\343\202\212\343\201\252\343\201\204." + + "w3.mag.keio.ac.jp" + )).to eq( + "www.xn--n8jaaaaai5bhf7as8fsfk3jnknefdde3" + + "fg11amb5gzdb4wi9bya3kc6lra.w3.mag.keio.ac.jp" + ) + end + + it "should convert '点心和烤鸭.w3.mag.keio.ac.jp' correctly" do + expect(Addressable::IDNA.to_ascii( + "点心和烤鸭.w3.mag.keio.ac.jp" + )).to eq("xn--0trv4xfvn8el34t.w3.mag.keio.ac.jp") + end + + it "should convert '가각갂갃간갅갆갇갈갉힢힣.com' correctly" do + expect(Addressable::IDNA.to_ascii( + "가각갂갃간갅갆갇갈갉힢힣.com" + )).to eq("xn--o39acdefghijk5883jma.com") + end + + it "should convert " + + "'\347\242\274\346\250\231\346\272\226\350" + + "\220\254\345\234\213\347\242\274.com' correctly" do + expect(Addressable::IDNA.to_ascii( + "\347\242\274\346\250\231\346\272\226\350" + + "\220\254\345\234\213\347\242\274.com" + )).to eq("xn--9cs565brid46mda086o.com") + end + + it "should convert 'リ宠퐱〹.com' correctly" do + expect(Addressable::IDNA.to_ascii( + "\357\276\230\345\256\240\355\220\261\343\200\271.com" + )).to eq("xn--eek174hoxfpr4k.com") + end + + it "should convert 'リ宠퐱卄.com' correctly" do + expect(Addressable::IDNA.to_ascii( + "\343\203\252\345\256\240\355\220\261\345\215\204.com" + )).to eq("xn--eek174hoxfpr4k.com") + end + + it "should convert 'ᆵ' correctly" do + expect(Addressable::IDNA.to_ascii( + "\341\206\265" + )).to eq("xn--4ud") + end + + it "should convert 'ᆵ' correctly" do + expect(Addressable::IDNA.to_ascii( + "\357\276\257" + )).to eq("xn--4ud") + end + + it "should convert '🌹🌹🌹.ws' correctly" do + expect(Addressable::IDNA.to_ascii( + "\360\237\214\271\360\237\214\271\360\237\214\271.ws" + )).to eq("xn--2h8haa.ws") + end + + it "should handle two adjacent '.'s correctly" do + expect(Addressable::IDNA.to_ascii( + "example..host" + )).to eq("example..host") + end +end + +shared_examples_for "converting from ASCII to unicode" do + long = 'AcinusFallumTrompetumNullunCreditumVisumEstAtCuadLongumEtCefallum.com' + it "should convert '#{long}' correctly" do + expect(Addressable::IDNA.to_unicode(long)).to eq(long) + end + + it "should return the identity conversion when punycode decode fails" do + expect(Addressable::IDNA.to_unicode("xn--zckp1cyg1.sblo.jp")).to eq( + "xn--zckp1cyg1.sblo.jp") + end + + it "should return the identity conversion when the ACE prefix has no suffix" do + expect(Addressable::IDNA.to_unicode("xn--...-")).to eq("xn--...-") + end + + it "should convert 'www.google.com' correctly" do + expect(Addressable::IDNA.to_unicode("www.google.com")).to eq( + "www.google.com") + end + + it "should convert 'www.詹姆斯.com' correctly" do + expect(Addressable::IDNA.to_unicode( + "www.xn--8ws00zhy3a.com" + )).to eq("www.詹姆斯.com") + end + + it "should convert '詹姆斯.com' correctly" do + expect(Addressable::IDNA.to_unicode( + "xn--8ws00zhy3a.com" + )).to eq("詹姆斯.com") + end + + it "should convert 'www.iñtërnâtiônàlizætiøn.com' correctly" do + expect(Addressable::IDNA.to_unicode( + "www.xn--itrntinliztin-vdb0a5exd8ewcye.com" + )).to eq("www.iñtërnâtiônàlizætiøn.com") + end + + it "should convert 'iñtërnâtiônàlizætiøn.com' correctly" do + expect(Addressable::IDNA.to_unicode( + "xn--itrntinliztin-vdb0a5exd8ewcye.com" + )).to eq("iñtërnâtiônàlizætiøn.com") + end + + it "should convert " + + "'www.ほんとうにながいわけのわからないどめいんめいのらべるまだながくしないとたりない.w3.mag.keio.ac.jp' " + + "correctly" do + expect(Addressable::IDNA.to_unicode( + "www.xn--n8jaaaaai5bhf7as8fsfk3jnknefdde3" + + "fg11amb5gzdb4wi9bya3kc6lra.w3.mag.keio.ac.jp" + )).to eq( + "www.ほんとうにながいわけのわからないどめいんめいのらべるまだながくしないとたりない.w3.mag.keio.ac.jp" + ) + end + + it "should convert '点心和烤鸭.w3.mag.keio.ac.jp' correctly" do + expect(Addressable::IDNA.to_unicode( + "xn--0trv4xfvn8el34t.w3.mag.keio.ac.jp" + )).to eq("点心和烤鸭.w3.mag.keio.ac.jp") + end + + it "should convert '가각갂갃간갅갆갇갈갉힢힣.com' correctly" do + expect(Addressable::IDNA.to_unicode( + "xn--o39acdefghijk5883jma.com" + )).to eq("가각갂갃간갅갆갇갈갉힢힣.com") + end + + it "should convert " + + "'\347\242\274\346\250\231\346\272\226\350" + + "\220\254\345\234\213\347\242\274.com' correctly" do + expect(Addressable::IDNA.to_unicode( + "xn--9cs565brid46mda086o.com" + )).to eq( + "\347\242\274\346\250\231\346\272\226\350" + + "\220\254\345\234\213\347\242\274.com" + ) + end + + it "should convert 'リ宠퐱卄.com' correctly" do + expect(Addressable::IDNA.to_unicode( + "xn--eek174hoxfpr4k.com" + )).to eq("\343\203\252\345\256\240\355\220\261\345\215\204.com") + end + + it "should convert 'ᆵ' correctly" do + expect(Addressable::IDNA.to_unicode( + "xn--4ud" + )).to eq("\341\206\265") + end + + it "should convert '🌹🌹🌹.ws' correctly" do + expect(Addressable::IDNA.to_unicode( + "xn--2h8haa.ws" + )).to eq("\360\237\214\271\360\237\214\271\360\237\214\271.ws") + end + + it "should handle two adjacent '.'s correctly" do + expect(Addressable::IDNA.to_unicode( + "example..host" + )).to eq("example..host") + end + + it "should normalize 'string' correctly" do + expect(Addressable::IDNA.unicode_normalize_kc(:'string')).to eq("string") + expect(Addressable::IDNA.unicode_normalize_kc("string")).to eq("string") + end +end + +describe Addressable::IDNA, "when using the pure-Ruby implementation" do + before do + Addressable.send(:remove_const, :IDNA) + load "addressable/idna/pure.rb" + end + + it_should_behave_like "converting from unicode to ASCII" + it_should_behave_like "converting from ASCII to unicode" + + begin + require "fiber" + + it "should not blow up inside fibers" do + f = Fiber.new do + Addressable.send(:remove_const, :IDNA) + load "addressable/idna/pure.rb" + end + f.resume + end + rescue LoadError + # Fibers aren't supported in this version of Ruby, skip this test. + warn('Fibers unsupported.') + end +end + +begin + require "idn" + + describe Addressable::IDNA, "when using the native-code implementation" do + before do + Addressable.send(:remove_const, :IDNA) + load "addressable/idna/native.rb" + end + + it_should_behave_like "converting from unicode to ASCII" + it_should_behave_like "converting from ASCII to unicode" + end +rescue LoadError + # Cannot test the native implementation without libidn support. + warn('Could not load native IDN implementation.') +end diff --git a/path/ruby/2.6.0/gems/addressable-2.7.0/spec/addressable/net_http_compat_spec.rb b/path/ruby/2.6.0/gems/addressable-2.7.0/spec/addressable/net_http_compat_spec.rb new file mode 100644 index 00000000..8663a867 --- /dev/null +++ b/path/ruby/2.6.0/gems/addressable-2.7.0/spec/addressable/net_http_compat_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +# coding: utf-8 +# Copyright (C) Bob Aman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +require "spec_helper" + +require "addressable/uri" +require "net/http" + +describe Net::HTTP do + it "should be compatible with Addressable" do + response_body = + Net::HTTP.get(Addressable::URI.parse('http://www.google.com/')) + expect(response_body).not_to be_nil + end +end diff --git a/path/ruby/2.6.0/gems/addressable-2.7.0/spec/addressable/rack_mount_compat_spec.rb b/path/ruby/2.6.0/gems/addressable-2.7.0/spec/addressable/rack_mount_compat_spec.rb new file mode 100644 index 00000000..7b02cb76 --- /dev/null +++ b/path/ruby/2.6.0/gems/addressable-2.7.0/spec/addressable/rack_mount_compat_spec.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +# coding: utf-8 +# Copyright (C) Bob Aman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +require "spec_helper" + +require "addressable/uri" +require "addressable/template" +require "rack/mount" + +describe Rack::Mount do + let(:app_one) do + proc { |env| [200, {'Content-Type' => 'text/plain'}, 'Route 1'] } + end + let(:app_two) do + proc { |env| [200, {'Content-Type' => 'text/plain'}, 'Route 2'] } + end + let(:app_three) do + proc { |env| [200, {'Content-Type' => 'text/plain'}, 'Route 3'] } + end + let(:routes) do + s = Rack::Mount::RouteSet.new do |set| + set.add_route(app_one, { + :request_method => 'GET', + :path_info => Addressable::Template.new('/one/{id}/') + }, {:id => 'unidentified'}, :one) + set.add_route(app_two, { + :request_method => 'GET', + :path_info => Addressable::Template.new('/two/') + }, {:id => 'unidentified'}, :two) + set.add_route(app_three, { + :request_method => 'GET', + :path_info => Addressable::Template.new('/three/{id}/').to_regexp + }, {:id => 'unidentified'}, :three) + end + s.rehash + s + end + + it "should generate from routes with Addressable::Template" do + path, _ = routes.generate(:path_info, :one, {:id => '123'}) + expect(path).to eq '/one/123/' + end + + it "should generate from routes with Addressable::Template using defaults" do + path, _ = routes.generate(:path_info, :one, {}) + expect(path).to eq '/one/unidentified/' + end + + it "should recognize routes with Addressable::Template" do + request = Rack::Request.new( + 'REQUEST_METHOD' => 'GET', + 'PATH_INFO' => '/one/123/' + ) + route, _, params = routes.recognize(request) + expect(route).not_to be_nil + expect(route.app).to eq app_one + expect(params).to eq({id: '123'}) + end + + it "should generate from routes with Addressable::Template" do + path, _ = routes.generate(:path_info, :two, {:id => '654'}) + expect(path).to eq '/two/' + end + + it "should generate from routes with Addressable::Template using defaults" do + path, _ = routes.generate(:path_info, :two, {}) + expect(path).to eq '/two/' + end + + it "should recognize routes with Addressable::Template" do + request = Rack::Request.new( + 'REQUEST_METHOD' => 'GET', + 'PATH_INFO' => '/two/' + ) + route, _, params = routes.recognize(request) + expect(route).not_to be_nil + expect(route.app).to eq app_two + expect(params).to eq({id: 'unidentified'}) + end + + it "should recognize routes with derived Regexp" do + request = Rack::Request.new( + 'REQUEST_METHOD' => 'GET', + 'PATH_INFO' => '/three/789/' + ) + route, _, params = routes.recognize(request) + expect(route).not_to be_nil + expect(route.app).to eq app_three + expect(params).to eq({id: '789'}) + end +end diff --git a/path/ruby/2.6.0/gems/addressable-2.7.0/spec/addressable/security_spec.rb b/path/ruby/2.6.0/gems/addressable-2.7.0/spec/addressable/security_spec.rb new file mode 100644 index 00000000..601e8088 --- /dev/null +++ b/path/ruby/2.6.0/gems/addressable-2.7.0/spec/addressable/security_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +# coding: utf-8 +# Copyright (C) Bob Aman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +require "spec_helper" + +require "addressable/uri" + +describe Addressable::URI, "when created with a URI known to cause crashes " + + "in certain browsers" do + it "should parse correctly" do + uri = Addressable::URI.parse('%%30%30') + expect(uri.path).to eq('%%30%30') + expect(uri.normalize.path).to eq('%2500') + end + + it "should parse correctly as a full URI" do + uri = Addressable::URI.parse('http://www.example.com/%%30%30') + expect(uri.path).to eq('/%%30%30') + expect(uri.normalize.path).to eq('/%2500') + end +end + +describe Addressable::URI, "when created with a URI known to cause crashes " + + "in certain browsers" do + it "should parse correctly" do + uri = Addressable::URI.parse('لُصّبُلُلصّبُررً ॣ ॣh ॣ ॣ 冗') + expect(uri.path).to eq('لُصّبُلُلصّبُررً ॣ ॣh ॣ ॣ 冗') + expect(uri.normalize.path).to eq( + '%D9%84%D9%8F%D8%B5%D9%91%D8%A8%D9%8F%D9%84%D9%8F%D9%84%D8%B5%D9%91' + + '%D8%A8%D9%8F%D8%B1%D8%B1%D9%8B%20%E0%A5%A3%20%E0%A5%A3h%20%E0%A5' + + '%A3%20%E0%A5%A3%20%E5%86%97' + ) + end + + it "should parse correctly as a full URI" do + uri = Addressable::URI.parse('http://www.example.com/لُصّبُلُلصّبُررً ॣ ॣh ॣ ॣ 冗') + expect(uri.path).to eq('/لُصّبُلُلصّبُررً ॣ ॣh ॣ ॣ 冗') + expect(uri.normalize.path).to eq( + '/%D9%84%D9%8F%D8%B5%D9%91%D8%A8%D9%8F%D9%84%D9%8F%D9%84%D8%B5%D9%91' + + '%D8%A8%D9%8F%D8%B1%D8%B1%D9%8B%20%E0%A5%A3%20%E0%A5%A3h%20%E0%A5' + + '%A3%20%E0%A5%A3%20%E5%86%97' + ) + end +end diff --git a/path/ruby/2.6.0/gems/addressable-2.7.0/spec/addressable/template_spec.rb b/path/ruby/2.6.0/gems/addressable-2.7.0/spec/addressable/template_spec.rb new file mode 100644 index 00000000..a0191652 --- /dev/null +++ b/path/ruby/2.6.0/gems/addressable-2.7.0/spec/addressable/template_spec.rb @@ -0,0 +1,1451 @@ +# frozen_string_literal: true + +# coding: utf-8 +# Copyright (C) Bob Aman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +require "spec_helper" + +require "bigdecimal" +require "addressable/template" + +shared_examples_for 'expands' do |tests| + tests.each do |template, expansion| + exp = expansion.is_a?(Array) ? expansion.first : expansion + it "#{template} to #{exp}" do + tmpl = Addressable::Template.new(template).expand(subject) + if expansion.is_a?(Array) + expect(expansion.any?{|i| i == tmpl.to_str}).to be true + else + expect(tmpl.to_str).to eq(expansion) + end + end + end +end + +describe "eql?" do + let(:template) { Addressable::Template.new('https://www.example.com/{foo}') } + it 'is equal when the pattern matches' do + other_template = Addressable::Template.new('https://www.example.com/{foo}') + expect(template).to be_eql(other_template) + expect(other_template).to be_eql(template) + end + it 'is not equal when the pattern differs' do + other_template = Addressable::Template.new('https://www.example.com/{bar}') + expect(template).to_not be_eql(other_template) + expect(other_template).to_not be_eql(template) + end + it 'is not equal to non-templates' do + uri = 'https://www.example.com/foo/bar' + addressable_template = Addressable::Template.new uri + addressable_uri = Addressable::URI.parse uri + expect(addressable_template).to_not be_eql(addressable_uri) + expect(addressable_uri).to_not be_eql(addressable_template) + end +end + +describe "==" do + let(:template) { Addressable::Template.new('https://www.example.com/{foo}') } + it 'is equal when the pattern matches' do + other_template = Addressable::Template.new('https://www.example.com/{foo}') + expect(template).to eq other_template + expect(other_template).to eq template + end + it 'is not equal when the pattern differs' do + other_template = Addressable::Template.new('https://www.example.com/{bar}') + expect(template).not_to eq other_template + expect(other_template).not_to eq template + end + it 'is not equal to non-templates' do + uri = 'https://www.example.com/foo/bar' + addressable_template = Addressable::Template.new uri + addressable_uri = Addressable::URI.parse uri + expect(addressable_template).not_to eq addressable_uri + expect(addressable_uri).not_to eq addressable_template + end +end + +describe "Type conversion" do + subject { + { + :var => true, + :hello => 1234, + :nothing => nil, + :sym => :symbolic, + :decimal => BigDecimal('1') + } + } + + it_behaves_like 'expands', { + '{var}' => 'true', + '{hello}' => '1234', + '{nothing}' => '', + '{sym}' => 'symbolic', + '{decimal}' => RUBY_VERSION < '2.4.0' ? '0.1E1' : '0.1e1' + } +end + +describe "Level 1:" do + subject { + {:var => "value", :hello => "Hello World!"} + } + it_behaves_like 'expands', { + '{var}' => 'value', + '{hello}' => 'Hello%20World%21' + } +end + +describe "Level 2" do + subject { + { + :var => "value", + :hello => "Hello World!", + :path => "/foo/bar" + } + } + context "Operator +:" do + it_behaves_like 'expands', { + '{+var}' => 'value', + '{+hello}' => 'Hello%20World!', + '{+path}/here' => '/foo/bar/here', + 'here?ref={+path}' => 'here?ref=/foo/bar' + } + end + context "Operator #:" do + it_behaves_like 'expands', { + 'X{#var}' => 'X#value', + 'X{#hello}' => 'X#Hello%20World!' + } + end +end + +describe "Level 3" do + subject { + { + :var => "value", + :hello => "Hello World!", + :empty => "", + :path => "/foo/bar", + :x => "1024", + :y => "768" + } + } + context "Operator nil (multiple vars):" do + it_behaves_like 'expands', { + 'map?{x,y}' => 'map?1024,768', + '{x,hello,y}' => '1024,Hello%20World%21,768' + } + end + context "Operator + (multiple vars):" do + it_behaves_like 'expands', { + '{+x,hello,y}' => '1024,Hello%20World!,768', + '{+path,x}/here' => '/foo/bar,1024/here' + } + end + context "Operator # (multiple vars):" do + it_behaves_like 'expands', { + '{#x,hello,y}' => '#1024,Hello%20World!,768', + '{#path,x}/here' => '#/foo/bar,1024/here' + } + end + context "Operator ." do + it_behaves_like 'expands', { + 'X{.var}' => 'X.value', + 'X{.x,y}' => 'X.1024.768' + } + end + context "Operator /" do + it_behaves_like 'expands', { + '{/var}' => '/value', + '{/var,x}/here' => '/value/1024/here' + } + end + context "Operator ;" do + it_behaves_like 'expands', { + '{;x,y}' => ';x=1024;y=768', + '{;x,y,empty}' => ';x=1024;y=768;empty' + } + end + context "Operator ?" do + it_behaves_like 'expands', { + '{?x,y}' => '?x=1024&y=768', + '{?x,y,empty}' => '?x=1024&y=768&empty=' + } + end + context "Operator &" do + it_behaves_like 'expands', { + '?fixed=yes{&x}' => '?fixed=yes&x=1024', + '{&x,y,empty}' => '&x=1024&y=768&empty=' + } + end +end + +describe "Level 4" do + subject { + { + :var => "value", + :hello => "Hello World!", + :path => "/foo/bar", + :semi => ";", + :list => %w(red green blue), + :keys => {"semi" => ';', "dot" => '.', "comma" => ','} + } + } + context "Expansion with value modifiers" do + it_behaves_like 'expands', { + '{var:3}' => 'val', + '{var:30}' => 'value', + '{list}' => 'red,green,blue', + '{list*}' => 'red,green,blue', + '{keys}' => [ + 'semi,%3B,dot,.,comma,%2C', + 'dot,.,semi,%3B,comma,%2C', + 'comma,%2C,semi,%3B,dot,.', + 'semi,%3B,comma,%2C,dot,.', + 'dot,.,comma,%2C,semi,%3B', + 'comma,%2C,dot,.,semi,%3B' + ], + '{keys*}' => [ + 'semi=%3B,dot=.,comma=%2C', + 'dot=.,semi=%3B,comma=%2C', + 'comma=%2C,semi=%3B,dot=.', + 'semi=%3B,comma=%2C,dot=.', + 'dot=.,comma=%2C,semi=%3B', + 'comma=%2C,dot=.,semi=%3B' + ] + } + end + context "Operator + with value modifiers" do + it_behaves_like 'expands', { + '{+path:6}/here' => '/foo/b/here', + '{+list}' => 'red,green,blue', + '{+list*}' => 'red,green,blue', + '{+keys}' => [ + 'semi,;,dot,.,comma,,', + 'dot,.,semi,;,comma,,', + 'comma,,,semi,;,dot,.', + 'semi,;,comma,,,dot,.', + 'dot,.,comma,,,semi,;', + 'comma,,,dot,.,semi,;' + ], + '{+keys*}' => [ + 'semi=;,dot=.,comma=,', + 'dot=.,semi=;,comma=,', + 'comma=,,semi=;,dot=.', + 'semi=;,comma=,,dot=.', + 'dot=.,comma=,,semi=;', + 'comma=,,dot=.,semi=;' + ] + } + end + context "Operator # with value modifiers" do + it_behaves_like 'expands', { + '{#path:6}/here' => '#/foo/b/here', + '{#list}' => '#red,green,blue', + '{#list*}' => '#red,green,blue', + '{#keys}' => [ + '#semi,;,dot,.,comma,,', + '#dot,.,semi,;,comma,,', + '#comma,,,semi,;,dot,.', + '#semi,;,comma,,,dot,.', + '#dot,.,comma,,,semi,;', + '#comma,,,dot,.,semi,;' + ], + '{#keys*}' => [ + '#semi=;,dot=.,comma=,', + '#dot=.,semi=;,comma=,', + '#comma=,,semi=;,dot=.', + '#semi=;,comma=,,dot=.', + '#dot=.,comma=,,semi=;', + '#comma=,,dot=.,semi=;' + ] + } + end + context "Operator . with value modifiers" do + it_behaves_like 'expands', { + 'X{.var:3}' => 'X.val', + 'X{.list}' => 'X.red,green,blue', + 'X{.list*}' => 'X.red.green.blue', + 'X{.keys}' => [ + 'X.semi,%3B,dot,.,comma,%2C', + 'X.dot,.,semi,%3B,comma,%2C', + 'X.comma,%2C,semi,%3B,dot,.', + 'X.semi,%3B,comma,%2C,dot,.', + 'X.dot,.,comma,%2C,semi,%3B', + 'X.comma,%2C,dot,.,semi,%3B' + ], + 'X{.keys*}' => [ + 'X.semi=%3B.dot=..comma=%2C', + 'X.dot=..semi=%3B.comma=%2C', + 'X.comma=%2C.semi=%3B.dot=.', + 'X.semi=%3B.comma=%2C.dot=.', + 'X.dot=..comma=%2C.semi=%3B', + 'X.comma=%2C.dot=..semi=%3B' + ] + } + end + context "Operator / with value modifiers" do + it_behaves_like 'expands', { + '{/var:1,var}' => '/v/value', + '{/list}' => '/red,green,blue', + '{/list*}' => '/red/green/blue', + '{/list*,path:4}' => '/red/green/blue/%2Ffoo', + '{/keys}' => [ + '/semi,%3B,dot,.,comma,%2C', + '/dot,.,semi,%3B,comma,%2C', + '/comma,%2C,semi,%3B,dot,.', + '/semi,%3B,comma,%2C,dot,.', + '/dot,.,comma,%2C,semi,%3B', + '/comma,%2C,dot,.,semi,%3B' + ], + '{/keys*}' => [ + '/semi=%3B/dot=./comma=%2C', + '/dot=./semi=%3B/comma=%2C', + '/comma=%2C/semi=%3B/dot=.', + '/semi=%3B/comma=%2C/dot=.', + '/dot=./comma=%2C/semi=%3B', + '/comma=%2C/dot=./semi=%3B' + ] + } + end + context "Operator ; with value modifiers" do + it_behaves_like 'expands', { + '{;hello:5}' => ';hello=Hello', + '{;list}' => ';list=red,green,blue', + '{;list*}' => ';list=red;list=green;list=blue', + '{;keys}' => [ + ';keys=semi,%3B,dot,.,comma,%2C', + ';keys=dot,.,semi,%3B,comma,%2C', + ';keys=comma,%2C,semi,%3B,dot,.', + ';keys=semi,%3B,comma,%2C,dot,.', + ';keys=dot,.,comma,%2C,semi,%3B', + ';keys=comma,%2C,dot,.,semi,%3B' + ], + '{;keys*}' => [ + ';semi=%3B;dot=.;comma=%2C', + ';dot=.;semi=%3B;comma=%2C', + ';comma=%2C;semi=%3B;dot=.', + ';semi=%3B;comma=%2C;dot=.', + ';dot=.;comma=%2C;semi=%3B', + ';comma=%2C;dot=.;semi=%3B' + ] + } + end + context "Operator ? with value modifiers" do + it_behaves_like 'expands', { + '{?var:3}' => '?var=val', + '{?list}' => '?list=red,green,blue', + '{?list*}' => '?list=red&list=green&list=blue', + '{?keys}' => [ + '?keys=semi,%3B,dot,.,comma,%2C', + '?keys=dot,.,semi,%3B,comma,%2C', + '?keys=comma,%2C,semi,%3B,dot,.', + '?keys=semi,%3B,comma,%2C,dot,.', + '?keys=dot,.,comma,%2C,semi,%3B', + '?keys=comma,%2C,dot,.,semi,%3B' + ], + '{?keys*}' => [ + '?semi=%3B&dot=.&comma=%2C', + '?dot=.&semi=%3B&comma=%2C', + '?comma=%2C&semi=%3B&dot=.', + '?semi=%3B&comma=%2C&dot=.', + '?dot=.&comma=%2C&semi=%3B', + '?comma=%2C&dot=.&semi=%3B' + ] + } + end + context "Operator & with value modifiers" do + it_behaves_like 'expands', { + '{&var:3}' => '&var=val', + '{&list}' => '&list=red,green,blue', + '{&list*}' => '&list=red&list=green&list=blue', + '{&keys}' => [ + '&keys=semi,%3B,dot,.,comma,%2C', + '&keys=dot,.,semi,%3B,comma,%2C', + '&keys=comma,%2C,semi,%3B,dot,.', + '&keys=semi,%3B,comma,%2C,dot,.', + '&keys=dot,.,comma,%2C,semi,%3B', + '&keys=comma,%2C,dot,.,semi,%3B' + ], + '{&keys*}' => [ + '&semi=%3B&dot=.&comma=%2C', + '&dot=.&semi=%3B&comma=%2C', + '&comma=%2C&semi=%3B&dot=.', + '&semi=%3B&comma=%2C&dot=.', + '&dot=.&comma=%2C&semi=%3B', + '&comma=%2C&dot=.&semi=%3B' + ] + } + end +end +describe "Modifiers" do + subject { + { + :var => "value", + :semi => ";", + :year => %w(1965 2000 2012), + :dom => %w(example com) + } + } + context "length" do + it_behaves_like 'expands', { + '{var:3}' => 'val', + '{var:30}' => 'value', + '{var}' => 'value', + '{semi}' => '%3B', + '{semi:2}' => '%3B' + } + end + context "explode" do + it_behaves_like 'expands', { + 'find{?year*}' => 'find?year=1965&year=2000&year=2012', + 'www{.dom*}' => 'www.example.com', + } + end +end +describe "Expansion" do + subject { + { + :count => ["one", "two", "three"], + :dom => ["example", "com"], + :dub => "me/too", + :hello => "Hello World!", + :half => "50%", + :var => "value", + :who => "fred", + :base => "http://example.com/home/", + :path => "/foo/bar", + :list => ["red", "green", "blue"], + :keys => {"semi" => ";","dot" => ".","comma" => ","}, + :v => "6", + :x => "1024", + :y => "768", + :empty => "", + :empty_keys => {}, + :undef => nil + } + } + context "concatenation" do + it_behaves_like 'expands', { + '{count}' => 'one,two,three', + '{count*}' => 'one,two,three', + '{/count}' => '/one,two,three', + '{/count*}' => '/one/two/three', + '{;count}' => ';count=one,two,three', + '{;count*}' => ';count=one;count=two;count=three', + '{?count}' => '?count=one,two,three', + '{?count*}' => '?count=one&count=two&count=three', + '{&count*}' => '&count=one&count=two&count=three' + } + end + context "simple expansion" do + it_behaves_like 'expands', { + '{var}' => 'value', + '{hello}' => 'Hello%20World%21', + '{half}' => '50%25', + 'O{empty}X' => 'OX', + 'O{undef}X' => 'OX', + '{x,y}' => '1024,768', + '{x,hello,y}' => '1024,Hello%20World%21,768', + '?{x,empty}' => '?1024,', + '?{x,undef}' => '?1024', + '?{undef,y}' => '?768', + '{var:3}' => 'val', + '{var:30}' => 'value', + '{list}' => 'red,green,blue', + '{list*}' => 'red,green,blue', + '{keys}' => [ + 'semi,%3B,dot,.,comma,%2C', + 'dot,.,semi,%3B,comma,%2C', + 'comma,%2C,semi,%3B,dot,.', + 'semi,%3B,comma,%2C,dot,.', + 'dot,.,comma,%2C,semi,%3B', + 'comma,%2C,dot,.,semi,%3B' + ], + '{keys*}' => [ + 'semi=%3B,dot=.,comma=%2C', + 'dot=.,semi=%3B,comma=%2C', + 'comma=%2C,semi=%3B,dot=.', + 'semi=%3B,comma=%2C,dot=.', + 'dot=.,comma=%2C,semi=%3B', + 'comma=%2C,dot=.,semi=%3B' + ] + } + end + context "reserved expansion (+)" do + it_behaves_like 'expands', { + '{+var}' => 'value', + '{+hello}' => 'Hello%20World!', + '{+half}' => '50%25', + '{base}index' => 'http%3A%2F%2Fexample.com%2Fhome%2Findex', + '{+base}index' => 'http://example.com/home/index', + 'O{+empty}X' => 'OX', + 'O{+undef}X' => 'OX', + '{+path}/here' => '/foo/bar/here', + 'here?ref={+path}' => 'here?ref=/foo/bar', + 'up{+path}{var}/here' => 'up/foo/barvalue/here', + '{+x,hello,y}' => '1024,Hello%20World!,768', + '{+path,x}/here' => '/foo/bar,1024/here', + '{+path:6}/here' => '/foo/b/here', + '{+list}' => 'red,green,blue', + '{+list*}' => 'red,green,blue', + '{+keys}' => [ + 'semi,;,dot,.,comma,,', + 'dot,.,semi,;,comma,,', + 'comma,,,semi,;,dot,.', + 'semi,;,comma,,,dot,.', + 'dot,.,comma,,,semi,;', + 'comma,,,dot,.,semi,;' + ], + '{+keys*}' => [ + 'semi=;,dot=.,comma=,', + 'dot=.,semi=;,comma=,', + 'comma=,,semi=;,dot=.', + 'semi=;,comma=,,dot=.', + 'dot=.,comma=,,semi=;', + 'comma=,,dot=.,semi=;' + ] + } + end + context "fragment expansion (#)" do + it_behaves_like 'expands', { + '{#var}' => '#value', + '{#hello}' => '#Hello%20World!', + '{#half}' => '#50%25', + 'foo{#empty}' => 'foo#', + 'foo{#undef}' => 'foo', + '{#x,hello,y}' => '#1024,Hello%20World!,768', + '{#path,x}/here' => '#/foo/bar,1024/here', + '{#path:6}/here' => '#/foo/b/here', + '{#list}' => '#red,green,blue', + '{#list*}' => '#red,green,blue', + '{#keys}' => [ + '#semi,;,dot,.,comma,,', + '#dot,.,semi,;,comma,,', + '#comma,,,semi,;,dot,.', + '#semi,;,comma,,,dot,.', + '#dot,.,comma,,,semi,;', + '#comma,,,dot,.,semi,;' + ], + '{#keys*}' => [ + '#semi=;,dot=.,comma=,', + '#dot=.,semi=;,comma=,', + '#comma=,,semi=;,dot=.', + '#semi=;,comma=,,dot=.', + '#dot=.,comma=,,semi=;', + '#comma=,,dot=.,semi=;' + ] + } + end + context "label expansion (.)" do + it_behaves_like 'expands', { + '{.who}' => '.fred', + '{.who,who}' => '.fred.fred', + '{.half,who}' => '.50%25.fred', + 'www{.dom*}' => 'www.example.com', + 'X{.var}' => 'X.value', + 'X{.empty}' => 'X.', + 'X{.undef}' => 'X', + 'X{.var:3}' => 'X.val', + 'X{.list}' => 'X.red,green,blue', + 'X{.list*}' => 'X.red.green.blue', + 'X{.keys}' => [ + 'X.semi,%3B,dot,.,comma,%2C', + 'X.dot,.,semi,%3B,comma,%2C', + 'X.comma,%2C,semi,%3B,dot,.', + 'X.semi,%3B,comma,%2C,dot,.', + 'X.dot,.,comma,%2C,semi,%3B', + 'X.comma,%2C,dot,.,semi,%3B' + ], + 'X{.keys*}' => [ + 'X.semi=%3B.dot=..comma=%2C', + 'X.dot=..semi=%3B.comma=%2C', + 'X.comma=%2C.semi=%3B.dot=.', + 'X.semi=%3B.comma=%2C.dot=.', + 'X.dot=..comma=%2C.semi=%3B', + 'X.comma=%2C.dot=..semi=%3B' + ], + 'X{.empty_keys}' => 'X', + 'X{.empty_keys*}' => 'X' + } + end + context "path expansion (/)" do + it_behaves_like 'expands', { + '{/who}' => '/fred', + '{/who,who}' => '/fred/fred', + '{/half,who}' => '/50%25/fred', + '{/who,dub}' => '/fred/me%2Ftoo', + '{/var}' => '/value', + '{/var,empty}' => '/value/', + '{/var,undef}' => '/value', + '{/var,x}/here' => '/value/1024/here', + '{/var:1,var}' => '/v/value', + '{/list}' => '/red,green,blue', + '{/list*}' => '/red/green/blue', + '{/list*,path:4}' => '/red/green/blue/%2Ffoo', + '{/keys}' => [ + '/semi,%3B,dot,.,comma,%2C', + '/dot,.,semi,%3B,comma,%2C', + '/comma,%2C,semi,%3B,dot,.', + '/semi,%3B,comma,%2C,dot,.', + '/dot,.,comma,%2C,semi,%3B', + '/comma,%2C,dot,.,semi,%3B' + ], + '{/keys*}' => [ + '/semi=%3B/dot=./comma=%2C', + '/dot=./semi=%3B/comma=%2C', + '/comma=%2C/semi=%3B/dot=.', + '/semi=%3B/comma=%2C/dot=.', + '/dot=./comma=%2C/semi=%3B', + '/comma=%2C/dot=./semi=%3B' + ] + } + end + context "path-style expansion (;)" do + it_behaves_like 'expands', { + '{;who}' => ';who=fred', + '{;half}' => ';half=50%25', + '{;empty}' => ';empty', + '{;v,empty,who}' => ';v=6;empty;who=fred', + '{;v,bar,who}' => ';v=6;who=fred', + '{;x,y}' => ';x=1024;y=768', + '{;x,y,empty}' => ';x=1024;y=768;empty', + '{;x,y,undef}' => ';x=1024;y=768', + '{;hello:5}' => ';hello=Hello', + '{;list}' => ';list=red,green,blue', + '{;list*}' => ';list=red;list=green;list=blue', + '{;keys}' => [ + ';keys=semi,%3B,dot,.,comma,%2C', + ';keys=dot,.,semi,%3B,comma,%2C', + ';keys=comma,%2C,semi,%3B,dot,.', + ';keys=semi,%3B,comma,%2C,dot,.', + ';keys=dot,.,comma,%2C,semi,%3B', + ';keys=comma,%2C,dot,.,semi,%3B' + ], + '{;keys*}' => [ + ';semi=%3B;dot=.;comma=%2C', + ';dot=.;semi=%3B;comma=%2C', + ';comma=%2C;semi=%3B;dot=.', + ';semi=%3B;comma=%2C;dot=.', + ';dot=.;comma=%2C;semi=%3B', + ';comma=%2C;dot=.;semi=%3B' + ] + } + end + context "form query expansion (?)" do + it_behaves_like 'expands', { + '{?who}' => '?who=fred', + '{?half}' => '?half=50%25', + '{?x,y}' => '?x=1024&y=768', + '{?x,y,empty}' => '?x=1024&y=768&empty=', + '{?x,y,undef}' => '?x=1024&y=768', + '{?var:3}' => '?var=val', + '{?list}' => '?list=red,green,blue', + '{?list*}' => '?list=red&list=green&list=blue', + '{?keys}' => [ + '?keys=semi,%3B,dot,.,comma,%2C', + '?keys=dot,.,semi,%3B,comma,%2C', + '?keys=comma,%2C,semi,%3B,dot,.', + '?keys=semi,%3B,comma,%2C,dot,.', + '?keys=dot,.,comma,%2C,semi,%3B', + '?keys=comma,%2C,dot,.,semi,%3B' + ], + '{?keys*}' => [ + '?semi=%3B&dot=.&comma=%2C', + '?dot=.&semi=%3B&comma=%2C', + '?comma=%2C&semi=%3B&dot=.', + '?semi=%3B&comma=%2C&dot=.', + '?dot=.&comma=%2C&semi=%3B', + '?comma=%2C&dot=.&semi=%3B' + ] + } + end + context "form query expansion (&)" do + it_behaves_like 'expands', { + '{&who}' => '&who=fred', + '{&half}' => '&half=50%25', + '?fixed=yes{&x}' => '?fixed=yes&x=1024', + '{&x,y,empty}' => '&x=1024&y=768&empty=', + '{&x,y,undef}' => '&x=1024&y=768', + '{&var:3}' => '&var=val', + '{&list}' => '&list=red,green,blue', + '{&list*}' => '&list=red&list=green&list=blue', + '{&keys}' => [ + '&keys=semi,%3B,dot,.,comma,%2C', + '&keys=dot,.,semi,%3B,comma,%2C', + '&keys=comma,%2C,semi,%3B,dot,.', + '&keys=semi,%3B,comma,%2C,dot,.', + '&keys=dot,.,comma,%2C,semi,%3B', + '&keys=comma,%2C,dot,.,semi,%3B' + ], + '{&keys*}' => [ + '&semi=%3B&dot=.&comma=%2C', + '&dot=.&semi=%3B&comma=%2C', + '&comma=%2C&semi=%3B&dot=.', + '&semi=%3B&comma=%2C&dot=.', + '&dot=.&comma=%2C&semi=%3B', + '&comma=%2C&dot=.&semi=%3B' + ] + } + end + context "non-string key in match data" do + subject {Addressable::Template.new("http://example.com/{one}")} + + it "raises TypeError" do + expect { subject.expand(Object.new => "1") }.to raise_error TypeError + end + end +end + +class ExampleTwoProcessor + def self.restore(name, value) + return value.gsub(/-/, " ") if name == "query" + return value + end + + def self.match(name) + return ".*?" if name == "first" + return ".*" + end + def self.validate(name, value) + return !!(value =~ /^[\w ]+$/) if name == "query" + return true + end + + def self.transform(name, value) + return value.gsub(/ /, "+") if name == "query" + return value + end +end + +class DumbProcessor + def self.match(name) + return ".*?" if name == "first" + end +end + +describe Addressable::Template do + describe 'initialize' do + context 'with a non-string' do + it 'raises a TypeError' do + expect { Addressable::Template.new(nil) }.to raise_error(TypeError) + end + end + end + + describe 'freeze' do + subject { Addressable::Template.new("http://example.com/{first}/{+second}/") } + it 'freezes the template' do + expect(subject.freeze).to be_frozen + end + end + + describe "Matching" do + let(:uri){ + Addressable::URI.parse( + "http://example.com/search/an-example-search-query/" + ) + } + let(:uri2){ + Addressable::URI.parse("http://example.com/a/b/c/") + } + let(:uri3){ + Addressable::URI.parse("http://example.com/;a=1;b=2;c=3;first=foo") + } + let(:uri4){ + Addressable::URI.parse("http://example.com/?a=1&b=2&c=3&first=foo") + } + let(:uri5){ + "http://example.com/foo" + } + context "first uri with ExampleTwoProcessor" do + subject { + Addressable::Template.new( + "http://example.com/search/{query}/" + ).match(uri, ExampleTwoProcessor) + } + its(:variables){ should == ["query"] } + its(:captures){ should == ["an example search query"] } + end + + context "second uri with ExampleTwoProcessor" do + subject { + Addressable::Template.new( + "http://example.com/{first}/{+second}/" + ).match(uri2, ExampleTwoProcessor) + } + its(:variables){ should == ["first", "second"] } + its(:captures){ should == ["a", "b/c"] } + end + + context "second uri with DumbProcessor" do + subject { + Addressable::Template.new( + "http://example.com/{first}/{+second}/" + ).match(uri2, DumbProcessor) + } + its(:variables){ should == ["first", "second"] } + its(:captures){ should == ["a", "b/c"] } + end + + context "second uri" do + subject { + Addressable::Template.new( + "http://example.com/{first}{/second*}/" + ).match(uri2) + } + its(:variables){ should == ["first", "second"] } + its(:captures){ should == ["a", ["b","c"]] } + end + context "third uri" do + subject { + Addressable::Template.new( + "http://example.com/{;hash*,first}" + ).match(uri3) + } + its(:variables){ should == ["hash", "first"] } + its(:captures){ should == [ + {"a" => "1", "b" => "2", "c" => "3", "first" => "foo"}, nil] } + end + # Note that this expansion is impossible to revert deterministically - the + # * operator means first could have been a key of hash or a separate key. + # Semantically, a separate key is more likely, but both are possible. + context "fourth uri" do + subject { + Addressable::Template.new( + "http://example.com/{?hash*,first}" + ).match(uri4) + } + its(:variables){ should == ["hash", "first"] } + its(:captures){ should == [ + {"a" => "1", "b" => "2", "c" => "3", "first"=> "foo"}, nil] } + end + context "fifth uri" do + subject { + Addressable::Template.new( + "http://example.com/{path}{?hash*,first}" + ).match(uri5) + } + its(:variables){ should == ["path", "hash", "first"] } + its(:captures){ should == ["foo", nil, nil] } + end + end + + describe 'match' do + subject { Addressable::Template.new('http://example.com/first/second/') } + context 'when the URI is the same as the template' do + it 'returns the match data itself with an empty mapping' do + uri = Addressable::URI.parse('http://example.com/first/second/') + match_data = subject.match(uri) + expect(match_data).to be_an Addressable::Template::MatchData + expect(match_data.uri).to eq(uri) + expect(match_data.template).to eq(subject) + expect(match_data.mapping).to be_empty + expect(match_data.inspect).to be_an String + end + end + end + + describe "extract" do + let(:template) { + Addressable::Template.new( + "http://{host}{/segments*}/{?one,two,bogus}{#fragment}" + ) + } + let(:uri){ "http://example.com/a/b/c/?one=1&two=2#foo" } + let(:uri2){ "http://example.com/a/b/c/#foo" } + it "should be able to extract with queries" do + expect(template.extract(uri)).to eq({ + "host" => "example.com", + "segments" => %w(a b c), + "one" => "1", + "bogus" => nil, + "two" => "2", + "fragment" => "foo" + }) + end + it "should be able to extract without queries" do + expect(template.extract(uri2)).to eq({ + "host" => "example.com", + "segments" => %w(a b c), + "one" => nil, + "bogus" => nil, + "two" => nil, + "fragment" => "foo" + }) + end + + context "issue #137" do + subject { Addressable::Template.new('/path{?page,per_page}') } + + it "can match empty" do + data = subject.extract("/path") + expect(data["page"]).to eq(nil) + expect(data["per_page"]).to eq(nil) + expect(data.keys.sort).to eq(['page', 'per_page']) + end + + it "can match first var" do + data = subject.extract("/path?page=1") + expect(data["page"]).to eq("1") + expect(data["per_page"]).to eq(nil) + expect(data.keys.sort).to eq(['page', 'per_page']) + end + + it "can match second var" do + data = subject.extract("/path?per_page=1") + expect(data["page"]).to eq(nil) + expect(data["per_page"]).to eq("1") + expect(data.keys.sort).to eq(['page', 'per_page']) + end + + it "can match both vars" do + data = subject.extract("/path?page=2&per_page=1") + expect(data["page"]).to eq("2") + expect(data["per_page"]).to eq("1") + expect(data.keys.sort).to eq(['page', 'per_page']) + end + end + end + + describe "Partial expand with symbols" do + context "partial_expand with two simple values" do + subject { + Addressable::Template.new("http://example.com/{one}/{two}/") + } + it "builds a new pattern" do + expect(subject.partial_expand(:one => "1").pattern).to eq( + "http://example.com/1/{two}/" + ) + end + end + context "partial_expand query with missing param in middle" do + subject { + Addressable::Template.new("http://example.com/{?one,two,three}/") + } + it "builds a new pattern" do + expect(subject.partial_expand(:one => "1", :three => "3").pattern).to eq( + "http://example.com/?one=1{&two}&three=3/" + ) + end + end + context "partial_expand form style query with missing param at beginning" do + subject { + Addressable::Template.new("http://example.com/{?one,two}/") + } + it "builds a new pattern" do + expect(subject.partial_expand(:two => "2").pattern).to eq( + "http://example.com/?two=2{&one}/" + ) + end + end + context "issue #307 - partial_expand form query with nil params" do + subject do + Addressable::Template.new("http://example.com/{?one,two,three}/") + end + it "builds a new pattern with two=nil" do + expect(subject.partial_expand(two: nil).pattern).to eq( + "http://example.com/{?one}{&three}/" + ) + end + it "builds a new pattern with one=nil and two=nil" do + expect(subject.partial_expand(one: nil, two: nil).pattern).to eq( + "http://example.com/{?three}/" + ) + end + it "builds a new pattern with one=1 and two=nil" do + expect(subject.partial_expand(one: 1, two: nil).pattern).to eq( + "http://example.com/?one=1{&three}/" + ) + end + it "builds a new pattern with one=nil and two=2" do + expect(subject.partial_expand(one: nil, two: 2).pattern).to eq( + "http://example.com/?two=2{&three}/" + ) + end + it "builds a new pattern with one=nil" do + expect(subject.partial_expand(one: nil).pattern).to eq( + "http://example.com/{?two}{&three}/" + ) + end + end + context "partial_expand with query string" do + subject { + Addressable::Template.new("http://example.com/{?two,one}/") + } + it "builds a new pattern" do + expect(subject.partial_expand(:one => "1").pattern).to eq( + "http://example.com/?one=1{&two}/" + ) + end + end + context "partial_expand with path operator" do + subject { + Addressable::Template.new("http://example.com{/one,two}/") + } + it "builds a new pattern" do + expect(subject.partial_expand(:one => "1").pattern).to eq( + "http://example.com/1{/two}/" + ) + end + end + context "partial expand with unicode values" do + subject do + Addressable::Template.new("http://example.com/{resource}/{query}/") + end + it "normalizes unicode by default" do + template = subject.partial_expand("query" => "Cafe\u0301") + expect(template.pattern).to eq( + "http://example.com/{resource}/Caf%C3%A9/" + ) + end + + it "does not normalize unicode when byte semantics requested" do + template = subject.partial_expand({"query" => "Cafe\u0301"}, nil, false) + expect(template.pattern).to eq( + "http://example.com/{resource}/Cafe%CC%81/" + ) + end + end + end + describe "Partial expand with strings" do + context "partial_expand with two simple values" do + subject { + Addressable::Template.new("http://example.com/{one}/{two}/") + } + it "builds a new pattern" do + expect(subject.partial_expand("one" => "1").pattern).to eq( + "http://example.com/1/{two}/" + ) + end + end + context "partial_expand query with missing param in middle" do + subject { + Addressable::Template.new("http://example.com/{?one,two,three}/") + } + it "builds a new pattern" do + expect(subject.partial_expand("one" => "1", "three" => "3").pattern).to eq( + "http://example.com/?one=1{&two}&three=3/" + ) + end + end + context "partial_expand with query string" do + subject { + Addressable::Template.new("http://example.com/{?two,one}/") + } + it "builds a new pattern" do + expect(subject.partial_expand("one" => "1").pattern).to eq( + "http://example.com/?one=1{&two}/" + ) + end + end + context "partial_expand with path operator" do + subject { + Addressable::Template.new("http://example.com{/one,two}/") + } + it "builds a new pattern" do + expect(subject.partial_expand("one" => "1").pattern).to eq( + "http://example.com/1{/two}/" + ) + end + end + end + describe "Expand" do + context "expand with unicode values" do + subject do + Addressable::Template.new("http://example.com/search/{query}/") + end + it "normalizes unicode by default" do + uri = subject.expand("query" => "Cafe\u0301").to_str + expect(uri).to eq("http://example.com/search/Caf%C3%A9/") + end + + it "does not normalize unicode when byte semantics requested" do + uri = subject.expand({ "query" => "Cafe\u0301" }, nil, false).to_str + expect(uri).to eq("http://example.com/search/Cafe%CC%81/") + end + end + context "expand with a processor" do + subject { + Addressable::Template.new("http://example.com/search/{query}/") + } + it "processes spaces" do + expect(subject.expand({"query" => "an example search query"}, + ExampleTwoProcessor).to_str).to eq( + "http://example.com/search/an+example+search+query/" + ) + end + it "validates" do + expect{ + subject.expand({"query" => "Bogus!"}, + ExampleTwoProcessor).to_str + }.to raise_error(Addressable::Template::InvalidTemplateValueError) + end + end + context "partial_expand query with missing param in middle" do + subject { + Addressable::Template.new("http://example.com/{?one,two,three}/") + } + it "builds a new pattern" do + expect(subject.partial_expand("one" => "1", "three" => "3").pattern).to eq( + "http://example.com/?one=1{&two}&three=3/" + ) + end + end + context "partial_expand with query string" do + subject { + Addressable::Template.new("http://example.com/{?two,one}/") + } + it "builds a new pattern" do + expect(subject.partial_expand("one" => "1").pattern).to eq( + "http://example.com/?one=1{&two}/" + ) + end + end + context "partial_expand with path operator" do + subject { + Addressable::Template.new("http://example.com{/one,two}/") + } + it "builds a new pattern" do + expect(subject.partial_expand("one" => "1").pattern).to eq( + "http://example.com/1{/two}/" + ) + end + end + end + context "Matching with operators" do + describe "Level 1:" do + subject { Addressable::Template.new("foo{foo}/{bar}baz") } + it "can match" do + data = subject.match("foofoo/bananabaz") + expect(data.mapping["foo"]).to eq("foo") + expect(data.mapping["bar"]).to eq("banana") + end + it "can fail" do + expect(subject.match("bar/foo")).to be_nil + expect(subject.match("foobaz")).to be_nil + end + it "can match empty" do + data = subject.match("foo/baz") + expect(data.mapping["foo"]).to eq(nil) + expect(data.mapping["bar"]).to eq(nil) + end + it "lists vars" do + expect(subject.variables).to eq(["foo", "bar"]) + end + end + + describe "Level 2:" do + subject { Addressable::Template.new("foo{+foo}{#bar}baz") } + it "can match" do + data = subject.match("foo/test/banana#bazbaz") + expect(data.mapping["foo"]).to eq("/test/banana") + expect(data.mapping["bar"]).to eq("baz") + end + it "can match empty level 2 #" do + data = subject.match("foo/test/bananabaz") + expect(data.mapping["foo"]).to eq("/test/banana") + expect(data.mapping["bar"]).to eq(nil) + data = subject.match("foo/test/banana#baz") + expect(data.mapping["foo"]).to eq("/test/banana") + expect(data.mapping["bar"]).to eq("") + end + it "can match empty level 2 +" do + data = subject.match("foobaz") + expect(data.mapping["foo"]).to eq(nil) + expect(data.mapping["bar"]).to eq(nil) + data = subject.match("foo#barbaz") + expect(data.mapping["foo"]).to eq(nil) + expect(data.mapping["bar"]).to eq("bar") + end + it "lists vars" do + expect(subject.variables).to eq(["foo", "bar"]) + end + end + + describe "Level 3:" do + context "no operator" do + subject { Addressable::Template.new("foo{foo,bar}baz") } + it "can match" do + data = subject.match("foofoo,barbaz") + expect(data.mapping["foo"]).to eq("foo") + expect(data.mapping["bar"]).to eq("bar") + end + it "lists vars" do + expect(subject.variables).to eq(["foo", "bar"]) + end + end + context "+ operator" do + subject { Addressable::Template.new("foo{+foo,bar}baz") } + it "can match" do + data = subject.match("foofoo/bar,barbaz") + expect(data.mapping["bar"]).to eq("foo/bar,bar") + expect(data.mapping["foo"]).to eq("") + end + it "lists vars" do + expect(subject.variables).to eq(["foo", "bar"]) + end + end + context ". operator" do + subject { Addressable::Template.new("foo{.foo,bar}baz") } + it "can match" do + data = subject.match("foo.foo.barbaz") + expect(data.mapping["foo"]).to eq("foo") + expect(data.mapping["bar"]).to eq("bar") + end + it "lists vars" do + expect(subject.variables).to eq(["foo", "bar"]) + end + end + context "/ operator" do + subject { Addressable::Template.new("foo{/foo,bar}baz") } + it "can match" do + data = subject.match("foo/foo/barbaz") + expect(data.mapping["foo"]).to eq("foo") + expect(data.mapping["bar"]).to eq("bar") + end + it "lists vars" do + expect(subject.variables).to eq(["foo", "bar"]) + end + end + context "; operator" do + subject { Addressable::Template.new("foo{;foo,bar,baz}baz") } + it "can match" do + data = subject.match("foo;foo=bar%20baz;bar=foo;bazbaz") + expect(data.mapping["foo"]).to eq("bar baz") + expect(data.mapping["bar"]).to eq("foo") + expect(data.mapping["baz"]).to eq("") + end + it "lists vars" do + expect(subject.variables).to eq(%w(foo bar baz)) + end + end + context "? operator" do + context "test" do + subject { Addressable::Template.new("foo{?foo,bar}baz") } + it "can match" do + data = subject.match("foo?foo=bar%20baz&bar=foobaz") + expect(data.mapping["foo"]).to eq("bar baz") + expect(data.mapping["bar"]).to eq("foo") + end + it "lists vars" do + expect(subject.variables).to eq(%w(foo bar)) + end + end + + context "issue #137" do + subject { Addressable::Template.new('/path{?page,per_page}') } + + it "can match empty" do + data = subject.match("/path") + expect(data.mapping["page"]).to eq(nil) + expect(data.mapping["per_page"]).to eq(nil) + expect(data.mapping.keys.sort).to eq(['page', 'per_page']) + end + + it "can match first var" do + data = subject.match("/path?page=1") + expect(data.mapping["page"]).to eq("1") + expect(data.mapping["per_page"]).to eq(nil) + expect(data.mapping.keys.sort).to eq(['page', 'per_page']) + end + + it "can match second var" do + data = subject.match("/path?per_page=1") + expect(data.mapping["page"]).to eq(nil) + expect(data.mapping["per_page"]).to eq("1") + expect(data.mapping.keys.sort).to eq(['page', 'per_page']) + end + + it "can match both vars" do + data = subject.match("/path?page=2&per_page=1") + expect(data.mapping["page"]).to eq("2") + expect(data.mapping["per_page"]).to eq("1") + expect(data.mapping.keys.sort).to eq(['page', 'per_page']) + end + end + + context "issue #71" do + subject { Addressable::Template.new("http://cyberscore.dev/api/users{?username}") } + it "can match" do + data = subject.match("http://cyberscore.dev/api/users?username=foobaz") + expect(data.mapping["username"]).to eq("foobaz") + end + it "lists vars" do + expect(subject.variables).to eq(%w(username)) + expect(subject.keys).to eq(%w(username)) + end + end + end + context "& operator" do + subject { Addressable::Template.new("foo{&foo,bar}baz") } + it "can match" do + data = subject.match("foo&foo=bar%20baz&bar=foobaz") + expect(data.mapping["foo"]).to eq("bar baz") + expect(data.mapping["bar"]).to eq("foo") + end + it "lists vars" do + expect(subject.variables).to eq(%w(foo bar)) + end + end + end + end + + context "support regexes:" do + context "EXPRESSION" do + subject { Addressable::Template::EXPRESSION } + it "should be able to match an expression" do + expect(subject).to match("{foo}") + expect(subject).to match("{foo,9}") + expect(subject).to match("{foo.bar,baz}") + expect(subject).to match("{+foo.bar,baz}") + expect(subject).to match("{foo,foo%20bar}") + expect(subject).to match("{#foo:20,baz*}") + expect(subject).to match("stuff{#foo:20,baz*}things") + end + it "should fail on non vars" do + expect(subject).not_to match("!{foo") + expect(subject).not_to match("{foo.bar.}") + expect(subject).not_to match("!{}") + end + end + context "VARNAME" do + subject { Addressable::Template::VARNAME } + it "should be able to match a variable" do + expect(subject).to match("foo") + expect(subject).to match("9") + expect(subject).to match("foo.bar") + expect(subject).to match("foo_bar") + expect(subject).to match("foo_bar.baz") + expect(subject).to match("foo%20bar") + expect(subject).to match("foo%20bar.baz") + end + it "should fail on non vars" do + expect(subject).not_to match("!foo") + expect(subject).not_to match("foo.bar.") + expect(subject).not_to match("foo%2%00bar") + expect(subject).not_to match("foo_ba%r") + expect(subject).not_to match("foo_bar*") + expect(subject).not_to match("foo_bar:20") + end + end + context "VARIABLE_LIST" do + subject { Addressable::Template::VARIABLE_LIST } + it "should be able to match a variable list" do + expect(subject).to match("foo,bar") + expect(subject).to match("foo") + expect(subject).to match("foo,bar*,baz") + expect(subject).to match("foo.bar,bar_baz*,baz:12") + end + it "should fail on non vars" do + expect(subject).not_to match(",foo,bar*,baz") + expect(subject).not_to match("foo,*bar,baz") + expect(subject).not_to match("foo,,bar*,baz") + end + end + context "VARSPEC" do + subject { Addressable::Template::VARSPEC } + it "should be able to match a variable with modifier" do + expect(subject).to match("9:8") + expect(subject).to match("foo.bar*") + expect(subject).to match("foo_bar:12") + expect(subject).to match("foo_bar.baz*") + expect(subject).to match("foo%20bar:12") + expect(subject).to match("foo%20bar.baz*") + end + it "should fail on non vars" do + expect(subject).not_to match("!foo") + expect(subject).not_to match("*foo") + expect(subject).not_to match("fo*o") + expect(subject).not_to match("fo:o") + expect(subject).not_to match("foo:") + end + end + end +end + +describe Addressable::Template::MatchData do + let(:template) { Addressable::Template.new('{foo}/{bar}') } + subject(:its) { template.match('ab/cd') } + its(:uri) { should == Addressable::URI.parse('ab/cd') } + its(:template) { should == template } + its(:mapping) { should == { 'foo' => 'ab', 'bar' => 'cd' } } + its(:variables) { should == ['foo', 'bar'] } + its(:keys) { should == ['foo', 'bar'] } + its(:names) { should == ['foo', 'bar'] } + its(:values) { should == ['ab', 'cd'] } + its(:captures) { should == ['ab', 'cd'] } + its(:to_a) { should == ['ab/cd', 'ab', 'cd'] } + its(:to_s) { should == 'ab/cd' } + its(:string) { should == its.to_s } + its(:pre_match) { should == "" } + its(:post_match) { should == "" } + + describe 'values_at' do + it 'returns an array with the values' do + expect(its.values_at(0, 2)).to eq(['ab/cd', 'cd']) + end + it 'allows mixing integer an string keys' do + expect(its.values_at('foo', 1)).to eq(['ab', 'ab']) + end + it 'accepts unknown keys' do + expect(its.values_at('baz', 'foo')).to eq([nil, 'ab']) + end + end + + describe '[]' do + context 'string key' do + it 'returns the corresponding capture' do + expect(its['foo']).to eq('ab') + expect(its['bar']).to eq('cd') + end + it 'returns nil for unknown keys' do + expect(its['baz']).to be_nil + end + end + context 'symbol key' do + it 'returns the corresponding capture' do + expect(its[:foo]).to eq('ab') + expect(its[:bar]).to eq('cd') + end + it 'returns nil for unknown keys' do + expect(its[:baz]).to be_nil + end + end + context 'integer key' do + it 'returns the full URI for index 0' do + expect(its[0]).to eq('ab/cd') + end + it 'returns the corresponding capture' do + expect(its[1]).to eq('ab') + expect(its[2]).to eq('cd') + end + it 'returns nil for unknown keys' do + expect(its[3]).to be_nil + end + end + context 'other key' do + it 'raises an exception' do + expect { its[Object.new] }.to raise_error(TypeError) + end + end + context 'with length' do + it 'returns an array starting at index with given length' do + expect(its[0, 2]).to eq(['ab/cd', 'ab']) + expect(its[2, 1]).to eq(['cd']) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/addressable-2.7.0/spec/addressable/uri_spec.rb b/path/ruby/2.6.0/gems/addressable-2.7.0/spec/addressable/uri_spec.rb new file mode 100644 index 00000000..2a938a84 --- /dev/null +++ b/path/ruby/2.6.0/gems/addressable-2.7.0/spec/addressable/uri_spec.rb @@ -0,0 +1,6603 @@ +# frozen_string_literal: true + +# coding: utf-8 +# Copyright (C) Bob Aman +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +require "spec_helper" + +require "addressable/uri" +require "uri" +require "ipaddr" + +if !"".respond_to?("force_encoding") + class String + def force_encoding(encoding) + @encoding = encoding + end + + def encoding + @encoding ||= Encoding::ASCII_8BIT + end + end + + class Encoding + def initialize(name) + @name = name + end + + def to_s + return @name + end + + UTF_8 = Encoding.new("UTF-8") + ASCII_8BIT = Encoding.new("US-ASCII") + end +end + +module Fake + module URI + class HTTP + def initialize(uri) + @uri = uri + end + + def to_s + return @uri.to_s + end + + alias :to_str :to_s + end + end +end + +describe Addressable::URI, "when created with a non-numeric port number" do + it "should raise an error" do + expect(lambda do + Addressable::URI.new(:port => "bogus") + end).to raise_error(Addressable::URI::InvalidURIError) + end +end + +describe Addressable::URI, "when created with a invalid encoded port number" do + it "should raise an error" do + expect(lambda do + Addressable::URI.new(:port => "%eb") + end).to raise_error(Addressable::URI::InvalidURIError) + end +end + +describe Addressable::URI, "when created with a non-string scheme" do + it "should raise an error" do + expect(lambda do + Addressable::URI.new(:scheme => :bogus) + end).to raise_error(TypeError) + end +end + +describe Addressable::URI, "when created with a non-string user" do + it "should raise an error" do + expect(lambda do + Addressable::URI.new(:user => :bogus) + end).to raise_error(TypeError) + end +end + +describe Addressable::URI, "when created with a non-string password" do + it "should raise an error" do + expect(lambda do + Addressable::URI.new(:password => :bogus) + end).to raise_error(TypeError) + end +end + +describe Addressable::URI, "when created with a non-string userinfo" do + it "should raise an error" do + expect(lambda do + Addressable::URI.new(:userinfo => :bogus) + end).to raise_error(TypeError) + end +end + +describe Addressable::URI, "when created with a non-string host" do + it "should raise an error" do + expect(lambda do + Addressable::URI.new(:host => :bogus) + end).to raise_error(TypeError) + end +end + +describe Addressable::URI, "when created with a non-string authority" do + it "should raise an error" do + expect(lambda do + Addressable::URI.new(:authority => :bogus) + end).to raise_error(TypeError) + end +end + +describe Addressable::URI, "when created with a non-string path" do + it "should raise an error" do + expect(lambda do + Addressable::URI.new(:path => :bogus) + end).to raise_error(TypeError) + end +end + +describe Addressable::URI, "when created with a non-string query" do + it "should raise an error" do + expect(lambda do + Addressable::URI.new(:query => :bogus) + end).to raise_error(TypeError) + end +end + +describe Addressable::URI, "when created with a non-string fragment" do + it "should raise an error" do + expect(lambda do + Addressable::URI.new(:fragment => :bogus) + end).to raise_error(TypeError) + end +end + +describe Addressable::URI, "when created with a scheme but no hierarchical " + + "segment" do + it "should raise an error" do + expect(lambda do + Addressable::URI.parse("http:") + end).to raise_error(Addressable::URI::InvalidURIError) + end +end + +describe Addressable::URI, "quote handling" do + describe 'in host name' do + it "should raise an error for single quote" do + expect(lambda do + Addressable::URI.parse("http://local\"host/") + end).to raise_error(Addressable::URI::InvalidURIError) + end + end +end + +describe Addressable::URI, "newline normalization" do + it "should not accept newlines in scheme" do + expect(lambda do + Addressable::URI.parse("ht%0atp://localhost/") + end).to raise_error(Addressable::URI::InvalidURIError) + end + + it "should not unescape newline in path" do + uri = Addressable::URI.parse("http://localhost/%0a").normalize + expect(uri.to_s).to eq("http://localhost/%0A") + end + + it "should not unescape newline in hostname" do + uri = Addressable::URI.parse("http://local%0ahost/").normalize + expect(uri.to_s).to eq("http://local%0Ahost/") + end + + it "should not unescape newline in username" do + uri = Addressable::URI.parse("http://foo%0abar@localhost/").normalize + expect(uri.to_s).to eq("http://foo%0Abar@localhost/") + end + + it "should not unescape newline in username" do + uri = Addressable::URI.parse("http://example:foo%0abar@example/").normalize + expect(uri.to_s).to eq("http://example:foo%0Abar@example/") + end + + it "should not accept newline in hostname" do + uri = Addressable::URI.parse("http://localhost/") + expect(lambda do + uri.host = "local\nhost" + end).to raise_error(Addressable::URI::InvalidURIError) + end +end + +describe Addressable::URI, "when created with ambiguous path" do + it "should raise an error" do + expect(lambda do + Addressable::URI.parse("::http") + end).to raise_error(Addressable::URI::InvalidURIError) + end +end + +describe Addressable::URI, "when created with an invalid host" do + it "should raise an error" do + expect(lambda do + Addressable::URI.new(:host => "") + end).to raise_error(Addressable::URI::InvalidURIError) + end +end + +describe Addressable::URI, "when created with a host consisting of " + + "sub-delims characters" do + it "should not raise an error" do + expect(lambda do + Addressable::URI.new( + :host => Addressable::URI::CharacterClasses::SUB_DELIMS.gsub(/\\/, '') + ) + end).not_to raise_error + end +end + +describe Addressable::URI, "when created with a host consisting of " + + "unreserved characters" do + it "should not raise an error" do + expect(lambda do + Addressable::URI.new( + :host => Addressable::URI::CharacterClasses::UNRESERVED.gsub(/\\/, '') + ) + end).not_to raise_error + end +end + +describe Addressable::URI, "when created from nil components" do + before do + @uri = Addressable::URI.new + end + + it "should have a nil site value" do + expect(@uri.site).to eq(nil) + end + + it "should have an empty path" do + expect(@uri.path).to eq("") + end + + it "should be an empty uri" do + expect(@uri.to_s).to eq("") + end + + it "should have a nil default port" do + expect(@uri.default_port).to eq(nil) + end + + it "should be empty" do + expect(@uri).to be_empty + end + + it "should raise an error if the scheme is set to whitespace" do + expect(lambda do + @uri.scheme = "\t \n" + end).to raise_error(Addressable::URI::InvalidURIError) + end + + it "should raise an error if the scheme is set to all digits" do + expect(lambda do + @uri.scheme = "123" + end).to raise_error(Addressable::URI::InvalidURIError) + end + + it "should raise an error if the scheme begins with a digit" do + expect(lambda do + @uri.scheme = "1scheme" + end).to raise_error(Addressable::URI::InvalidURIError) + end + + it "should raise an error if the scheme begins with a plus" do + expect(lambda do + @uri.scheme = "+scheme" + end).to raise_error(Addressable::URI::InvalidURIError) + end + + it "should raise an error if the scheme begins with a dot" do + expect(lambda do + @uri.scheme = ".scheme" + end).to raise_error(Addressable::URI::InvalidURIError) + end + + it "should raise an error if the scheme begins with a dash" do + expect(lambda do + @uri.scheme = "-scheme" + end).to raise_error(Addressable::URI::InvalidURIError) + end + + it "should raise an error if the scheme contains an illegal character" do + expect(lambda do + @uri.scheme = "scheme!" + end).to raise_error(Addressable::URI::InvalidURIError) + end + + it "should raise an error if the scheme contains whitespace" do + expect(lambda do + @uri.scheme = "sch eme" + end).to raise_error(Addressable::URI::InvalidURIError) + end + + it "should raise an error if the scheme contains a newline" do + expect(lambda do + @uri.scheme = "sch\neme" + end).to raise_error(Addressable::URI::InvalidURIError) + end + + it "should raise an error if set into an invalid state" do + expect(lambda do + @uri.user = "user" + end).to raise_error(Addressable::URI::InvalidURIError) + end + + it "should raise an error if set into an invalid state" do + expect(lambda do + @uri.password = "pass" + end).to raise_error(Addressable::URI::InvalidURIError) + end + + it "should raise an error if set into an invalid state" do + expect(lambda do + @uri.scheme = "http" + @uri.fragment = "fragment" + end).to raise_error(Addressable::URI::InvalidURIError) + end + + it "should raise an error if set into an invalid state" do + expect(lambda do + @uri.fragment = "fragment" + @uri.scheme = "http" + end).to raise_error(Addressable::URI::InvalidURIError) + end +end + +describe Addressable::URI, "when initialized from individual components" do + before do + @uri = Addressable::URI.new( + :scheme => "http", + :user => "user", + :password => "password", + :host => "example.com", + :port => 8080, + :path => "/path", + :query => "query=value", + :fragment => "fragment" + ) + end + + it "returns 'http' for #scheme" do + expect(@uri.scheme).to eq("http") + end + + it "returns 'http' for #normalized_scheme" do + expect(@uri.normalized_scheme).to eq("http") + end + + it "returns 'user' for #user" do + expect(@uri.user).to eq("user") + end + + it "returns 'user' for #normalized_user" do + expect(@uri.normalized_user).to eq("user") + end + + it "returns 'password' for #password" do + expect(@uri.password).to eq("password") + end + + it "returns 'password' for #normalized_password" do + expect(@uri.normalized_password).to eq("password") + end + + it "returns 'user:password' for #userinfo" do + expect(@uri.userinfo).to eq("user:password") + end + + it "returns 'user:password' for #normalized_userinfo" do + expect(@uri.normalized_userinfo).to eq("user:password") + end + + it "returns 'example.com' for #host" do + expect(@uri.host).to eq("example.com") + end + + it "returns 'example.com' for #normalized_host" do + expect(@uri.normalized_host).to eq("example.com") + end + + it "returns 'com' for #tld" do + expect(@uri.tld).to eq("com") + end + + it "returns 'user:password@example.com:8080' for #authority" do + expect(@uri.authority).to eq("user:password@example.com:8080") + end + + it "returns 'user:password@example.com:8080' for #normalized_authority" do + expect(@uri.normalized_authority).to eq("user:password@example.com:8080") + end + + it "returns 8080 for #port" do + expect(@uri.port).to eq(8080) + end + + it "returns 8080 for #normalized_port" do + expect(@uri.normalized_port).to eq(8080) + end + + it "returns 80 for #default_port" do + expect(@uri.default_port).to eq(80) + end + + it "returns 'http://user:password@example.com:8080' for #site" do + expect(@uri.site).to eq("http://user:password@example.com:8080") + end + + it "returns 'http://user:password@example.com:8080' for #normalized_site" do + expect(@uri.normalized_site).to eq("http://user:password@example.com:8080") + end + + it "returns '/path' for #path" do + expect(@uri.path).to eq("/path") + end + + it "returns '/path' for #normalized_path" do + expect(@uri.normalized_path).to eq("/path") + end + + it "returns 'query=value' for #query" do + expect(@uri.query).to eq("query=value") + end + + it "returns 'query=value' for #normalized_query" do + expect(@uri.normalized_query).to eq("query=value") + end + + it "returns 'fragment' for #fragment" do + expect(@uri.fragment).to eq("fragment") + end + + it "returns 'fragment' for #normalized_fragment" do + expect(@uri.normalized_fragment).to eq("fragment") + end + + it "returns #hash" do + expect(@uri.hash).not_to be nil + end + + it "returns #to_s" do + expect(@uri.to_s).to eq( + "http://user:password@example.com:8080/path?query=value#fragment" + ) + end + + it "should not be empty" do + expect(@uri).not_to be_empty + end + + it "should not be frozen" do + expect(@uri).not_to be_frozen + end + + it "should allow destructive operations" do + expect { @uri.normalize! }.not_to raise_error + end +end + +describe Addressable::URI, "when initialized from " + + "frozen individual components" do + before do + @uri = Addressable::URI.new( + :scheme => "http".freeze, + :user => "user".freeze, + :password => "password".freeze, + :host => "example.com".freeze, + :port => "8080".freeze, + :path => "/path".freeze, + :query => "query=value".freeze, + :fragment => "fragment".freeze + ) + end + + it "returns 'http' for #scheme" do + expect(@uri.scheme).to eq("http") + end + + it "returns 'http' for #normalized_scheme" do + expect(@uri.normalized_scheme).to eq("http") + end + + it "returns 'user' for #user" do + expect(@uri.user).to eq("user") + end + + it "returns 'user' for #normalized_user" do + expect(@uri.normalized_user).to eq("user") + end + + it "returns 'password' for #password" do + expect(@uri.password).to eq("password") + end + + it "returns 'password' for #normalized_password" do + expect(@uri.normalized_password).to eq("password") + end + + it "returns 'user:password' for #userinfo" do + expect(@uri.userinfo).to eq("user:password") + end + + it "returns 'user:password' for #normalized_userinfo" do + expect(@uri.normalized_userinfo).to eq("user:password") + end + + it "returns 'example.com' for #host" do + expect(@uri.host).to eq("example.com") + end + + it "returns 'example.com' for #normalized_host" do + expect(@uri.normalized_host).to eq("example.com") + end + + it "returns 'user:password@example.com:8080' for #authority" do + expect(@uri.authority).to eq("user:password@example.com:8080") + end + + it "returns 'user:password@example.com:8080' for #normalized_authority" do + expect(@uri.normalized_authority).to eq("user:password@example.com:8080") + end + + it "returns 8080 for #port" do + expect(@uri.port).to eq(8080) + end + + it "returns 8080 for #normalized_port" do + expect(@uri.normalized_port).to eq(8080) + end + + it "returns 80 for #default_port" do + expect(@uri.default_port).to eq(80) + end + + it "returns 'http://user:password@example.com:8080' for #site" do + expect(@uri.site).to eq("http://user:password@example.com:8080") + end + + it "returns 'http://user:password@example.com:8080' for #normalized_site" do + expect(@uri.normalized_site).to eq("http://user:password@example.com:8080") + end + + it "returns '/path' for #path" do + expect(@uri.path).to eq("/path") + end + + it "returns '/path' for #normalized_path" do + expect(@uri.normalized_path).to eq("/path") + end + + it "returns 'query=value' for #query" do + expect(@uri.query).to eq("query=value") + end + + it "returns 'query=value' for #normalized_query" do + expect(@uri.normalized_query).to eq("query=value") + end + + it "returns 'fragment' for #fragment" do + expect(@uri.fragment).to eq("fragment") + end + + it "returns 'fragment' for #normalized_fragment" do + expect(@uri.normalized_fragment).to eq("fragment") + end + + it "returns #hash" do + expect(@uri.hash).not_to be nil + end + + it "returns #to_s" do + expect(@uri.to_s).to eq( + "http://user:password@example.com:8080/path?query=value#fragment" + ) + end + + it "should not be empty" do + expect(@uri).not_to be_empty + end + + it "should not be frozen" do + expect(@uri).not_to be_frozen + end + + it "should allow destructive operations" do + expect { @uri.normalize! }.not_to raise_error + end +end + +describe Addressable::URI, "when parsed from a frozen string" do + before do + @uri = Addressable::URI.parse( + "http://user:password@example.com:8080/path?query=value#fragment".freeze + ) + end + + it "returns 'http' for #scheme" do + expect(@uri.scheme).to eq("http") + end + + it "returns 'http' for #normalized_scheme" do + expect(@uri.normalized_scheme).to eq("http") + end + + it "returns 'user' for #user" do + expect(@uri.user).to eq("user") + end + + it "returns 'user' for #normalized_user" do + expect(@uri.normalized_user).to eq("user") + end + + it "returns 'password' for #password" do + expect(@uri.password).to eq("password") + end + + it "returns 'password' for #normalized_password" do + expect(@uri.normalized_password).to eq("password") + end + + it "returns 'user:password' for #userinfo" do + expect(@uri.userinfo).to eq("user:password") + end + + it "returns 'user:password' for #normalized_userinfo" do + expect(@uri.normalized_userinfo).to eq("user:password") + end + + it "returns 'example.com' for #host" do + expect(@uri.host).to eq("example.com") + end + + it "returns 'example.com' for #normalized_host" do + expect(@uri.normalized_host).to eq("example.com") + end + + it "returns 'user:password@example.com:8080' for #authority" do + expect(@uri.authority).to eq("user:password@example.com:8080") + end + + it "returns 'user:password@example.com:8080' for #normalized_authority" do + expect(@uri.normalized_authority).to eq("user:password@example.com:8080") + end + + it "returns 8080 for #port" do + expect(@uri.port).to eq(8080) + end + + it "returns 8080 for #normalized_port" do + expect(@uri.normalized_port).to eq(8080) + end + + it "returns 80 for #default_port" do + expect(@uri.default_port).to eq(80) + end + + it "returns 'http://user:password@example.com:8080' for #site" do + expect(@uri.site).to eq("http://user:password@example.com:8080") + end + + it "returns 'http://user:password@example.com:8080' for #normalized_site" do + expect(@uri.normalized_site).to eq("http://user:password@example.com:8080") + end + + it "returns '/path' for #path" do + expect(@uri.path).to eq("/path") + end + + it "returns '/path' for #normalized_path" do + expect(@uri.normalized_path).to eq("/path") + end + + it "returns 'query=value' for #query" do + expect(@uri.query).to eq("query=value") + end + + it "returns 'query=value' for #normalized_query" do + expect(@uri.normalized_query).to eq("query=value") + end + + it "returns 'fragment' for #fragment" do + expect(@uri.fragment).to eq("fragment") + end + + it "returns 'fragment' for #normalized_fragment" do + expect(@uri.normalized_fragment).to eq("fragment") + end + + it "returns #hash" do + expect(@uri.hash).not_to be nil + end + + it "returns #to_s" do + expect(@uri.to_s).to eq( + "http://user:password@example.com:8080/path?query=value#fragment" + ) + end + + it "should not be empty" do + expect(@uri).not_to be_empty + end + + it "should not be frozen" do + expect(@uri).not_to be_frozen + end + + it "should allow destructive operations" do + expect { @uri.normalize! }.not_to raise_error + end +end + +describe Addressable::URI, "when frozen" do + before do + @uri = Addressable::URI.new.freeze + end + + it "returns nil for #scheme" do + expect(@uri.scheme).to eq(nil) + end + + it "returns nil for #normalized_scheme" do + expect(@uri.normalized_scheme).to eq(nil) + end + + it "returns nil for #user" do + expect(@uri.user).to eq(nil) + end + + it "returns nil for #normalized_user" do + expect(@uri.normalized_user).to eq(nil) + end + + it "returns nil for #password" do + expect(@uri.password).to eq(nil) + end + + it "returns nil for #normalized_password" do + expect(@uri.normalized_password).to eq(nil) + end + + it "returns nil for #userinfo" do + expect(@uri.userinfo).to eq(nil) + end + + it "returns nil for #normalized_userinfo" do + expect(@uri.normalized_userinfo).to eq(nil) + end + + it "returns nil for #host" do + expect(@uri.host).to eq(nil) + end + + it "returns nil for #normalized_host" do + expect(@uri.normalized_host).to eq(nil) + end + + it "returns nil for #authority" do + expect(@uri.authority).to eq(nil) + end + + it "returns nil for #normalized_authority" do + expect(@uri.normalized_authority).to eq(nil) + end + + it "returns nil for #port" do + expect(@uri.port).to eq(nil) + end + + it "returns nil for #normalized_port" do + expect(@uri.normalized_port).to eq(nil) + end + + it "returns nil for #default_port" do + expect(@uri.default_port).to eq(nil) + end + + it "returns nil for #site" do + expect(@uri.site).to eq(nil) + end + + it "returns nil for #normalized_site" do + expect(@uri.normalized_site).to eq(nil) + end + + it "returns '' for #path" do + expect(@uri.path).to eq('') + end + + it "returns '' for #normalized_path" do + expect(@uri.normalized_path).to eq('') + end + + it "returns nil for #query" do + expect(@uri.query).to eq(nil) + end + + it "returns nil for #normalized_query" do + expect(@uri.normalized_query).to eq(nil) + end + + it "returns nil for #fragment" do + expect(@uri.fragment).to eq(nil) + end + + it "returns nil for #normalized_fragment" do + expect(@uri.normalized_fragment).to eq(nil) + end + + it "returns #hash" do + expect(@uri.hash).not_to be nil + end + + it "returns #to_s" do + expect(@uri.to_s).to eq('') + end + + it "should be empty" do + expect(@uri).to be_empty + end + + it "should be frozen" do + expect(@uri).to be_frozen + end + + it "should not be frozen after duping" do + expect(@uri.dup).not_to be_frozen + end + + it "should not allow destructive operations" do + expect { @uri.normalize! }.to raise_error { |error| + expect(error.message).to match(/can't modify frozen/) + expect(error).to satisfy { |e| RuntimeError === e || TypeError === e } + } + end +end + +describe Addressable::URI, "when frozen" do + before do + @uri = Addressable::URI.parse( + "HTTP://example.com.:%38%30/%70a%74%68?a=%31#1%323" + ).freeze + end + + it "returns 'HTTP' for #scheme" do + expect(@uri.scheme).to eq("HTTP") + end + + it "returns 'http' for #normalized_scheme" do + expect(@uri.normalized_scheme).to eq("http") + expect(@uri.normalize.scheme).to eq("http") + end + + it "returns nil for #user" do + expect(@uri.user).to eq(nil) + end + + it "returns nil for #normalized_user" do + expect(@uri.normalized_user).to eq(nil) + end + + it "returns nil for #password" do + expect(@uri.password).to eq(nil) + end + + it "returns nil for #normalized_password" do + expect(@uri.normalized_password).to eq(nil) + end + + it "returns nil for #userinfo" do + expect(@uri.userinfo).to eq(nil) + end + + it "returns nil for #normalized_userinfo" do + expect(@uri.normalized_userinfo).to eq(nil) + end + + it "returns 'example.com.' for #host" do + expect(@uri.host).to eq("example.com.") + end + + it "returns nil for #normalized_host" do + expect(@uri.normalized_host).to eq("example.com") + expect(@uri.normalize.host).to eq("example.com") + end + + it "returns 'example.com.:80' for #authority" do + expect(@uri.authority).to eq("example.com.:80") + end + + it "returns 'example.com:80' for #normalized_authority" do + expect(@uri.normalized_authority).to eq("example.com") + expect(@uri.normalize.authority).to eq("example.com") + end + + it "returns 80 for #port" do + expect(@uri.port).to eq(80) + end + + it "returns nil for #normalized_port" do + expect(@uri.normalized_port).to eq(nil) + expect(@uri.normalize.port).to eq(nil) + end + + it "returns 80 for #default_port" do + expect(@uri.default_port).to eq(80) + end + + it "returns 'HTTP://example.com.:80' for #site" do + expect(@uri.site).to eq("HTTP://example.com.:80") + end + + it "returns 'http://example.com' for #normalized_site" do + expect(@uri.normalized_site).to eq("http://example.com") + expect(@uri.normalize.site).to eq("http://example.com") + end + + it "returns '/%70a%74%68' for #path" do + expect(@uri.path).to eq("/%70a%74%68") + end + + it "returns '/path' for #normalized_path" do + expect(@uri.normalized_path).to eq("/path") + expect(@uri.normalize.path).to eq("/path") + end + + it "returns 'a=%31' for #query" do + expect(@uri.query).to eq("a=%31") + end + + it "returns 'a=1' for #normalized_query" do + expect(@uri.normalized_query).to eq("a=1") + expect(@uri.normalize.query).to eq("a=1") + end + + it "returns '/%70a%74%68?a=%31' for #request_uri" do + expect(@uri.request_uri).to eq("/%70a%74%68?a=%31") + end + + it "returns '1%323' for #fragment" do + expect(@uri.fragment).to eq("1%323") + end + + it "returns '123' for #normalized_fragment" do + expect(@uri.normalized_fragment).to eq("123") + expect(@uri.normalize.fragment).to eq("123") + end + + it "returns #hash" do + expect(@uri.hash).not_to be nil + end + + it "returns #to_s" do + expect(@uri.to_s).to eq('HTTP://example.com.:80/%70a%74%68?a=%31#1%323') + expect(@uri.normalize.to_s).to eq('http://example.com/path?a=1#123') + end + + it "should not be empty" do + expect(@uri).not_to be_empty + end + + it "should be frozen" do + expect(@uri).to be_frozen + end + + it "should not be frozen after duping" do + expect(@uri.dup).not_to be_frozen + end + + it "should not allow destructive operations" do + expect { @uri.normalize! }.to raise_error { |error| + expect(error.message).to match(/can't modify frozen/) + expect(error).to satisfy { |e| RuntimeError === e || TypeError === e } + } + end +end + +describe Addressable::URI, "when created from string components" do + before do + @uri = Addressable::URI.new( + :scheme => "http", :host => "example.com" + ) + end + + it "should have a site value of 'http://example.com'" do + expect(@uri.site).to eq("http://example.com") + end + + it "should be equal to the equivalent parsed URI" do + expect(@uri).to eq(Addressable::URI.parse("http://example.com")) + end + + it "should raise an error if invalid components omitted" do + expect(lambda do + @uri.omit(:bogus) + end).to raise_error(ArgumentError) + expect(lambda do + @uri.omit(:scheme, :bogus, :path) + end).to raise_error(ArgumentError) + end +end + +describe Addressable::URI, "when created with a nil host but " + + "non-nil authority components" do + it "should raise an error" do + expect(lambda do + Addressable::URI.new(:user => "user", :password => "pass", :port => 80) + end).to raise_error(Addressable::URI::InvalidURIError) + end +end + +describe Addressable::URI, "when created with both an authority and a user" do + it "should raise an error" do + expect(lambda do + Addressable::URI.new( + :user => "user", :authority => "user@example.com:80" + ) + end).to raise_error(ArgumentError) + end +end + +describe Addressable::URI, "when created with an authority and no port" do + before do + @uri = Addressable::URI.new(:authority => "user@example.com") + end + + it "should not infer a port" do + expect(@uri.port).to eq(nil) + expect(@uri.default_port).to eq(nil) + expect(@uri.inferred_port).to eq(nil) + end + + it "should have a site value of '//user@example.com'" do + expect(@uri.site).to eq("//user@example.com") + end + + it "should have a 'null' origin" do + expect(@uri.origin).to eq('null') + end +end + +describe Addressable::URI, "when created with a host with trailing dots" do + before do + @uri = Addressable::URI.new(:authority => "example...") + end + + it "should have a stable normalized form" do + expect(@uri.normalize.normalize.normalize.host).to eq( + @uri.normalize.host + ) + end +end + +describe Addressable::URI, "when created with a host with a backslash" do + it "should raise an error" do + expect(lambda do + Addressable::URI.new(:authority => "example\\example") + end).to raise_error(Addressable::URI::InvalidURIError) + end +end + +describe Addressable::URI, "when created with a host with a slash" do + it "should raise an error" do + expect(lambda do + Addressable::URI.new(:authority => "example/example") + end).to raise_error(Addressable::URI::InvalidURIError) + end +end + +describe Addressable::URI, "when created with a host with a space" do + it "should raise an error" do + expect(lambda do + Addressable::URI.new(:authority => "example example") + end).to raise_error(Addressable::URI::InvalidURIError) + end +end + +describe Addressable::URI, "when created with both a userinfo and a user" do + it "should raise an error" do + expect(lambda do + Addressable::URI.new(:user => "user", :userinfo => "user:pass") + end).to raise_error(ArgumentError) + end +end + +describe Addressable::URI, "when created with a path that hasn't been " + + "prefixed with a '/' but a host specified" do + before do + @uri = Addressable::URI.new( + :scheme => "http", :host => "example.com", :path => "path" + ) + end + + it "should prefix a '/' to the path" do + expect(@uri).to eq(Addressable::URI.parse("http://example.com/path")) + end + + it "should have a site value of 'http://example.com'" do + expect(@uri.site).to eq("http://example.com") + end + + it "should have an origin of 'http://example.com" do + expect(@uri.origin).to eq('http://example.com') + end +end + +describe Addressable::URI, "when created with a path that hasn't been " + + "prefixed with a '/' but no host specified" do + before do + @uri = Addressable::URI.new( + :scheme => "http", :path => "path" + ) + end + + it "should not prefix a '/' to the path" do + expect(@uri).to eq(Addressable::URI.parse("http:path")) + end + + it "should have a site value of 'http:'" do + expect(@uri.site).to eq("http:") + end + + it "should have a 'null' origin" do + expect(@uri.origin).to eq('null') + end +end + +describe Addressable::URI, "when parsed from an Addressable::URI object" do + it "should not have unexpected side-effects" do + original_uri = Addressable::URI.parse("http://example.com/") + new_uri = Addressable::URI.parse(original_uri) + new_uri.host = 'www.example.com' + expect(new_uri.host).to eq('www.example.com') + expect(new_uri.to_s).to eq('http://www.example.com/') + expect(original_uri.host).to eq('example.com') + expect(original_uri.to_s).to eq('http://example.com/') + end + + it "should not have unexpected side-effects" do + original_uri = Addressable::URI.parse("http://example.com/") + new_uri = Addressable::URI.heuristic_parse(original_uri) + new_uri.host = 'www.example.com' + expect(new_uri.host).to eq('www.example.com') + expect(new_uri.to_s).to eq('http://www.example.com/') + expect(original_uri.host).to eq('example.com') + expect(original_uri.to_s).to eq('http://example.com/') + end + + it "should not have unexpected side-effects" do + original_uri = Addressable::URI.parse("http://example.com/") + new_uri = Addressable::URI.parse(original_uri) + new_uri.origin = 'https://www.example.com:8080' + expect(new_uri.host).to eq('www.example.com') + expect(new_uri.to_s).to eq('https://www.example.com:8080/') + expect(original_uri.host).to eq('example.com') + expect(original_uri.to_s).to eq('http://example.com/') + end + + it "should not have unexpected side-effects" do + original_uri = Addressable::URI.parse("http://example.com/") + new_uri = Addressable::URI.heuristic_parse(original_uri) + new_uri.origin = 'https://www.example.com:8080' + expect(new_uri.host).to eq('www.example.com') + expect(new_uri.to_s).to eq('https://www.example.com:8080/') + expect(original_uri.host).to eq('example.com') + expect(original_uri.to_s).to eq('http://example.com/') + end +end + +describe Addressable::URI, "when parsed from something that looks " + + "like a URI object" do + it "should parse without error" do + uri = Addressable::URI.parse(Fake::URI::HTTP.new("http://example.com/")) + expect(lambda do + Addressable::URI.parse(uri) + end).not_to raise_error + end +end + +describe Addressable::URI, "when parsed from a standard library URI object" do + it "should parse without error" do + uri = Addressable::URI.parse(URI.parse("http://example.com/")) + expect(lambda do + Addressable::URI.parse(uri) + end).not_to raise_error + end +end + +describe Addressable::URI, "when parsed from ''" do + before do + @uri = Addressable::URI.parse("") + end + + it "should have no scheme" do + expect(@uri.scheme).to eq(nil) + end + + it "should not be considered to be ip-based" do + expect(@uri).not_to be_ip_based + end + + it "should have a path of ''" do + expect(@uri.path).to eq("") + end + + it "should have a request URI of '/'" do + expect(@uri.request_uri).to eq("/") + end + + it "should be considered relative" do + expect(@uri).to be_relative + end + + it "should be considered to be in normal form" do + expect(@uri.normalize).to be_eql(@uri) + end + + it "should have a 'null' origin" do + expect(@uri.origin).to eq('null') + end +end + +# Section 1.1.2 of RFC 3986 +describe Addressable::URI, "when parsed from " + + "'ftp://ftp.is.co.za/rfc/rfc1808.txt'" do + before do + @uri = Addressable::URI.parse("ftp://ftp.is.co.za/rfc/rfc1808.txt") + end + + it "should use the 'ftp' scheme" do + expect(@uri.scheme).to eq("ftp") + end + + it "should be considered to be ip-based" do + expect(@uri).to be_ip_based + end + + it "should have a host of 'ftp.is.co.za'" do + expect(@uri.host).to eq("ftp.is.co.za") + end + + it "should have inferred_port of 21" do + expect(@uri.inferred_port).to eq(21) + end + + it "should have a path of '/rfc/rfc1808.txt'" do + expect(@uri.path).to eq("/rfc/rfc1808.txt") + end + + it "should not have a request URI" do + expect(@uri.request_uri).to eq(nil) + end + + it "should be considered to be in normal form" do + expect(@uri.normalize).to be_eql(@uri) + end + + it "should have an origin of 'ftp://ftp.is.co.za'" do + expect(@uri.origin).to eq('ftp://ftp.is.co.za') + end +end + +# Section 1.1.2 of RFC 3986 +describe Addressable::URI, "when parsed from " + + "'http://www.ietf.org/rfc/rfc2396.txt'" do + before do + @uri = Addressable::URI.parse("http://www.ietf.org/rfc/rfc2396.txt") + end + + it "should use the 'http' scheme" do + expect(@uri.scheme).to eq("http") + end + + it "should be considered to be ip-based" do + expect(@uri).to be_ip_based + end + + it "should have a host of 'www.ietf.org'" do + expect(@uri.host).to eq("www.ietf.org") + end + + it "should have inferred_port of 80" do + expect(@uri.inferred_port).to eq(80) + end + + it "should have a path of '/rfc/rfc2396.txt'" do + expect(@uri.path).to eq("/rfc/rfc2396.txt") + end + + it "should have a request URI of '/rfc/rfc2396.txt'" do + expect(@uri.request_uri).to eq("/rfc/rfc2396.txt") + end + + it "should be considered to be in normal form" do + expect(@uri.normalize).to be_eql(@uri) + end + + it "should correctly omit components" do + expect(@uri.omit(:scheme).to_s).to eq("//www.ietf.org/rfc/rfc2396.txt") + expect(@uri.omit(:path).to_s).to eq("http://www.ietf.org") + end + + it "should correctly omit components destructively" do + @uri.omit!(:scheme) + expect(@uri.to_s).to eq("//www.ietf.org/rfc/rfc2396.txt") + end + + it "should have an origin of 'http://www.ietf.org'" do + expect(@uri.origin).to eq('http://www.ietf.org') + end +end + +# Section 1.1.2 of RFC 3986 +describe Addressable::URI, "when parsed from " + + "'ldap://[2001:db8::7]/c=GB?objectClass?one'" do + before do + @uri = Addressable::URI.parse("ldap://[2001:db8::7]/c=GB?objectClass?one") + end + + it "should use the 'ldap' scheme" do + expect(@uri.scheme).to eq("ldap") + end + + it "should be considered to be ip-based" do + expect(@uri).to be_ip_based + end + + it "should have a host of '[2001:db8::7]'" do + expect(@uri.host).to eq("[2001:db8::7]") + end + + it "should have inferred_port of 389" do + expect(@uri.inferred_port).to eq(389) + end + + it "should have a path of '/c=GB'" do + expect(@uri.path).to eq("/c=GB") + end + + it "should not have a request URI" do + expect(@uri.request_uri).to eq(nil) + end + + it "should not allow request URI assignment" do + expect(lambda do + @uri.request_uri = "/" + end).to raise_error(Addressable::URI::InvalidURIError) + end + + it "should have a query of 'objectClass?one'" do + expect(@uri.query).to eq("objectClass?one") + end + + it "should be considered to be in normal form" do + expect(@uri.normalize).to be_eql(@uri) + end + + it "should correctly omit components" do + expect(@uri.omit(:scheme, :authority).to_s).to eq("/c=GB?objectClass?one") + expect(@uri.omit(:path).to_s).to eq("ldap://[2001:db8::7]?objectClass?one") + end + + it "should correctly omit components destructively" do + @uri.omit!(:scheme, :authority) + expect(@uri.to_s).to eq("/c=GB?objectClass?one") + end + + it "should raise an error if omission would create an invalid URI" do + expect(lambda do + @uri.omit(:authority, :path) + end).to raise_error(Addressable::URI::InvalidURIError) + end + + it "should have an origin of 'ldap://[2001:db8::7]'" do + expect(@uri.origin).to eq('ldap://[2001:db8::7]') + end +end + +# Section 1.1.2 of RFC 3986 +describe Addressable::URI, "when parsed from " + + "'mailto:John.Doe@example.com'" do + before do + @uri = Addressable::URI.parse("mailto:John.Doe@example.com") + end + + it "should use the 'mailto' scheme" do + expect(@uri.scheme).to eq("mailto") + end + + it "should not be considered to be ip-based" do + expect(@uri).not_to be_ip_based + end + + it "should not have an inferred_port" do + expect(@uri.inferred_port).to eq(nil) + end + + it "should have a path of 'John.Doe@example.com'" do + expect(@uri.path).to eq("John.Doe@example.com") + end + + it "should not have a request URI" do + expect(@uri.request_uri).to eq(nil) + end + + it "should be considered to be in normal form" do + expect(@uri.normalize).to be_eql(@uri) + end + + it "should have a 'null' origin" do + expect(@uri.origin).to eq('null') + end +end + +# Section 2 of RFC 6068 +describe Addressable::URI, "when parsed from " + + "'mailto:?to=addr1@an.example,addr2@an.example'" do + before do + @uri = Addressable::URI.parse( + "mailto:?to=addr1@an.example,addr2@an.example" + ) + end + + it "should use the 'mailto' scheme" do + expect(@uri.scheme).to eq("mailto") + end + + it "should not be considered to be ip-based" do + expect(@uri).not_to be_ip_based + end + + it "should not have an inferred_port" do + expect(@uri.inferred_port).to eq(nil) + end + + it "should have a path of ''" do + expect(@uri.path).to eq("") + end + + it "should not have a request URI" do + expect(@uri.request_uri).to eq(nil) + end + + it "should have the To: field value parameterized" do + expect(@uri.query_values(Hash)["to"]).to eq( + "addr1@an.example,addr2@an.example" + ) + end + + it "should be considered to be in normal form" do + expect(@uri.normalize).to be_eql(@uri) + end + + it "should have a 'null' origin" do + expect(@uri.origin).to eq('null') + end +end + +# Section 1.1.2 of RFC 3986 +describe Addressable::URI, "when parsed from " + + "'news:comp.infosystems.www.servers.unix'" do + before do + @uri = Addressable::URI.parse("news:comp.infosystems.www.servers.unix") + end + + it "should use the 'news' scheme" do + expect(@uri.scheme).to eq("news") + end + + it "should not have an inferred_port" do + expect(@uri.inferred_port).to eq(nil) + end + + it "should not be considered to be ip-based" do + expect(@uri).not_to be_ip_based + end + + it "should have a path of 'comp.infosystems.www.servers.unix'" do + expect(@uri.path).to eq("comp.infosystems.www.servers.unix") + end + + it "should not have a request URI" do + expect(@uri.request_uri).to eq(nil) + end + + it "should be considered to be in normal form" do + expect(@uri.normalize).to be_eql(@uri) + end + + it "should have a 'null' origin" do + expect(@uri.origin).to eq('null') + end +end + +# Section 1.1.2 of RFC 3986 +describe Addressable::URI, "when parsed from " + + "'tel:+1-816-555-1212'" do + before do + @uri = Addressable::URI.parse("tel:+1-816-555-1212") + end + + it "should use the 'tel' scheme" do + expect(@uri.scheme).to eq("tel") + end + + it "should not be considered to be ip-based" do + expect(@uri).not_to be_ip_based + end + + it "should not have an inferred_port" do + expect(@uri.inferred_port).to eq(nil) + end + + it "should have a path of '+1-816-555-1212'" do + expect(@uri.path).to eq("+1-816-555-1212") + end + + it "should not have a request URI" do + expect(@uri.request_uri).to eq(nil) + end + + it "should be considered to be in normal form" do + expect(@uri.normalize).to be_eql(@uri) + end + + it "should have a 'null' origin" do + expect(@uri.origin).to eq('null') + end +end + +# Section 1.1.2 of RFC 3986 +describe Addressable::URI, "when parsed from " + + "'telnet://192.0.2.16:80/'" do + before do + @uri = Addressable::URI.parse("telnet://192.0.2.16:80/") + end + + it "should use the 'telnet' scheme" do + expect(@uri.scheme).to eq("telnet") + end + + it "should have a host of '192.0.2.16'" do + expect(@uri.host).to eq("192.0.2.16") + end + + it "should have a port of 80" do + expect(@uri.port).to eq(80) + end + + it "should have a inferred_port of 80" do + expect(@uri.inferred_port).to eq(80) + end + + it "should have a default_port of 23" do + expect(@uri.default_port).to eq(23) + end + + it "should be considered to be ip-based" do + expect(@uri).to be_ip_based + end + + it "should have a path of '/'" do + expect(@uri.path).to eq("/") + end + + it "should not have a request URI" do + expect(@uri.request_uri).to eq(nil) + end + + it "should be considered to be in normal form" do + expect(@uri.normalize).to be_eql(@uri) + end + + it "should have an origin of 'telnet://192.0.2.16:80'" do + expect(@uri.origin).to eq('telnet://192.0.2.16:80') + end +end + +# Section 1.1.2 of RFC 3986 +describe Addressable::URI, "when parsed from " + + "'urn:oasis:names:specification:docbook:dtd:xml:4.1.2'" do + before do + @uri = Addressable::URI.parse( + "urn:oasis:names:specification:docbook:dtd:xml:4.1.2") + end + + it "should use the 'urn' scheme" do + expect(@uri.scheme).to eq("urn") + end + + it "should not have an inferred_port" do + expect(@uri.inferred_port).to eq(nil) + end + + it "should not be considered to be ip-based" do + expect(@uri).not_to be_ip_based + end + + it "should have a path of " + + "'oasis:names:specification:docbook:dtd:xml:4.1.2'" do + expect(@uri.path).to eq("oasis:names:specification:docbook:dtd:xml:4.1.2") + end + + it "should not have a request URI" do + expect(@uri.request_uri).to eq(nil) + end + + it "should be considered to be in normal form" do + expect(@uri.normalize).to be_eql(@uri) + end + + it "should have a 'null' origin" do + expect(@uri.origin).to eq('null') + end +end + +describe Addressable::URI, "when heuristically parsed from " + + "'192.0.2.16:8000/path'" do + before do + @uri = Addressable::URI.heuristic_parse("192.0.2.16:8000/path") + end + + it "should use the 'http' scheme" do + expect(@uri.scheme).to eq("http") + end + + it "should have a host of '192.0.2.16'" do + expect(@uri.host).to eq("192.0.2.16") + end + + it "should have a port of '8000'" do + expect(@uri.port).to eq(8000) + end + + it "should be considered to be ip-based" do + expect(@uri).to be_ip_based + end + + it "should have a path of '/path'" do + expect(@uri.path).to eq("/path") + end + + it "should be considered to be in normal form" do + expect(@uri.normalize).to be_eql(@uri) + end + + it "should have an origin of 'http://192.0.2.16:8000'" do + expect(@uri.origin).to eq('http://192.0.2.16:8000') + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com'" do + before do + @uri = Addressable::URI.parse("http://example.com") + end + + it "when inspected, should have the correct URI" do + expect(@uri.inspect).to include("http://example.com") + end + + it "when inspected, should have the correct class name" do + expect(@uri.inspect).to include("Addressable::URI") + end + + it "when inspected, should have the correct object id" do + expect(@uri.inspect).to include("%#0x" % @uri.object_id) + end + + it "should use the 'http' scheme" do + expect(@uri.scheme).to eq("http") + end + + it "should be considered to be ip-based" do + expect(@uri).to be_ip_based + end + + it "should have an authority segment of 'example.com'" do + expect(@uri.authority).to eq("example.com") + end + + it "should have a host of 'example.com'" do + expect(@uri.host).to eq("example.com") + end + + it "should be considered ip-based" do + expect(@uri).to be_ip_based + end + + it "should have no username" do + expect(@uri.user).to eq(nil) + end + + it "should have no password" do + expect(@uri.password).to eq(nil) + end + + it "should use port 80" do + expect(@uri.inferred_port).to eq(80) + end + + it "should not have a specified port" do + expect(@uri.port).to eq(nil) + end + + it "should have an empty path" do + expect(@uri.path).to eq("") + end + + it "should have no query string" do + expect(@uri.query).to eq(nil) + expect(@uri.query_values).to eq(nil) + end + + it "should have a request URI of '/'" do + expect(@uri.request_uri).to eq("/") + end + + it "should have no fragment" do + expect(@uri.fragment).to eq(nil) + end + + it "should be considered absolute" do + expect(@uri).to be_absolute + end + + it "should not be considered relative" do + expect(@uri).not_to be_relative + end + + it "should not be exactly equal to 42" do + expect(@uri.eql?(42)).to eq(false) + end + + it "should not be equal to 42" do + expect(@uri == 42).to eq(false) + end + + it "should not be roughly equal to 42" do + expect(@uri === 42).to eq(false) + end + + it "should be exactly equal to http://example.com" do + expect(@uri.eql?(Addressable::URI.parse("http://example.com"))).to eq(true) + end + + it "should be roughly equal to http://example.com/" do + expect(@uri === Addressable::URI.parse("http://example.com/")).to eq(true) + end + + it "should be roughly equal to the string 'http://example.com/'" do + expect(@uri === "http://example.com/").to eq(true) + end + + it "should not be roughly equal to the string " + + "'http://example.com:bogus/'" do + expect(lambda do + expect(@uri === "http://example.com:bogus/").to eq(false) + end).not_to raise_error + end + + it "should result in itself when joined with itself" do + expect(@uri.join(@uri).to_s).to eq("http://example.com") + expect(@uri.join!(@uri).to_s).to eq("http://example.com") + end + + it "should be equivalent to http://EXAMPLE.com" do + expect(@uri).to eq(Addressable::URI.parse("http://EXAMPLE.com")) + end + + it "should be equivalent to http://EXAMPLE.com:80/" do + expect(@uri).to eq(Addressable::URI.parse("http://EXAMPLE.com:80/")) + end + + it "should have the same hash as http://example.com" do + expect(@uri.hash).to eq(Addressable::URI.parse("http://example.com").hash) + end + + it "should have the same hash as http://EXAMPLE.com after assignment" do + @uri.origin = "http://EXAMPLE.com" + expect(@uri.hash).to eq(Addressable::URI.parse("http://EXAMPLE.com").hash) + end + + it "should have a different hash from http://EXAMPLE.com" do + expect(@uri.hash).not_to eq(Addressable::URI.parse("http://EXAMPLE.com").hash) + end + + it "should not allow origin assignment without scheme" do + expect(lambda do + @uri.origin = "example.com" + end).to raise_error(Addressable::URI::InvalidURIError) + end + + it "should not allow origin assignment without host" do + expect(lambda do + @uri.origin = "http://" + end).to raise_error(Addressable::URI::InvalidURIError) + end + + it "should not allow origin assignment with bogus type" do + expect(lambda do + @uri.origin = :bogus + end).to raise_error(TypeError) + end + + # Section 6.2.3 of RFC 3986 + it "should be equivalent to http://example.com/" do + expect(@uri).to eq(Addressable::URI.parse("http://example.com/")) + end + + # Section 6.2.3 of RFC 3986 + it "should be equivalent to http://example.com:/" do + expect(@uri).to eq(Addressable::URI.parse("http://example.com:/")) + end + + # Section 6.2.3 of RFC 3986 + it "should be equivalent to http://example.com:80/" do + expect(@uri).to eq(Addressable::URI.parse("http://example.com:80/")) + end + + # Section 6.2.2.1 of RFC 3986 + it "should be equivalent to http://EXAMPLE.COM/" do + expect(@uri).to eq(Addressable::URI.parse("http://EXAMPLE.COM/")) + end + + it "should have a route of '/path/' to 'http://example.com/path/'" do + expect(@uri.route_to("http://example.com/path/")).to eq( + Addressable::URI.parse("/path/") + ) + end + + it "should have a route of '..' from 'http://example.com/path/'" do + expect(@uri.route_from("http://example.com/path/")).to eq( + Addressable::URI.parse("..") + ) + end + + it "should have a route of '#' to 'http://example.com/'" do + expect(@uri.route_to("http://example.com/")).to eq( + Addressable::URI.parse("#") + ) + end + + it "should have a route of 'http://elsewhere.com/' to " + + "'http://elsewhere.com/'" do + expect(@uri.route_to("http://elsewhere.com/")).to eq( + Addressable::URI.parse("http://elsewhere.com/") + ) + end + + it "when joined with 'relative/path' should be " + + "'http://example.com/relative/path'" do + expect(@uri.join('relative/path')).to eq( + Addressable::URI.parse("http://example.com/relative/path") + ) + end + + it "when joined with a bogus object a TypeError should be raised" do + expect(lambda do + @uri.join(42) + end).to raise_error(TypeError) + end + + it "should have the correct username after assignment" do + @uri.user = "newuser" + expect(@uri.user).to eq("newuser") + expect(@uri.password).to eq(nil) + expect(@uri.to_s).to eq("http://newuser@example.com") + end + + it "should have the correct username after assignment" do + @uri.user = "user@123!" + expect(@uri.user).to eq("user@123!") + expect(@uri.normalized_user).to eq("user%40123%21") + expect(@uri.password).to eq(nil) + expect(@uri.normalize.to_s).to eq("http://user%40123%21@example.com/") + end + + it "should have the correct password after assignment" do + @uri.password = "newpass" + expect(@uri.password).to eq("newpass") + expect(@uri.user).to eq("") + expect(@uri.to_s).to eq("http://:newpass@example.com") + end + + it "should have the correct password after assignment" do + @uri.password = "#secret@123!" + expect(@uri.password).to eq("#secret@123!") + expect(@uri.normalized_password).to eq("%23secret%40123%21") + expect(@uri.user).to eq("") + expect(@uri.normalize.to_s).to eq("http://:%23secret%40123%21@example.com/") + expect(@uri.omit(:password).to_s).to eq("http://example.com") + end + + it "should have the correct user/pass after repeated assignment" do + @uri.user = nil + expect(@uri.user).to eq(nil) + @uri.password = "newpass" + expect(@uri.password).to eq("newpass") + # Username cannot be nil if the password is set + expect(@uri.user).to eq("") + expect(@uri.to_s).to eq("http://:newpass@example.com") + @uri.user = "newuser" + expect(@uri.user).to eq("newuser") + @uri.password = nil + expect(@uri.password).to eq(nil) + expect(@uri.to_s).to eq("http://newuser@example.com") + @uri.user = "newuser" + expect(@uri.user).to eq("newuser") + @uri.password = "" + expect(@uri.password).to eq("") + expect(@uri.to_s).to eq("http://newuser:@example.com") + @uri.password = "newpass" + expect(@uri.password).to eq("newpass") + @uri.user = nil + # Username cannot be nil if the password is set + expect(@uri.user).to eq("") + expect(@uri.to_s).to eq("http://:newpass@example.com") + end + + it "should have the correct user/pass after userinfo assignment" do + @uri.user = "newuser" + expect(@uri.user).to eq("newuser") + @uri.password = "newpass" + expect(@uri.password).to eq("newpass") + @uri.userinfo = nil + expect(@uri.userinfo).to eq(nil) + expect(@uri.user).to eq(nil) + expect(@uri.password).to eq(nil) + end + + it "should correctly convert to a hash" do + expect(@uri.to_hash).to eq({ + :scheme => "http", + :user => nil, + :password => nil, + :host => "example.com", + :port => nil, + :path => "", + :query => nil, + :fragment => nil + }) + end + + it "should be identical to its duplicate" do + expect(@uri).to eq(@uri.dup) + end + + it "should have an origin of 'http://example.com'" do + expect(@uri.origin).to eq('http://example.com') + end +end + +# Section 5.1.2 of RFC 2616 +describe Addressable::URI, "when parsed from " + + "'HTTP://www.w3.org/pub/WWW/TheProject.html'" do + before do + @uri = Addressable::URI.parse("HTTP://www.w3.org/pub/WWW/TheProject.html") + end + + it "should have the correct request URI" do + expect(@uri.request_uri).to eq("/pub/WWW/TheProject.html") + end + + it "should have the correct request URI after assignment" do + @uri.request_uri = "/pub/WWW/TheProject.html?" + expect(@uri.request_uri).to eq("/pub/WWW/TheProject.html?") + expect(@uri.path).to eq("/pub/WWW/TheProject.html") + expect(@uri.query).to eq("") + end + + it "should have the correct request URI after assignment" do + @uri.request_uri = "/some/where/else.html" + expect(@uri.request_uri).to eq("/some/where/else.html") + expect(@uri.path).to eq("/some/where/else.html") + expect(@uri.query).to eq(nil) + end + + it "should have the correct request URI after assignment" do + @uri.request_uri = "/some/where/else.html?query?string" + expect(@uri.request_uri).to eq("/some/where/else.html?query?string") + expect(@uri.path).to eq("/some/where/else.html") + expect(@uri.query).to eq("query?string") + end + + it "should have the correct request URI after assignment" do + @uri.request_uri = "?x=y" + expect(@uri.request_uri).to eq("/?x=y") + expect(@uri.path).to eq("/") + expect(@uri.query).to eq("x=y") + end + + it "should raise an error if the site value is set to something bogus" do + expect(lambda do + @uri.site = 42 + end).to raise_error(TypeError) + end + + it "should raise an error if the request URI is set to something bogus" do + expect(lambda do + @uri.request_uri = 42 + end).to raise_error(TypeError) + end + + it "should correctly convert to a hash" do + expect(@uri.to_hash).to eq({ + :scheme => "HTTP", + :user => nil, + :password => nil, + :host => "www.w3.org", + :port => nil, + :path => "/pub/WWW/TheProject.html", + :query => nil, + :fragment => nil + }) + end + + it "should have an origin of 'http://www.w3.org'" do + expect(@uri.origin).to eq('http://www.w3.org') + end +end + +describe Addressable::URI, "when parsing IPv6 addresses" do + it "should not raise an error for " + + "'http://[3ffe:1900:4545:3:200:f8ff:fe21:67cf]/'" do + Addressable::URI.parse("http://[3ffe:1900:4545:3:200:f8ff:fe21:67cf]/") + end + + it "should not raise an error for " + + "'http://[fe80:0:0:0:200:f8ff:fe21:67cf]/'" do + Addressable::URI.parse("http://[fe80:0:0:0:200:f8ff:fe21:67cf]/") + end + + it "should not raise an error for " + + "'http://[fe80::200:f8ff:fe21:67cf]/'" do + Addressable::URI.parse("http://[fe80::200:f8ff:fe21:67cf]/") + end + + it "should not raise an error for " + + "'http://[::1]/'" do + Addressable::URI.parse("http://[::1]/") + end + + it "should not raise an error for " + + "'http://[fe80::1]/'" do + Addressable::URI.parse("http://[fe80::1]/") + end + + it "should raise an error for " + + "'http://[]/'" do + expect(lambda do + Addressable::URI.parse("http://[]/") + end).to raise_error(Addressable::URI::InvalidURIError) + end +end + +describe Addressable::URI, "when parsing IPv6 address" do + subject { Addressable::URI.parse("http://[3ffe:1900:4545:3:200:f8ff:fe21:67cf]/") } + its(:host) { should == '[3ffe:1900:4545:3:200:f8ff:fe21:67cf]' } + its(:hostname) { should == '3ffe:1900:4545:3:200:f8ff:fe21:67cf' } +end + +describe Addressable::URI, "when assigning IPv6 address" do + it "should allow to set bare IPv6 address as hostname" do + uri = Addressable::URI.parse("http://[::1]/") + uri.hostname = '3ffe:1900:4545:3:200:f8ff:fe21:67cf' + expect(uri.to_s).to eq('http://[3ffe:1900:4545:3:200:f8ff:fe21:67cf]/') + end + + it "should allow to set bare IPv6 address as hostname with IPAddr object" do + uri = Addressable::URI.parse("http://[::1]/") + uri.hostname = IPAddr.new('3ffe:1900:4545:3:200:f8ff:fe21:67cf') + expect(uri.to_s).to eq('http://[3ffe:1900:4545:3:200:f8ff:fe21:67cf]/') + end + + it "should not allow to set bare IPv6 address as host" do + uri = Addressable::URI.parse("http://[::1]/") + skip "not checked" + expect(lambda do + uri.host = '3ffe:1900:4545:3:200:f8ff:fe21:67cf' + end).to raise_error(Addressable::URI::InvalidURIError) + end +end + +describe Addressable::URI, "when parsing IPvFuture addresses" do + it "should not raise an error for " + + "'http://[v9.3ffe:1900:4545:3:200:f8ff:fe21:67cf]/'" do + Addressable::URI.parse("http://[v9.3ffe:1900:4545:3:200:f8ff:fe21:67cf]/") + end + + it "should not raise an error for " + + "'http://[vff.fe80:0:0:0:200:f8ff:fe21:67cf]/'" do + Addressable::URI.parse("http://[vff.fe80:0:0:0:200:f8ff:fe21:67cf]/") + end + + it "should not raise an error for " + + "'http://[v12.fe80::200:f8ff:fe21:67cf]/'" do + Addressable::URI.parse("http://[v12.fe80::200:f8ff:fe21:67cf]/") + end + + it "should not raise an error for " + + "'http://[va0.::1]/'" do + Addressable::URI.parse("http://[va0.::1]/") + end + + it "should not raise an error for " + + "'http://[v255.fe80::1]/'" do + Addressable::URI.parse("http://[v255.fe80::1]/") + end + + it "should raise an error for " + + "'http://[v0.]/'" do + expect(lambda do + Addressable::URI.parse("http://[v0.]/") + end).to raise_error(Addressable::URI::InvalidURIError) + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/'" do + before do + @uri = Addressable::URI.parse("http://example.com/") + end + + # Based on http://intertwingly.net/blog/2004/07/31/URI-Equivalence + it "should be equivalent to http://example.com" do + expect(@uri).to eq(Addressable::URI.parse("http://example.com")) + end + + # Based on http://intertwingly.net/blog/2004/07/31/URI-Equivalence + it "should be equivalent to HTTP://example.com/" do + expect(@uri).to eq(Addressable::URI.parse("HTTP://example.com/")) + end + + # Based on http://intertwingly.net/blog/2004/07/31/URI-Equivalence + it "should be equivalent to http://example.com:/" do + expect(@uri).to eq(Addressable::URI.parse("http://example.com:/")) + end + + # Based on http://intertwingly.net/blog/2004/07/31/URI-Equivalence + it "should be equivalent to http://example.com:80/" do + expect(@uri).to eq(Addressable::URI.parse("http://example.com:80/")) + end + + # Based on http://intertwingly.net/blog/2004/07/31/URI-Equivalence + it "should be equivalent to http://Example.com/" do + expect(@uri).to eq(Addressable::URI.parse("http://Example.com/")) + end + + it "should have the correct username after assignment" do + @uri.user = nil + expect(@uri.user).to eq(nil) + expect(@uri.password).to eq(nil) + expect(@uri.to_s).to eq("http://example.com/") + end + + it "should have the correct password after assignment" do + @uri.password = nil + expect(@uri.password).to eq(nil) + expect(@uri.user).to eq(nil) + expect(@uri.to_s).to eq("http://example.com/") + end + + it "should have a request URI of '/'" do + expect(@uri.request_uri).to eq("/") + end + + it "should correctly convert to a hash" do + expect(@uri.to_hash).to eq({ + :scheme => "http", + :user => nil, + :password => nil, + :host => "example.com", + :port => nil, + :path => "/", + :query => nil, + :fragment => nil + }) + end + + it "should be identical to its duplicate" do + expect(@uri).to eq(@uri.dup) + end + + it "should have the same hash as its duplicate" do + expect(@uri.hash).to eq(@uri.dup.hash) + end + + it "should have a different hash from its equivalent String value" do + expect(@uri.hash).not_to eq(@uri.to_s.hash) + end + + it "should have the same hash as an equal URI" do + expect(@uri.hash).to eq(Addressable::URI.parse("http://example.com/").hash) + end + + it "should be equivalent to http://EXAMPLE.com" do + expect(@uri).to eq(Addressable::URI.parse("http://EXAMPLE.com")) + end + + it "should be equivalent to http://EXAMPLE.com:80/" do + expect(@uri).to eq(Addressable::URI.parse("http://EXAMPLE.com:80/")) + end + + it "should have the same hash as http://example.com/" do + expect(@uri.hash).to eq(Addressable::URI.parse("http://example.com/").hash) + end + + it "should have the same hash as http://example.com after assignment" do + @uri.path = "" + expect(@uri.hash).to eq(Addressable::URI.parse("http://example.com").hash) + end + + it "should have the same hash as http://example.com/? after assignment" do + @uri.query = "" + expect(@uri.hash).to eq(Addressable::URI.parse("http://example.com/?").hash) + end + + it "should have the same hash as http://example.com/? after assignment" do + @uri.query_values = {} + expect(@uri.hash).to eq(Addressable::URI.parse("http://example.com/?").hash) + end + + it "should have the same hash as http://example.com/# after assignment" do + @uri.fragment = "" + expect(@uri.hash).to eq(Addressable::URI.parse("http://example.com/#").hash) + end + + it "should have a different hash from http://example.com" do + expect(@uri.hash).not_to eq(Addressable::URI.parse("http://example.com").hash) + end + + it "should have an origin of 'http://example.com'" do + expect(@uri.origin).to eq('http://example.com') + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com?#'" do + before do + @uri = Addressable::URI.parse("http://example.com?#") + end + + it "should correctly convert to a hash" do + expect(@uri.to_hash).to eq({ + :scheme => "http", + :user => nil, + :password => nil, + :host => "example.com", + :port => nil, + :path => "", + :query => "", + :fragment => "" + }) + end + + it "should have a request URI of '/?'" do + expect(@uri.request_uri).to eq("/?") + end + + it "should normalize to 'http://example.com/'" do + expect(@uri.normalize.to_s).to eq("http://example.com/") + end + + it "should have an origin of 'http://example.com'" do + expect(@uri.origin).to eq("http://example.com") + end +end + +describe Addressable::URI, "when parsed from " + + "'http://@example.com/'" do + before do + @uri = Addressable::URI.parse("http://@example.com/") + end + + it "should be equivalent to http://example.com" do + expect(@uri).to eq(Addressable::URI.parse("http://example.com")) + end + + it "should correctly convert to a hash" do + expect(@uri.to_hash).to eq({ + :scheme => "http", + :user => "", + :password => nil, + :host => "example.com", + :port => nil, + :path => "/", + :query => nil, + :fragment => nil + }) + end + + it "should be identical to its duplicate" do + expect(@uri).to eq(@uri.dup) + end + + it "should have an origin of 'http://example.com'" do + expect(@uri.origin).to eq('http://example.com') + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com./'" do + before do + @uri = Addressable::URI.parse("http://example.com./") + end + + it "should be equivalent to http://example.com" do + expect(@uri).to eq(Addressable::URI.parse("http://example.com")) + end + + it "should not be considered to be in normal form" do + expect(@uri.normalize).not_to be_eql(@uri) + end + + it "should be identical to its duplicate" do + expect(@uri).to eq(@uri.dup) + end + + it "should have an origin of 'http://example.com'" do + expect(@uri.origin).to eq('http://example.com') + end +end + +describe Addressable::URI, "when parsed from " + + "'http://:@example.com/'" do + before do + @uri = Addressable::URI.parse("http://:@example.com/") + end + + it "should be equivalent to http://example.com" do + expect(@uri).to eq(Addressable::URI.parse("http://example.com")) + end + + it "should correctly convert to a hash" do + expect(@uri.to_hash).to eq({ + :scheme => "http", + :user => "", + :password => "", + :host => "example.com", + :port => nil, + :path => "/", + :query => nil, + :fragment => nil + }) + end + + it "should be identical to its duplicate" do + expect(@uri).to eq(@uri.dup) + end + + it "should have an origin of 'http://example.com'" do + expect(@uri.origin).to eq('http://example.com') + end +end + +describe Addressable::URI, "when parsed from " + + "'HTTP://EXAMPLE.COM/'" do + before do + @uri = Addressable::URI.parse("HTTP://EXAMPLE.COM/") + end + + it "should be equivalent to http://example.com" do + expect(@uri).to eq(Addressable::URI.parse("http://example.com")) + end + + it "should correctly convert to a hash" do + expect(@uri.to_hash).to eq({ + :scheme => "HTTP", + :user => nil, + :password => nil, + :host => "EXAMPLE.COM", + :port => nil, + :path => "/", + :query => nil, + :fragment => nil + }) + end + + it "should be identical to its duplicate" do + expect(@uri).to eq(@uri.dup) + end + + it "should have an origin of 'http://example.com'" do + expect(@uri.origin).to eq('http://example.com') + end + + it "should have a tld of 'com'" do + expect(@uri.tld).to eq('com') + end +end + +describe Addressable::URI, "when parsed from " + + "'http://www.example.co.uk/'" do + before do + @uri = Addressable::URI.parse("http://www.example.co.uk/") + end + + it "should have an origin of 'http://www.example.co.uk'" do + expect(@uri.origin).to eq('http://www.example.co.uk') + end + + it "should have a tld of 'co.uk'" do + expect(@uri.tld).to eq('co.uk') + end + + it "should have a domain of 'example.co.uk'" do + expect(@uri.domain).to eq('example.co.uk') + end +end + +describe Addressable::URI, "when parsed from " + + "'http://sub_domain.blogspot.com/'" do + before do + @uri = Addressable::URI.parse("http://sub_domain.blogspot.com/") + end + + it "should have an origin of 'http://sub_domain.blogspot.com'" do + expect(@uri.origin).to eq('http://sub_domain.blogspot.com') + end + + it "should have a tld of 'com'" do + expect(@uri.tld).to eq('com') + end + + it "should have a domain of 'blogspot.com'" do + expect(@uri.domain).to eq('blogspot.com') + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/~smith/'" do + before do + @uri = Addressable::URI.parse("http://example.com/~smith/") + end + + # Based on http://intertwingly.net/blog/2004/07/31/URI-Equivalence + it "should be equivalent to http://example.com/%7Esmith/" do + expect(@uri).to eq(Addressable::URI.parse("http://example.com/%7Esmith/")) + end + + # Based on http://intertwingly.net/blog/2004/07/31/URI-Equivalence + it "should be equivalent to http://example.com/%7esmith/" do + expect(@uri).to eq(Addressable::URI.parse("http://example.com/%7esmith/")) + end + + it "should be identical to its duplicate" do + expect(@uri).to eq(@uri.dup) + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/%E8'" do + before do + @uri = Addressable::URI.parse("http://example.com/%E8") + end + + it "should not raise an exception when normalized" do + expect(lambda do + @uri.normalize + end).not_to raise_error + end + + it "should be considered to be in normal form" do + expect(@uri.normalize).to be_eql(@uri) + end + + it "should not change if encoded with the normalizing algorithm" do + expect(Addressable::URI.normalized_encode(@uri).to_s).to eq( + "http://example.com/%E8" + ) + expect(Addressable::URI.normalized_encode(@uri, Addressable::URI).to_s).to be === + "http://example.com/%E8" + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/path%2Fsegment/'" do + before do + @uri = Addressable::URI.parse("http://example.com/path%2Fsegment/") + end + + it "should be considered to be in normal form" do + expect(@uri.normalize).to be_eql(@uri) + end + + it "should be equal to 'http://example.com/path%2Fsegment/'" do + expect(@uri.normalize).to be_eql( + Addressable::URI.parse("http://example.com/path%2Fsegment/") + ) + end + + it "should not be equal to 'http://example.com/path/segment/'" do + expect(@uri).not_to eq( + Addressable::URI.parse("http://example.com/path/segment/") + ) + end + + it "should not be equal to 'http://example.com/path/segment/'" do + expect(@uri.normalize).not_to be_eql( + Addressable::URI.parse("http://example.com/path/segment/") + ) + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/?%F6'" do + before do + @uri = Addressable::URI.parse("http://example.com/?%F6") + end + + it "should not raise an exception when normalized" do + expect(lambda do + @uri.normalize + end).not_to raise_error + end + + it "should be considered to be in normal form" do + expect(@uri.normalize).to be_eql(@uri) + end + + it "should not change if encoded with the normalizing algorithm" do + expect(Addressable::URI.normalized_encode(@uri).to_s).to eq( + "http://example.com/?%F6" + ) + expect(Addressable::URI.normalized_encode(@uri, Addressable::URI).to_s).to be === + "http://example.com/?%F6" + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/#%F6'" do + before do + @uri = Addressable::URI.parse("http://example.com/#%F6") + end + + it "should not raise an exception when normalized" do + expect(lambda do + @uri.normalize + end).not_to raise_error + end + + it "should be considered to be in normal form" do + expect(@uri.normalize).to be_eql(@uri) + end + + it "should not change if encoded with the normalizing algorithm" do + expect(Addressable::URI.normalized_encode(@uri).to_s).to eq( + "http://example.com/#%F6" + ) + expect(Addressable::URI.normalized_encode(@uri, Addressable::URI).to_s).to be === + "http://example.com/#%F6" + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/%C3%87'" do + before do + @uri = Addressable::URI.parse("http://example.com/%C3%87") + end + + # Based on http://intertwingly.net/blog/2004/07/31/URI-Equivalence + it "should be equivalent to 'http://example.com/C%CC%A7'" do + expect(@uri).to eq(Addressable::URI.parse("http://example.com/C%CC%A7")) + end + + it "should not change if encoded with the normalizing algorithm" do + expect(Addressable::URI.normalized_encode(@uri).to_s).to eq( + "http://example.com/%C3%87" + ) + expect(Addressable::URI.normalized_encode(@uri, Addressable::URI).to_s).to be === + "http://example.com/%C3%87" + end + + it "should raise an error if encoding with an unexpected return type" do + expect(lambda do + Addressable::URI.normalized_encode(@uri, Integer) + end).to raise_error(TypeError) + end + + it "if percent encoded should be 'http://example.com/C%25CC%25A7'" do + expect(Addressable::URI.encode(@uri).to_s).to eq( + "http://example.com/%25C3%2587" + ) + end + + it "if percent encoded should be 'http://example.com/C%25CC%25A7'" do + expect(Addressable::URI.encode(@uri, Addressable::URI)).to eq( + Addressable::URI.parse("http://example.com/%25C3%2587") + ) + end + + it "should raise an error if encoding with an unexpected return type" do + expect(lambda do + Addressable::URI.encode(@uri, Integer) + end).to raise_error(TypeError) + end + + it "should be identical to its duplicate" do + expect(@uri).to eq(@uri.dup) + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/?q=string'" do + before do + @uri = Addressable::URI.parse("http://example.com/?q=string") + end + + it "should use the 'http' scheme" do + expect(@uri.scheme).to eq("http") + end + + it "should have an authority segment of 'example.com'" do + expect(@uri.authority).to eq("example.com") + end + + it "should have a host of 'example.com'" do + expect(@uri.host).to eq("example.com") + end + + it "should have no username" do + expect(@uri.user).to eq(nil) + end + + it "should have no password" do + expect(@uri.password).to eq(nil) + end + + it "should use port 80" do + expect(@uri.inferred_port).to eq(80) + end + + it "should have a path of '/'" do + expect(@uri.path).to eq("/") + end + + it "should have a query string of 'q=string'" do + expect(@uri.query).to eq("q=string") + end + + it "should have no fragment" do + expect(@uri.fragment).to eq(nil) + end + + it "should be considered absolute" do + expect(@uri).to be_absolute + end + + it "should not be considered relative" do + expect(@uri).not_to be_relative + end + + it "should be considered to be in normal form" do + expect(@uri.normalize).to be_eql(@uri) + end + + it "should be identical to its duplicate" do + expect(@uri).to eq(@uri.dup) + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com:80/'" do + before do + @uri = Addressable::URI.parse("http://example.com:80/") + end + + it "should use the 'http' scheme" do + expect(@uri.scheme).to eq("http") + end + + it "should have an authority segment of 'example.com:80'" do + expect(@uri.authority).to eq("example.com:80") + end + + it "should have a host of 'example.com'" do + expect(@uri.host).to eq("example.com") + end + + it "should have no username" do + expect(@uri.user).to eq(nil) + end + + it "should have no password" do + expect(@uri.password).to eq(nil) + end + + it "should use port 80" do + expect(@uri.inferred_port).to eq(80) + end + + it "should have explicit port 80" do + expect(@uri.port).to eq(80) + end + + it "should have a path of '/'" do + expect(@uri.path).to eq("/") + end + + it "should have no query string" do + expect(@uri.query).to eq(nil) + end + + it "should have no fragment" do + expect(@uri.fragment).to eq(nil) + end + + it "should be considered absolute" do + expect(@uri).to be_absolute + end + + it "should not be considered relative" do + expect(@uri).not_to be_relative + end + + it "should be exactly equal to http://example.com:80/" do + expect(@uri.eql?(Addressable::URI.parse("http://example.com:80/"))).to eq(true) + end + + it "should be roughly equal to http://example.com/" do + expect(@uri === Addressable::URI.parse("http://example.com/")).to eq(true) + end + + it "should be roughly equal to the string 'http://example.com/'" do + expect(@uri === "http://example.com/").to eq(true) + end + + it "should not be roughly equal to the string " + + "'http://example.com:bogus/'" do + expect(lambda do + expect(@uri === "http://example.com:bogus/").to eq(false) + end).not_to raise_error + end + + it "should result in itself when joined with itself" do + expect(@uri.join(@uri).to_s).to eq("http://example.com:80/") + expect(@uri.join!(@uri).to_s).to eq("http://example.com:80/") + end + + # Section 6.2.3 of RFC 3986 + it "should be equal to http://example.com/" do + expect(@uri).to eq(Addressable::URI.parse("http://example.com/")) + end + + # Section 6.2.3 of RFC 3986 + it "should be equal to http://example.com:/" do + expect(@uri).to eq(Addressable::URI.parse("http://example.com:/")) + end + + # Section 6.2.3 of RFC 3986 + it "should be equal to http://example.com:80/" do + expect(@uri).to eq(Addressable::URI.parse("http://example.com:80/")) + end + + # Section 6.2.2.1 of RFC 3986 + it "should be equal to http://EXAMPLE.COM/" do + expect(@uri).to eq(Addressable::URI.parse("http://EXAMPLE.COM/")) + end + + it "should correctly convert to a hash" do + expect(@uri.to_hash).to eq({ + :scheme => "http", + :user => nil, + :password => nil, + :host => "example.com", + :port => 80, + :path => "/", + :query => nil, + :fragment => nil + }) + end + + it "should be identical to its duplicate" do + expect(@uri).to eq(@uri.dup) + end + + it "should have an origin of 'http://example.com'" do + expect(@uri.origin).to eq('http://example.com') + end + + it "should not change if encoded with the normalizing algorithm" do + expect(Addressable::URI.normalized_encode(@uri).to_s).to eq( + "http://example.com:80/" + ) + expect(Addressable::URI.normalized_encode(@uri, Addressable::URI).to_s).to be === + "http://example.com:80/" + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com:8080/'" do + before do + @uri = Addressable::URI.parse("http://example.com:8080/") + end + + it "should use the 'http' scheme" do + expect(@uri.scheme).to eq("http") + end + + it "should have an authority segment of 'example.com:8080'" do + expect(@uri.authority).to eq("example.com:8080") + end + + it "should have a host of 'example.com'" do + expect(@uri.host).to eq("example.com") + end + + it "should have no username" do + expect(@uri.user).to eq(nil) + end + + it "should have no password" do + expect(@uri.password).to eq(nil) + end + + it "should use port 8080" do + expect(@uri.inferred_port).to eq(8080) + end + + it "should have explicit port 8080" do + expect(@uri.port).to eq(8080) + end + + it "should have default port 80" do + expect(@uri.default_port).to eq(80) + end + + it "should have a path of '/'" do + expect(@uri.path).to eq("/") + end + + it "should have no query string" do + expect(@uri.query).to eq(nil) + end + + it "should have no fragment" do + expect(@uri.fragment).to eq(nil) + end + + it "should be considered absolute" do + expect(@uri).to be_absolute + end + + it "should not be considered relative" do + expect(@uri).not_to be_relative + end + + it "should be exactly equal to http://example.com:8080/" do + expect(@uri.eql?(Addressable::URI.parse( + "http://example.com:8080/"))).to eq(true) + end + + it "should have a route of 'http://example.com:8080/' from " + + "'http://example.com/path/to/'" do + expect(@uri.route_from("http://example.com/path/to/")).to eq( + Addressable::URI.parse("http://example.com:8080/") + ) + end + + it "should have a route of 'http://example.com:8080/' from " + + "'http://example.com:80/path/to/'" do + expect(@uri.route_from("http://example.com:80/path/to/")).to eq( + Addressable::URI.parse("http://example.com:8080/") + ) + end + + it "should have a route of '../../' from " + + "'http://example.com:8080/path/to/'" do + expect(@uri.route_from("http://example.com:8080/path/to/")).to eq( + Addressable::URI.parse("../../") + ) + end + + it "should have a route of 'http://example.com:8080/' from " + + "'http://user:pass@example.com/path/to/'" do + expect(@uri.route_from("http://user:pass@example.com/path/to/")).to eq( + Addressable::URI.parse("http://example.com:8080/") + ) + end + + it "should correctly convert to a hash" do + expect(@uri.to_hash).to eq({ + :scheme => "http", + :user => nil, + :password => nil, + :host => "example.com", + :port => 8080, + :path => "/", + :query => nil, + :fragment => nil + }) + end + + it "should be identical to its duplicate" do + expect(@uri).to eq(@uri.dup) + end + + it "should have an origin of 'http://example.com:8080'" do + expect(@uri.origin).to eq('http://example.com:8080') + end + + it "should not change if encoded with the normalizing algorithm" do + expect(Addressable::URI.normalized_encode(@uri).to_s).to eq( + "http://example.com:8080/" + ) + expect(Addressable::URI.normalized_encode(@uri, Addressable::URI).to_s).to be === + "http://example.com:8080/" + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com:%38%30/'" do + before do + @uri = Addressable::URI.parse("http://example.com:%38%30/") + end + + it "should have the correct port" do + expect(@uri.port).to eq(80) + end + + it "should not be considered to be in normal form" do + expect(@uri.normalize).not_to be_eql(@uri) + end + + it "should normalize to 'http://example.com/'" do + expect(@uri.normalize.to_s).to eq("http://example.com/") + end + + it "should have an origin of 'http://example.com'" do + expect(@uri.origin).to eq('http://example.com') + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/%2E/'" do + before do + @uri = Addressable::URI.parse("http://example.com/%2E/") + end + + it "should be considered to be in normal form" do + skip( + 'path segment normalization should happen before ' + + 'percent escaping normalization' + ) + @uri.normalize.should be_eql(@uri) + end + + it "should normalize to 'http://example.com/%2E/'" do + skip( + 'path segment normalization should happen before ' + + 'percent escaping normalization' + ) + expect(@uri.normalize).to eq("http://example.com/%2E/") + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/..'" do + before do + @uri = Addressable::URI.parse("http://example.com/..") + end + + it "should have the correct port" do + expect(@uri.inferred_port).to eq(80) + end + + it "should not be considered to be in normal form" do + expect(@uri.normalize).not_to be_eql(@uri) + end + + it "should normalize to 'http://example.com/'" do + expect(@uri.normalize.to_s).to eq("http://example.com/") + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/../..'" do + before do + @uri = Addressable::URI.parse("http://example.com/../..") + end + + it "should have the correct port" do + expect(@uri.inferred_port).to eq(80) + end + + it "should not be considered to be in normal form" do + expect(@uri.normalize).not_to be_eql(@uri) + end + + it "should normalize to 'http://example.com/'" do + expect(@uri.normalize.to_s).to eq("http://example.com/") + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/path(/..'" do + before do + @uri = Addressable::URI.parse("http://example.com/path(/..") + end + + it "should have the correct port" do + expect(@uri.inferred_port).to eq(80) + end + + it "should not be considered to be in normal form" do + expect(@uri.normalize).not_to be_eql(@uri) + end + + it "should normalize to 'http://example.com/'" do + expect(@uri.normalize.to_s).to eq("http://example.com/") + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/(path)/..'" do + before do + @uri = Addressable::URI.parse("http://example.com/(path)/..") + end + + it "should have the correct port" do + expect(@uri.inferred_port).to eq(80) + end + + it "should not be considered to be in normal form" do + expect(@uri.normalize).not_to be_eql(@uri) + end + + it "should normalize to 'http://example.com/'" do + expect(@uri.normalize.to_s).to eq("http://example.com/") + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/path(/../'" do + before do + @uri = Addressable::URI.parse("http://example.com/path(/../") + end + + it "should have the correct port" do + expect(@uri.inferred_port).to eq(80) + end + + it "should not be considered to be in normal form" do + expect(@uri.normalize).not_to be_eql(@uri) + end + + it "should normalize to 'http://example.com/'" do + expect(@uri.normalize.to_s).to eq("http://example.com/") + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/(path)/../'" do + before do + @uri = Addressable::URI.parse("http://example.com/(path)/../") + end + + it "should have the correct port" do + expect(@uri.inferred_port).to eq(80) + end + + it "should not be considered to be in normal form" do + expect(@uri.normalize).not_to be_eql(@uri) + end + + it "should normalize to 'http://example.com/'" do + expect(@uri.normalize.to_s).to eq("http://example.com/") + end +end + +describe Addressable::URI, "when parsed from " + + "'/..//example.com'" do + before do + @uri = Addressable::URI.parse("/..//example.com") + end + + it "should become invalid when normalized" do + expect(lambda do + @uri.normalize + end).to raise_error(Addressable::URI::InvalidURIError, /authority/) + end + + it "should have a path of '/..//example.com'" do + expect(@uri.path).to eq("/..//example.com") + end +end + +describe Addressable::URI, "when parsed from '/a/b/c/./../../g'" do + before do + @uri = Addressable::URI.parse("/a/b/c/./../../g") + end + + it "should not be considered to be in normal form" do + expect(@uri.normalize).not_to be_eql(@uri) + end + + # Section 5.2.4 of RFC 3986 + it "should normalize to '/a/g'" do + expect(@uri.normalize.to_s).to eq("/a/g") + end +end + +describe Addressable::URI, "when parsed from 'mid/content=5/../6'" do + before do + @uri = Addressable::URI.parse("mid/content=5/../6") + end + + it "should not be considered to be in normal form" do + expect(@uri.normalize).not_to be_eql(@uri) + end + + # Section 5.2.4 of RFC 3986 + it "should normalize to 'mid/6'" do + expect(@uri.normalize.to_s).to eq("mid/6") + end +end + +describe Addressable::URI, "when parsed from " + + "'http://www.example.com///../'" do + before do + @uri = Addressable::URI.parse('http://www.example.com///../') + end + + it "should not be considered to be in normal form" do + expect(@uri.normalize).not_to be_eql(@uri) + end + + it "should normalize to 'http://www.example.com//'" do + expect(@uri.normalize.to_s).to eq("http://www.example.com//") + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/path/to/resource/'" do + before do + @uri = Addressable::URI.parse("http://example.com/path/to/resource/") + end + + it "should use the 'http' scheme" do + expect(@uri.scheme).to eq("http") + end + + it "should have an authority segment of 'example.com'" do + expect(@uri.authority).to eq("example.com") + end + + it "should have a host of 'example.com'" do + expect(@uri.host).to eq("example.com") + end + + it "should have no username" do + expect(@uri.user).to eq(nil) + end + + it "should have no password" do + expect(@uri.password).to eq(nil) + end + + it "should use port 80" do + expect(@uri.inferred_port).to eq(80) + end + + it "should have a path of '/path/to/resource/'" do + expect(@uri.path).to eq("/path/to/resource/") + end + + it "should have no query string" do + expect(@uri.query).to eq(nil) + end + + it "should have no fragment" do + expect(@uri.fragment).to eq(nil) + end + + it "should be considered absolute" do + expect(@uri).to be_absolute + end + + it "should not be considered relative" do + expect(@uri).not_to be_relative + end + + it "should be exactly equal to http://example.com:8080/" do + expect(@uri.eql?(Addressable::URI.parse( + "http://example.com/path/to/resource/"))).to eq(true) + end + + it "should have a route of 'resource/' from " + + "'http://example.com/path/to/'" do + expect(@uri.route_from("http://example.com/path/to/")).to eq( + Addressable::URI.parse("resource/") + ) + end + + it "should have a route of '../' from " + + "'http://example.com/path/to/resource/sub'" do + expect(@uri.route_from("http://example.com/path/to/resource/sub")).to eq( + Addressable::URI.parse("../") + ) + end + + + it "should have a route of 'resource/' from " + + "'http://example.com/path/to/another'" do + expect(@uri.route_from("http://example.com/path/to/another")).to eq( + Addressable::URI.parse("resource/") + ) + end + + it "should have a route of 'resource/' from " + + "'http://example.com/path/to/res'" do + expect(@uri.route_from("http://example.com/path/to/res")).to eq( + Addressable::URI.parse("resource/") + ) + end + + it "should have a route of 'resource/' from " + + "'http://example.com:80/path/to/'" do + expect(@uri.route_from("http://example.com:80/path/to/")).to eq( + Addressable::URI.parse("resource/") + ) + end + + it "should have a route of 'http://example.com/path/to/' from " + + "'http://example.com:8080/path/to/'" do + expect(@uri.route_from("http://example.com:8080/path/to/")).to eq( + Addressable::URI.parse("http://example.com/path/to/resource/") + ) + end + + it "should have a route of 'http://example.com/path/to/' from " + + "'http://user:pass@example.com/path/to/'" do + expect(@uri.route_from("http://user:pass@example.com/path/to/")).to eq( + Addressable::URI.parse("http://example.com/path/to/resource/") + ) + end + + it "should have a route of '../../path/to/resource/' from " + + "'http://example.com/to/resource/'" do + expect(@uri.route_from("http://example.com/to/resource/")).to eq( + Addressable::URI.parse("../../path/to/resource/") + ) + end + + it "should correctly convert to a hash" do + expect(@uri.to_hash).to eq({ + :scheme => "http", + :user => nil, + :password => nil, + :host => "example.com", + :port => nil, + :path => "/path/to/resource/", + :query => nil, + :fragment => nil + }) + end + + it "should be identical to its duplicate" do + expect(@uri).to eq(@uri.dup) + end +end + +describe Addressable::URI, "when parsed from " + + "'relative/path/to/resource'" do + before do + @uri = Addressable::URI.parse("relative/path/to/resource") + end + + it "should not have a scheme" do + expect(@uri.scheme).to eq(nil) + end + + it "should not be considered ip-based" do + expect(@uri).not_to be_ip_based + end + + it "should not have an authority segment" do + expect(@uri.authority).to eq(nil) + end + + it "should not have a host" do + expect(@uri.host).to eq(nil) + end + + it "should have no username" do + expect(@uri.user).to eq(nil) + end + + it "should have no password" do + expect(@uri.password).to eq(nil) + end + + it "should not have a port" do + expect(@uri.port).to eq(nil) + end + + it "should have a path of 'relative/path/to/resource'" do + expect(@uri.path).to eq("relative/path/to/resource") + end + + it "should have no query string" do + expect(@uri.query).to eq(nil) + end + + it "should have no fragment" do + expect(@uri.fragment).to eq(nil) + end + + it "should not be considered absolute" do + expect(@uri).not_to be_absolute + end + + it "should be considered relative" do + expect(@uri).to be_relative + end + + it "should raise an error if routing is attempted" do + expect(lambda do + @uri.route_to("http://example.com/") + end).to raise_error(ArgumentError, /relative\/path\/to\/resource/) + expect(lambda do + @uri.route_from("http://example.com/") + end).to raise_error(ArgumentError, /relative\/path\/to\/resource/) + end + + it "when joined with 'another/relative/path' should be " + + "'relative/path/to/another/relative/path'" do + expect(@uri.join('another/relative/path')).to eq( + Addressable::URI.parse("relative/path/to/another/relative/path") + ) + end + + it "should be identical to its duplicate" do + expect(@uri).to eq(@uri.dup) + end +end + +describe Addressable::URI, "when parsed from " + + "'relative_path_with_no_slashes'" do + before do + @uri = Addressable::URI.parse("relative_path_with_no_slashes") + end + + it "should not have a scheme" do + expect(@uri.scheme).to eq(nil) + end + + it "should not be considered ip-based" do + expect(@uri).not_to be_ip_based + end + + it "should not have an authority segment" do + expect(@uri.authority).to eq(nil) + end + + it "should not have a host" do + expect(@uri.host).to eq(nil) + end + + it "should have no username" do + expect(@uri.user).to eq(nil) + end + + it "should have no password" do + expect(@uri.password).to eq(nil) + end + + it "should not have a port" do + expect(@uri.port).to eq(nil) + end + + it "should have a path of 'relative_path_with_no_slashes'" do + expect(@uri.path).to eq("relative_path_with_no_slashes") + end + + it "should have no query string" do + expect(@uri.query).to eq(nil) + end + + it "should have no fragment" do + expect(@uri.fragment).to eq(nil) + end + + it "should not be considered absolute" do + expect(@uri).not_to be_absolute + end + + it "should be considered relative" do + expect(@uri).to be_relative + end + + it "when joined with 'another_relative_path' should be " + + "'another_relative_path'" do + expect(@uri.join('another_relative_path')).to eq( + Addressable::URI.parse("another_relative_path") + ) + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/file.txt'" do + before do + @uri = Addressable::URI.parse("http://example.com/file.txt") + end + + it "should have a scheme of 'http'" do + expect(@uri.scheme).to eq("http") + end + + it "should have an authority segment of 'example.com'" do + expect(@uri.authority).to eq("example.com") + end + + it "should have a host of 'example.com'" do + expect(@uri.host).to eq("example.com") + end + + it "should have no username" do + expect(@uri.user).to eq(nil) + end + + it "should have no password" do + expect(@uri.password).to eq(nil) + end + + it "should use port 80" do + expect(@uri.inferred_port).to eq(80) + end + + it "should have a path of '/file.txt'" do + expect(@uri.path).to eq("/file.txt") + end + + it "should have a basename of 'file.txt'" do + expect(@uri.basename).to eq("file.txt") + end + + it "should have an extname of '.txt'" do + expect(@uri.extname).to eq(".txt") + end + + it "should have no query string" do + expect(@uri.query).to eq(nil) + end + + it "should have no fragment" do + expect(@uri.fragment).to eq(nil) + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/file.txt;parameter'" do + before do + @uri = Addressable::URI.parse("http://example.com/file.txt;parameter") + end + + it "should have a scheme of 'http'" do + expect(@uri.scheme).to eq("http") + end + + it "should have an authority segment of 'example.com'" do + expect(@uri.authority).to eq("example.com") + end + + it "should have a host of 'example.com'" do + expect(@uri.host).to eq("example.com") + end + + it "should have no username" do + expect(@uri.user).to eq(nil) + end + + it "should have no password" do + expect(@uri.password).to eq(nil) + end + + it "should use port 80" do + expect(@uri.inferred_port).to eq(80) + end + + it "should have a path of '/file.txt;parameter'" do + expect(@uri.path).to eq("/file.txt;parameter") + end + + it "should have a basename of 'file.txt'" do + expect(@uri.basename).to eq("file.txt") + end + + it "should have an extname of '.txt'" do + expect(@uri.extname).to eq(".txt") + end + + it "should have no query string" do + expect(@uri.query).to eq(nil) + end + + it "should have no fragment" do + expect(@uri.fragment).to eq(nil) + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/file.txt;x=y'" do + before do + @uri = Addressable::URI.parse("http://example.com/file.txt;x=y") + end + + it "should have a scheme of 'http'" do + expect(@uri.scheme).to eq("http") + end + + it "should have a scheme of 'http'" do + expect(@uri.scheme).to eq("http") + end + + it "should have an authority segment of 'example.com'" do + expect(@uri.authority).to eq("example.com") + end + + it "should have a host of 'example.com'" do + expect(@uri.host).to eq("example.com") + end + + it "should have no username" do + expect(@uri.user).to eq(nil) + end + + it "should have no password" do + expect(@uri.password).to eq(nil) + end + + it "should use port 80" do + expect(@uri.inferred_port).to eq(80) + end + + it "should have a path of '/file.txt;x=y'" do + expect(@uri.path).to eq("/file.txt;x=y") + end + + it "should have an extname of '.txt'" do + expect(@uri.extname).to eq(".txt") + end + + it "should have no query string" do + expect(@uri.query).to eq(nil) + end + + it "should have no fragment" do + expect(@uri.fragment).to eq(nil) + end + + it "should be considered to be in normal form" do + expect(@uri.normalize).to be_eql(@uri) + end +end + +describe Addressable::URI, "when parsed from " + + "'svn+ssh://developername@rubyforge.org/var/svn/project'" do + before do + @uri = Addressable::URI.parse( + "svn+ssh://developername@rubyforge.org/var/svn/project" + ) + end + + it "should have a scheme of 'svn+ssh'" do + expect(@uri.scheme).to eq("svn+ssh") + end + + it "should be considered to be ip-based" do + expect(@uri).to be_ip_based + end + + it "should have a path of '/var/svn/project'" do + expect(@uri.path).to eq("/var/svn/project") + end + + it "should have a username of 'developername'" do + expect(@uri.user).to eq("developername") + end + + it "should have no password" do + expect(@uri.password).to eq(nil) + end + + it "should be considered to be in normal form" do + expect(@uri.normalize).to be_eql(@uri) + end +end + +describe Addressable::URI, "when parsed from " + + "'ssh+svn://developername@RUBYFORGE.ORG/var/svn/project'" do + before do + @uri = Addressable::URI.parse( + "ssh+svn://developername@RUBYFORGE.ORG/var/svn/project" + ) + end + + it "should have a scheme of 'ssh+svn'" do + expect(@uri.scheme).to eq("ssh+svn") + end + + it "should have a normalized scheme of 'svn+ssh'" do + expect(@uri.normalized_scheme).to eq("svn+ssh") + end + + it "should have a normalized site of 'svn+ssh'" do + expect(@uri.normalized_site).to eq("svn+ssh://developername@rubyforge.org") + end + + it "should not be considered to be ip-based" do + expect(@uri).not_to be_ip_based + end + + it "should have a path of '/var/svn/project'" do + expect(@uri.path).to eq("/var/svn/project") + end + + it "should have a username of 'developername'" do + expect(@uri.user).to eq("developername") + end + + it "should have no password" do + expect(@uri.password).to eq(nil) + end + + it "should not be considered to be in normal form" do + expect(@uri.normalize).not_to be_eql(@uri) + end +end + +describe Addressable::URI, "when parsed from " + + "'mailto:user@example.com'" do + before do + @uri = Addressable::URI.parse("mailto:user@example.com") + end + + it "should have a scheme of 'mailto'" do + expect(@uri.scheme).to eq("mailto") + end + + it "should not be considered to be ip-based" do + expect(@uri).not_to be_ip_based + end + + it "should have a path of 'user@example.com'" do + expect(@uri.path).to eq("user@example.com") + end + + it "should have no user" do + expect(@uri.user).to eq(nil) + end + + it "should be considered to be in normal form" do + expect(@uri.normalize).to be_eql(@uri) + end +end + +describe Addressable::URI, "when parsed from " + + "'tag:example.com,2006-08-18:/path/to/something'" do + before do + @uri = Addressable::URI.parse( + "tag:example.com,2006-08-18:/path/to/something") + end + + it "should have a scheme of 'tag'" do + expect(@uri.scheme).to eq("tag") + end + + it "should be considered to be ip-based" do + expect(@uri).not_to be_ip_based + end + + it "should have a path of " + + "'example.com,2006-08-18:/path/to/something'" do + expect(@uri.path).to eq("example.com,2006-08-18:/path/to/something") + end + + it "should have no user" do + expect(@uri.user).to eq(nil) + end + + it "should be considered to be in normal form" do + expect(@uri.normalize).to be_eql(@uri) + end + + it "should have a 'null' origin" do + expect(@uri.origin).to eq('null') + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/x;y/'" do + before do + @uri = Addressable::URI.parse("http://example.com/x;y/") + end + + it "should be considered to be in normal form" do + expect(@uri.normalize).to be_eql(@uri) + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/?x=1&y=2'" do + before do + @uri = Addressable::URI.parse("http://example.com/?x=1&y=2") + end + + it "should be considered to be in normal form" do + expect(@uri.normalize).to be_eql(@uri) + end +end + +describe Addressable::URI, "when parsed from " + + "'view-source:http://example.com/'" do + before do + @uri = Addressable::URI.parse("view-source:http://example.com/") + end + + it "should have a scheme of 'view-source'" do + expect(@uri.scheme).to eq("view-source") + end + + it "should have a path of 'http://example.com/'" do + expect(@uri.path).to eq("http://example.com/") + end + + it "should be considered to be in normal form" do + expect(@uri.normalize).to be_eql(@uri) + end + + it "should have a 'null' origin" do + expect(@uri.origin).to eq('null') + end +end + +describe Addressable::URI, "when parsed from " + + "'http://user:pass@example.com/path/to/resource?query=x#fragment'" do + before do + @uri = Addressable::URI.parse( + "http://user:pass@example.com/path/to/resource?query=x#fragment") + end + + it "should use the 'http' scheme" do + expect(@uri.scheme).to eq("http") + end + + it "should have an authority segment of 'user:pass@example.com'" do + expect(@uri.authority).to eq("user:pass@example.com") + end + + it "should have a username of 'user'" do + expect(@uri.user).to eq("user") + end + + it "should have a password of 'pass'" do + expect(@uri.password).to eq("pass") + end + + it "should have a host of 'example.com'" do + expect(@uri.host).to eq("example.com") + end + + it "should use port 80" do + expect(@uri.inferred_port).to eq(80) + end + + it "should have a path of '/path/to/resource'" do + expect(@uri.path).to eq("/path/to/resource") + end + + it "should have a query string of 'query=x'" do + expect(@uri.query).to eq("query=x") + end + + it "should have a fragment of 'fragment'" do + expect(@uri.fragment).to eq("fragment") + end + + it "should be considered to be in normal form" do + expect(@uri.normalize).to be_eql(@uri) + end + + it "should have a route of '../../' to " + + "'http://user:pass@example.com/path/'" do + expect(@uri.route_to("http://user:pass@example.com/path/")).to eq( + Addressable::URI.parse("../../") + ) + end + + it "should have a route of 'to/resource?query=x#fragment' " + + "from 'http://user:pass@example.com/path/'" do + expect(@uri.route_from("http://user:pass@example.com/path/")).to eq( + Addressable::URI.parse("to/resource?query=x#fragment") + ) + end + + it "should have a route of '?query=x#fragment' " + + "from 'http://user:pass@example.com/path/to/resource'" do + expect(@uri.route_from("http://user:pass@example.com/path/to/resource")).to eq( + Addressable::URI.parse("?query=x#fragment") + ) + end + + it "should have a route of '#fragment' " + + "from 'http://user:pass@example.com/path/to/resource?query=x'" do + expect(@uri.route_from( + "http://user:pass@example.com/path/to/resource?query=x")).to eq( + Addressable::URI.parse("#fragment") + ) + end + + it "should have a route of '#fragment' from " + + "'http://user:pass@example.com/path/to/resource?query=x#fragment'" do + expect(@uri.route_from( + "http://user:pass@example.com/path/to/resource?query=x#fragment" + )).to eq(Addressable::URI.parse("#fragment")) + end + + it "should have a route of 'http://elsewhere.com/' to " + + "'http://elsewhere.com/'" do + expect(@uri.route_to("http://elsewhere.com/")).to eq( + Addressable::URI.parse("http://elsewhere.com/") + ) + end + + it "should have a route of " + + "'http://user:pass@example.com/path/to/resource?query=x#fragment' " + + "from 'http://example.com/path/to/'" do + expect(@uri.route_from("http://elsewhere.com/path/to/")).to eq( + Addressable::URI.parse( + "http://user:pass@example.com/path/to/resource?query=x#fragment") + ) + end + + it "should have the correct scheme after assignment" do + @uri.scheme = "ftp" + expect(@uri.scheme).to eq("ftp") + expect(@uri.to_s).to eq( + "ftp://user:pass@example.com/path/to/resource?query=x#fragment" + ) + expect(@uri.to_str).to eq( + "ftp://user:pass@example.com/path/to/resource?query=x#fragment" + ) + end + + it "should have the correct site segment after assignment" do + @uri.site = "https://newuser:newpass@example.com:443" + expect(@uri.scheme).to eq("https") + expect(@uri.authority).to eq("newuser:newpass@example.com:443") + expect(@uri.user).to eq("newuser") + expect(@uri.password).to eq("newpass") + expect(@uri.userinfo).to eq("newuser:newpass") + expect(@uri.normalized_userinfo).to eq("newuser:newpass") + expect(@uri.host).to eq("example.com") + expect(@uri.port).to eq(443) + expect(@uri.inferred_port).to eq(443) + expect(@uri.to_s).to eq( + "https://newuser:newpass@example.com:443" + + "/path/to/resource?query=x#fragment" + ) + end + + it "should have the correct authority segment after assignment" do + @uri.authority = "newuser:newpass@example.com:80" + expect(@uri.authority).to eq("newuser:newpass@example.com:80") + expect(@uri.user).to eq("newuser") + expect(@uri.password).to eq("newpass") + expect(@uri.userinfo).to eq("newuser:newpass") + expect(@uri.normalized_userinfo).to eq("newuser:newpass") + expect(@uri.host).to eq("example.com") + expect(@uri.port).to eq(80) + expect(@uri.inferred_port).to eq(80) + expect(@uri.to_s).to eq( + "http://newuser:newpass@example.com:80" + + "/path/to/resource?query=x#fragment" + ) + end + + it "should have the correct userinfo segment after assignment" do + @uri.userinfo = "newuser:newpass" + expect(@uri.userinfo).to eq("newuser:newpass") + expect(@uri.authority).to eq("newuser:newpass@example.com") + expect(@uri.user).to eq("newuser") + expect(@uri.password).to eq("newpass") + expect(@uri.host).to eq("example.com") + expect(@uri.port).to eq(nil) + expect(@uri.inferred_port).to eq(80) + expect(@uri.to_s).to eq( + "http://newuser:newpass@example.com" + + "/path/to/resource?query=x#fragment" + ) + end + + it "should have the correct username after assignment" do + @uri.user = "newuser" + expect(@uri.user).to eq("newuser") + expect(@uri.authority).to eq("newuser:pass@example.com") + end + + it "should have the correct password after assignment" do + @uri.password = "newpass" + expect(@uri.password).to eq("newpass") + expect(@uri.authority).to eq("user:newpass@example.com") + end + + it "should have the correct host after assignment" do + @uri.host = "newexample.com" + expect(@uri.host).to eq("newexample.com") + expect(@uri.authority).to eq("user:pass@newexample.com") + end + + it "should have the correct host after assignment" do + @uri.hostname = "newexample.com" + expect(@uri.host).to eq("newexample.com") + expect(@uri.hostname).to eq("newexample.com") + expect(@uri.authority).to eq("user:pass@newexample.com") + end + + it "should raise an error if assigning a bogus object to the hostname" do + expect(lambda do + @uri.hostname = Object.new + end).to raise_error(TypeError) + end + + it "should have the correct port after assignment" do + @uri.port = 8080 + expect(@uri.port).to eq(8080) + expect(@uri.authority).to eq("user:pass@example.com:8080") + end + + it "should have the correct origin after assignment" do + @uri.origin = "http://newexample.com" + expect(@uri.host).to eq("newexample.com") + expect(@uri.authority).to eq("newexample.com") + end + + it "should have the correct path after assignment" do + @uri.path = "/newpath/to/resource" + expect(@uri.path).to eq("/newpath/to/resource") + expect(@uri.to_s).to eq( + "http://user:pass@example.com/newpath/to/resource?query=x#fragment" + ) + end + + it "should have the correct scheme and authority after nil assignment" do + @uri.site = nil + expect(@uri.scheme).to eq(nil) + expect(@uri.authority).to eq(nil) + expect(@uri.to_s).to eq("/path/to/resource?query=x#fragment") + end + + it "should have the correct scheme and authority after assignment" do + @uri.site = "file://" + expect(@uri.scheme).to eq("file") + expect(@uri.authority).to eq("") + expect(@uri.to_s).to eq("file:///path/to/resource?query=x#fragment") + end + + it "should have the correct path after nil assignment" do + @uri.path = nil + expect(@uri.path).to eq("") + expect(@uri.to_s).to eq( + "http://user:pass@example.com?query=x#fragment" + ) + end + + it "should have the correct query string after assignment" do + @uri.query = "newquery=x" + expect(@uri.query).to eq("newquery=x") + expect(@uri.to_s).to eq( + "http://user:pass@example.com/path/to/resource?newquery=x#fragment" + ) + @uri.query = nil + expect(@uri.query).to eq(nil) + expect(@uri.to_s).to eq( + "http://user:pass@example.com/path/to/resource#fragment" + ) + end + + it "should have the correct query string after hash assignment" do + @uri.query_values = {"?uestion mark" => "=sign", "hello" => "g\xC3\xBCnther"} + expect(@uri.query.split("&")).to include("%3Fuestion%20mark=%3Dsign") + expect(@uri.query.split("&")).to include("hello=g%C3%BCnther") + expect(@uri.query_values).to eq({ + "?uestion mark" => "=sign", "hello" => "g\xC3\xBCnther" + }) + end + + it "should have the correct query string after flag hash assignment" do + @uri.query_values = {'flag?1' => nil, 'fl=ag2' => nil, 'flag3' => nil} + expect(@uri.query.split("&")).to include("flag%3F1") + expect(@uri.query.split("&")).to include("fl%3Dag2") + expect(@uri.query.split("&")).to include("flag3") + expect(@uri.query_values(Array).sort).to eq([["fl=ag2"], ["flag3"], ["flag?1"]]) + expect(@uri.query_values(Hash)).to eq({ + 'flag?1' => nil, 'fl=ag2' => nil, 'flag3' => nil + }) + end + + it "should raise an error if query values are set to a bogus type" do + expect(lambda do + @uri.query_values = "bogus" + end).to raise_error(TypeError) + end + + it "should have the correct fragment after assignment" do + @uri.fragment = "newfragment" + expect(@uri.fragment).to eq("newfragment") + expect(@uri.to_s).to eq( + "http://user:pass@example.com/path/to/resource?query=x#newfragment" + ) + + @uri.fragment = nil + expect(@uri.fragment).to eq(nil) + expect(@uri.to_s).to eq( + "http://user:pass@example.com/path/to/resource?query=x" + ) + end + + it "should have the correct values after a merge" do + expect(@uri.merge(:fragment => "newfragment").to_s).to eq( + "http://user:pass@example.com/path/to/resource?query=x#newfragment" + ) + end + + it "should have the correct values after a merge" do + expect(@uri.merge(:fragment => nil).to_s).to eq( + "http://user:pass@example.com/path/to/resource?query=x" + ) + end + + it "should have the correct values after a merge" do + expect(@uri.merge(:userinfo => "newuser:newpass").to_s).to eq( + "http://newuser:newpass@example.com/path/to/resource?query=x#fragment" + ) + end + + it "should have the correct values after a merge" do + expect(@uri.merge(:userinfo => nil).to_s).to eq( + "http://example.com/path/to/resource?query=x#fragment" + ) + end + + it "should have the correct values after a merge" do + expect(@uri.merge(:path => "newpath").to_s).to eq( + "http://user:pass@example.com/newpath?query=x#fragment" + ) + end + + it "should have the correct values after a merge" do + expect(@uri.merge(:port => "42", :path => "newpath", :query => "").to_s).to eq( + "http://user:pass@example.com:42/newpath?#fragment" + ) + end + + it "should have the correct values after a merge" do + expect(@uri.merge(:authority => "foo:bar@baz:42").to_s).to eq( + "http://foo:bar@baz:42/path/to/resource?query=x#fragment" + ) + # Ensure the operation was not destructive + expect(@uri.to_s).to eq( + "http://user:pass@example.com/path/to/resource?query=x#fragment" + ) + end + + it "should have the correct values after a destructive merge" do + @uri.merge!(:authority => "foo:bar@baz:42") + # Ensure the operation was destructive + expect(@uri.to_s).to eq( + "http://foo:bar@baz:42/path/to/resource?query=x#fragment" + ) + end + + it "should fail to merge with bogus values" do + expect(lambda do + @uri.merge(:port => "bogus") + end).to raise_error(Addressable::URI::InvalidURIError) + end + + it "should fail to merge with bogus values" do + expect(lambda do + @uri.merge(:authority => "bar@baz:bogus") + end).to raise_error(Addressable::URI::InvalidURIError) + end + + it "should fail to merge with bogus parameters" do + expect(lambda do + @uri.merge(42) + end).to raise_error(TypeError) + end + + it "should fail to merge with bogus parameters" do + expect(lambda do + @uri.merge("http://example.com/") + end).to raise_error(TypeError) + end + + it "should fail to merge with both authority and subcomponents" do + expect(lambda do + @uri.merge(:authority => "foo:bar@baz:42", :port => "42") + end).to raise_error(ArgumentError) + end + + it "should fail to merge with both userinfo and subcomponents" do + expect(lambda do + @uri.merge(:userinfo => "foo:bar", :user => "foo") + end).to raise_error(ArgumentError) + end + + it "should be identical to its duplicate" do + expect(@uri).to eq(@uri.dup) + end + + it "should have an origin of 'http://example.com'" do + expect(@uri.origin).to eq('http://example.com') + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/search?q=Q%26A'" do + + before do + @uri = Addressable::URI.parse("http://example.com/search?q=Q%26A") + end + + it "should have a query of 'q=Q%26A'" do + expect(@uri.query).to eq("q=Q%26A") + end + + it "should have query_values of {'q' => 'Q&A'}" do + expect(@uri.query_values).to eq({ 'q' => 'Q&A' }) + end + + it "should normalize to the original uri " + + "(with the ampersand properly percent-encoded)" do + expect(@uri.normalize.to_s).to eq("http://example.com/search?q=Q%26A") + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/?&x=b'" do + before do + @uri = Addressable::URI.parse("http://example.com/?&x=b") + end + + it "should have a query of '&x=b'" do + expect(@uri.query).to eq("&x=b") + end + + it "should have query_values of {'x' => 'b'}" do + expect(@uri.query_values).to eq({'x' => 'b'}) + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/?q='one;two'&x=1'" do + before do + @uri = Addressable::URI.parse("http://example.com/?q='one;two'&x=1") + end + + it "should have a query of 'q='one;two'&x=1'" do + expect(@uri.query).to eq("q='one;two'&x=1") + end + + it "should have query_values of {\"q\" => \"'one;two'\", \"x\" => \"1\"}" do + expect(@uri.query_values).to eq({"q" => "'one;two'", "x" => "1"}) + end + + it "should escape the ';' character when normalizing to avoid ambiguity " + + "with the W3C HTML 4.01 specification" do + # HTML 4.01 Section B.2.2 + expect(@uri.normalize.query).to eq("q='one%3Btwo'&x=1") + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/?&&x=b'" do + before do + @uri = Addressable::URI.parse("http://example.com/?&&x=b") + end + + it "should have a query of '&&x=b'" do + expect(@uri.query).to eq("&&x=b") + end + + it "should have query_values of {'x' => 'b'}" do + expect(@uri.query_values).to eq({'x' => 'b'}) + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/?q=a&&x=b'" do + before do + @uri = Addressable::URI.parse("http://example.com/?q=a&&x=b") + end + + it "should have a query of 'q=a&&x=b'" do + expect(@uri.query).to eq("q=a&&x=b") + end + + it "should have query_values of {'q' => 'a, 'x' => 'b'}" do + expect(@uri.query_values).to eq({'q' => 'a', 'x' => 'b'}) + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/?q&&x=b'" do + before do + @uri = Addressable::URI.parse("http://example.com/?q&&x=b") + end + + it "should have a query of 'q&&x=b'" do + expect(@uri.query).to eq("q&&x=b") + end + + it "should have query_values of {'q' => true, 'x' => 'b'}" do + expect(@uri.query_values).to eq({'q' => nil, 'x' => 'b'}) + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/?q=a+b'" do + before do + @uri = Addressable::URI.parse("http://example.com/?q=a+b") + end + + it "should have a query of 'q=a+b'" do + expect(@uri.query).to eq("q=a+b") + end + + it "should have query_values of {'q' => 'a b'}" do + expect(@uri.query_values).to eq({'q' => 'a b'}) + end + + it "should have a normalized query of 'q=a+b'" do + expect(@uri.normalized_query).to eq("q=a+b") + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/?q=a%2bb'" do + before do + @uri = Addressable::URI.parse("http://example.com/?q=a%2bb") + end + + it "should have a query of 'q=a+b'" do + expect(@uri.query).to eq("q=a%2bb") + end + + it "should have query_values of {'q' => 'a+b'}" do + expect(@uri.query_values).to eq({'q' => 'a+b'}) + end + + it "should have a normalized query of 'q=a%2Bb'" do + expect(@uri.normalized_query).to eq("q=a%2Bb") + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/?v=%7E&w=%&x=%25&y=%2B&z=C%CC%A7'" do + before do + @uri = Addressable::URI.parse("http://example.com/?v=%7E&w=%&x=%25&y=%2B&z=C%CC%A7") + end + + it "should have a normalized query of 'v=~&w=%25&x=%25&y=%2B&z=%C3%87'" do + expect(@uri.normalized_query).to eq("v=~&w=%25&x=%25&y=%2B&z=%C3%87") + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/?v=%7E&w=%&x=%25&y=+&z=C%CC%A7'" do + before do + @uri = Addressable::URI.parse("http://example.com/?v=%7E&w=%&x=%25&y=+&z=C%CC%A7") + end + + it "should have a normalized query of 'v=~&w=%25&x=%25&y=+&z=%C3%87'" do + expect(@uri.normalized_query).to eq("v=~&w=%25&x=%25&y=+&z=%C3%87") + end +end + +describe Addressable::URI, "when parsed from 'http://example/?b=1&a=2&c=3'" do + before do + @uri = Addressable::URI.parse("http://example/?b=1&a=2&c=3") + end + + it "should have a sorted normalized query of 'a=2&b=1&c=3'" do + expect(@uri.normalized_query(:sorted)).to eq("a=2&b=1&c=3") + end +end + +describe Addressable::URI, "when parsed from 'http://example/?&a&&c&'" do + before do + @uri = Addressable::URI.parse("http://example/?&a&&c&") + end + + it "should have a compacted normalized query of 'a&c'" do + expect(@uri.normalized_query(:compacted)).to eq("a&c") + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/sound%2bvision'" do + before do + @uri = Addressable::URI.parse("http://example.com/sound%2bvision") + end + + it "should have a normalized path of '/sound+vision'" do + expect(@uri.normalized_path).to eq('/sound+vision') + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/?q='" do + before do + @uri = Addressable::URI.parse("http://example.com/?q=") + end + + it "should have a query of 'q='" do + expect(@uri.query).to eq("q=") + end + + it "should have query_values of {'q' => ''}" do + expect(@uri.query_values).to eq({'q' => ''}) + end +end + +describe Addressable::URI, "when parsed from " + + "'http://user@example.com'" do + before do + @uri = Addressable::URI.parse("http://user@example.com") + end + + it "should use the 'http' scheme" do + expect(@uri.scheme).to eq("http") + end + + it "should have a username of 'user'" do + expect(@uri.user).to eq("user") + end + + it "should have no password" do + expect(@uri.password).to eq(nil) + end + + it "should have a userinfo of 'user'" do + expect(@uri.userinfo).to eq("user") + end + + it "should have a normalized userinfo of 'user'" do + expect(@uri.normalized_userinfo).to eq("user") + end + + it "should have a host of 'example.com'" do + expect(@uri.host).to eq("example.com") + end + + it "should have default_port 80" do + expect(@uri.default_port).to eq(80) + end + + it "should use port 80" do + expect(@uri.inferred_port).to eq(80) + end + + it "should have the correct username after assignment" do + @uri.user = "newuser" + expect(@uri.user).to eq("newuser") + expect(@uri.password).to eq(nil) + expect(@uri.to_s).to eq("http://newuser@example.com") + end + + it "should have the correct password after assignment" do + @uri.password = "newpass" + expect(@uri.password).to eq("newpass") + expect(@uri.to_s).to eq("http://user:newpass@example.com") + end + + it "should have the correct userinfo segment after assignment" do + @uri.userinfo = "newuser:newpass" + expect(@uri.userinfo).to eq("newuser:newpass") + expect(@uri.user).to eq("newuser") + expect(@uri.password).to eq("newpass") + expect(@uri.host).to eq("example.com") + expect(@uri.port).to eq(nil) + expect(@uri.inferred_port).to eq(80) + expect(@uri.to_s).to eq("http://newuser:newpass@example.com") + end + + it "should have the correct userinfo segment after nil assignment" do + @uri.userinfo = nil + expect(@uri.userinfo).to eq(nil) + expect(@uri.user).to eq(nil) + expect(@uri.password).to eq(nil) + expect(@uri.host).to eq("example.com") + expect(@uri.port).to eq(nil) + expect(@uri.inferred_port).to eq(80) + expect(@uri.to_s).to eq("http://example.com") + end + + it "should have the correct authority segment after assignment" do + @uri.authority = "newuser@example.com" + expect(@uri.authority).to eq("newuser@example.com") + expect(@uri.user).to eq("newuser") + expect(@uri.password).to eq(nil) + expect(@uri.host).to eq("example.com") + expect(@uri.port).to eq(nil) + expect(@uri.inferred_port).to eq(80) + expect(@uri.to_s).to eq("http://newuser@example.com") + end + + it "should raise an error after nil assignment of authority segment" do + expect(lambda do + # This would create an invalid URI + @uri.authority = nil + end).to raise_error(Addressable::URI::InvalidURIError) + end +end + +describe Addressable::URI, "when parsed from " + + "'http://user:@example.com'" do + before do + @uri = Addressable::URI.parse("http://user:@example.com") + end + + it "should use the 'http' scheme" do + expect(@uri.scheme).to eq("http") + end + + it "should have a username of 'user'" do + expect(@uri.user).to eq("user") + end + + it "should have a password of ''" do + expect(@uri.password).to eq("") + end + + it "should have a normalized userinfo of 'user:'" do + expect(@uri.normalized_userinfo).to eq("user:") + end + + it "should have a host of 'example.com'" do + expect(@uri.host).to eq("example.com") + end + + it "should use port 80" do + expect(@uri.inferred_port).to eq(80) + end + + it "should have the correct username after assignment" do + @uri.user = "newuser" + expect(@uri.user).to eq("newuser") + expect(@uri.password).to eq("") + expect(@uri.to_s).to eq("http://newuser:@example.com") + end + + it "should have the correct password after assignment" do + @uri.password = "newpass" + expect(@uri.password).to eq("newpass") + expect(@uri.to_s).to eq("http://user:newpass@example.com") + end + + it "should have the correct authority segment after assignment" do + @uri.authority = "newuser:@example.com" + expect(@uri.authority).to eq("newuser:@example.com") + expect(@uri.user).to eq("newuser") + expect(@uri.password).to eq("") + expect(@uri.host).to eq("example.com") + expect(@uri.port).to eq(nil) + expect(@uri.inferred_port).to eq(80) + expect(@uri.to_s).to eq("http://newuser:@example.com") + end +end + +describe Addressable::URI, "when parsed from " + + "'http://:pass@example.com'" do + before do + @uri = Addressable::URI.parse("http://:pass@example.com") + end + + it "should use the 'http' scheme" do + expect(@uri.scheme).to eq("http") + end + + it "should have a username of ''" do + expect(@uri.user).to eq("") + end + + it "should have a password of 'pass'" do + expect(@uri.password).to eq("pass") + end + + it "should have a userinfo of ':pass'" do + expect(@uri.userinfo).to eq(":pass") + end + + it "should have a normalized userinfo of ':pass'" do + expect(@uri.normalized_userinfo).to eq(":pass") + end + + it "should have a host of 'example.com'" do + expect(@uri.host).to eq("example.com") + end + + it "should use port 80" do + expect(@uri.inferred_port).to eq(80) + end + + it "should have the correct username after assignment" do + @uri.user = "newuser" + expect(@uri.user).to eq("newuser") + expect(@uri.password).to eq("pass") + expect(@uri.to_s).to eq("http://newuser:pass@example.com") + end + + it "should have the correct password after assignment" do + @uri.password = "newpass" + expect(@uri.password).to eq("newpass") + expect(@uri.user).to eq("") + expect(@uri.to_s).to eq("http://:newpass@example.com") + end + + it "should have the correct authority segment after assignment" do + @uri.authority = ":newpass@example.com" + expect(@uri.authority).to eq(":newpass@example.com") + expect(@uri.user).to eq("") + expect(@uri.password).to eq("newpass") + expect(@uri.host).to eq("example.com") + expect(@uri.port).to eq(nil) + expect(@uri.inferred_port).to eq(80) + expect(@uri.to_s).to eq("http://:newpass@example.com") + end +end + +describe Addressable::URI, "when parsed from " + + "'http://:@example.com'" do + before do + @uri = Addressable::URI.parse("http://:@example.com") + end + + it "should use the 'http' scheme" do + expect(@uri.scheme).to eq("http") + end + + it "should have a username of ''" do + expect(@uri.user).to eq("") + end + + it "should have a password of ''" do + expect(@uri.password).to eq("") + end + + it "should have a normalized userinfo of nil" do + expect(@uri.normalized_userinfo).to eq(nil) + end + + it "should have a host of 'example.com'" do + expect(@uri.host).to eq("example.com") + end + + it "should use port 80" do + expect(@uri.inferred_port).to eq(80) + end + + it "should have the correct username after assignment" do + @uri.user = "newuser" + expect(@uri.user).to eq("newuser") + expect(@uri.password).to eq("") + expect(@uri.to_s).to eq("http://newuser:@example.com") + end + + it "should have the correct password after assignment" do + @uri.password = "newpass" + expect(@uri.password).to eq("newpass") + expect(@uri.user).to eq("") + expect(@uri.to_s).to eq("http://:newpass@example.com") + end + + it "should have the correct authority segment after assignment" do + @uri.authority = ":@newexample.com" + expect(@uri.authority).to eq(":@newexample.com") + expect(@uri.user).to eq("") + expect(@uri.password).to eq("") + expect(@uri.host).to eq("newexample.com") + expect(@uri.port).to eq(nil) + expect(@uri.inferred_port).to eq(80) + expect(@uri.to_s).to eq("http://:@newexample.com") + end +end + +describe Addressable::URI, "when parsed from " + + "'#example'" do + before do + @uri = Addressable::URI.parse("#example") + end + + it "should be considered relative" do + expect(@uri).to be_relative + end + + it "should have a host of nil" do + expect(@uri.host).to eq(nil) + end + + it "should have a site of nil" do + expect(@uri.site).to eq(nil) + end + + it "should have a normalized_site of nil" do + expect(@uri.normalized_site).to eq(nil) + end + + it "should have a path of ''" do + expect(@uri.path).to eq("") + end + + it "should have a query string of nil" do + expect(@uri.query).to eq(nil) + end + + it "should have a fragment of 'example'" do + expect(@uri.fragment).to eq("example") + end +end + +describe Addressable::URI, "when parsed from " + + "the network-path reference '//example.com/'" do + before do + @uri = Addressable::URI.parse("//example.com/") + end + + it "should be considered relative" do + expect(@uri).to be_relative + end + + it "should have a host of 'example.com'" do + expect(@uri.host).to eq("example.com") + end + + it "should have a path of '/'" do + expect(@uri.path).to eq("/") + end + + it "should raise an error if routing is attempted" do + expect(lambda do + @uri.route_to("http://example.com/") + end).to raise_error(ArgumentError, /\/\/example.com\//) + expect(lambda do + @uri.route_from("http://example.com/") + end).to raise_error(ArgumentError, /\/\/example.com\//) + end + + it "should have a 'null' origin" do + expect(@uri.origin).to eq('null') + end +end + +describe Addressable::URI, "when parsed from " + + "'feed://http://example.com/'" do + before do + @uri = Addressable::URI.parse("feed://http://example.com/") + end + + it "should have a host of 'http'" do + expect(@uri.host).to eq("http") + end + + it "should have a path of '//example.com/'" do + expect(@uri.path).to eq("//example.com/") + end +end + +describe Addressable::URI, "when parsed from " + + "'feed:http://example.com/'" do + before do + @uri = Addressable::URI.parse("feed:http://example.com/") + end + + it "should have a path of 'http://example.com/'" do + expect(@uri.path).to eq("http://example.com/") + end + + it "should normalize to 'http://example.com/'" do + expect(@uri.normalize.to_s).to eq("http://example.com/") + expect(@uri.normalize!.to_s).to eq("http://example.com/") + end + + it "should have a 'null' origin" do + expect(@uri.origin).to eq('null') + end +end + +describe Addressable::URI, "when parsed from " + + "'example://a/b/c/%7Bfoo%7D'" do + before do + @uri = Addressable::URI.parse("example://a/b/c/%7Bfoo%7D") + end + + # Section 6.2.2 of RFC 3986 + it "should be equivalent to eXAMPLE://a/./b/../b/%63/%7bfoo%7d" do + expect(@uri).to eq( + Addressable::URI.parse("eXAMPLE://a/./b/../b/%63/%7bfoo%7d") + ) + end + + it "should have an origin of 'example://a'" do + expect(@uri.origin).to eq('example://a') + end +end + +describe Addressable::URI, "when parsed from " + + "'http://example.com/indirect/path/./to/../resource/'" do + before do + @uri = Addressable::URI.parse( + "http://example.com/indirect/path/./to/../resource/") + end + + it "should use the 'http' scheme" do + expect(@uri.scheme).to eq("http") + end + + it "should have a host of 'example.com'" do + expect(@uri.host).to eq("example.com") + end + + it "should use port 80" do + expect(@uri.inferred_port).to eq(80) + end + + it "should have a path of '/indirect/path/./to/../resource/'" do + expect(@uri.path).to eq("/indirect/path/./to/../resource/") + end + + # Section 6.2.2.3 of RFC 3986 + it "should have a normalized path of '/indirect/path/resource/'" do + expect(@uri.normalize.path).to eq("/indirect/path/resource/") + expect(@uri.normalize!.path).to eq("/indirect/path/resource/") + end +end + +describe Addressable::URI, "when parsed from " + + "'http://under_score.example.com/'" do + it "should not cause an error" do + expect(lambda do + Addressable::URI.parse("http://under_score.example.com/") + end).not_to raise_error + end +end + +describe Addressable::URI, "when parsed from " + + "'./this:that'" do + before do + @uri = Addressable::URI.parse("./this:that") + end + + it "should be considered relative" do + expect(@uri).to be_relative + end + + it "should have no scheme" do + expect(@uri.scheme).to eq(nil) + end + + it "should have a 'null' origin" do + expect(@uri.origin).to eq('null') + end +end + +describe Addressable::URI, "when parsed from " + + "'this:that'" do + before do + @uri = Addressable::URI.parse("this:that") + end + + it "should be considered absolute" do + expect(@uri).to be_absolute + end + + it "should have a scheme of 'this'" do + expect(@uri.scheme).to eq("this") + end + + it "should have a 'null' origin" do + expect(@uri.origin).to eq('null') + end +end + +describe Addressable::URI, "when parsed from '?'" do + before do + @uri = Addressable::URI.parse("?") + end + + it "should normalize to ''" do + expect(@uri.normalize.to_s).to eq("") + end + + it "should have the correct return type" do + expect(@uri.query_values).to eq({}) + expect(@uri.query_values(Hash)).to eq({}) + expect(@uri.query_values(Array)).to eq([]) + end + + it "should have a 'null' origin" do + expect(@uri.origin).to eq('null') + end +end + +describe Addressable::URI, "when parsed from '?one=1&two=2&three=3'" do + before do + @uri = Addressable::URI.parse("?one=1&two=2&three=3") + end + + it "should have the correct query values" do + expect(@uri.query_values).to eq({"one" => "1", "two" => "2", "three" => "3"}) + end + + it "should raise an error for invalid return type values" do + expect(lambda do + @uri.query_values(Integer) + end).to raise_error(ArgumentError) + end + + it "should have the correct array query values" do + expect(@uri.query_values(Array)).to eq([ + ["one", "1"], ["two", "2"], ["three", "3"] + ]) + end + + it "should have a 'null' origin" do + expect(@uri.origin).to eq('null') + end +end + +describe Addressable::URI, "when parsed from '?one=1=uno&two=2=dos'" do + before do + @uri = Addressable::URI.parse("?one=1=uno&two=2=dos") + end + + it "should have the correct query values" do + expect(@uri.query_values).to eq({"one" => "1=uno", "two" => "2=dos"}) + end + + it "should have the correct array query values" do + expect(@uri.query_values(Array)).to eq([ + ["one", "1=uno"], ["two", "2=dos"] + ]) + end +end + +describe Addressable::URI, "when parsed from '?one[two][three]=four'" do + before do + @uri = Addressable::URI.parse("?one[two][three]=four") + end + + it "should have the correct query values" do + expect(@uri.query_values).to eq({"one[two][three]" => "four"}) + end + + it "should have the correct array query values" do + expect(@uri.query_values(Array)).to eq([ + ["one[two][three]", "four"] + ]) + end +end + +describe Addressable::URI, "when parsed from '?one.two.three=four'" do + before do + @uri = Addressable::URI.parse("?one.two.three=four") + end + + it "should have the correct query values" do + expect(@uri.query_values).to eq({ + "one.two.three" => "four" + }) + end + + it "should have the correct array query values" do + expect(@uri.query_values(Array)).to eq([ + ["one.two.three", "four"] + ]) + end +end + +describe Addressable::URI, "when parsed from " + + "'?one[two][three]=four&one[two][five]=six'" do + before do + @uri = Addressable::URI.parse("?one[two][three]=four&one[two][five]=six") + end + + it "should have the correct query values" do + expect(@uri.query_values).to eq({ + "one[two][three]" => "four", "one[two][five]" => "six" + }) + end + + it "should have the correct array query values" do + expect(@uri.query_values(Array)).to eq([ + ["one[two][three]", "four"], ["one[two][five]", "six"] + ]) + end +end + +describe Addressable::URI, "when parsed from " + + "'?one.two.three=four&one.two.five=six'" do + before do + @uri = Addressable::URI.parse("?one.two.three=four&one.two.five=six") + end + + it "should have the correct query values" do + expect(@uri.query_values).to eq({ + "one.two.three" => "four", "one.two.five" => "six" + }) + end + + it "should have the correct array query values" do + expect(@uri.query_values(Array)).to eq([ + ["one.two.three", "four"], ["one.two.five", "six"] + ]) + end +end + +describe Addressable::URI, "when parsed from " + + "'?one=two&one=three'" do + before do + @uri = Addressable::URI.parse( + "?one=two&one=three&one=four" + ) + end + + it "should have correct array query values" do + expect(@uri.query_values(Array)).to eq( + [['one', 'two'], ['one', 'three'], ['one', 'four']] + ) + end + + it "should have correct hash query values" do + skip("This is probably more desirable behavior.") + expect(@uri.query_values(Hash)).to eq( + {'one' => ['two', 'three', 'four']} + ) + end + + it "should handle assignment with keys of mixed type" do + @uri.query_values = @uri.query_values(Hash).merge({:one => 'three'}) + expect(@uri.query_values(Hash)).to eq({'one' => 'three'}) + end +end + +describe Addressable::URI, "when parsed from " + + "'?one[two][three][]=four&one[two][three][]=five'" do + before do + @uri = Addressable::URI.parse( + "?one[two][three][]=four&one[two][three][]=five" + ) + end + + it "should have correct query values" do + expect(@uri.query_values(Hash)).to eq({"one[two][three][]" => "five"}) + end + + it "should have correct array query values" do + expect(@uri.query_values(Array)).to eq([ + ["one[two][three][]", "four"], ["one[two][three][]", "five"] + ]) + end +end + +describe Addressable::URI, "when parsed from " + + "'?one[two][three][0]=four&one[two][three][1]=five'" do + before do + @uri = Addressable::URI.parse( + "?one[two][three][0]=four&one[two][three][1]=five" + ) + end + + it "should have the correct query values" do + expect(@uri.query_values).to eq({ + "one[two][three][0]" => "four", "one[two][three][1]" => "five" + }) + end +end + +describe Addressable::URI, "when parsed from " + + "'?one[two][three][1]=four&one[two][three][0]=five'" do + before do + @uri = Addressable::URI.parse( + "?one[two][three][1]=four&one[two][three][0]=five" + ) + end + + it "should have the correct query values" do + expect(@uri.query_values).to eq({ + "one[two][three][1]" => "four", "one[two][three][0]" => "five" + }) + end +end + +describe Addressable::URI, "when parsed from " + + "'?one[two][three][2]=four&one[two][three][1]=five'" do + before do + @uri = Addressable::URI.parse( + "?one[two][three][2]=four&one[two][three][1]=five" + ) + end + + it "should have the correct query values" do + expect(@uri.query_values).to eq({ + "one[two][three][2]" => "four", "one[two][three][1]" => "five" + }) + end +end + +describe Addressable::URI, "when parsed from " + + "'http://www.詹姆斯.com/'" do + before do + @uri = Addressable::URI.parse("http://www.詹姆斯.com/") + end + + it "should be equivalent to 'http://www.xn--8ws00zhy3a.com/'" do + expect(@uri).to eq( + Addressable::URI.parse("http://www.xn--8ws00zhy3a.com/") + ) + end + + it "should not have domain name encoded during normalization" do + expect(Addressable::URI.normalized_encode(@uri.to_s)).to eq( + "http://www.詹姆斯.com/" + ) + end + + it "should have an origin of 'http://www.xn--8ws00zhy3a.com'" do + expect(@uri.origin).to eq('http://www.xn--8ws00zhy3a.com') + end +end + +describe Addressable::URI, "when parsed from " + + "'http://www.詹姆斯.com/ some spaces /'" do + before do + @uri = Addressable::URI.parse("http://www.詹姆斯.com/ some spaces /") + end + + it "should be equivalent to " + + "'http://www.xn--8ws00zhy3a.com/%20some%20spaces%20/'" do + expect(@uri).to eq( + Addressable::URI.parse( + "http://www.xn--8ws00zhy3a.com/%20some%20spaces%20/") + ) + end + + it "should not have domain name encoded during normalization" do + expect(Addressable::URI.normalized_encode(@uri.to_s)).to eq( + "http://www.詹姆斯.com/%20some%20spaces%20/" + ) + end + + it "should have an origin of 'http://www.xn--8ws00zhy3a.com'" do + expect(@uri.origin).to eq('http://www.xn--8ws00zhy3a.com') + end +end + +describe Addressable::URI, "when parsed from " + + "'http://www.xn--8ws00zhy3a.com/'" do + before do + @uri = Addressable::URI.parse("http://www.xn--8ws00zhy3a.com/") + end + + it "should be displayed as http://www.詹姆斯.com/" do + expect(@uri.display_uri.to_s).to eq("http://www.詹姆斯.com/") + end + + it "should properly force the encoding" do + display_string = @uri.display_uri.to_str + expect(display_string).to eq("http://www.詹姆斯.com/") + if display_string.respond_to?(:encoding) + expect(display_string.encoding.to_s).to eq(Encoding::UTF_8.to_s) + end + end + + it "should have an origin of 'http://www.xn--8ws00zhy3a.com'" do + expect(@uri.origin).to eq('http://www.xn--8ws00zhy3a.com') + end +end + +describe Addressable::URI, "when parsed from " + + "'http://www.詹姆斯.com/atomtests/iri/詹.html'" do + before do + @uri = Addressable::URI.parse("http://www.詹姆斯.com/atomtests/iri/詹.html") + end + + it "should normalize to " + + "http://www.xn--8ws00zhy3a.com/atomtests/iri/%E8%A9%B9.html" do + expect(@uri.normalize.to_s).to eq( + "http://www.xn--8ws00zhy3a.com/atomtests/iri/%E8%A9%B9.html" + ) + expect(@uri.normalize!.to_s).to eq( + "http://www.xn--8ws00zhy3a.com/atomtests/iri/%E8%A9%B9.html" + ) + end +end + +describe Addressable::URI, "when parsed from a percent-encoded IRI" do + before do + @uri = Addressable::URI.parse( + "http://www.%E3%81%BB%E3%82%93%E3%81%A8%E3%81%86%E3%81%AB%E3%81%AA" + + "%E3%81%8C%E3%81%84%E3%82%8F%E3%81%91%E3%81%AE%E3%82%8F%E3%81%8B%E3" + + "%82%89%E3%81%AA%E3%81%84%E3%81%A9%E3%82%81%E3%81%84%E3%82%93%E3%82" + + "%81%E3%81%84%E3%81%AE%E3%82%89%E3%81%B9%E3%82%8B%E3%81%BE%E3%81%A0" + + "%E3%81%AA%E3%81%8C%E3%81%8F%E3%81%97%E3%81%AA%E3%81%84%E3%81%A8%E3" + + "%81%9F%E3%82%8A%E3%81%AA%E3%81%84.w3.mag.keio.ac.jp" + ) + end + + it "should normalize to something sane" do + expect(@uri.normalize.to_s).to eq( + "http://www.xn--n8jaaaaai5bhf7as8fsfk3jnknefdde3f" + + "g11amb5gzdb4wi9bya3kc6lra.w3.mag.keio.ac.jp/" + ) + expect(@uri.normalize!.to_s).to eq( + "http://www.xn--n8jaaaaai5bhf7as8fsfk3jnknefdde3f" + + "g11amb5gzdb4wi9bya3kc6lra.w3.mag.keio.ac.jp/" + ) + end + + it "should have the correct origin" do + expect(@uri.origin).to eq( + "http://www.xn--n8jaaaaai5bhf7as8fsfk3jnknefdde3f" + + "g11amb5gzdb4wi9bya3kc6lra.w3.mag.keio.ac.jp" + ) + end +end + +describe Addressable::URI, "with a base uri of 'http://a/b/c/d;p?q'" do + before do + @uri = Addressable::URI.parse("http://a/b/c/d;p?q") + end + + # Section 5.4.1 of RFC 3986 + it "when joined with 'g:h' should resolve to g:h" do + expect((@uri + "g:h").to_s).to eq("g:h") + expect(Addressable::URI.join(@uri, "g:h").to_s).to eq("g:h") + end + + # Section 5.4.1 of RFC 3986 + it "when joined with 'g' should resolve to http://a/b/c/g" do + expect((@uri + "g").to_s).to eq("http://a/b/c/g") + expect(Addressable::URI.join(@uri.to_s, "g").to_s).to eq("http://a/b/c/g") + end + + # Section 5.4.1 of RFC 3986 + it "when joined with './g' should resolve to http://a/b/c/g" do + expect((@uri + "./g").to_s).to eq("http://a/b/c/g") + expect(Addressable::URI.join(@uri.to_s, "./g").to_s).to eq("http://a/b/c/g") + end + + # Section 5.4.1 of RFC 3986 + it "when joined with 'g/' should resolve to http://a/b/c/g/" do + expect((@uri + "g/").to_s).to eq("http://a/b/c/g/") + expect(Addressable::URI.join(@uri.to_s, "g/").to_s).to eq("http://a/b/c/g/") + end + + # Section 5.4.1 of RFC 3986 + it "when joined with '/g' should resolve to http://a/g" do + expect((@uri + "/g").to_s).to eq("http://a/g") + expect(Addressable::URI.join(@uri.to_s, "/g").to_s).to eq("http://a/g") + end + + # Section 5.4.1 of RFC 3986 + it "when joined with '//g' should resolve to http://g" do + expect((@uri + "//g").to_s).to eq("http://g") + expect(Addressable::URI.join(@uri.to_s, "//g").to_s).to eq("http://g") + end + + # Section 5.4.1 of RFC 3986 + it "when joined with '?y' should resolve to http://a/b/c/d;p?y" do + expect((@uri + "?y").to_s).to eq("http://a/b/c/d;p?y") + expect(Addressable::URI.join(@uri.to_s, "?y").to_s).to eq("http://a/b/c/d;p?y") + end + + # Section 5.4.1 of RFC 3986 + it "when joined with 'g?y' should resolve to http://a/b/c/g?y" do + expect((@uri + "g?y").to_s).to eq("http://a/b/c/g?y") + expect(Addressable::URI.join(@uri.to_s, "g?y").to_s).to eq("http://a/b/c/g?y") + end + + # Section 5.4.1 of RFC 3986 + it "when joined with '#s' should resolve to http://a/b/c/d;p?q#s" do + expect((@uri + "#s").to_s).to eq("http://a/b/c/d;p?q#s") + expect(Addressable::URI.join(@uri.to_s, "#s").to_s).to eq( + "http://a/b/c/d;p?q#s" + ) + end + + # Section 5.4.1 of RFC 3986 + it "when joined with 'g#s' should resolve to http://a/b/c/g#s" do + expect((@uri + "g#s").to_s).to eq("http://a/b/c/g#s") + expect(Addressable::URI.join(@uri.to_s, "g#s").to_s).to eq("http://a/b/c/g#s") + end + + # Section 5.4.1 of RFC 3986 + it "when joined with 'g?y#s' should resolve to http://a/b/c/g?y#s" do + expect((@uri + "g?y#s").to_s).to eq("http://a/b/c/g?y#s") + expect(Addressable::URI.join( + @uri.to_s, "g?y#s").to_s).to eq("http://a/b/c/g?y#s") + end + + # Section 5.4.1 of RFC 3986 + it "when joined with ';x' should resolve to http://a/b/c/;x" do + expect((@uri + ";x").to_s).to eq("http://a/b/c/;x") + expect(Addressable::URI.join(@uri.to_s, ";x").to_s).to eq("http://a/b/c/;x") + end + + # Section 5.4.1 of RFC 3986 + it "when joined with 'g;x' should resolve to http://a/b/c/g;x" do + expect((@uri + "g;x").to_s).to eq("http://a/b/c/g;x") + expect(Addressable::URI.join(@uri.to_s, "g;x").to_s).to eq("http://a/b/c/g;x") + end + + # Section 5.4.1 of RFC 3986 + it "when joined with 'g;x?y#s' should resolve to http://a/b/c/g;x?y#s" do + expect((@uri + "g;x?y#s").to_s).to eq("http://a/b/c/g;x?y#s") + expect(Addressable::URI.join( + @uri.to_s, "g;x?y#s").to_s).to eq("http://a/b/c/g;x?y#s") + end + + # Section 5.4.1 of RFC 3986 + it "when joined with '' should resolve to http://a/b/c/d;p?q" do + expect((@uri + "").to_s).to eq("http://a/b/c/d;p?q") + expect(Addressable::URI.join(@uri.to_s, "").to_s).to eq("http://a/b/c/d;p?q") + end + + # Section 5.4.1 of RFC 3986 + it "when joined with '.' should resolve to http://a/b/c/" do + expect((@uri + ".").to_s).to eq("http://a/b/c/") + expect(Addressable::URI.join(@uri.to_s, ".").to_s).to eq("http://a/b/c/") + end + + # Section 5.4.1 of RFC 3986 + it "when joined with './' should resolve to http://a/b/c/" do + expect((@uri + "./").to_s).to eq("http://a/b/c/") + expect(Addressable::URI.join(@uri.to_s, "./").to_s).to eq("http://a/b/c/") + end + + # Section 5.4.1 of RFC 3986 + it "when joined with '..' should resolve to http://a/b/" do + expect((@uri + "..").to_s).to eq("http://a/b/") + expect(Addressable::URI.join(@uri.to_s, "..").to_s).to eq("http://a/b/") + end + + # Section 5.4.1 of RFC 3986 + it "when joined with '../' should resolve to http://a/b/" do + expect((@uri + "../").to_s).to eq("http://a/b/") + expect(Addressable::URI.join(@uri.to_s, "../").to_s).to eq("http://a/b/") + end + + # Section 5.4.1 of RFC 3986 + it "when joined with '../g' should resolve to http://a/b/g" do + expect((@uri + "../g").to_s).to eq("http://a/b/g") + expect(Addressable::URI.join(@uri.to_s, "../g").to_s).to eq("http://a/b/g") + end + + # Section 5.4.1 of RFC 3986 + it "when joined with '../..' should resolve to http://a/" do + expect((@uri + "../..").to_s).to eq("http://a/") + expect(Addressable::URI.join(@uri.to_s, "../..").to_s).to eq("http://a/") + end + + # Section 5.4.1 of RFC 3986 + it "when joined with '../../' should resolve to http://a/" do + expect((@uri + "../../").to_s).to eq("http://a/") + expect(Addressable::URI.join(@uri.to_s, "../../").to_s).to eq("http://a/") + end + + # Section 5.4.1 of RFC 3986 + it "when joined with '../../g' should resolve to http://a/g" do + expect((@uri + "../../g").to_s).to eq("http://a/g") + expect(Addressable::URI.join(@uri.to_s, "../../g").to_s).to eq("http://a/g") + end + + # Section 5.4.2 of RFC 3986 + it "when joined with '../../../g' should resolve to http://a/g" do + expect((@uri + "../../../g").to_s).to eq("http://a/g") + expect(Addressable::URI.join(@uri.to_s, "../../../g").to_s).to eq("http://a/g") + end + + it "when joined with '../.././../g' should resolve to http://a/g" do + expect((@uri + "../.././../g").to_s).to eq("http://a/g") + expect(Addressable::URI.join(@uri.to_s, "../.././../g").to_s).to eq( + "http://a/g" + ) + end + + # Section 5.4.2 of RFC 3986 + it "when joined with '../../../../g' should resolve to http://a/g" do + expect((@uri + "../../../../g").to_s).to eq("http://a/g") + expect(Addressable::URI.join( + @uri.to_s, "../../../../g").to_s).to eq("http://a/g") + end + + # Section 5.4.2 of RFC 3986 + it "when joined with '/./g' should resolve to http://a/g" do + expect((@uri + "/./g").to_s).to eq("http://a/g") + expect(Addressable::URI.join(@uri.to_s, "/./g").to_s).to eq("http://a/g") + end + + # Section 5.4.2 of RFC 3986 + it "when joined with '/../g' should resolve to http://a/g" do + expect((@uri + "/../g").to_s).to eq("http://a/g") + expect(Addressable::URI.join(@uri.to_s, "/../g").to_s).to eq("http://a/g") + end + + # Section 5.4.2 of RFC 3986 + it "when joined with 'g.' should resolve to http://a/b/c/g." do + expect((@uri + "g.").to_s).to eq("http://a/b/c/g.") + expect(Addressable::URI.join(@uri.to_s, "g.").to_s).to eq("http://a/b/c/g.") + end + + # Section 5.4.2 of RFC 3986 + it "when joined with '.g' should resolve to http://a/b/c/.g" do + expect((@uri + ".g").to_s).to eq("http://a/b/c/.g") + expect(Addressable::URI.join(@uri.to_s, ".g").to_s).to eq("http://a/b/c/.g") + end + + # Section 5.4.2 of RFC 3986 + it "when joined with 'g..' should resolve to http://a/b/c/g.." do + expect((@uri + "g..").to_s).to eq("http://a/b/c/g..") + expect(Addressable::URI.join(@uri.to_s, "g..").to_s).to eq("http://a/b/c/g..") + end + + # Section 5.4.2 of RFC 3986 + it "when joined with '..g' should resolve to http://a/b/c/..g" do + expect((@uri + "..g").to_s).to eq("http://a/b/c/..g") + expect(Addressable::URI.join(@uri.to_s, "..g").to_s).to eq("http://a/b/c/..g") + end + + # Section 5.4.2 of RFC 3986 + it "when joined with './../g' should resolve to http://a/b/g" do + expect((@uri + "./../g").to_s).to eq("http://a/b/g") + expect(Addressable::URI.join(@uri.to_s, "./../g").to_s).to eq("http://a/b/g") + end + + # Section 5.4.2 of RFC 3986 + it "when joined with './g/.' should resolve to http://a/b/c/g/" do + expect((@uri + "./g/.").to_s).to eq("http://a/b/c/g/") + expect(Addressable::URI.join(@uri.to_s, "./g/.").to_s).to eq("http://a/b/c/g/") + end + + # Section 5.4.2 of RFC 3986 + it "when joined with 'g/./h' should resolve to http://a/b/c/g/h" do + expect((@uri + "g/./h").to_s).to eq("http://a/b/c/g/h") + expect(Addressable::URI.join(@uri.to_s, "g/./h").to_s).to eq("http://a/b/c/g/h") + end + + # Section 5.4.2 of RFC 3986 + it "when joined with 'g/../h' should resolve to http://a/b/c/h" do + expect((@uri + "g/../h").to_s).to eq("http://a/b/c/h") + expect(Addressable::URI.join(@uri.to_s, "g/../h").to_s).to eq("http://a/b/c/h") + end + + # Section 5.4.2 of RFC 3986 + it "when joined with 'g;x=1/./y' " + + "should resolve to http://a/b/c/g;x=1/y" do + expect((@uri + "g;x=1/./y").to_s).to eq("http://a/b/c/g;x=1/y") + expect(Addressable::URI.join( + @uri.to_s, "g;x=1/./y").to_s).to eq("http://a/b/c/g;x=1/y") + end + + # Section 5.4.2 of RFC 3986 + it "when joined with 'g;x=1/../y' should resolve to http://a/b/c/y" do + expect((@uri + "g;x=1/../y").to_s).to eq("http://a/b/c/y") + expect(Addressable::URI.join( + @uri.to_s, "g;x=1/../y").to_s).to eq("http://a/b/c/y") + end + + # Section 5.4.2 of RFC 3986 + it "when joined with 'g?y/./x' " + + "should resolve to http://a/b/c/g?y/./x" do + expect((@uri + "g?y/./x").to_s).to eq("http://a/b/c/g?y/./x") + expect(Addressable::URI.join( + @uri.to_s, "g?y/./x").to_s).to eq("http://a/b/c/g?y/./x") + end + + # Section 5.4.2 of RFC 3986 + it "when joined with 'g?y/../x' " + + "should resolve to http://a/b/c/g?y/../x" do + expect((@uri + "g?y/../x").to_s).to eq("http://a/b/c/g?y/../x") + expect(Addressable::URI.join( + @uri.to_s, "g?y/../x").to_s).to eq("http://a/b/c/g?y/../x") + end + + # Section 5.4.2 of RFC 3986 + it "when joined with 'g#s/./x' " + + "should resolve to http://a/b/c/g#s/./x" do + expect((@uri + "g#s/./x").to_s).to eq("http://a/b/c/g#s/./x") + expect(Addressable::URI.join( + @uri.to_s, "g#s/./x").to_s).to eq("http://a/b/c/g#s/./x") + end + + # Section 5.4.2 of RFC 3986 + it "when joined with 'g#s/../x' " + + "should resolve to http://a/b/c/g#s/../x" do + expect((@uri + "g#s/../x").to_s).to eq("http://a/b/c/g#s/../x") + expect(Addressable::URI.join( + @uri.to_s, "g#s/../x").to_s).to eq("http://a/b/c/g#s/../x") + end + + # Section 5.4.2 of RFC 3986 + it "when joined with 'http:g' should resolve to http:g" do + expect((@uri + "http:g").to_s).to eq("http:g") + expect(Addressable::URI.join(@uri.to_s, "http:g").to_s).to eq("http:g") + end + + # Edge case to be sure + it "when joined with '//example.com/' should " + + "resolve to http://example.com/" do + expect((@uri + "//example.com/").to_s).to eq("http://example.com/") + expect(Addressable::URI.join( + @uri.to_s, "//example.com/").to_s).to eq("http://example.com/") + end + + it "when joined with a bogus object a TypeError should be raised" do + expect(lambda do + Addressable::URI.join(@uri, 42) + end).to raise_error(TypeError) + end +end + +describe Addressable::URI, "when converting the path " + + "'relative/path/to/something'" do + before do + @path = 'relative/path/to/something' + end + + it "should convert to " + + "\'relative/path/to/something\'" do + @uri = Addressable::URI.convert_path(@path) + expect(@uri.to_str).to eq("relative/path/to/something") + end + + it "should join with an absolute file path correctly" do + @base = Addressable::URI.convert_path("/absolute/path/") + @uri = Addressable::URI.convert_path(@path) + expect((@base + @uri).to_str).to eq( + "file:///absolute/path/relative/path/to/something" + ) + end +end + +describe Addressable::URI, "when converting a bogus path" do + it "should raise a TypeError" do + expect(lambda do + Addressable::URI.convert_path(42) + end).to raise_error(TypeError) + end +end + +describe Addressable::URI, "when given a UNIX root directory" do + before do + @path = "/" + end + + it "should convert to \'file:///\'" do + @uri = Addressable::URI.convert_path(@path) + expect(@uri.to_str).to eq("file:///") + end + + it "should have an origin of 'file://'" do + @uri = Addressable::URI.convert_path(@path) + expect(@uri.origin).to eq('file://') + end +end + +describe Addressable::URI, "when given a Windows root directory" do + before do + @path = "C:\\" + end + + it "should convert to \'file:///c:/\'" do + @uri = Addressable::URI.convert_path(@path) + expect(@uri.to_str).to eq("file:///c:/") + end + + it "should have an origin of 'file://'" do + @uri = Addressable::URI.convert_path(@path) + expect(@uri.origin).to eq('file://') + end +end + +describe Addressable::URI, "when given the path '/one/two/'" do + before do + @path = '/one/two/' + end + + it "should convert to " + + "\'file:///one/two/\'" do + @uri = Addressable::URI.convert_path(@path) + expect(@uri.to_str).to eq("file:///one/two/") + end + + it "should have an origin of 'file://'" do + @uri = Addressable::URI.convert_path(@path) + expect(@uri.origin).to eq('file://') + end +end + +describe Addressable::URI, "when given the tld " do + it "'uk' should have a tld of 'uk'" do + uri = Addressable::URI.parse("http://example.com") + uri.tld = "uk" + + expect(uri.tld).to eq("uk") + end + + context "which " do + let (:uri) { Addressable::URI.parse("http://www.comrade.net/path/to/source/") } + + it "contains a subdomain" do + uri.tld = "co.uk" + + expect(uri.to_s).to eq("http://www.comrade.co.uk/path/to/source/") + end + + it "is part of the domain" do + uri.tld = "com" + + expect(uri.to_s).to eq("http://www.comrade.com/path/to/source/") + end + end +end + +describe Addressable::URI, "when given the path " + + "'c:\\windows\\My Documents 100%20\\foo.txt'" do + before do + @path = "c:\\windows\\My Documents 100%20\\foo.txt" + end + + it "should convert to " + + "\'file:///c:/windows/My%20Documents%20100%20/foo.txt\'" do + @uri = Addressable::URI.convert_path(@path) + expect(@uri.to_str).to eq("file:///c:/windows/My%20Documents%20100%20/foo.txt") + end + + it "should have an origin of 'file://'" do + @uri = Addressable::URI.convert_path(@path) + expect(@uri.origin).to eq('file://') + end +end + +describe Addressable::URI, "when given the path " + + "'file://c:\\windows\\My Documents 100%20\\foo.txt'" do + before do + @path = "file://c:\\windows\\My Documents 100%20\\foo.txt" + end + + it "should convert to " + + "\'file:///c:/windows/My%20Documents%20100%20/foo.txt\'" do + @uri = Addressable::URI.convert_path(@path) + expect(@uri.to_str).to eq("file:///c:/windows/My%20Documents%20100%20/foo.txt") + end + + it "should have an origin of 'file://'" do + @uri = Addressable::URI.convert_path(@path) + expect(@uri.origin).to eq('file://') + end +end + +describe Addressable::URI, "when given the path " + + "'file:c:\\windows\\My Documents 100%20\\foo.txt'" do + before do + @path = "file:c:\\windows\\My Documents 100%20\\foo.txt" + end + + it "should convert to " + + "\'file:///c:/windows/My%20Documents%20100%20/foo.txt\'" do + @uri = Addressable::URI.convert_path(@path) + expect(@uri.to_str).to eq("file:///c:/windows/My%20Documents%20100%20/foo.txt") + end + + it "should have an origin of 'file://'" do + @uri = Addressable::URI.convert_path(@path) + expect(@uri.origin).to eq('file://') + end +end + +describe Addressable::URI, "when given the path " + + "'file:/c:\\windows\\My Documents 100%20\\foo.txt'" do + before do + @path = "file:/c:\\windows\\My Documents 100%20\\foo.txt" + end + + it "should convert to " + + "\'file:///c:/windows/My%20Documents%20100%20/foo.txt\'" do + @uri = Addressable::URI.convert_path(@path) + expect(@uri.to_str).to eq("file:///c:/windows/My%20Documents%20100%20/foo.txt") + end + + it "should have an origin of 'file://'" do + @uri = Addressable::URI.convert_path(@path) + expect(@uri.origin).to eq('file://') + end +end + +describe Addressable::URI, "when given the path " + + "'file:///c|/windows/My%20Documents%20100%20/foo.txt'" do + before do + @path = "file:///c|/windows/My%20Documents%20100%20/foo.txt" + end + + it "should convert to " + + "\'file:///c:/windows/My%20Documents%20100%20/foo.txt\'" do + @uri = Addressable::URI.convert_path(@path) + expect(@uri.to_str).to eq("file:///c:/windows/My%20Documents%20100%20/foo.txt") + end + + it "should have an origin of 'file://'" do + @uri = Addressable::URI.convert_path(@path) + expect(@uri.origin).to eq('file://') + end +end + +describe Addressable::URI, "when given an http protocol URI" do + before do + @path = "http://example.com/" + end + + it "should not do any conversion at all" do + @uri = Addressable::URI.convert_path(@path) + expect(@uri.to_str).to eq("http://example.com/") + end +end + +class SuperString + def initialize(string) + @string = string.to_s + end + + def to_str + return @string + end +end + +describe Addressable::URI, "when parsing a non-String object" do + it "should correctly parse anything with a 'to_str' method" do + Addressable::URI.parse(SuperString.new(42)) + end + + it "should raise a TypeError for objects than cannot be converted" do + expect(lambda do + Addressable::URI.parse(42) + end).to raise_error(TypeError) + end + + it "should correctly parse heuristically anything with a 'to_str' method" do + Addressable::URI.heuristic_parse(SuperString.new(42)) + end + + it "should raise a TypeError for objects than cannot be converted" do + expect(lambda do + Addressable::URI.heuristic_parse(42) + end).to raise_error(TypeError) + end +end + +describe Addressable::URI, "when form encoding a hash" do + it "should result in correct percent encoded sequence" do + expect(Addressable::URI.form_encode( + [["&one", "/1"], ["=two", "?2"], [":three", "#3"]] + )).to eq("%26one=%2F1&%3Dtwo=%3F2&%3Athree=%233") + end + + it "should result in correct percent encoded sequence" do + expect(Addressable::URI.form_encode( + {"q" => "one two three"} + )).to eq("q=one+two+three") + end + + it "should result in correct percent encoded sequence" do + expect(Addressable::URI.form_encode( + {"key" => nil} + )).to eq("key=") + end + + it "should result in correct percent encoded sequence" do + expect(Addressable::URI.form_encode( + {"q" => ["one", "two", "three"]} + )).to eq("q=one&q=two&q=three") + end + + it "should result in correctly encoded newlines" do + expect(Addressable::URI.form_encode( + {"text" => "one\ntwo\rthree\r\nfour\n\r"} + )).to eq("text=one%0D%0Atwo%0D%0Athree%0D%0Afour%0D%0A%0D%0A") + end + + it "should result in a sorted percent encoded sequence" do + expect(Addressable::URI.form_encode( + [["a", "1"], ["dup", "3"], ["dup", "2"]], true + )).to eq("a=1&dup=2&dup=3") + end +end + +describe Addressable::URI, "when form encoding a non-Array object" do + it "should raise a TypeError for objects than cannot be converted" do + expect(lambda do + Addressable::URI.form_encode(42) + end).to raise_error(TypeError) + end +end + +# See https://tools.ietf.org/html/rfc6749#appendix-B +describe Addressable::URI, "when form encoding the example value from OAuth 2" do + it "should result in correct values" do + expect(Addressable::URI.form_encode( + {"value" => " %&+£€"} + )).to eq("value=+%25%26%2B%C2%A3%E2%82%AC") + end +end + +# See https://tools.ietf.org/html/rfc6749#appendix-B +describe Addressable::URI, "when form unencoding the example value from OAuth 2" do + it "should result in correct values" do + expect(Addressable::URI.form_unencode( + "value=+%25%26%2B%C2%A3%E2%82%AC" + )).to eq([["value", " %&+£€"]]) + end +end + +describe Addressable::URI, "when form unencoding a string" do + it "should result in correct values" do + expect(Addressable::URI.form_unencode( + "%26one=%2F1&%3Dtwo=%3F2&%3Athree=%233" + )).to eq([["&one", "/1"], ["=two", "?2"], [":three", "#3"]]) + end + + it "should result in correct values" do + expect(Addressable::URI.form_unencode( + "q=one+two+three" + )).to eq([["q", "one two three"]]) + end + + it "should result in correct values" do + expect(Addressable::URI.form_unencode( + "text=one%0D%0Atwo%0D%0Athree%0D%0Afour%0D%0A%0D%0A" + )).to eq([["text", "one\ntwo\nthree\nfour\n\n"]]) + end + + it "should result in correct values" do + expect(Addressable::URI.form_unencode( + "a=1&dup=2&dup=3" + )).to eq([["a", "1"], ["dup", "2"], ["dup", "3"]]) + end + + it "should result in correct values" do + expect(Addressable::URI.form_unencode( + "key" + )).to eq([["key", nil]]) + end + + it "should result in correct values" do + expect(Addressable::URI.form_unencode("GivenName=Ren%C3%A9")).to eq( + [["GivenName", "René"]] + ) + end +end + +describe Addressable::URI, "when form unencoding a non-String object" do + it "should correctly parse anything with a 'to_str' method" do + Addressable::URI.form_unencode(SuperString.new(42)) + end + + it "should raise a TypeError for objects than cannot be converted" do + expect(lambda do + Addressable::URI.form_unencode(42) + end).to raise_error(TypeError) + end +end + +describe Addressable::URI, "when normalizing a non-String object" do + it "should correctly parse anything with a 'to_str' method" do + Addressable::URI.normalize_component(SuperString.new(42)) + end + + it "should raise a TypeError for objects than cannot be converted" do + expect(lambda do + Addressable::URI.normalize_component(42) + end).to raise_error(TypeError) + end + + it "should raise a TypeError for objects than cannot be converted" do + expect(lambda do + Addressable::URI.normalize_component("component", 42) + end).to raise_error(TypeError) + end +end + +describe Addressable::URI, "when normalizing a path with an encoded slash" do + it "should result in correct percent encoded sequence" do + expect(Addressable::URI.parse("/path%2Fsegment/").normalize.path).to eq( + "/path%2Fsegment/" + ) + end +end + +describe Addressable::URI, "when normalizing a partially encoded string" do + it "should result in correct percent encoded sequence" do + expect(Addressable::URI.normalize_component( + "partially % encoded%21" + )).to eq("partially%20%25%20encoded!") + end + + it "should result in correct percent encoded sequence" do + expect(Addressable::URI.normalize_component( + "partially %25 encoded!" + )).to eq("partially%20%25%20encoded!") + end +end + +describe Addressable::URI, "when normalizing a unicode sequence" do + it "should result in correct percent encoded sequence" do + expect(Addressable::URI.normalize_component( + "/C%CC%A7" + )).to eq("/%C3%87") + end + + it "should result in correct percent encoded sequence" do + expect(Addressable::URI.normalize_component( + "/%C3%87" + )).to eq("/%C3%87") + end +end + +describe Addressable::URI, "when normalizing a multibyte string" do + it "should result in correct percent encoded sequence" do + expect(Addressable::URI.normalize_component("günther")).to eq( + "g%C3%BCnther" + ) + end + + it "should result in correct percent encoded sequence" do + expect(Addressable::URI.normalize_component("g%C3%BCnther")).to eq( + "g%C3%BCnther" + ) + end +end + +describe Addressable::URI, "when normalizing a string but leaving some characters encoded" do + it "should result in correct percent encoded sequence" do + expect(Addressable::URI.normalize_component("%58X%59Y%5AZ", "0-9a-zXY", "Y")).to eq( + "XX%59Y%5A%5A" + ) + end + + it "should not modify the character class" do + character_class = "0-9a-zXY" + + character_class_copy = character_class.dup + + Addressable::URI.normalize_component("%58X%59Y%5AZ", character_class, "Y") + + expect(character_class).to eq(character_class_copy) + end +end + +describe Addressable::URI, "when encoding a string with existing encodings to upcase" do + it "should result in correct percent encoded sequence" do + expect(Addressable::URI.encode_component("JK%4c", "0-9A-IKM-Za-z%", "L")).to eq("%4AK%4C") + end +end + +describe Addressable::URI, "when encoding a multibyte string" do + it "should result in correct percent encoded sequence" do + expect(Addressable::URI.encode_component("günther")).to eq("g%C3%BCnther") + end + + it "should result in correct percent encoded sequence" do + expect(Addressable::URI.encode_component( + "günther", /[^a-zA-Z0-9\:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,\;\=\-\.\_\~]/ + )).to eq("g%C3%BCnther") + end +end + +describe Addressable::URI, "when form encoding a multibyte string" do + it "should result in correct percent encoded sequence" do + expect(Addressable::URI.form_encode({"GivenName" => "René"})).to eq( + "GivenName=Ren%C3%A9" + ) + end +end + +describe Addressable::URI, "when encoding a string with ASCII chars 0-15" do + it "should result in correct percent encoded sequence" do + expect(Addressable::URI.encode_component("one\ntwo")).to eq("one%0Atwo") + end + + it "should result in correct percent encoded sequence" do + expect(Addressable::URI.encode_component( + "one\ntwo", /[^a-zA-Z0-9\:\/\?\#\[\]\@\!\$\&\'\(\)\*\+\,\;\=\-\.\_\~]/ + )).to eq("one%0Atwo") + end +end + +describe Addressable::URI, "when unencoding a multibyte string" do + it "should result in correct percent encoded sequence" do + expect(Addressable::URI.unencode_component("g%C3%BCnther")).to eq("günther") + end + + it "should consistently use UTF-8 internally" do + expect(Addressable::URI.unencode_component("ski=%BA%DAɫ")).to eq("ski=\xBA\xDAɫ") + end + + it "should result in correct percent encoded sequence as a URI" do + expect(Addressable::URI.unencode( + "/path?g%C3%BCnther", ::Addressable::URI + )).to eq(Addressable::URI.new( + :path => "/path", :query => "günther" + )) + end +end + +describe Addressable::URI, "when partially unencoding a string" do + it "should unencode all characters by default" do + expect(Addressable::URI.unencode('%%25~%7e+%2b', String)).to eq('%%~~++') + end + + it "should unencode characters not in leave_encoded" do + expect(Addressable::URI.unencode('%%25~%7e+%2b', String, '~')).to eq('%%~%7e++') + end + + it "should leave characters in leave_encoded alone" do + expect(Addressable::URI.unencode('%%25~%7e+%2b', String, '%~+')).to eq('%%25~%7e+%2b') + end +end + +describe Addressable::URI, "when unencoding a bogus object" do + it "should raise a TypeError" do + expect(lambda do + Addressable::URI.unencode_component(42) + end).to raise_error(TypeError) + end + + it "should raise a TypeError" do + expect(lambda do + Addressable::URI.unencode("/path?g%C3%BCnther", Integer) + end).to raise_error(TypeError) + end +end + +describe Addressable::URI, "when encoding a bogus object" do + it "should raise a TypeError" do + expect(lambda do + Addressable::URI.encode(Object.new) + end).to raise_error(TypeError) + end + + it "should raise a TypeError" do + expect(lambda do + Addressable::URI.normalized_encode(Object.new) + end).to raise_error(TypeError) + end + + it "should raise a TypeError" do + expect(lambda do + Addressable::URI.encode_component("günther", Object.new) + end).to raise_error(TypeError) + end + + it "should raise a TypeError" do + expect(lambda do + Addressable::URI.encode_component(Object.new) + end).to raise_error(TypeError) + end +end + +describe Addressable::URI, "when given the input " + + "'http://example.com/'" do + before do + @input = "http://example.com/" + end + + it "should heuristically parse to 'http://example.com/'" do + @uri = Addressable::URI.heuristic_parse(@input) + expect(@uri.to_s).to eq("http://example.com/") + end + + it "should not raise error when frozen" do + expect(lambda do + Addressable::URI.heuristic_parse(@input).freeze.to_s + end).not_to raise_error + end +end + +describe Addressable::URI, "when given the input " + + "'https://example.com/'" do + before do + @input = "https://example.com/" + end + + it "should heuristically parse to 'https://example.com/'" do + @uri = Addressable::URI.heuristic_parse(@input) + expect(@uri.to_s).to eq("https://example.com/") + end +end + +describe Addressable::URI, "when given the input " + + "'http:example.com/'" do + before do + @input = "http:example.com/" + end + + it "should heuristically parse to 'http://example.com/'" do + @uri = Addressable::URI.heuristic_parse(@input) + expect(@uri.to_s).to eq("http://example.com/") + end + + it "should heuristically parse to 'http://example.com/' " + + "even with a scheme hint of 'ftp'" do + @uri = Addressable::URI.heuristic_parse(@input, {:scheme => 'ftp'}) + expect(@uri.to_s).to eq("http://example.com/") + end +end + +describe Addressable::URI, "when given the input " + + "'https:example.com/'" do + before do + @input = "https:example.com/" + end + + it "should heuristically parse to 'https://example.com/'" do + @uri = Addressable::URI.heuristic_parse(@input) + expect(@uri.to_s).to eq("https://example.com/") + end + + it "should heuristically parse to 'https://example.com/' " + + "even with a scheme hint of 'ftp'" do + @uri = Addressable::URI.heuristic_parse(@input, {:scheme => 'ftp'}) + expect(@uri.to_s).to eq("https://example.com/") + end +end + +describe Addressable::URI, "when given the input " + + "'http://example.com/example.com/'" do + before do + @input = "http://example.com/example.com/" + end + + it "should heuristically parse to 'http://example.com/example.com/'" do + @uri = Addressable::URI.heuristic_parse(@input) + expect(@uri.to_s).to eq("http://example.com/example.com/") + end +end + +describe Addressable::URI, "when given the input " + + "'http://prefix\\.example.com/'" do + before do + @input = "http://prefix\\.example.com/" + end + + it "should heuristically parse to 'http://prefix/.example.com/'" do + @uri = Addressable::URI.heuristic_parse(@input) + expect(@uri.authority).to eq("prefix") + expect(@uri.to_s).to eq("http://prefix/.example.com/") + end + + it "should heuristically parse to 'http://prefix/.example.com/' " + + "even with a scheme hint of 'ftp'" do + @uri = Addressable::URI.heuristic_parse(@input, {:scheme => 'ftp'}) + expect(@uri.to_s).to eq("http://prefix/.example.com/") + end +end + +describe Addressable::URI, "when given the input " + + "'http://p:\\/'" do + before do + @input = "http://p:\\/" + end + + it "should heuristically parse to 'http://p//'" do + @uri = Addressable::URI.heuristic_parse(@input) + expect(@uri.authority).to eq("p") + expect(@uri.to_s).to eq("http://p//") + end + + it "should heuristically parse to 'http://p//' " + + "even with a scheme hint of 'ftp'" do + @uri = Addressable::URI.heuristic_parse(@input, {:scheme => 'ftp'}) + expect(@uri.to_s).to eq("http://p//") + end +end + +describe Addressable::URI, "when given the input " + + "'http://p://'" do + before do + @input = "http://p://" + end + + it "should heuristically parse to 'http://p//'" do + @uri = Addressable::URI.heuristic_parse(@input) + expect(@uri.authority).to eq("p") + expect(@uri.to_s).to eq("http://p//") + end + + it "should heuristically parse to 'http://p//' " + + "even with a scheme hint of 'ftp'" do + @uri = Addressable::URI.heuristic_parse(@input, {:scheme => 'ftp'}) + expect(@uri.to_s).to eq("http://p//") + end +end + +describe Addressable::URI, "when given the input " + + "'http://p://p'" do + before do + @input = "http://p://p" + end + + it "should heuristically parse to 'http://p//p'" do + @uri = Addressable::URI.heuristic_parse(@input) + expect(@uri.authority).to eq("p") + expect(@uri.to_s).to eq("http://p//p") + end + + it "should heuristically parse to 'http://p//p' " + + "even with a scheme hint of 'ftp'" do + @uri = Addressable::URI.heuristic_parse(@input, {:scheme => 'ftp'}) + expect(@uri.to_s).to eq("http://p//p") + end +end + +describe Addressable::URI, "when given the input " + + "'http://prefix .example.com/'" do + before do + @input = "http://prefix .example.com/" + end + + # Justification here being that no browser actually tries to resolve this. + # They all treat this as a web search. + it "should heuristically parse to 'http://prefix%20.example.com/'" do + @uri = Addressable::URI.heuristic_parse(@input) + expect(@uri.authority).to eq("prefix%20.example.com") + expect(@uri.to_s).to eq("http://prefix%20.example.com/") + end + + it "should heuristically parse to 'http://prefix%20.example.com/' " + + "even with a scheme hint of 'ftp'" do + @uri = Addressable::URI.heuristic_parse(@input, {:scheme => 'ftp'}) + expect(@uri.to_s).to eq("http://prefix%20.example.com/") + end +end + +describe Addressable::URI, "when given the input " + + "' http://www.example.com/ '" do + before do + @input = " http://www.example.com/ " + end + + it "should heuristically parse to 'http://prefix%20.example.com/'" do + @uri = Addressable::URI.heuristic_parse(@input) + expect(@uri.scheme).to eq("http") + expect(@uri.path).to eq("/") + expect(@uri.to_s).to eq("http://www.example.com/") + end +end + +describe Addressable::URI, "when given the input " + + "'http://prefix%2F.example.com/'" do + before do + @input = "http://prefix%2F.example.com/" + end + + it "should heuristically parse to 'http://prefix%2F.example.com/'" do + @uri = Addressable::URI.heuristic_parse(@input) + expect(@uri.authority).to eq("prefix%2F.example.com") + expect(@uri.to_s).to eq("http://prefix%2F.example.com/") + end + + it "should heuristically parse to 'http://prefix%2F.example.com/' " + + "even with a scheme hint of 'ftp'" do + @uri = Addressable::URI.heuristic_parse(@input, {:scheme => 'ftp'}) + expect(@uri.to_s).to eq("http://prefix%2F.example.com/") + end +end + +describe Addressable::URI, "when given the input " + + "'/path/to/resource'" do + before do + @input = "/path/to/resource" + end + + it "should heuristically parse to '/path/to/resource'" do + @uri = Addressable::URI.heuristic_parse(@input) + expect(@uri.to_s).to eq("/path/to/resource") + end +end + +describe Addressable::URI, "when given the input " + + "'relative/path/to/resource'" do + before do + @input = "relative/path/to/resource" + end + + it "should heuristically parse to 'relative/path/to/resource'" do + @uri = Addressable::URI.heuristic_parse(@input) + expect(@uri.to_s).to eq("relative/path/to/resource") + end +end + +describe Addressable::URI, "when given the input " + + "'example.com'" do + before do + @input = "example.com" + end + + it "should heuristically parse to 'http://example.com'" do + @uri = Addressable::URI.heuristic_parse(@input) + expect(@uri.to_s).to eq("http://example.com") + end +end + +describe Addressable::URI, "when given the input " + + "'example.com' and a scheme hint of 'ftp'" do + before do + @input = "example.com" + @hints = {:scheme => 'ftp'} + end + + it "should heuristically parse to 'http://example.com'" do + @uri = Addressable::URI.heuristic_parse(@input, @hints) + expect(@uri.to_s).to eq("ftp://example.com") + end +end + +describe Addressable::URI, "when given the input " + + "'example.com:21' and a scheme hint of 'ftp'" do + before do + @input = "example.com:21" + @hints = {:scheme => 'ftp'} + end + + it "should heuristically parse to 'http://example.com:21'" do + @uri = Addressable::URI.heuristic_parse(@input, @hints) + expect(@uri.to_s).to eq("ftp://example.com:21") + end +end + +describe Addressable::URI, "when given the input " + + "'example.com/path/to/resource'" do + before do + @input = "example.com/path/to/resource" + end + + it "should heuristically parse to 'http://example.com/path/to/resource'" do + @uri = Addressable::URI.heuristic_parse(@input) + expect(@uri.to_s).to eq("http://example.com/path/to/resource") + end +end + +describe Addressable::URI, "when given the input " + + "'http:///example.com'" do + before do + @input = "http:///example.com" + end + + it "should heuristically parse to 'http://example.com'" do + @uri = Addressable::URI.heuristic_parse(@input) + expect(@uri.to_s).to eq("http://example.com") + end +end + +describe Addressable::URI, "when given the input which "\ + "start with digits and has specified port" do + before do + @input = "7777.example.org:8089" + end + + it "should heuristically parse to 'http://7777.example.org:8089'" do + uri = Addressable::URI.heuristic_parse(@input) + expect(uri.to_s).to eq("http://7777.example.org:8089") + end +end + +describe Addressable::URI, "when given the input " + + "'feed:///example.com'" do + before do + @input = "feed:///example.com" + end + + it "should heuristically parse to 'feed://example.com'" do + @uri = Addressable::URI.heuristic_parse(@input) + expect(@uri.to_s).to eq("feed://example.com") + end +end + +describe Addressable::URI, "when given the input " + + "'file://localhost/path/to/resource/'" do + before do + @input = "file://localhost/path/to/resource/" + end + + it "should heuristically parse to 'file:///path/to/resource/'" do + @uri = Addressable::URI.heuristic_parse(@input) + expect(@uri.to_s).to eq("file:///path/to/resource/") + end +end + +describe Addressable::URI, "when given the input " + + "'file://path/to/resource/'" do + before do + @input = "file://path/to/resource/" + end + + it "should heuristically parse to 'file:///path/to/resource/'" do + @uri = Addressable::URI.heuristic_parse(@input) + expect(@uri.to_s).to eq("file:///path/to/resource/") + end +end + +describe Addressable::URI, "when given the input " + + "'file://///path/to/resource/'" do + before do + @input = "file:///////path/to/resource/" + end + + it "should heuristically parse to 'file:////path/to/resource/'" do + @uri = Addressable::URI.heuristic_parse(@input) + expect(@uri.to_s).to eq("file:////path/to/resource/") + end +end + +describe Addressable::URI, "when given the input " + + "'feed://http://example.com'" do + before do + @input = "feed://http://example.com" + end + + it "should heuristically parse to 'feed:http://example.com'" do + @uri = Addressable::URI.heuristic_parse(@input) + expect(@uri.to_s).to eq("feed:http://example.com") + end +end + +describe Addressable::URI, "when given the input " + + "::URI.parse('http://example.com')" do + before do + @input = ::URI.parse('http://example.com') + end + + it "should heuristically parse to 'http://example.com'" do + @uri = Addressable::URI.heuristic_parse(@input) + expect(@uri.to_s).to eq("http://example.com") + end +end + +describe Addressable::URI, "when given the input: 'user@domain.com'" do + before do + @input = "user@domain.com" + end + + context "for heuristic parse" do + it "should remain 'mailto:user@domain.com'" do + uri = Addressable::URI.heuristic_parse("mailto:#{@input}") + expect(uri.to_s).to eq("mailto:user@domain.com") + end + + it "should have a scheme of 'mailto'" do + uri = Addressable::URI.heuristic_parse(@input) + expect(uri.to_s).to eq("mailto:user@domain.com") + expect(uri.scheme).to eq("mailto") + end + + it "should remain 'acct:user@domain.com'" do + uri = Addressable::URI.heuristic_parse("acct:#{@input}") + expect(uri.to_s).to eq("acct:user@domain.com") + end + + context "HTTP" do + before do + @uri = Addressable::URI.heuristic_parse("http://#{@input}/") + end + + it "should remain 'http://user@domain.com/'" do + expect(@uri.to_s).to eq("http://user@domain.com/") + end + + it "should have the username 'user' for HTTP basic authentication" do + expect(@uri.user).to eq("user") + end + end + end +end + +describe Addressable::URI, "when assigning query values" do + before do + @uri = Addressable::URI.new + end + + it "should correctly assign {:a => 'a', :b => ['c', 'd', 'e']}" do + @uri.query_values = {:a => "a", :b => ["c", "d", "e"]} + expect(@uri.query).to eq("a=a&b=c&b=d&b=e") + end + + it "should raise an error attempting to assign {'a' => {'b' => ['c']}}" do + expect(lambda do + @uri.query_values = { 'a' => {'b' => ['c'] } } + end).to raise_error(TypeError) + end + + it "should raise an error attempting to assign " + + "{:b => '2', :a => {:c => '1'}}" do + expect(lambda do + @uri.query_values = {:b => '2', :a => {:c => '1'}} + end).to raise_error(TypeError) + end + + it "should raise an error attempting to assign " + + "{:a => 'a', :b => [{:c => 'c', :d => 'd'}, " + + "{:e => 'e', :f => 'f'}]}" do + expect(lambda do + @uri.query_values = { + :a => "a", :b => [{:c => "c", :d => "d"}, {:e => "e", :f => "f"}] + } + end).to raise_error(TypeError) + end + + it "should raise an error attempting to assign " + + "{:a => 'a', :b => [{:c => true, :d => 'd'}, " + + "{:e => 'e', :f => 'f'}]}" do + expect(lambda do + @uri.query_values = { + :a => 'a', :b => [{:c => true, :d => 'd'}, {:e => 'e', :f => 'f'}] + } + end).to raise_error(TypeError) + end + + it "should raise an error attempting to assign " + + "{:a => 'a', :b => {:c => true, :d => 'd'}}" do + expect(lambda do + @uri.query_values = { + :a => 'a', :b => {:c => true, :d => 'd'} + } + end).to raise_error(TypeError) + end + + it "should raise an error attempting to assign " + + "{:a => 'a', :b => {:c => true, :d => 'd'}}" do + expect(lambda do + @uri.query_values = { + :a => 'a', :b => {:c => true, :d => 'd'} + } + end).to raise_error(TypeError) + end + + it "should correctly assign {:a => 1, :b => 1.5}" do + @uri.query_values = { :a => 1, :b => 1.5 } + expect(@uri.query).to eq("a=1&b=1.5") + end + + it "should raise an error attempting to assign " + + "{:z => 1, :f => [2, {999.1 => [3,'4']}, ['h', 'i']], " + + ":a => {:b => ['c', 'd'], :e => true, :y => 0.5}}" do + expect(lambda do + @uri.query_values = { + :z => 1, + :f => [ 2, {999.1 => [3,'4']}, ['h', 'i'] ], + :a => { :b => ['c', 'd'], :e => true, :y => 0.5 } + } + end).to raise_error(TypeError) + end + + it "should correctly assign {}" do + @uri.query_values = {} + expect(@uri.query).to eq('') + end + + it "should correctly assign nil" do + @uri.query_values = nil + expect(@uri.query).to eq(nil) + end + + it "should correctly sort {'ab' => 'c', :ab => 'a', :a => 'x'}" do + @uri.query_values = {'ab' => 'c', :ab => 'a', :a => 'x'} + expect(@uri.query).to eq("a=x&ab=a&ab=c") + end + + it "should correctly assign " + + "[['b', 'c'], ['b', 'a'], ['a', 'a']]" do + # Order can be guaranteed in this format, so preserve it. + @uri.query_values = [['b', 'c'], ['b', 'a'], ['a', 'a']] + expect(@uri.query).to eq("b=c&b=a&a=a") + end + + it "should preserve query string order" do + query_string = (('a'..'z').to_a.reverse.map { |e| "#{e}=#{e}" }).join("&") + @uri.query = query_string + original_uri = @uri.to_s + @uri.query_values = @uri.query_values(Array) + expect(@uri.to_s).to eq(original_uri) + end + + describe 'when a hash with mixed types is assigned to query_values' do + it 'should not raise an error' do + skip 'Issue #94' + expect { subject.query_values = { "page" => "1", :page => 2 } }.to_not raise_error + end + end +end + +describe Addressable::URI, "when assigning path values" do + before do + @uri = Addressable::URI.new + end + + it "should correctly assign paths containing colons" do + @uri.path = "acct:bob@sporkmonger.com" + expect(@uri.path).to eq("acct:bob@sporkmonger.com") + expect(@uri.normalize.to_str).to eq("acct%2Fbob@sporkmonger.com") + expect(lambda { @uri.to_s }).to raise_error( + Addressable::URI::InvalidURIError + ) + end + + it "should correctly assign paths containing colons" do + @uri.path = "/acct:bob@sporkmonger.com" + @uri.authority = "example.com" + expect(@uri.normalize.to_str).to eq("//example.com/acct:bob@sporkmonger.com") + end + + it "should correctly assign paths containing colons" do + @uri.path = "acct:bob@sporkmonger.com" + @uri.scheme = "something" + expect(@uri.normalize.to_str).to eq("something:acct:bob@sporkmonger.com") + end + + it "should not allow relative paths to be assigned on absolute URIs" do + expect(lambda do + @uri.scheme = "http" + @uri.host = "example.com" + @uri.path = "acct:bob@sporkmonger.com" + end).to raise_error(Addressable::URI::InvalidURIError) + end + + it "should not allow relative paths to be assigned on absolute URIs" do + expect(lambda do + @uri.path = "acct:bob@sporkmonger.com" + @uri.scheme = "http" + @uri.host = "example.com" + end).to raise_error(Addressable::URI::InvalidURIError) + end + + it "should not allow relative paths to be assigned on absolute URIs" do + expect(lambda do + @uri.path = "uuid:0b3ecf60-3f93-11df-a9c3-001f5bfffe12" + @uri.scheme = "urn" + end).not_to raise_error + end +end + +describe Addressable::URI, "when initializing a subclass of Addressable::URI" do + before do + @uri = Class.new(Addressable::URI).new + end + + it "should have the same class after being parsed" do + expect(@uri.class).to eq(Addressable::URI.parse(@uri).class) + end + + it "should have the same class as its duplicate" do + expect(@uri.class).to eq(@uri.dup.class) + end + + it "should have the same class after being normalized" do + expect(@uri.class).to eq(@uri.normalize.class) + end + + it "should have the same class after being merged" do + expect(@uri.class).to eq(@uri.merge(:path => 'path').class) + end + + it "should have the same class after being joined" do + expect(@uri.class).to eq(@uri.join('path').class) + end +end diff --git a/path/ruby/2.6.0/gems/addressable-2.7.0/spec/spec_helper.rb b/path/ruby/2.6.0/gems/addressable-2.7.0/spec/spec_helper.rb new file mode 100644 index 00000000..4427f214 --- /dev/null +++ b/path/ruby/2.6.0/gems/addressable-2.7.0/spec/spec_helper.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'bundler/setup' +require 'rspec/its' + +begin + require 'coveralls' + Coveralls.wear! do + add_filter "spec/" + add_filter "vendor/" + end +rescue LoadError + warn "warning: coveralls gem not found; skipping Coveralls" + require 'simplecov' + SimpleCov.start do + add_filter "spec/" + add_filter "vendor/" + end +end + +RSpec.configure do |config| + config.warnings = true + config.filter_run_when_matching :focus +end diff --git a/path/ruby/2.6.0/gems/addressable-2.7.0/tasks/clobber.rake b/path/ruby/2.6.0/gems/addressable-2.7.0/tasks/clobber.rake new file mode 100644 index 00000000..a9e32b34 --- /dev/null +++ b/path/ruby/2.6.0/gems/addressable-2.7.0/tasks/clobber.rake @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +desc "Remove all build products" +task "clobber" diff --git a/path/ruby/2.6.0/gems/addressable-2.7.0/tasks/gem.rake b/path/ruby/2.6.0/gems/addressable-2.7.0/tasks/gem.rake new file mode 100644 index 00000000..64c5e4bf --- /dev/null +++ b/path/ruby/2.6.0/gems/addressable-2.7.0/tasks/gem.rake @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require "rubygems/package_task" + +namespace :gem do + GEM_SPEC = Gem::Specification.new do |s| + s.name = PKG_NAME + s.version = PKG_VERSION + s.summary = PKG_SUMMARY + s.description = PKG_DESCRIPTION + + s.files = PKG_FILES.to_a + + s.has_rdoc = true + s.extra_rdoc_files = %w( README.md ) + s.rdoc_options.concat ["--main", "README.md"] + + if !s.respond_to?(:add_development_dependency) + puts "Cannot build Gem with this version of RubyGems." + exit(1) + end + + s.required_ruby_version = ">= 2.0" + + s.add_runtime_dependency "public_suffix", ">= 2.0.2", "< 5.0" + s.add_development_dependency "bundler", ">= 1.0", "< 3.0" + + s.require_path = "lib" + + s.author = "Bob Aman" + s.email = "bob@sporkmonger.com" + s.homepage = "https://github.com/sporkmonger/addressable" + s.license = "Apache-2.0" + end + + Gem::PackageTask.new(GEM_SPEC) do |p| + p.gem_spec = GEM_SPEC + p.need_tar = true + p.need_zip = true + end + + desc "Generates .gemspec file" + task :gemspec do + spec_string = GEM_SPEC.to_ruby + File.open("#{GEM_SPEC.name}.gemspec", "w") do |file| + file.write spec_string + end + end + + desc "Show information about the gem" + task :debug do + puts GEM_SPEC.to_ruby + end + + desc "Install the gem" + task :install => ["clobber", "gem:package"] do + sh "#{SUDO} gem install --local pkg/#{GEM_SPEC.full_name}" + end + + desc "Uninstall the gem" + task :uninstall do + installed_list = Gem.source_index.find_name(PKG_NAME) + if installed_list && + (installed_list.collect { |s| s.version.to_s}.include?(PKG_VERSION)) + sh( + "#{SUDO} gem uninstall --version '#{PKG_VERSION}' " + + "--ignore-dependencies --executables #{PKG_NAME}" + ) + end + end + + desc "Reinstall the gem" + task :reinstall => [:uninstall, :install] + + desc "Package for release" + task :release => ["gem:package", "gem:gemspec"] do |t| + v = ENV["VERSION"] or abort "Must supply VERSION=x.y.z" + abort "Versions don't match #{v} vs #{PROJ.version}" if v != PKG_VERSION + pkg = "pkg/#{GEM_SPEC.full_name}" + + changelog = File.open("CHANGELOG.md") { |file| file.read } + + puts "Releasing #{PKG_NAME} v. #{PKG_VERSION}" + Rake::Task["git:tag:create"].invoke + end +end + +desc "Alias to gem:package" +task "gem" => "gem:package" + +task "gem:release" => "gem:gemspec" + +task "clobber" => ["gem:clobber_package"] diff --git a/path/ruby/2.6.0/gems/addressable-2.7.0/tasks/git.rake b/path/ruby/2.6.0/gems/addressable-2.7.0/tasks/git.rake new file mode 100644 index 00000000..1238c8d2 --- /dev/null +++ b/path/ruby/2.6.0/gems/addressable-2.7.0/tasks/git.rake @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +namespace :git do + namespace :tag do + desc "List tags from the Git repository" + task :list do + tags = `git tag -l` + tags.gsub!("\r", "") + tags = tags.split("\n").sort {|a, b| b <=> a } + puts tags.join("\n") + end + + desc "Create a new tag in the Git repository" + task :create do + changelog = File.open("CHANGELOG.md", "r") { |file| file.read } + puts "-" * 80 + puts changelog + puts "-" * 80 + puts + + v = ENV["VERSION"] or abort "Must supply VERSION=x.y.z" + abort "Versions don't match #{v} vs #{PKG_VERSION}" if v != PKG_VERSION + + git_status = `git status` + if git_status !~ /^nothing to commit/ + abort "Working directory isn't clean." + end + + tag = "#{PKG_NAME}-#{PKG_VERSION}" + msg = "Release #{PKG_NAME}-#{PKG_VERSION}" + + existing_tags = `git tag -l #{PKG_NAME}-*`.split('\n') + if existing_tags.include?(tag) + warn("Tag already exists, deleting...") + unless system "git tag -d #{tag}" + abort "Tag deletion failed." + end + end + puts "Creating git tag '#{tag}'..." + unless system "git tag -a -m \"#{msg}\" #{tag}" + abort "Tag creation failed." + end + end + end +end + +task "gem:release" => "git:tag:create" diff --git a/path/ruby/2.6.0/gems/addressable-2.7.0/tasks/metrics.rake b/path/ruby/2.6.0/gems/addressable-2.7.0/tasks/metrics.rake new file mode 100644 index 00000000..107cc244 --- /dev/null +++ b/path/ruby/2.6.0/gems/addressable-2.7.0/tasks/metrics.rake @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +namespace :metrics do + task :lines do + lines, codelines, total_lines, total_codelines = 0, 0, 0, 0 + for file_name in FileList["lib/**/*.rb"] + f = File.open(file_name) + while line = f.gets + lines += 1 + next if line =~ /^\s*$/ + next if line =~ /^\s*#/ + codelines += 1 + end + puts "L: #{sprintf("%4d", lines)}, " + + "LOC #{sprintf("%4d", codelines)} | #{file_name}" + total_lines += lines + total_codelines += codelines + + lines, codelines = 0, 0 + end + + puts "Total: Lines #{total_lines}, LOC #{total_codelines}" + end +end diff --git a/path/ruby/2.6.0/gems/addressable-2.7.0/tasks/rspec.rake b/path/ruby/2.6.0/gems/addressable-2.7.0/tasks/rspec.rake new file mode 100644 index 00000000..85288438 --- /dev/null +++ b/path/ruby/2.6.0/gems/addressable-2.7.0/tasks/rspec.rake @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require "rspec/core/rake_task" + +namespace :spec do + RSpec::Core::RakeTask.new(:simplecov) do |t| + t.pattern = FileList['spec/**/*_spec.rb'] + t.rspec_opts = ['--color', '--format', 'documentation'] + end + + namespace :simplecov do + desc "Browse the code coverage report." + task :browse => "spec:simplecov" do + require "launchy" + Launchy.open("coverage/index.html") + end + end +end + +desc "Alias to spec:simplecov" +task "spec" => "spec:simplecov" + +task "clobber" => ["spec:clobber_simplecov"] diff --git a/path/ruby/2.6.0/gems/addressable-2.7.0/tasks/yard.rake b/path/ruby/2.6.0/gems/addressable-2.7.0/tasks/yard.rake new file mode 100644 index 00000000..515f9603 --- /dev/null +++ b/path/ruby/2.6.0/gems/addressable-2.7.0/tasks/yard.rake @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require "rake" + +begin + require "yard" + require "yard/rake/yardoc_task" + + namespace :doc do + desc "Generate Yardoc documentation" + YARD::Rake::YardocTask.new do |yardoc| + yardoc.name = "yard" + yardoc.options = ["--verbose", "--markup", "markdown"] + yardoc.files = FileList[ + "lib/**/*.rb", "ext/**/*.c", + "README.md", "CHANGELOG.md", "LICENSE.txt" + ].exclude(/idna/) + end + end + + task "clobber" => ["doc:clobber_yard"] + + desc "Alias to doc:yard" + task "doc" => "doc:yard" +rescue LoadError + # If yard isn't available, it's not the end of the world + desc "Alias to doc:rdoc" + task "doc" => "doc:rdoc" +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/.yardopts b/path/ruby/2.6.0/gems/archive-zip-0.12.0/.yardopts new file mode 100644 index 00000000..248963b4 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/.yardopts @@ -0,0 +1 @@ +--protected --private --main README.md lib/**/*.rb - NEWS.md LICENSE diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/LICENSE b/path/ruby/2.6.0/gems/archive-zip-0.12.0/LICENSE new file mode 100644 index 00000000..3e1e4f5c --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/LICENSE @@ -0,0 +1,22 @@ +(The MIT License) + +Copyright (c) 2019 Jeremy Bopp + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/NEWS.md b/path/ruby/2.6.0/gems/archive-zip-0.12.0/NEWS.md new file mode 100644 index 00000000..f3be623b --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/NEWS.md @@ -0,0 +1,145 @@ +# News and Notifications by Version + +This file lists noteworthy changes which may affect users of this project. More +detailed information is available in the rest of the documentation. + +**NOTE:** Date stamps in the following entries are in YYYY/MM/DD format. + + +## v0.12.0 (2019/02/28) + +### Fixes + +* Check for codec availability before attempting to initialize a codec instance + during extraction. (Kouhei Sutou) + +## v0.11.0 (2018/01/28) + +### Fixes + +* Upgrade gems required for development. + +## v0.10.0 (2017/09/04) + +### Fixes + +* Avoid Ruby warnings for uninitialized attributes. (Tatsuya Sato) + +## v0.9.0 (2016/12/18) + +### Fixes + +* Initialize DOSTime correctly when not given a struct to parse. + +## v0.8.0 (2015/01/05) + +### Fixes + +* Avoid Ruby warnings. (Akira Matsuda) + +## v0.7.0 (2014/08/18) + +### Fixes + +* Avoid corrupting the archive when storing entries that have multibyte names. + +### Notes + +* Ruby 1.8.6 support has been dropped. + * This may come back if demand warrants it. +* Switched to the MIT license. +* Now using minitest instead of mspec for tests. + +## v0.6.0 (2013/03/24) + +### Fixes + +* Only define Zlib constants when they are not already defined. + * Fixes constant redefinition warnings under MRI 2.0.0-p0. +* Force Zlib::ZWriter and Zlib::ZReader #checksum methods to return nil for raw + streams. + * The behavior of the underlying Zlib::Deflate and Zlib::Inflate classes' + #adler methods appear inconsistent for raw streams and really aren't + necessary in this case anyway. + +### Notes + +* Broke backward compatibility with the behavior of Zlib::ZWriter#checksum and + Zlib::ZReader#checksum when working with raw streams. + * This should not affect direct users of Archive::Zip because the checksum + methods of those classes are never used. + +## v0.5.0 (2012/03/01) + +### Fixes + +* Avoid timezone discrepancies in encryption tests. +* Moved the DOSTime class to the Archive namespace (Chris Schneider). + +### Notes + +* Broke backward compatibility of the DOSTime class. + +## v0.4.0 (2011/08/29) + +### Features + +* Added Ruby 1.9 support. +* Simplified arguments for Archive::Zip.new. + * Archives cannot be directly opened for modification. + * Archive::Zip.archive can still emulate modifying an archive in place. +* Added a bunch of tests (many more still needed). +* Updated and simplified rake tasks. +* Created a standalone gemspec file. + +### Fixes + +* Fixed a potential data loss bug in Zlib::ZReader. +* Archives larger than the maximum Fixnum for the platform don't falsely raise a + "non-integer windows position given" error. + +### Notes + +* Broke backward compatibility for Archive::Zip.new. + * Wrapper class methods continue to work as before. +* Broke backward compatibility for Archive::Zip::ExtraField. + * Allows separate handling of extra fields in central and local records. + +## v0.3.0 (2009/01/23) + +* Made a significant performance improvement for the extraction of compressed + entries for performance on par with InfoZIP's unzip. Parsing archives with + many entries is still a bit subpar however. + + +## v0.2.0 (2008/08/06) + +* Traditional (weak) encryption is now supported. +* Adding new encryption methods should be easier now. +* Fixed a bug where the compression codec for an entry loaded from an archive + was not being recorded. +* The _compression_codec_ attribute for Entry instances is now used instead of + the _codec_ attribute to access the compression codec of the entry. + + +## v0.1.1 (2008/07/11) + +* Archive files are now closed when the Archive::Zip object is closed even when + no changes were made to the archive, a problem on Windows if you want to + delete the archive after extracting it within the same script. + + +## v0.1.0 (2008/07/10) + +* Initial release. +* Archive creation and extraction is supported with only a few lines of code. + (See README) +* Archives can be updated "in place" or dumped out to other files or pipes. +* Files, symlinks, and directories are supported within archives. +* Unix permission/mode bits are supported. +* Unix user and group ownerships are supported. +* Unix last accessed and last modified times are supported. +* Entry extension (AKA extra field) implementations can be added on the fly. +* Unknown entry extension types are preserved during archive processing. +* Deflate and Store compression codecs are supported out of the box. +* More compression codecs can be added on the fly. diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/README.md b/path/ruby/2.6.0/gems/archive-zip-0.12.0/README.md new file mode 100644 index 00000000..f7cf8d63 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/README.md @@ -0,0 +1,253 @@ +# Archive::Zip - ZIP Archival Made Easy + +Simple, extensible, pure Ruby ZIP archive support. + +Basic archive creation and extraction can be handled using only a few methods. +More complex operations involving the manipulation of existing archives in place +(adding, removing, and modifying entries) are also possible with a little more +work. Even adding advanced features such as new compression codecs are +supported with a moderate amount of effort. + +## LINKS + +* Homepage :: http://github.com/javanthropus/archive-zip +* Documentation :: http://rdoc.info/gems/archive-zip/frames +* Source :: http://github.com/javanthropus/archive-zip + +## DESCRIPTION + +Archive::Zip provides a simple Ruby-esque interface to creating, extracting, and +updating ZIP archives. This implementation is 100% Ruby and loosely modeled on +the archive creation and extraction capabilities of InfoZip's zip and unzip +tools. + +## FEATURES + +* 100% native Ruby. (Well, almost... depends on zlib.) +* Archive creation and extraction is supported with only a few lines of code. +* Archives can be updated "in place" or dumped out to other files or pipes. +* Files, symlinks, and directories are supported within archives. +* Unix permission/mode bits are supported. +* Unix user and group ownerships are supported. +* Unix last accessed and last modified times are supported. +* Entry extension (AKA extra field) implementations can be added on the fly. +* Unknown entry extension types are preserved during archive processing. +* The Deflate and Store compression codecs are supported out of the box. +* More compression codecs can be added on the fly. +* Traditional (weak) encryption is supported out of the box. + +## KNOWN BUGS/LIMITATIONS + +* More testcases are needed. +* All file entries are archived and extracted in binary mode. No attempt is + made to normalize text files to the line ending convention of any target + system. +* Hard links and device files are not currently supported within archives. +* Reading archives from non-seekable IO, such as pipes and sockets, is not + supported. +* MSDOS permission attributes are not supported. +* Strong encryption is not supported. +* Zip64 is not supported. +* Digital signatures are not supported. + +## SYNOPSIS + +More examples can be found in the `examples` directory of the source +distribution. + +Create a few archives: + +```ruby +require 'archive/zip' + +# Add a_directory and its contents to example1.zip. +Archive::Zip.archive('example1.zip', 'a_directory') + +# Add the contents of a_directory to example2.zip. +Archive::Zip.archive('example2.zip', 'a_directory/.') + +# Add a_file and a_directory and its contents to example3.zip. +Archive::Zip.archive('example3.zip', ['a_directory', 'a_file']) + +# Add only the files and symlinks contained in a_directory under the path +# a/b/c/a_directory in example4.zip. +Archive::Zip.archive( + 'example4.zip', + 'a_directory', + :directories => false, + :path_prefix => 'a/b/c' +) + +# Add the contents of a_directory to example5.zip and encrypt Ruby source +# files. +require 'archive/zip/codec/null_encryption' +require 'archive/zip/codec/traditional_encryption' +Archive::Zip.archive( + 'example5.zip', + 'a_directory/.', + :encryption_codec => lambda do |entry| + if entry.file? and entry.zip_path =~ /\.rb$/ then + Archive::Zip::Codec::TraditionalEncryption + else + Archive::Zip::Codec::NullEncryption + end + end, + :password => 'seakrit' +) + +# Create a new archive which will be written to a pipe. +# Assume $stdout is the write end a pipe. +# (ruby example.rb | cat >example.zip) +Archive::Zip.open($stdout, :w) do |z| + z.archive('a_directory') +end +``` + +Now extract those archives: + +```ruby +require 'archive/zip' + +# Extract example1.zip to a_destination. +Archive::Zip.extract('example1.zip', 'a_destination') + +# Extract example2.zip to a_destination, skipping directory entries. +Archive::Zip.extract( + 'example2.zip', + 'a_destination', + :directories => false +) + +# Extract example3.zip to a_destination, skipping symlinks. +Archive::Zip.extract( + 'example3.zip', + 'a_destination', + :symlinks => false +) + +# Extract example4.zip to a_destination, skipping entries for which files +# already exist but are newer or for which files do not exist at all. +Archive::Zip.extract( + 'example4.zip', + 'a_destination', + :create => false, + :overwrite => :older +) + +# Extract example5.zip to a_destination, decrypting the contents. +Archive::Zip.extract( + 'example5.zip', + 'a_destination', + :password => 'seakrit' +) +``` + +## FUTURE WORK ITEMS (in no particular order): + +* Add test cases for all classes. +* Add support for using non-seekable IO objects as archive sources. +* Add support for 0x5855 and 0x7855 extra fields. + +## REQUIREMENTS + +* io-like + +## INSTALL + +Download the GEM file and install it with: + + $ gem install archive-zip-VERSION.gem + +or directly with: + + $ gem install archive-zip + +Removal is the same in either case: + + $ gem uninstall archive-zip + +## DEVELOPERS + +After checking out the source, run: + + $ bundle install + $ bundle exec rake test yard + +This will install all dependencies, run the tests/specs, and generate the +documentation. + +## AUTHORS and CONTRIBUTORS + +Thanks to all contributors. Without your help this project would not exist. + +* Jeremy Bopp :: jeremy@bopp.net +* Akira Matsuda :: ronnie@dio.jp +* Tatsuya Sato :: tatsuya.b.sato@rakuten.com +* Kouhei Sutou :: kou@clear-code.com + +## CONTRIBUTING + +Contributions for bug fixes, documentation, extensions, tests, etc. are +encouraged. + +1. Clone the repository. +2. Fix a bug or add a feature. +3. Add tests for the fix or feature. +4. Make a pull request. + +### CODING STYLE + +The following points are not necessarily set in stone but should rather be used +as a good guideline. Consistency is the goal of coding style, and changes will +be more easily accepted if they are consistent with the rest of the code. + +* **File Encoding** + * UTF-8 +* **Indentation** + * Two spaces; no tabs +* **Line length** + * Limit lines to a maximum of 80 characters +* **Comments** + * Document classes, attributes, methods, and code +* **Method Calls with Arguments** + * Use `a_method(arg, arg, etc)`; **not** `a_method( arg, arg, etc )`, + `a_method arg, arg, etc`, or any other variation +* **Method Calls without Arguments** + * Use `a_method`; avoid parenthesis +* **String Literals** + * Use single quotes by default + * Use double quotes when interpolation is necessary + * Use `%{...}` and similar when embedding the quoting character is cumbersome +* **Blocks** + * `do ... end` for multi-line blocks and `{ ... }` for single-line blocks +* **Boolean Operators** + * Use `&&` and `||` for boolean tests; avoid `and` and `or` +* **In General** + * Try to follow the flow and style of the rest of the code + +## LICENSE + +``` +(The MIT License) + +Copyright (c) 2019 Jeremy Bopp + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +``` diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/Rakefile b/path/ruby/2.6.0/gems/archive-zip-0.12.0/Rakefile new file mode 100644 index 00000000..c9af86ed --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/Rakefile @@ -0,0 +1,247 @@ +# encoding: UTF-8 +# -*- ruby -*- + +require 'rubygems' + +require 'erb' +require 'rake/testtask' +require 'rubygems/package_task' +require 'rake/clean' +require 'yard' + +# Load the gemspec file for this project. +GEMSPEC = Dir['*.gemspec'].first +SPEC = eval(File.read(GEMSPEC), nil, GEMSPEC) + +# The path to the version.rb file and a string to eval to find the version. +VERSION_RB = "lib/#{SPEC.name.gsub('-', '/')}/version.rb" +VERSION_REF = + SPEC.name.split('-').map do |subname| + subname.split('_').map(&:capitalize).join + end.join('::') + "::VERSION" + +# A dynamically generated list of files that should match the manifest (the +# combined contents of SPEC.files and SPEC.test_files). The idea is for this +# list to contain all project files except for those that have been explicitly +# excluded. This list will be compared with the manifest from the SPEC in order +# to help catch the addition or removal of files to or from the project that +# have not been accounted for either by an exclusion here or an inclusion in the +# SPEC manifest. +# +# NOTE: +# It is critical that the manifest is *not* automatically generated via globbing +# and the like; otherwise, this will yield a simple comparison between +# redundantly generated lists of files that probably will not protect the +# project from the unintentional inclusion or exclusion of files in the +# distribution. +PKG_FILES = FileList.new(Dir.glob('**/*', File::FNM_DOTMATCH)) do |files| + # Exclude anything that doesn't exist as well as directories. + files.exclude {|file| ! File.exist?(file) || File.directory?(file)} + # Exclude Git administrative files. + files.exclude(%r{(^|[/\\])\.git(ignore|modules|keep)?([/\\]|$)}) + # Exclude editor swap/temporary files. + files.exclude('**/.*.sw?') + # Exclude gemspec files. + files.exclude('*.gemspec') + # Exclude the README template file. + files.exclude('README.md.erb') + # Exclude resources for bundler. + files.exclude('Gemfile', 'Gemfile.lock') + files.exclude(%r{^.bundle([/\\]|$)}) + files.exclude(%r{^vendor/bundle([/\\]|$)}) + # Exclude generated content, except for the README file. + files.exclude(%r{^(pkg|doc|.yardoc)([/\\]|$)}) + # Exclude Rubinius compiled Ruby files. + files.exclude('**/*.rbc') + files.exclude('.rbx/**/*') +end + +# Make sure that :clean and :clobber will not whack the repository files. +CLEAN.exclude('.git/**') +# Vim swap files are fair game for clean up. +CLEAN.include('**/.*.sw?') + +# Returns the value of the VERSION environment variable as a Gem::Version object +# assuming it is set and a valid Gem version string. Otherwise, raises an +# exception. +def get_version_argument + version = ENV['VERSION'] + if version.to_s.empty? + raise "No version specified: Add VERSION=X.Y.Z to the command line" + end + begin + Gem::Version.create(version.dup) + rescue ArgumentError + raise "Invalid version specified in `VERSION=#{version}'" + end +end + +# Performs an in place, per line edit of the file indicated by _path_ by calling +# the sub method on each line and passing _pattern_, _replacement_, and _b_ as +# arguments. +def file_sub(path, pattern, replacement = nil, &b) + tmp_path = "#{path}.tmp" + File.open(path) do |infile| + File.open(tmp_path, 'w') do |outfile| + infile.each do |line| + outfile.write(line.sub(pattern, replacement, &b)) + end + end + end + File.rename(tmp_path, path) +end + +# Updates the version string in the gemspec file and a version.rb file it to the +# string in _version_. +def set_version(version) + file_sub(GEMSPEC, /(\.version\s*=\s*).*/, "\\1'#{version}'") + file_sub(VERSION_RB, /^(\s*VERSION\s*=\s*).*/, "\\1'#{version}'") +end + +# Returns a string that is line wrapped at word boundaries, where each line is +# no longer than _line_width_ characters. +# +# This is mostly lifted directly from ActionView::Helpers::TextHelper. +def word_wrap(text, line_width = 80) + text.split("\n").collect do |line| + line.length > line_width ? + line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : + line + end * "\n" +end + +desc 'Alias for build:gem' +task :build => 'build:gem' + +# Build related tasks. +namespace :build do + # Ensure that the manifest is consulted when building the gem. Any + # generated/compiled files should be available at that time. + task :gem => :check_manifest + + # Create the gem and package tasks. + Gem::PackageTask.new(SPEC).define + + desc 'Verify the manifest' + task :check_manifest do + manifest_files = (SPEC.files + SPEC.test_files).sort.uniq + pkg_files = PKG_FILES.sort.uniq + if manifest_files != pkg_files then + common_files = manifest_files & pkg_files + manifest_files -= common_files + pkg_files -= common_files + message = ["The manifest does not match the automatic file list."] + unless manifest_files.empty? then + message << " Extraneous files:\n " + manifest_files.join("\n ") + end + unless pkg_files.empty? + message << " Missing files:\n " + pkg_files.join("\n ") + end + raise message.join("\n") + end + end + + # Creates the README.md file from a template, the license file and the gemspec + # contents. + file 'README.md' => ['README.md.erb', 'LICENSE', GEMSPEC] do + spec = SPEC + File.open('README.md', 'w') do |readme| + readme.write( + ERB.new(File.read('README.md.erb'), nil, '-').result(binding) + ) + end + end +end + +# Ensure that the clobber task also clobbers package files. +task :clobber => 'build:clobber_package' + +# Create the documentation task. +YARD::Rake::YardocTask.new +# Ensure that the README file is (re)generated first. +task :yard => 'README.md' + +# Gem related tasks. +namespace :gem do + desc 'Alias for build:gem' + task :build => 'build:gem' + + desc 'Publish the gemfile' + task :publish => ['version:check', :test, 'repo:tag', :build] do + sh "gem push pkg/#{SPEC.name}-#{SPEC.version}*.gem" + end +end + +Rake::TestTask.new do |t| + t.pattern = 'spec/**/*_spec.rb' +end + +# Version string management tasks. +namespace :version do + desc 'Set the version for the project to a specified version' + task :set do + set_version(get_version_argument) + end + + desc 'Set the version for the project back to 0.0.0' + task :reset do + set_version('0.0.0') + end + + desc 'Check that all version strings are correctly set' + task :check => ['version:check:spec', 'version:check:version_rb', 'version:check:news'] + + namespace :check do + desc 'Check that the version in the gemspec is correctly set' + task :spec do + version = get_version_argument + if version != SPEC.version + raise "The given version `#{version}' does not match the gemspec version `#{SPEC.version}'" + end + end + + desc 'Check that the version in the version.rb file is correctly set' + task :version_rb do + version = get_version_argument + begin + load VERSION_RB + internal_version = Gem::Version.create(eval(VERSION_REF)) + if version != internal_version + raise "The given version `#{version}' does not match the version.rb version `#{internal_version}'" + end + rescue ArgumentError + raise "Invalid version specified in `#{VERSION_RB}'" + end + end + + desc 'Check that the NEWS.md file mentions the version' + task :news do + version = get_version_argument + begin + File.open('NEWS.md') do |news| + unless news.each_line.any? {|l| l =~ /^## v#{Regexp.escape(version.to_s)} /} + raise "The NEWS.md file does not mention version `#{version}'" + end + end + rescue Errno::ENOENT + raise 'No NEWS.md file found' + end + end + end +end + +# Repository and workspace management tasks. +namespace :repo do + desc 'Tag the current HEAD with the version string' + task :tag => :check_workspace do + version = get_version_argument + sh "git tag -s -m 'Release v#{version}' v#{version}" + end + + desc 'Ensure the workspace is fully committed and clean' + task :check_workspace => ['README.md'] do + unless `git status --untracked-files=all --porcelain`.empty? + raise 'Workspace has been modified. Commit pending changes and try again.' + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/support/binary_stringio.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/support/binary_stringio.rb new file mode 100644 index 00000000..a64bd81a --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/support/binary_stringio.rb @@ -0,0 +1,30 @@ +# encoding: UTF-8 + +require 'stringio' + +# This class is a version of StringIO that always uses the binary encoding on +# any Ruby platform that has a notion of encodings. On Ruby platforms without +# encoding support, this class is equivalent to StringIO. +class BinaryStringIO < StringIO + # Creates a new instance of this class. + # + # This takes all the arguments of StringIO.new. + def initialize(*args) + super + + # Force a binary encoding when possible. + if respond_to?(:set_encoding, true) + @encoding_locked = false + set_encoding('binary') + end + end + + if instance_methods.include?(:set_encoding) + # Raise an exception on attempts to change the encoding. + def set_encoding(*args) + raise 'Changing encoding is not allowed' if @encoding_locked + @encoding_locked = true + super + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/support/integer.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/support/integer.rb new file mode 100644 index 00000000..7f6dc83c --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/support/integer.rb @@ -0,0 +1,13 @@ +# encoding: UTF-8 + +class Integer + unless public_method_defined?('ord') then + # Returns the int itself. + # + # This method is defined here only if not already defined elsewhere, such as + # versions of Ruby prior to 1.8.7. + def ord + self + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/support/io-like.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/support/io-like.rb new file mode 100644 index 00000000..439137d6 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/support/io-like.rb @@ -0,0 +1,14 @@ +# encoding: UTF-8 + +# Try to first load io-like from rubygems and then fall back to assuming a +# standard installation. +begin + require 'rubygems' + gem 'io-like', '>= 0.3.0' +rescue LoadError + # Failed to load via rubygems. +end + +# This will work for the gem and standard install assuming io-like is available +# at all. +require 'io/like' diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/support/ioextensions.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/support/ioextensions.rb new file mode 100644 index 00000000..9732472b --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/support/ioextensions.rb @@ -0,0 +1,16 @@ +# encoding: UTF-8 + +# IOExtensions provides convenience wrappers for certain IO functionality. +module IOExtensions + # Reads and returns exactly _length_ bytes from _io_ using the read method on + # _io_. If there is insufficient data available, an EOFError is raised. + def self.read_exactly(io, length, buffer = '') + buffer.slice!(0..-1) unless buffer.empty? + while buffer.size < length do + internal = io.read(length - buffer.size) + raise EOFError, 'unexpected end of file' if internal.nil? + buffer << internal + end + buffer + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/support/iowindow.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/support/iowindow.rb new file mode 100644 index 00000000..3db013c0 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/support/iowindow.rb @@ -0,0 +1,115 @@ +# encoding: UTF-8 + +require 'archive/support/io-like' + +# IOWindow represents an IO object which wraps another one allowing read and/or +# write access to a subset of the data within the stream. +# +# NOTE: This object is NOT thread safe. +class IOWindow + include IO::Like + + # Creates a new instance of this class using _io_ as the data source + # and where _window_position_ and _window_size_ define the location and size + # of data window respectively. + # + # _io_ must be opened for reading and must be seekable. _window_position_ + # must be an integer greater than or equal to 0. _window_size_ must be an + # integer greater than or equal to 0. + def initialize(io, window_position, window_size) + @io = io + @unbuffered_pos = 0 + self.window_position = window_position + self.window_size = window_size + end + + # The file position at which this window begins. + attr_reader :window_position + + # Set the file position at which this window begins. + # _window_position_ must be an integer greater than or equal to 0. + def window_position=(window_position) + unless window_position.respond_to?(:to_int) then + raise TypeError, "can't convert #{window_position.class} into Integer" + end + window_position = window_position.to_int + if window_position < 0 then + raise ArgumentError, 'non-positive window position given' + end + + @window_position = window_position + end + + # The size of the window. + attr_reader :window_size + + # Set the size of the window. + # _window_size_ must be an integer greater than or equal to 0. + def window_size=(window_size) + unless window_size.respond_to?(:to_int) then + raise TypeError, "can't convert #{window_size.class} into Integer" + end + window_size = window_size.to_int + raise ArgumentError, 'non-positive window size given' if window_size < 0 + + @window_size = window_size + end + + private + + def unbuffered_read(length) + restore_self + + # Error out if the end of the window is reached. + raise EOFError, 'end of file reached' if @unbuffered_pos >= @window_size + + # Limit the read operation to the window. + length = @window_size - @unbuffered_pos if @unbuffered_pos + length > @window_size + + # Fill a buffer with the data from the delegate. + buffer = @io.read(length) + # Error out if the end of the delegate is reached. + raise EOFError, 'end of file reached' if buffer.nil? + + # Update the position. + @unbuffered_pos += buffer.length + + buffer + ensure + restore_delegate + end + + def unbuffered_seek(offset, whence = IO::SEEK_SET) + # Convert the offset and whence into an absolute position. + case whence + when IO::SEEK_SET + new_pos = offset + when IO::SEEK_CUR + new_pos = @unbuffered_pos + offset + when IO::SEEK_END + new_pos = @window_size + offset + end + + # Error out if the position is outside the window. + raise Errno::EINVAL, 'Invalid argument' if new_pos < 0 or new_pos > @window_size + + # Set the new position. + @unbuffered_pos = new_pos + end + + # Restores the state of the delegate IO object to that saved by a prior call + # to #restore_self. + def restore_delegate + @io.pos = @delegate_pos + @io.lineno = @delegate_lineno + end + + # Saves the state of the delegate IO object so that it can be restored later + # using #restore_delegate and then configures the delegate so as to restore + # the state of this object. + def restore_self + @delegate_pos = @io.pos + @delegate_lineno = @io.lineno + @io.pos = @window_position + @unbuffered_pos + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/support/time.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/support/time.rb new file mode 100644 index 00000000..7c266b1c --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/support/time.rb @@ -0,0 +1,119 @@ +# encoding: UTF-8 + +class Time + # Returns a DOSTime representing this time object as a DOS date-time + # structure. Times are bracketed by the limits of the ability of the DOS + # date-time structure to represent them. Accuracy is 2 seconds and years + # range from 1980 to 2099. The returned structure represents as closely as + # possible the time of this object. + # + # See DOSTime#new for a description of this structure. + def to_dos_time + dos_sec = sec/2 + dos_year = year - 1980 + dos_year = 0 if dos_year < 0 + dos_year = 119 if dos_year > 119 + + Archive::DOSTime.new( + (dos_sec ) | + (min << 5) | + (hour << 11) | + (day << 16) | + (month << 21) | + (dos_year << 25) + ) + end +end + +module Archive + # A representation of the DOS time structure which can be converted into + # instances of Time. + class DOSTime + include Comparable + + # Creates a new instance of DOSTime. _dos_time_ is a 4 byte String or + # unsigned number (Integer) representing an MS-DOS time structure where: + # Bits 0-4:: 2 second increments (0-29) + # Bits 5-10:: minutes (0-59) + # Bits 11-15:: hours (0-24) + # Bits 16-20:: day (1-31) + # Bits 21-24:: month (1-12) + # Bits 25-31:: four digit year minus 1980 (0-119) + # + # If _dos_time_ is ommitted or +nil+, a new instance is created based on the + # current time. + def initialize(dos_time = nil) + case dos_time + when nil + @dos_time = Time.now.to_dos_time.to_i + when Integer + @dos_time = dos_time + else + unless dos_time.length == 4 then + raise ArgumentError, 'length of DOS time structure is not 4' + end + @dos_time = dos_time.unpack('V')[0] + end + + validate + end + + # Returns -1 if _other_ is a time earlier than this one, 0 if _other_ is the + # same time, and 1 if _other_ is a later time. + def cmp(other) + to_i <=> other.to_i + end + alias :<=> :cmp + + # Returns the time value of this object as an integer representing the DOS + # time structure. + def to_i + @dos_time + end + + # Returns the 32 bit integer that backs this object packed into a String in + # little endian format. This is suitable for use with #new. + def pack + [to_i].pack('V') + end + + # Returns a Time instance which is equivalent to the time represented by + # this object. + def to_time + second = ((0b11111 & @dos_time) ) * 2 + minute = ((0b111111 << 5 & @dos_time) >> 5) + hour = ((0b11111 << 11 & @dos_time) >> 11) + day = ((0b11111 << 16 & @dos_time) >> 16) + month = ((0b1111 << 21 & @dos_time) >> 21) + year = ((0b1111111 << 25 & @dos_time) >> 25) + 1980 + return Time.local(year, month, day, hour, minute, second) + end + + private + + def validate + second = (0b11111 & @dos_time) + minute = (0b111111 << 5 & @dos_time) >> 5 + hour = (0b11111 << 11 & @dos_time) >> 11 + day = (0b11111 << 16 & @dos_time) >> 16 + month = (0b1111 << 21 & @dos_time) >> 21 + year = (0b1111111 << 25 & @dos_time) >> 25 + + if second > 29 + raise ArgumentError, 'second must not be greater than 29' + elsif minute > 59 + raise ArgumentError, 'minute must not be greater than 59' + elsif hour > 24 + raise ArgumentError, 'hour must not be greater than 24' + elsif day < 1 + raise ArgumentError, 'day must not be less than 1' + elsif month < 1 + raise ArgumentError, 'month must not be less than 1' + elsif month > 12 + raise ArgumentError, 'month must not be greater than 12' + elsif year > 119 + raise ArgumentError, 'year must not be greater than 119' + end + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/support/zlib.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/support/zlib.rb new file mode 100644 index 00000000..a1b9c7c7 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/support/zlib.rb @@ -0,0 +1,455 @@ +# encoding: UTF-8 + +require 'zlib' + +require 'archive/support/io-like' + +module Zlib # :nodoc: + # The maximum size of the zlib history buffer. Note that zlib allows larger + # values to enable different inflate modes. See Zlib::Inflate.new for details. + # Provided here only for Ruby versions that do not provide it. + MAX_WBITS = Deflate::MAX_WBITS unless const_defined?(:MAX_WBITS) + + # A deflate strategy which limits match distances to 1, also known as + # run-length encoding. Provided here only for Ruby versions that do not + # provide it. + RLE = 3 unless const_defined?(:RLE) + + # A deflate strategy which does not use dynamic Huffman codes, allowing for a + # simpler decoder to be used to inflate. Provided here only for Ruby versions + # that do not provide it. + FIXED = 4 unless const_defined?(:FIXED) + + # Zlib::ZWriter is a writable, IO-like object (includes IO::Like) which wraps + # other writable, IO-like objects in order to facilitate writing data to those + # objects using the deflate method of compression. + class ZWriter + include IO::Like + + # Creates a new instance of this class with the given arguments using #new + # and then passes the instance to the given block. The #close method is + # guaranteed to be called after the block completes. + # + # Equivalent to #new if no block is given. + def self.open(delegate, level = nil, window_bits = nil, mem_level = nil, strategy = nil) + zw = new(delegate, level, window_bits, mem_level, strategy) + return zw unless block_given? + + begin + yield(zw) + ensure + zw.close unless zw.closed? + end + end + + # Creates a new instance of this class. _delegate_ must respond to the + # _write_ method as an instance of IO would. _level_, _window_bits_, + # _mem_level_, and _strategy_ are all passed directly to + # Zlib::Deflate.new(). + # + # + # The following descriptions of _level_, _window_bits_, _mem_level_, and + # _strategy_ are based upon or pulled largely verbatim from descriptions + # found in zlib.h version 1.2.3 with changes made to account for different + # parameter names and to improve readability. Some of the statements + # concerning default settings or value ranges may not be accurate depending + # on the version of the zlib library used by a given Ruby interpreter. + # + # + # The _level_ parameter must be +nil+, Zlib::DEFAULT_COMPRESSION, or between + # 0 and 9: 1 gives best speed, 9 gives + # best compression, 0 gives no compression at all (the input data + # is simply copied a block at a time). Zlib::DEFAULT_COMPRESSION requests a + # default compromise between speed and compression (currently equivalent to + # level 6). If unspecified or +nil+, _level_ defaults to + # Zlib::DEFAULT_COMPRESSION. + # + # The _window_bits_ parameter specifies the size of the history buffer, the + # format of the compressed stream, and the kind of checksum returned by the + # checksum method. The size of the history buffer is specified by setting + # the value of _window_bits_ in the range of 8..15, + # inclusive. A value of 8 indicates a small window which reduces + # memory usage but lowers the compression ratio while a value of 15 + # indicates a larger window which increases memory usage but raises the + # compression ratio. Modification of this base value for _window_bits_ as + # noted below dictates what kind of compressed stream and checksum will be + # produced while preserving the setting for the history buffer. + # + # If nothing else is done to the base value of _window_bits_, a zlib stream + # is to be produced with an appropriate header and trailer. In this case + # the checksum method of this object will be an adler32. + # + # Adding 16 to the base value of _window_bits_ indicates that a + # gzip stream is to be produced with an appropriate header and trailer. The + # gzip header will have no file name, no extra data, no comment, no + # modification time (set to zero), no header crc, and the operating system + # will be set to 255 (unknown). In this case the checksum + # attribute of this object will be a crc32. + # + # Finally, negating the base value of _window_bits_ indicates that a raw + # zlib stream is to be produced without any header or trailer. In this case + # the checksum method of this object will always return nil. This is + # for use with other formats that use the deflate compressed data format + # such as zip. Such formats should provide their own check values. + # + # If unspecified or +nil+, _window_bits_ defaults to 15. + # + # The _mem_level_ parameter specifies how much memory should be allocated + # for the internal compression state. A value of 1 uses minimum + # memory but is slow and reduces compression ratio; a value of 9 + # uses maximum memory for optimal speed. The default value is 8 if + # unspecified or +nil+. + # + # The _strategy_ parameter is used to tune the compression algorithm. It + # only affects the compression ratio but not the correctness of the + # compressed output even if it is not set appropriately. The default value + # is Zlib::DEFAULT_STRATEGY if unspecified or +nil+. + # + # Use the value Zlib::DEFAULT_STRATEGY for normal data, Zlib::FILTERED for + # data produced by a filter (or predictor), Zlib::HUFFMAN_ONLY to force + # Huffman encoding only (no string match), Zlib::RLE to limit match + # distances to 1 (run-length encoding), or Zlib::FIXED to simplify decoder + # requirements. + # + # The effect of Zlib::FILTERED is to force more Huffman coding and less + # string matching; it is somewhat intermediate between + # Zlib::DEFAULT_STRATEGY and Zlib::HUFFMAN_ONLY. Filtered data consists + # mostly of small values with a somewhat random distribution. In this case, + # the compression algorithm is tuned to compress them better. + # + # Zlib::RLE is designed to be almost as fast as Zlib::HUFFMAN_ONLY, but give + # better compression for PNG image data. + # + # Zlib::FIXED prevents the use of dynamic Huffman codes, allowing for a + # simpler decoder for special applications. + # + # This class has extremely limited seek capabilities. It is possible to + # seek with an offset of 0 and a whence of IO::SEEK_CUR. + # As a result, the _pos_ and _tell_ methods also work as expected. + # + # If _delegate_ also responds to _rewind_, then the _rewind_ method of this + # class can be used to reset the whole stream back to the beginning. Using + # _seek_ of this class to seek directly to offset 0 using + # IO::SEEK_SET for whence will also work in this case. + # + # NOTE: Due to limitations in Ruby's finalization capabilities, the + # #close method is _not_ automatically called when this object is garbage + # collected. Make sure to call #close when finished with this object. + def initialize(delegate, level = nil, window_bits = nil, mem_level = nil, strategy = nil) + @delegate = delegate + @level = level + @window_bits = window_bits + @mem_level = mem_level + @strategy = strategy + @deflater = Zlib::Deflate.new(@level, @window_bits, @mem_level, @strategy) + @deflate_buffer = '' + @checksum = nil + @compressed_size = nil + @uncompressed_size = nil + end + + protected + + # The delegate object to which compressed data is written. + attr_reader :delegate + + public + + # Returns the checksum computed over the data written to this stream so far. + # + # NOTE: Refer to the documentation of #new concerning _window_bits_ + # to learn what kind of checksum will be returned. + # + # NOTE: Anything still in the internal write buffer has not been + # processed, so calling #flush prior to calling this method may be necessary + # for an accurate checksum. + def checksum + return nil if @window_bits < 0 + @deflater.closed? ? @checksum : @deflater.adler + end + + # Closes the writer by finishing the compressed data and flushing it to the + # delegate. + # + # Raises IOError if called more than once. + def close + flush() + @deflate_buffer << @deflater.finish unless @deflater.finished? + begin + until @deflate_buffer.empty? do + @deflate_buffer.slice!(0, delegate.write(@deflate_buffer)) + end + rescue Errno::EAGAIN, Errno::EINTR + retry if write_ready? + end + @checksum = @deflater.adler + @compressed_size = @deflater.total_out + @uncompressed_size = @deflater.total_in + @deflater.close + super() + nil + end + + # Returns the number of bytes of compressed data produced so far. + # + # NOTE: This value is only updated when both the internal write + # buffer is flushed and there is enough data to produce a compressed block. + # It does not necessarily reflect the amount of data written to the + # delegate until this stream is closed however. Until then the only + # guarantee is that the value will be greater than or equal to 0. + def compressed_size + @deflater.closed? ? @compressed_size : @deflater.total_out + end + + # Returns the number of bytes sent to be compressed so far. + # + # NOTE: This value is only updated when the internal write buffer is + # flushed. + def uncompressed_size + @deflater.closed? ? @uncompressed_size : @deflater.total_in + end + + private + + # Allows resetting this object and the delegate object back to the beginning + # of the stream or reporting the current position in the stream. + # + # Raises Errno::EINVAL unless _offset_ is 0 and _whence_ is either + # IO::SEEK_SET or IO::SEEK_CUR. Raises Errno::EINVAL if _whence_ is + # IO::SEEK_SEK and the delegate object does not respond to the _rewind_ + # method. + def unbuffered_seek(offset, whence = IO::SEEK_SET) + unless offset == 0 && + ((whence == IO::SEEK_SET && delegate.respond_to?(:rewind)) || + whence == IO::SEEK_CUR) then + raise Errno::EINVAL + end + + case whence + when IO::SEEK_SET + delegate.rewind + @deflater.finish + @deflater.close + @deflater = Zlib::Deflate.new( + @level, @window_bits, @mem_level, @strategy + ) + @deflate_buffer = '' + 0 + when IO::SEEK_CUR + @deflater.total_in + end + end + + def unbuffered_write(string) + # First try to write out the contents of the deflate buffer because if + # that raises a failure we can let that pass up the call stack without + # having polluted the deflater instance. + until @deflate_buffer.empty? do + @deflate_buffer.slice!(0, delegate.write(@deflate_buffer)) + end + # At this point we can deflate the given string into a new buffer and + # behave as if it was written. + @deflate_buffer = @deflater.deflate(string) + string.length + end + end + + # Zlib::ZReader is a readable, IO-like object (includes IO::Like) which wraps + # other readable, IO-like objects in order to facilitate reading data from + # those objects using the inflate method of decompression. + class ZReader + include IO::Like + + # The number of bytes to read from the delegate object each time the + # internal read buffer is filled. + DEFAULT_DELEGATE_READ_SIZE = 4096 + + # Creates a new instance of this class with the given arguments using #new + # and then passes the instance to the given block. The #close method is + # guaranteed to be called after the block completes. + # + # Equivalent to #new if no block is given. + def self.open(delegate, window_bits = nil) + zr = new(delegate, window_bits) + return zr unless block_given? + + begin + yield(zr) + ensure + zr.close unless zr.closed? + end + end + + # Creates a new instance of this class. _delegate_ must respond to the + # _read_ method as an IO instance would. _window_bits_ is passed directly + # to Zlib::Inflate.new(). + # + # + # The following description of _window_bits_ is based on the description + # found in zlib.h version 1.2.3. Some of the statements concerning default + # settings or value ranges may not be accurate depending on the version of + # the zlib library used by a given Ruby interpreter. + # + # + # The _window_bits_ parameter specifies the size of the history buffer, the + # format of the compressed stream, and the kind of checksum returned by the + # checksum method. The size of the history buffer is specified by setting + # the value of _window_bits_ in the range of 8..15, + # inclusive. It must be at least as large as the setting used to create the + # stream or a Zlib::DataError will be raised. Modification of this base + # value for _window_bits_ as noted below dictates what kind of compressed + # stream is expected and what kind of checksum will be produced while + # preserving the setting for the history buffer. + # + # If nothing else is done to the base value of _window_bits_, a zlib stream + # is expected with an appropriate header and trailer. In this case the + # checksum method of this object will be an adler32. + # + # Adding 16 to the base value of _window_bits_ indicates that a + # gzip stream is expected with an appropriate header and trailer. In this + # case the checksum method of this object will be a crc32. + # + # Adding 32 to the base value of _window_bits_ indicates that an + # automatic detection of the stream format should be made based on the + # header in the stream. In this case the checksum method of this object + # will depend on whether a zlib or a gzip stream is detected. + # + # Finally, negating the base value of _window_bits_ indicates that a raw + # zlib stream is expected without any header or trailer. In this case the + # checksum method of this object will always return nil. This is for + # use with other formats that use the deflate compressed data format such as + # zip. Such formats should provide their own check values. + # + # If unspecified or +nil+, _window_bits_ defaults to 15. + # + # In all cases, Zlib::DataError is raised if the wrong stream format is + # found when reading. + # + # This class has extremely limited seek capabilities. It is possible to + # seek with an offset of 0 and a whence of IO::SEEK_CUR. + # As a result, the _pos_ and _tell_ methods also work as expected. + # + # Due to certain optimizations within IO::Like#seek and if there is data in + # the read buffer, the _seek_ method can be used to seek forward from the + # current stream position up to the end of the buffer. Unless it is known + # definitively how much data is in the buffer, it is best to avoid relying + # on this behavior. + # + # If _delegate_ also responds to _rewind_, then the _rewind_ method of this + # class can be used to reset the whole stream back to the beginning. Using + # _seek_ of this class to seek directly to offset 0 using + # IO::SEEK_SET for whence will also work in this case. + # + # Any other seeking attempts, will raise Errno::EINVAL exceptions. + # + # NOTE: Due to limitations in Ruby's finalization capabilities, the + # #close method is _not_ automatically called when this object is garbage + # collected. Make sure to call #close when finished with this object. + def initialize(delegate, window_bits = nil) + @delegate = delegate + @delegate_read_size = DEFAULT_DELEGATE_READ_SIZE + @window_bits = window_bits + @inflater = Zlib::Inflate.new(@window_bits) + @inflate_buffer = '' + @checksum = nil + @compressed_size = nil + @uncompressed_size = nil + end + + # The number of bytes to read from the delegate object each time the + # internal read buffer is filled. + attr_accessor :delegate_read_size + + protected + + # The delegate object from which compressed data is read. + attr_reader :delegate + + public + + # Returns the checksum computed over the data read from this stream. + # + # NOTE: Refer to the documentation of #new concerning _window_bits_ + # to learn what kind of checksum will be returned. + # + # NOTE: The contents of the internal read buffer are immediately + # processed any time the internal buffer is filled, so this checksum is only + # accurate if all data has been read out of this object. + def checksum + return nil if @window_bits < 0 + @inflater.closed? ? @checksum : @inflater.adler + end + + # Closes the reader. + # + # Raises IOError if called more than once. + def close + super() + @checksum = @inflater.adler + @compressed_size = @inflater.total_in + @uncompressed_size = @inflater.total_out + @inflater.close + nil + end + + # Returns the number of bytes sent to be compressed so far. + # + # NOTE: This value is updated whenever the internal read buffer needs + # to be filled, not when data is read out of this stream. + def compressed_size + @inflater.closed? ? @compressed_size : @inflater.total_in + end + + # Returns the number of bytes of decompressed data produced so far. + # + # NOTE: This value is updated whenever the internal read buffer needs + # to be filled, not when data is read out of this stream. + def uncompressed_size + @inflater.closed? ? @uncompressed_size : @inflater.total_out + end + + private + + def unbuffered_read(length) + if @inflate_buffer.empty? && @inflater.finished? then + raise EOFError, 'end of file reached' + end + + begin + while @inflate_buffer.length < length && ! @inflater.finished? do + @inflate_buffer << + @inflater.inflate(delegate.read(@delegate_read_size)) + end + rescue Errno::EINTR, Errno::EAGAIN + raise if @inflate_buffer.empty? + end + @inflate_buffer.slice!(0, length) + end + + # Allows resetting this object and the delegate object back to the beginning + # of the stream or reporting the current position in the stream. + # + # Raises Errno::EINVAL unless _offset_ is 0 and _whence_ is either + # IO::SEEK_SET or IO::SEEK_CUR. Raises Errno::EINVAL if _whence_ is + # IO::SEEK_SEK and the delegate object does not respond to the _rewind_ + # method. + def unbuffered_seek(offset, whence = IO::SEEK_SET) + unless offset == 0 && + ((whence == IO::SEEK_SET && delegate.respond_to?(:rewind)) || + whence == IO::SEEK_CUR) then + raise Errno::EINVAL + end + + case whence + when IO::SEEK_SET + delegate.rewind + @inflater.close + @inflater = Zlib::Inflate.new(@window_bits) + @inflate_buffer = '' + 0 + when IO::SEEK_CUR + @inflater.total_out - @inflate_buffer.length + end + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip.rb new file mode 100644 index 00000000..ea57fedb --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip.rb @@ -0,0 +1,738 @@ +# encoding: UTF-8 + +require 'fileutils' +require 'set' +require 'tempfile' + +require 'archive/support/ioextensions' +require 'archive/support/iowindow' +require 'archive/support/time' +require 'archive/support/zlib' +require 'archive/zip/codec' +require 'archive/zip/entry' +require 'archive/zip/error' + +module Archive # :nodoc: + # Archive::Zip represents a ZIP archive compatible with InfoZip tools and the + # archives they generate. It currently supports both stored and deflated ZIP + # entries, directory entries, file entries, and symlink entries. File and + # directory accessed and modified times, POSIX permissions, and ownerships can + # be archived and restored as well depending on platform support for such + # metadata. Traditional (weak) encryption is also supported. + # + # Zip64, digital signatures, and strong encryption are not supported. ZIP + # archives can only be read from seekable kinds of IO, such as files; reading + # archives from pipes or any other non-seekable kind of IO is not supported. + # However, writing to such IO objects IS supported. + class Zip + include Enumerable + + # The lead-in marker for the end of central directory record. + EOCD_SIGNATURE = "PK\x5\x6" # 0x06054b50 + # The lead-in marker for the digital signature record. + DS_SIGNATURE = "PK\x5\x5" # 0x05054b50 + # The lead-in marker for the ZIP64 end of central directory record. + Z64EOCD_SIGNATURE = "PK\x6\x6" # 0x06064b50 + # The lead-in marker for the ZIP64 end of central directory locator record. + Z64EOCDL_SIGNATURE = "PK\x6\x7" # 0x07064b50 + # The lead-in marker for a central file record. + CFH_SIGNATURE = "PK\x1\x2" # 0x02014b50 + # The lead-in marker for a local file record. + LFH_SIGNATURE = "PK\x3\x4" # 0x04034b50 + # The lead-in marker for data descriptor record. + DD_SIGNATURE = "PK\x7\x8" # 0x08074b50 + + + # Creates or possibly updates an archive using _paths_ for new contents. + # + # If _archive_ is a String, it is treated as a file path which will receive + # the archive contents. If the file already exists, it is assumed to be an + # archive and will be updated "in place". Otherwise, a new archive + # is created. The archive will be closed once written. + # + # If _archive_ has any other kind of value, it is treated as a writable + # IO-like object which will be left open after the completion of this + # method. + # + # NOTE: No attempt is made to prevent adding multiple entries with + # the same archive path. + # + # See the instance method #archive for more information about _paths_ and + # _options_. + def self.archive(archive, paths, options = {}) + if archive.kind_of?(String) && File.exist?(archive) then + # Update the archive "in place". + tmp_archive_path = nil + File.open(archive) do |archive_in| + Tempfile.open(*File.split(archive_in.path).reverse) do |archive_out| + # Save off the path so that the temporary file can be renamed to the + # archive file later. + tmp_archive_path = archive_out.path + # Ensure the file is in binary mode for Windows. + archive_out.binmode + # Update the archive. + open(archive_in, :r) do |z_in| + open(archive_out, :w) do |z_out| + z_in.each { |entry| z_out << entry } + z_out.archive(paths, options) + end + end + end + end + # Set more reasonable permissions than those set by Tempfile. + File.chmod(0666 & ~File.umask, tmp_archive_path) + # Replace the input archive with the output archive. + File.rename(tmp_archive_path, archive) + else + open(archive, :w) { |z| z.archive(paths, options) } + end + end + + # Extracts the entries from an archive to _destination_. + # + # If _archive_ is a String, it is treated as a file path pointing to an + # existing archive file. Otherwise, it is treated as a seekable and + # readable IO-like object. + # + # See the instance method #extract for more information about _destination_ + # and _options_. + def self.extract(archive, destination, options = {}) + open(archive, :r) { |z| z.extract(destination, options) } + end + + # Calls #new with the given arguments and yields the resulting Zip instance + # to the given block. Returns the result of the block and ensures that the + # Zip instance is closed. + # + # This is a synonym for #new if no block is given. + def self.open(archive, mode = :r) + zf = new(archive, mode) + return zf unless block_given? + + begin + yield(zf) + ensure + zf.close unless zf.closed? + end + end + + # Opens an existing archive and/or creates a new archive. + # + # If _archive_ is a String, it will be treated as a file path; otherwise, it + # is assumed to be an IO-like object with the necessary read or write + # support depending on the setting of _mode_. IO-like objects are not + # closed when the archive is closed, but files opened from file paths are. + # Set _mode_ to :r or "r" to read the archive, and set it + # to :w or "w" to write the archive. + # + # NOTE: The #close method must be called in order to save any + # modifications to the archive. Due to limitations in the Ruby finalization + # capabilities, the #close method is _not_ automatically called when this + # object is garbage collected. Make sure to call #close when finished with + # this object. + def initialize(archive, mode = :r) + @archive = archive + mode = mode.to_sym + if mode == :r || mode == :w then + @mode = mode + else + raise ArgumentError, "illegal access mode #{mode}" + end + + @close_delegate = false + if @archive.kind_of?(String) then + @close_delegate = true + if mode == :r then + @archive = File.open(@archive, 'rb') + else + @archive = File.open(@archive, 'wb') + end + end + @entries = [] + @comment = '' + @closed = false + @parse_complete = false + end + + # A comment string for the ZIP archive. + attr_accessor :comment + + # Closes the archive. + # + # Failure to close the archive by calling this method may result in a loss + # of data for writable archives. + # + # NOTE: The underlying stream is only closed if the archive was + # opened with a String for the _archive_ parameter. + # + # Raises Archive::Zip::IOError if called more than once. + def close + raise IOError, 'closed archive' if closed? + + if writable? then + # Write the new archive contents. + dump(@archive) + end + + # Note that we only close delegate streams which are opened by us so that + # the user may do so for other delegate streams at his/her discretion. + @archive.close if @close_delegate + + @closed = true + nil + end + + # Returns +true+ if the ZIP archive is closed, +false+ otherwise. + def closed? + @closed + end + + # Returns +true+ if the ZIP archive is readable, +false+ otherwise. + def readable? + @mode == :r + end + + # Returns +true+ if the ZIP archive is writable, +false+ otherwise. + def writable? + @mode == :w + end + + # Iterates through each entry of a readable ZIP archive in turn yielding + # each one to the given block. + # + # Raises Archive::Zip::IOError if called on a non-readable archive or after + # the archive is closed. + def each(&b) + raise IOError, 'non-readable archive' unless readable? + raise IOError, 'closed archive' if closed? + + unless @parse_complete then + parse(@archive) + @parse_complete = true + end + @entries.each(&b) + end + + # Adds _entry_ into a writable ZIP archive. + # + # NOTE: No attempt is made to prevent adding multiple entries with + # the same archive path. + # + # Raises Archive::Zip::IOError if called on a non-writable archive or after + # the archive is closed. + def add_entry(entry) + raise IOError, 'non-writable archive' unless writable? + raise IOError, 'closed archive' if closed? + unless entry.kind_of?(Entry) then + raise ArgumentError, 'Archive::Zip::Entry instance required' + end + + @entries << entry + self + end + alias :<< :add_entry + + # Adds _paths_ to the archive. _paths_ may be either a single path or an + # Array of paths. The files and directories referenced by _paths_ are added + # using their respective basenames as their zip paths. The exception to + # this is when the basename for a path is either "." or + # "..". In this case, the path is replaced with the paths to the + # contents of the directory it references. + # + # _options_ is a Hash optionally containing the following: + # :path_prefix:: + # Specifies a prefix to be added to the zip_path attribute of each entry + # where `/' is the file separator character. This defaults to the empty + # string. All values are passed through Archive::Zip::Entry.expand_path + # before use. + # :recursion:: + # When set to +true+ (the default), the contents of directories are + # recursively added to the archive. + # :directories:: + # When set to +true+ (the default), entries are added to the archive for + # directories. Otherwise, the entries for directories will not be added; + # however, the contents of the directories will still be considered if the + # :recursion option is +true+. + # :symlinks:: + # When set to +false+ (the default), entries for symlinks are excluded + # from the archive. Otherwise, they are included. NOTE: Unless + # :follow_symlinks is explicitly set, it will be set to the logical + # NOT of this option in calls to Archive::Zip::Entry.from_file. If + # symlinks should be completely ignored, set both this option and + # :follow_symlinks to +false+. See Archive::Zip::Entry.from_file + # for details regarding :follow_symlinks. + # :flatten:: + # When set to +false+ (the default), the directory paths containing + # archived files will be included in the zip paths of entries representing + # the files. When set to +true+, files are archived without any + # containing directory structure in the zip paths. Setting to +true+ + # implies that :directories is +false+ and :path_prefix is + # empty. + # :exclude:: + # Specifies a proc or lambda which takes a single argument containing a + # prospective zip entry and returns +true+ if the entry should be excluded + # from the archive and +false+ if it should be included. NOTE: If + # a directory is excluded in this way, the :recursion option has no + # effect for it. + # :password:: + # Specifies a proc, lambda, or a String. If a proc or lambda is used, it + # must take a single argument containing a zip entry and return a String + # to be used as an encryption key for the entry. If a String is used, it + # will be used as an encryption key for all encrypted entries. + # :on_error:: + # Specifies a proc or lambda which is called when an exception is raised + # during the archival of an entry. It takes two arguments, a file path + # and an exception object generated while attempting to archive the entry. + # If :retry is returned, archival of the entry is attempted + # again. If :skip is returned, the entry is skipped. Otherwise, + # the exception is raised. + # Any other options which are supported by Archive::Zip::Entry.from_file are + # also supported. + # + # NOTE: No attempt is made to prevent adding multiple entries with + # the same archive path. + # + # Raises Archive::Zip::IOError if called on a non-writable archive or after + # the archive is closed. Raises Archive::Zip::EntryError if the + # :on_error option is either unset or indicates that the error should + # be raised and Archive::Zip::Entry.from_file raises an error. + # + # == Example + # + # A directory contains: + # zip-test + # +- dir1 + # | +- file2.txt + # +- dir2 + # +- file1.txt + # + # Create some archives: + # Archive::Zip.open('zip-test1.zip') do |z| + # z.archive('zip-test') + # end + # + # Archive::Zip.open('zip-test2.zip') do |z| + # z.archive('zip-test/.', :path_prefix => 'a/b/c/d') + # end + # + # Archive::Zip.open('zip-test3.zip') do |z| + # z.archive('zip-test', :directories => false) + # end + # + # Archive::Zip.open('zip-test4.zip') do |z| + # z.archive('zip-test', :exclude => lambda { |e| e.file? }) + # end + # + # The archives contain: + # zip-test1.zip -> zip-test/ + # zip-test/dir1/ + # zip-test/dir1/file2.txt + # zip-test/dir2/ + # zip-test/file1.txt + # + # zip-test2.zip -> a/b/c/d/dir1/ + # a/b/c/d/dir1/file2.txt + # a/b/c/d/dir2/ + # a/b/c/d/file1.txt + # + # zip-test3.zip -> zip-test/dir1/file2.txt + # zip-test/file1.txt + # + # zip-test4.zip -> zip-test/ + # zip-test/dir1/ + # zip-test/dir2/ + def archive(paths, options = {}) + raise IOError, 'non-writable archive' unless writable? + raise IOError, 'closed archive' if closed? + + # Ensure that paths is an enumerable. + paths = [paths] unless paths.kind_of?(Enumerable) + # If the basename of a path is '.' or '..', replace the path with the + # paths of all the entries contained within the directory referenced by + # the original path. + paths = paths.collect do |path| + basename = File.basename(path) + if basename == '.' || basename == '..' then + Dir.entries(path).reject do |e| + e == '.' || e == '..' + end.collect do |e| + File.join(path, e) + end + else + path + end + end.flatten.uniq + + # Ensure that unspecified options have default values. + options[:path_prefix] = '' unless options.has_key?(:path_prefix) + options[:recursion] = true unless options.has_key?(:recursion) + options[:directories] = true unless options.has_key?(:directories) + options[:symlinks] = false unless options.has_key?(:symlinks) + options[:flatten] = false unless options.has_key?(:flatten) + + # Flattening the directory structure implies that directories are skipped + # and that the path prefix should be ignored. + if options[:flatten] then + options[:path_prefix] = '' + options[:directories] = false + end + + # Clean up the path prefix. + options[:path_prefix] = Entry.expand_path(options[:path_prefix].to_s) + + paths.each do |path| + # Generate the zip path. + zip_entry_path = File.basename(path) + zip_entry_path += '/' if File.directory?(path) + unless options[:path_prefix].empty? then + zip_entry_path = "#{options[:path_prefix]}/#{zip_entry_path}" + end + + begin + # Create the entry, but do not add it to the archive yet. + zip_entry = Zip::Entry.from_file( + path, + options.merge( + :zip_path => zip_entry_path, + :follow_symlinks => options.has_key?(:follow_symlinks) ? + options[:follow_symlinks] : + ! options[:symlinks] + ) + ) + rescue StandardError => error + unless options[:on_error].nil? then + case options[:on_error][path, error] + when :retry + retry + when :skip + next + else + raise + end + else + raise + end + end + + # Skip this entry if so directed. + if (zip_entry.symlink? && ! options[:symlinks]) || + (! options[:exclude].nil? && options[:exclude][zip_entry]) then + next + end + + # Set the encryption key for the entry. + if options[:password].kind_of?(String) then + zip_entry.password = options[:password] + elsif ! options[:password].nil? then + zip_entry.password = options[:password][zip_entry] + end + + # Add entries for directories (if requested) and files/symlinks. + if (! zip_entry.directory? || options[:directories]) then + add_entry(zip_entry) + end + + # Recurse into subdirectories (if requested). + if zip_entry.directory? && options[:recursion] then + archive( + Dir.entries(path).reject do |e| + e == '.' || e == '..' + end.collect do |e| + File.join(path, e) + end, + options.merge(:path_prefix => zip_entry_path) + ) + end + end + + nil + end + + # Extracts the contents of the archive to _destination_, where _destination_ + # is a path to a directory which will contain the contents of the archive. + # The destination path will be created if it does not already exist. + # + # _options_ is a Hash optionally containing the following: + # :directories:: + # When set to +true+ (the default), entries representing directories in + # the archive are extracted. This happens after all non-directory entries + # are extracted so that directory metadata can be properly updated. + # :symlinks:: + # When set to +false+ (the default), entries representing symlinks in the + # archive are skipped. When set to +true+, such entries are extracted. + # Exceptions may be raised on plaforms/file systems which do not support + # symlinks. + # :overwrite:: + # When set to :all (the default), files which already exist will + # be replaced. When set to :older, such files will only be + # replaced if they are older according to their last modified times than + # the zip entry which would replace them. When set to :none, + # such files will never be replaced. Any other value is the same as + # :all. + # :create:: + # When set to +true+ (the default), files and directories which do not + # already exist will be extracted. When set to +false+, only files and + # directories which already exist will be extracted (depending on the + # setting of :overwrite). + # :flatten:: + # When set to +false+ (the default), the directory paths containing + # extracted files will be created within +destination+ in order to contain + # the files. When set to +true+, files are extracted directly to + # +destination+ and directory entries are skipped. + # :exclude:: + # Specifies a proc or lambda which takes a single argument containing a + # zip entry and returns +true+ if the entry should be skipped during + # extraction and +false+ if it should be extracted. + # :password:: + # Specifies a proc, lambda, or a String. If a proc or lambda is used, it + # must take a single argument containing a zip entry and return a String + # to be used as a decryption key for the entry. If a String is used, it + # will be used as a decryption key for all encrypted entries. + # :on_error:: + # Specifies a proc or lambda which is called when an exception is raised + # during the extraction of an entry. It takes two arguments, a zip entry + # and an exception object generated while attempting to extract the entry. + # If :retry is returned, extraction of the entry is attempted + # again. If :skip is returned, the entry is skipped. Otherwise, + # the exception is raised. + # Any other options which are supported by Archive::Zip::Entry#extract are + # also supported. + # + # Raises Archive::Zip::IOError if called on a non-readable archive or after + # the archive is closed. + # + # == Example + # + # An archive, archive.zip, contains: + # zip-test/ + # zip-test/dir1/ + # zip-test/dir1/file2.txt + # zip-test/dir2/ + # zip-test/file1.txt + # + # A directory, extract4, contains: + # zip-test + # +- dir1 + # +- file1.txt + # + # Extract the archive: + # Archive::Zip.open('archive.zip') do |z| + # z.extract('extract1') + # end + # + # Archive::Zip.open('archive.zip') do |z| + # z.extract('extract2', :flatten => true) + # end + # + # Archive::Zip.open('archive.zip') do |z| + # z.extract('extract3', :create => false) + # end + # + # Archive::Zip.open('archive.zip') do |z| + # z.extract('extract3', :create => true) + # end + # + # Archive::Zip.open('archive.zip') do |z| + # z.extract( 'extract5', :exclude => lambda { |e| e.file? }) + # end + # + # The directories contain: + # extract1 -> zip-test + # +- dir1 + # | +- file2.txt + # +- dir2 + # +- file1.txt + # + # extract2 -> file2.txt + # file1.txt + # + # extract3 -> + # + # extract4 -> zip-test + # +- dir2 + # +- file1.txt <- from archive contents + # + # extract5 -> zip-test + # +- dir1 + # +- dir2 + def extract(destination, options = {}) + raise IOError, 'non-readable archive' unless readable? + raise IOError, 'closed archive' if closed? + + # Ensure that unspecified options have default values. + options[:directories] = true unless options.has_key?(:directories) + options[:symlinks] = false unless options.has_key?(:symlinks) + options[:overwrite] = :all unless options[:overwrite] == :older || + options[:overwrite] == :never + options[:create] = true unless options.has_key?(:create) + options[:flatten] = false unless options.has_key?(:flatten) + + # Flattening the archive structure implies that directory entries are + # skipped. + options[:directories] = false if options[:flatten] + + # First extract all non-directory entries. + directories = [] + each do |entry| + # Compute the target file path. + file_path = entry.zip_path + file_path = File.basename(file_path) if options[:flatten] + file_path = File.join(destination, file_path) + + # Cache some information about the file path. + file_exists = File.exist?(file_path) + file_mtime = File.mtime(file_path) if file_exists + + begin + # Skip this entry if so directed. + if (! file_exists && ! options[:create]) || + (file_exists && + (options[:overwrite] == :never || + options[:overwrite] == :older && entry.mtime <= file_mtime)) || + (! options[:exclude].nil? && options[:exclude][entry]) then + next + end + + # Set the decryption key for the entry. + if options[:password].kind_of?(String) then + entry.password = options[:password] + elsif ! options[:password].nil? then + entry.password = options[:password][entry] + end + + if entry.directory? then + # Record the directories as they are encountered. + directories << entry + elsif entry.file? || (entry.symlink? && options[:symlinks]) then + # Extract files and symlinks. + entry.extract( + options.merge(:file_path => file_path) + ) + end + rescue StandardError => error + unless options[:on_error].nil? then + case options[:on_error][entry, error] + when :retry + retry + when :skip + else + raise + end + else + raise + end + end + end + + if options[:directories] then + # Then extract the directory entries in depth first order so that time + # stamps, ownerships, and permissions can be properly restored. + directories.sort { |a, b| b.zip_path <=> a.zip_path }.each do |entry| + begin + entry.extract( + options.merge( + :file_path => File.join(destination, entry.zip_path) + ) + ) + rescue StandardError => error + unless options[:on_error].nil? then + case options[:on_error][entry, error] + when :retry + retry + when :skip + else + raise + end + else + raise + end + end + end + end + + nil + end + + private + + # NOTE: For now _io_ MUST be seekable. + def parse(io) + socd_pos = find_central_directory(io) + io.seek(socd_pos) + # Parse each entry in the central directory. + loop do + signature = IOExtensions.read_exactly(io, 4) + break unless signature == CFH_SIGNATURE + @entries << Zip::Entry.parse(io) + end + # Maybe add support for digital signatures and ZIP64 records... Later + + nil + end + + # Returns the file offset of the first record in the central directory. + # _io_ must be a seekable, readable, IO-like object. + # + # Raises Archive::Zip::UnzipError if the end of central directory signature + # is not found where expected or at all. + def find_central_directory(io) + # First find the offset to the end of central directory record. + # It is expected that the variable length comment field will usually be + # empty and as a result the initial value of eocd_offset is all that is + # necessary. + # + # NOTE: A cleverly crafted comment could throw this thing off if the + # comment itself looks like a valid end of central directory record. + eocd_offset = -22 + loop do + io.seek(eocd_offset, IO::SEEK_END) + if IOExtensions.read_exactly(io, 4) == EOCD_SIGNATURE then + io.seek(16, IO::SEEK_CUR) + if IOExtensions.read_exactly(io, 2).unpack('v')[0] == + (eocd_offset + 22).abs then + break + end + end + eocd_offset -= 1 + end + # At this point, eocd_offset should point to the location of the end of + # central directory record relative to the end of the archive. + # Now, jump into the location in the record which contains a pointer to + # the start of the central directory record and return the value. + io.seek(eocd_offset + 16, IO::SEEK_END) + return IOExtensions.read_exactly(io, 4).unpack('V')[0] + rescue Errno::EINVAL + raise Zip::UnzipError, 'unable to locate end-of-central-directory record' + end + + # Writes all the entries of this archive to _io_. _io_ must be a writable, + # IO-like object providing a _write_ method. Returns the total number of + # bytes written. + def dump(io) + bytes_written = 0 + @entries.each do |entry| + bytes_written += entry.dump_local_file_record(io, bytes_written) + end + central_directory_offset = bytes_written + @entries.each do |entry| + bytes_written += entry.dump_central_file_record(io) + end + central_directory_length = bytes_written - central_directory_offset + bytes_written += io.write(EOCD_SIGNATURE) + bytes_written += io.write( + [ + 0, + 0, + @entries.length, + @entries.length, + central_directory_length, + central_directory_offset, + comment.bytesize + ].pack('vvvvVVv') + ) + bytes_written += io.write(comment) + + bytes_written + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/codec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/codec.rb new file mode 100644 index 00000000..1c1795ae --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/codec.rb @@ -0,0 +1,53 @@ +# encoding: UTF-8 + +module Archive; class Zip + # Archive::Zip::Codec is a factory class for generating codec object instances + # based on the compression method and general purpose flag fields of ZIP + # entries. When adding a new codec, add a mapping in the _CODECS_ constant + # from the compression method field value reserved for the codec in the ZIP + # specification to the class implementing the codec. See the implementations + # of Archive::Zip::Codec::Deflate and Archive::Zip::Codec::Store for details + # on implementing custom codecs. + module Codec + # A Hash mapping compression methods to compression codec implementations. + # New compression codecs must add a mapping here when defined in order to be + # used. + COMPRESSION_CODECS = {} + + # A Hash mapping encryption methods to encryption codec implementations. + # New encryption codecs must add a mapping here when defined in order to be + # used. + ENCRYPTION_CODECS = {} + + # Returns a new compression codec instance based on _compression_method_ and + # _general_purpose_flags_. + def self.create_compression_codec(compression_method, general_purpose_flags) + # Load the standard compression codecs. + require 'archive/zip/codec/deflate' + require 'archive/zip/codec/store' + + codec = COMPRESSION_CODECS[compression_method] + raise Zip::Error, 'unsupported compression codec' if codec.nil? + codec.new(general_purpose_flags) + end + + # Returns a new encryption codec instance based on _general_purpose_flags_. + # + # NOTE: The signature of this method will have to change in order to + # support the strong encryption codecs. This is intended to be an internal + # method anyway, so this fact should not cause major issues for users of + # this library. + def self.create_encryption_codec(general_purpose_flags) + general_purpose_flags &= 0b0000000001000001 + if general_purpose_flags == 0b0000000000000000 then + require 'archive/zip/codec/null_encryption' + codec = NullEncryption.new + elsif general_purpose_flags == 0b0000000000000001 then + require 'archive/zip/codec/traditional_encryption' + codec = TraditionalEncryption.new + end + raise Zip::Error, 'unsupported encryption codec' if codec.nil? + codec + end + end +end; end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/codec/deflate.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/codec/deflate.rb new file mode 100644 index 00000000..06e17573 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/codec/deflate.rb @@ -0,0 +1,256 @@ +# encoding: UTF-8 + +require 'archive/support/zlib' +require 'archive/zip/codec' +require 'archive/zip/data_descriptor' + +module Archive; class Zip; module Codec + # Archive::Zip::Codec::Deflate is a handle for the deflate-inflate codec + # as defined in Zlib which provides convenient interfaces for writing and + # reading deflated streams. + class Deflate + # Archive::Zip::Codec::Deflate::Compress extends Zlib::ZWriter in order to + # specify the standard Zlib options required by ZIP archives and to provide + # a close method which can optionally close the delegate IO-like object. + # In addition a convenience method is provided for generating DataDescriptor + # objects based on the data which is passed through this object. + # + # Instances of this class should only be accessed via the + # Archive::Zip::Codec::Deflate#compressor method. + class Compress < Zlib::ZWriter + # Creates a new instance of this class with the given arguments using #new + # and then passes the instance to the given block. The #close method is + # guaranteed to be called after the block completes. + # + # Equivalent to #new if no block is given. + def self.open(io, compression_level) + deflate_io = new(io, compression_level) + return deflate_io unless block_given? + + begin + yield(deflate_io) + ensure + deflate_io.close unless deflate_io.closed? + end + end + + # Creates a new instance of this class using _io_ as a data sink. _io_ + # must be writable and must provide a _write_ method as IO does or errors + # will be raised when performing write operations. _compression_level_ + # must be one of Zlib::DEFAULT_COMPRESSION, Zlib::BEST_COMPRESSION, + # Zlib::BEST_SPEED, or Zlib::NO_COMPRESSION and specifies the amount of + # compression to be applied to the data stream. + def initialize(io, compression_level) + super(io, compression_level, -Zlib::MAX_WBITS) + @crc32 = 0 + end + + # The CRC32 checksum of the uncompressed data written using this object. + # + # NOTE: Anything still in the internal write buffer has not been + # processed, so calling #flush prior to examining this attribute may be + # necessary for an accurate computation. + attr_reader :crc32 + alias :checksum :crc32 + + # Closes this object so that further write operations will fail. If + # _close_delegate_ is +true+, the delegate object used as a data sink will + # also be closed using its close method. + def close(close_delegate = true) + super() + delegate.close if close_delegate + end + + # Returns an instance of Archive::Zip::DataDescriptor with information + # regarding the data which has passed through this object to the delegate + # object. The close or flush methods should be called before using this + # method in order to ensure that any possibly buffered data is flushed to + # the delegate object; otherwise, the contents of the data descriptor may + # be inaccurate. + def data_descriptor + DataDescriptor.new(crc32, compressed_size, uncompressed_size) + end + + private + + def unbuffered_seek(offset, whence = IO::SEEK_SET) + result = super(offset, whence) + @crc32 = 0 if whence == IO::SEEK_SET + result + end + + def unbuffered_write(string) + result = super(string) + @crc32 = Zlib.crc32(string, @crc32) + result + end + end + + # Archive::Zip::Codec::Deflate::Decompress extends Zlib::ZReader in order to + # specify the standard Zlib options required by ZIP archives and to provide + # a close method which can optionally close the delegate IO-like object. + # In addition a convenience method is provided for generating DataDescriptor + # objects based on the data which is passed through this object. + # + # Instances of this class should only be accessed via the + # Archive::Zip::Codec::Deflate#decompressor method. + class Decompress < Zlib::ZReader + # Creates a new instance of this class with the given arguments using #new + # and then passes the instance to the given block. The #close method is + # guaranteed to be called after the block completes. + # + # Equivalent to #new if no block is given. + def self.open(io) + inflate_io = new(io) + return inflate_io unless block_given? + + begin + yield(inflate_io) + ensure + inflate_io.close unless inflate_io.closed? + end + end + + # Creates a new instance of this class using _io_ as a data source. _io_ + # must be readable and provide a _read_ method as IO does or errors will + # be raised when performing read operations. If _io_ provides a _rewind_ + # method, this class' _rewind_ method will be enabled. + def initialize(io) + super(io, -Zlib::MAX_WBITS) + @crc32 = 0 + end + + # The CRC32 checksum of the uncompressed data read using this object. + # + # NOTE: The contents of the internal read buffer are immediately + # processed any time the internal buffer is filled, so this checksum is + # only accurate if all data has been read out of this object. + attr_reader :crc32 + alias :checksum :crc32 + + # Closes this object so that further read operations will fail. If + # _close_delegate_ is +true+, the delegate object used as a data source + # will also be closed using its close method. + def close(close_delegate = true) + super() + delegate.close if close_delegate + end + + # Returns an instance of Archive::Zip::DataDescriptor with information + # regarding the data which has passed through this object from the + # delegate object. It is recommended to call the close method before + # calling this in order to ensure that no further read operations change + # the state of this object. + def data_descriptor + DataDescriptor.new(crc32, compressed_size, uncompressed_size) + end + + private + + def unbuffered_read(length) + result = super(length) + @crc32 = Zlib.crc32(result, @crc32) + result + end + + def unbuffered_seek(offset, whence = IO::SEEK_SET) + result = super(offset, whence) + @crc32 = 0 if whence == IO::SEEK_SET + result + end + end + + # The numeric identifier assigned to this compression codec by the ZIP + # specification. + ID = 8 + + # Register this compression codec. + COMPRESSION_CODECS[ID] = self + + # A bit mask used to denote that Zlib's default compression level should be + # used. + NORMAL = 0b000 + # A bit mask used to denote that Zlib's highest/slowest compression level + # should be used. + MAXIMUM = 0b010 + # A bit mask used to denote that Zlib's lowest/fastest compression level + # should be used. + FAST = 0b100 + # A bit mask used to denote that Zlib should not compress data at all. + SUPER_FAST = 0b110 + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for compression codec objects. + # + # Creates a new instance of this class using bits 1 and 2 of + # _general_purpose_flags_ to select a compression level to be used by + # #compressor to set up a compression IO object. The constants NORMAL, + # MAXIMUM, FAST, and SUPER_FAST can be used for _general_purpose_flags_ to + # manually set the compression level. + def initialize(general_purpose_flags = NORMAL) + @compression_level = general_purpose_flags & 0b110 + @zlib_compression_level = case @compression_level + when NORMAL + Zlib::DEFAULT_COMPRESSION + when MAXIMUM + Zlib::BEST_COMPRESSION + when FAST + Zlib::BEST_SPEED + when SUPER_FAST + Zlib::NO_COMPRESSION + else + raise Error, 'Invalid compression level' + end + end + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for compression codec objects. + # + # A convenience method for creating an + # Archive::Zip::Codec::Deflate::Compress object using that class' open + # method. The compression level for the open method is pulled from the + # value of the _general_purpose_flags_ argument of new. + def compressor(io, &b) + Compress.open(io, @zlib_compression_level, &b) + end + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for compression codec objects. + # + # A convenience method for creating an + # Archive::Zip::Codec::Deflate::Decompress object using that class' open + # method. + def decompressor(io, &b) + Decompress.open(io, &b) + end + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for compression codec objects. + # + # Returns an integer which indicates the version of the official ZIP + # specification which introduced support for this compression codec. + def version_needed_to_extract + 0x0014 + end + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for compression codec objects. + # + # Returns an integer used to flag that this compression codec is used for a + # particular ZIP archive entry. + def compression_method + ID + end + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for compression codec objects. + # + # Returns an integer representing the general purpose flags of a ZIP archive + # entry where bits 1 and 2 are set according to the compression level + # selected for this object. All other bits are zero'd out. + def general_purpose_flags + @compression_level + end + end +end; end; end + diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/codec/null_encryption.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/codec/null_encryption.rb new file mode 100644 index 00000000..9496097f --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/codec/null_encryption.rb @@ -0,0 +1,237 @@ +# encoding: UTF-8 + +require 'archive/support/io-like' +require 'archive/zip/codec' + +module Archive; class Zip; module Codec + # Archive::Zip::Codec::NullEncryption is a handle for an encryption codec + # which passes data through itself unchanged. + class NullEncryption + # Archive::Zip::Codec::NullEncryption::Encrypt is a writable, IO-like object + # which writes all data written to it directly to a delegate IO object. A + # _close_ method is also provided which can optionally closed the delegate + # object. + class Encrypt + include IO::Like + + # Creates a new instance of this class with the given argument using #new + # and then passes the instance to the given block. The #close method is + # guaranteed to be called after the block completes. + # + # Equivalent to #new if no block is given. + def self.open(io) + encrypt_io = new(io) + return encrypt_io unless block_given? + + begin + yield(encrypt_io) + ensure + encrypt_io.close unless encrypt_io.closed? + end + end + + # Creates a new instance of this class using _io_ as a data sink. _io_ + # must be writable and must provide a write method as IO does or errors + # will be raised when performing write operations. + # + # The _flush_size_ attribute is set to 0 by default under the + # assumption that _io_ is already buffered. + def initialize(io) + @io = io + + # Keep track of the total number of bytes written. + @total_bytes_in = 0 + + # Assume that the delegate IO object is already buffered. + self.flush_size = 0 + end + + # Closes this object so that further write operations will fail. If + # _close_delegate_ is +true+, the delegate object used as a data sink will + # also be closed using its close method. + def close(close_delegate = true) + super() + @io.close if close_delegate + end + + private + + # Allows resetting this object and the delegate object back to the + # beginning of the stream or reporting the current position in the stream. + # + # Raises Errno::EINVAL unless _offset_ is 0 and _whence_ is + # either IO::SEEK_SET or IO::SEEK_CUR. Raises Errno::EINVAL if _whence_ + # is IO::SEEK_SEK and the delegate object does not respond to the _rewind_ + # method. + def unbuffered_seek(offset, whence = IO::SEEK_SET) + unless offset == 0 && + ((whence == IO::SEEK_SET && @io.respond_to?(:rewind)) || + whence == IO::SEEK_CUR) then + raise Errno::EINVAL + end + + case whence + when IO::SEEK_SET + @io.rewind + @total_bytes_in = 0 + when IO::SEEK_CUR + @total_bytes_in + end + end + + # Writes _string_ to the delegate IO object and returns the result. + def unbuffered_write(string) + bytes_written = @io.write(string) + @total_bytes_in += bytes_written + bytes_written + end + end + + # Archive::Zip::Codec::NullEncryption::Decrypt is a readable, IO-like object + # which reads data directly from a delegate IO object, making no changes. A + # _close_ method is also provided which can optionally closed the delegate + # object. + class Decrypt + include IO::Like + + # Creates a new instance of this class with the given argument using #new + # and then passes the instance to the given block. The #close method is + # guaranteed to be called after the block completes. + # + # Equivalent to #new if no block is given. + def self.open(io) + decrypt_io = new(io) + return decrypt_io unless block_given? + + begin + yield(decrypt_io) + ensure + decrypt_io.close unless decrypt_io.closed? + end + end + + # Creates a new instance of this class using _io_ as a data source. _io_ + # must be readable and provide a _read_ method as an IO instance would or + # errors will be raised when performing read operations. + # + # This class has extremely limited seek capabilities. It is possible to + # seek with an offset of 0 and a whence of IO::SEEK_CUR. + # As a result, the _pos_ and _tell_ methods also work as expected. + # + # Due to certain optimizations within IO::Like#seek and if there is data + # in the read buffer, the _seek_ method can be used to seek forward from + # the current stream position up to the end of the buffer. Unless it is + # known definitively how much data is in the buffer, it is best to avoid + # relying on this behavior. + # + # If _io_ also responds to _rewind_, then the _rewind_ method of this + # class can be used to reset the whole stream back to the beginning. Using + # _seek_ of this class to seek directly to offset 0 using + # IO::SEEK_SET for whence will also work in this case. + # + # Any other seeking attempts, will raise Errno::EINVAL exceptions. + # + # The _fill_size_ attribute is set to 0 by default under the + # assumption that _io_ is already buffered. + def initialize(io) + @io = io + + # Keep track of the total number of bytes read. + @total_bytes_out = 0 + + # Assume that the delegate IO object is already buffered. + self.fill_size = 0 + end + + # Closes this object so that further write operations will fail. If + # _close_delegate_ is +true+, the delegate object used as a data source + # will also be closed using its close method. + def close(close_delegate = true) + super() + @io.close if close_delegate + end + + private + + # Reads and returns at most _length_ bytes from the delegate IO object. + # + # Raises EOFError if there is no data to read. + def unbuffered_read(length) + buffer = @io.read(length) + raise EOFError, 'end of file reached' if buffer.nil? + @total_bytes_out += buffer.length + + buffer + end + + # Allows resetting this object and the delegate object back to the + # beginning of the stream or reporting the current position in the stream. + # + # Raises Errno::EINVAL unless _offset_ is 0 and _whence_ is + # either IO::SEEK_SET or IO::SEEK_CUR. Raises Errno::EINVAL if _whence_ + # is IO::SEEK_SEK and the delegate object does not respond to the _rewind_ + # method. + def unbuffered_seek(offset, whence = IO::SEEK_SET) + unless offset == 0 && + ((whence == IO::SEEK_SET && @io.respond_to?(:rewind)) || + whence == IO::SEEK_CUR) then + raise Errno::EINVAL + end + + case whence + when IO::SEEK_SET + @io.rewind + @total_bytes_out = 0 + when IO::SEEK_CUR + @total_bytes_out + end + end + end + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for encryption codec objects. + # + # A convenience method for creating an + # Archive::Zip::Codec::NullEncryption::Encrypt object using that class' open + # method. + def encryptor(io, password, &b) + Encrypt.open(io, &b) + end + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for encryption codec objects. + # + # A convenience method for creating an + # Archive::Zip::Codec::NullEncryption::Decrypt object using that class' open + # method. + def decryptor(io, password, &b) + Decrypt.open(io, &b) + end + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for encryption codec objects. + # + # Returns an integer which indicates the version of the official ZIP + # specification which introduced support for this encryption codec. + def version_needed_to_extract + 0x0014 + end + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for encryption codec objects. + # + # Returns an integer representing the general purpose flags of a ZIP archive + # entry using this encryption codec. + def general_purpose_flags + 0b0000000000000000 + end + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for encryption codec objects. + # + # Returns the size of the encryption header in bytes. + def header_size + 0 + end + end +end; end; end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/codec/store.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/codec/store.rb new file mode 100644 index 00000000..c150bfb9 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/codec/store.rb @@ -0,0 +1,296 @@ +# encoding: UTF-8 + +require 'archive/support/io-like' +require 'archive/support/zlib' +require 'archive/zip/codec' +require 'archive/zip/data_descriptor' + +module Archive; class Zip; module Codec + # Archive::Zip::Codec::Store is a handle for the store-unstore (no + # compression) codec. + class Store + # Archive::Zip::Codec::Store::Compress is simply a writable, IO-like wrapper + # around a writable, IO-like object which provides a CRC32 checksum of the + # data written through it as well as the count of the total amount of data. + # A _close_ method is also provided which can optionally close the delegate + # object. In addition a convenience method is provided for generating + # DataDescriptor objects based on the data which is passed through this + # object. + # + # Instances of this class should only be accessed via the + # Archive::Zip::Codec::Store#compressor method. + class Compress + include IO::Like + + # Creates a new instance of this class with the given argument using #new + # and then passes the instance to the given block. The #close method is + # guaranteed to be called after the block completes. + # + # Equivalent to #new if no block is given. + def self.open(io) + store_io = new(io) + return store_io unless block_given? + + begin + yield(store_io) + ensure + store_io.close unless store_io.closed? + end + end + + # Creates a new instance of this class using _io_ as a data sink. _io_ + # must be writable and must provide a write method as IO does or errors + # will be raised when performing write operations. + # + # The _flush_size_ attribute is set to 0 by default under the + # assumption that _io_ is already buffered. + def initialize(io) + @io = io + @crc32 = 0 + @uncompressed_size = 0 + # Assume that the delegate IO object is already buffered. + self.flush_size = 0 + end + + # Closes this object so that further write operations will fail. If + # _close_delegate_ is +true+, the delegate object used as a data sink will + # also be closed using its close method. + def close(close_delegate = true) + super() + @io.close if close_delegate + nil + end + + # Returns an instance of Archive::Zip::DataDescriptor with information + # regarding the data which has passed through this object to the delegate + # object. The close or flush methods should be called before using this + # method in order to ensure that any possibly buffered data is flushed to + # the delegate object; otherwise, the contents of the data descriptor may + # be inaccurate. + def data_descriptor + DataDescriptor.new( + @crc32, + @uncompressed_size, + @uncompressed_size + ) + end + + private + + # Allows resetting this object and the delegate object back to the + # beginning of the stream or reporting the current position in the stream. + # + # Raises Errno::EINVAL unless _offset_ is 0 and _whence_ is + # either IO::SEEK_SET or IO::SEEK_CUR. Raises Errno::EINVAL if _whence_ + # is IO::SEEK_SEK and the delegate object does not respond to the _rewind_ + # method. + def unbuffered_seek(offset, whence = IO::SEEK_SET) + unless offset == 0 && + ((whence == IO::SEEK_SET && @io.respond_to?(:rewind)) || + whence == IO::SEEK_CUR) then + raise Errno::EINVAL + end + + case whence + when IO::SEEK_SET + @io.rewind + @crc32 = 0 + @uncompressed_size = 0 + when IO::SEEK_CUR + @uncompressed_size + end + end + + # Writes _string_ to the delegate object and returns the number of bytes + # actually written. Updates the uncompressed_size and crc32 attributes as + # a side effect. + def unbuffered_write(string) + bytes_written = @io.write(string) + @uncompressed_size += bytes_written + @crc32 = Zlib.crc32(string.slice(0, bytes_written), @crc32) + bytes_written + end + end + + # Archive::Zip::Codec::Store::Decompress is a readable, IO-like wrapper + # around a readable, IO-like object which provides a CRC32 checksum of the + # data read through it as well as the count of the total amount of data. A + # _close_ method is also provided which can optionally close the delegate + # object. In addition a convenience method is provided for generating + # DataDescriptor objects based on the data which is passed through this + # object. + # + # Instances of this class should only be accessed via the + # Archive::Zip::Codec::Store#decompressor method. + class Decompress + include IO::Like + + # Creates a new instance of this class with the given arguments using #new + # and then passes the instance to the given block. The #close method is + # guaranteed to be called after the block completes. + # + # Equivalent to #new if no block is given. + def self.open(io) + unstore_io = new(io) + return unstore_io unless block_given? + + begin + yield(unstore_io) + ensure + unstore_io.close unless unstore_io.closed? + end + end + + # Creates a new instance of this class using _io_ as a data source. _io_ + # must be readable and provide a _read_ method as an IO instance would or + # errors will be raised when performing read operations. + # + # This class has extremely limited seek capabilities. It is possible to + # seek with an offset of 0 and a whence of IO::SEEK_CUR. + # As a result, the _pos_ and _tell_ methods also work as expected. + # + # Due to certain optimizations within IO::Like#seek and if there is data + # in the read buffer, the _seek_ method can be used to seek forward from + # the current stream position up to the end of the buffer. Unless it is + # known definitively how much data is in the buffer, it is best to avoid + # relying on this behavior. + # + # If _io_ also responds to _rewind_, then the _rewind_ method of this + # class can be used to reset the whole stream back to the beginning. Using + # _seek_ of this class to seek directly to offset 0 using + # IO::SEEK_SET for whence will also work in this case. + # + # Any other seeking attempts, will raise Errno::EINVAL exceptions. + # + # The _fill_size_ attribute is set to 0 by default under the + # assumption that _io_ is already buffered. + def initialize(io) + @io = io + @crc32 = 0 + @uncompressed_size = 0 + # Assume that the delegate IO object is already buffered. + self.fill_size = 0 + end + + # Closes this object so that further read operations will fail. If + # _close_delegate_ is +true+, the delegate object used as a data source + # will also be closed using its close method. + def close(close_delegate = true) + super() + @io.close if close_delegate + nil + end + + # Returns an instance of Archive::Zip::DataDescriptor with information + # regarding the data which has passed through this object from the + # delegate object. It is recommended to call the close method before + # calling this in order to ensure that no further read operations change + # the state of this object. + def data_descriptor + DataDescriptor.new( + @crc32, + @uncompressed_size, + @uncompressed_size + ) + end + + private + + # Returns at most _length_ bytes from the delegate object. Updates the + # uncompressed_size and crc32 attributes as a side effect. + def unbuffered_read(length) + buffer = @io.read(length) + raise EOFError, 'end of file reached' if buffer.nil? + + @uncompressed_size += buffer.length + @crc32 = Zlib.crc32(buffer, @crc32) + buffer + end + + # Allows resetting this object and the delegate object back to the + # beginning of the stream or reporting the current position in the stream. + # + # Raises Errno::EINVAL unless _offset_ is 0 and _whence_ is + # either IO::SEEK_SET or IO::SEEK_CUR. Raises Errno::EINVAL if _whence_ + # is IO::SEEK_SEK and the delegate object does not respond to the _rewind_ + # method. + def unbuffered_seek(offset, whence = IO::SEEK_SET) + unless offset == 0 && + ((whence == IO::SEEK_SET && @io.respond_to?(:rewind)) || + whence == IO::SEEK_CUR) then + raise Errno::EINVAL + end + + case whence + when IO::SEEK_SET + @io.rewind + @crc32 = 0 + @uncompressed_size = 0 + when IO::SEEK_CUR + @uncompressed_size + end + end + end + + # The numeric identifier assigned to this compresion codec by the ZIP + # specification. + ID = 0 + + # Register this compression codec. + COMPRESSION_CODECS[ID] = self + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for compression codec objects. + # + # Creates a new instance of this class. _general_purpose_flags_ is not + # used. + def initialize(general_purpose_flags = 0) + end + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for compression codec objects. + # + # A convenience method for creating an Archive::Zip::Codec::Store::Compress + # object using that class' open method. + def compressor(io, &b) + Compress.open(io, &b) + end + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for compression codec objects. + # + # A convenience method for creating an + # Archive::Zip::Codec::Store::Decompress object using that class' open + # method. + def decompressor(io, &b) + Decompress.open(io, &b) + end + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for compression codec objects. + # + # Returns an integer which indicates the version of the official ZIP + # specification which introduced support for this compression codec. + def version_needed_to_extract + 0x000a + end + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for compression codec objects. + # + # Returns an integer used to flag that this compression codec is used for a + # particular ZIP archive entry. + def compression_method + ID + end + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for compression codec objects. + # + # Returns 0 since this compression codec does not make use of + # general purpose flags of ZIP archive entries. + def general_purpose_flags + 0 + end + end +end; end; end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/codec/traditional_encryption.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/codec/traditional_encryption.rb new file mode 100644 index 00000000..d500b3ed --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/codec/traditional_encryption.rb @@ -0,0 +1,414 @@ +# encoding: UTF-8 + +require 'archive/support/integer' +require 'archive/support/io-like' +require 'archive/support/time' +require 'archive/support/zlib' +require 'archive/zip/codec' + +module Archive; class Zip; module Codec + # Archive::Zip::Codec::TraditionalEncryption is a handle for the traditional + # encryption codec. + class TraditionalEncryption + # Archive::Zip::Codec::TraditionalEncryption::Base provides some basic + # methods which are shared between + # Archive::Zip::Codec::TraditionalEncryption::Encrypt and + # Archive::Zip::Codec::TraditionalEncryption::Decrypt. + # + # Do not use this class directly. + class Base + # Creates a new instance of this class. _io_ must be an IO-like object to + # be used as a delegate for IO operations. _password_ should be the + # encryption key. _mtime_ must be the last modified time of the entry to + # be encrypted/decrypted. + def initialize(io, password, mtime) + @io = io + @password = password.nil? ? '' : password + @mtime = mtime + initialize_keys + end + + protected + + # The delegate IO-like object. + attr_reader :io + # The encryption key. + attr_reader :password + # The last modified time of the entry being encrypted. This is used in + # the entryption header as a way to check the password. + attr_reader :mtime + + private + + # Initializes the keys used for encrypting/decrypting data by setting the + # keys to well known values and then processing them with the password. + def initialize_keys + @key0 = 0x12345678 + @key1 = 0x23456789 + @key2 = 0x34567890 + @password.each_byte { |byte| update_keys(byte.chr) } + nil + end + + # Updates the keys following the ZIP specification using _char_, which + # must be a single byte String. + def update_keys(char) + # For some reason not explained in the ZIP specification but discovered + # in the source for InfoZIP, the old CRC value must first have its bits + # flipped before processing. The new CRC value must have its bits + # flipped as well for storage and later use. This applies to the + # handling of @key0 and @key2. + @key0 = ~Zlib.crc32(char, ~@key0) + @key1 = ((@key1 + (@key0 & 0xff)) * 134775813 + 1) & 0xffffffff + @key2 = ~Zlib.crc32((@key1 >> 24).chr, ~@key2) + nil + end + + # Returns the next decryption byte based on the current keys. + def decrypt_byte + temp = (@key2 | 2) & 0x0000ffff + ((temp * (temp ^ 1)) >> 8) & 0x000000ff + end + end + + # Archive::Zip::Codec::TraditionalEncryption::Encrypt is a writable, IO-like + # object which encrypts data written to it using the traditional encryption + # algorithm as documented in the ZIP specification and writes the result to + # a delegate IO object. A _close_ method is also provided which can + # optionally close the delegate object. + # + # Instances of this class should only be accessed via the + # Archive::Zip::Codec::TraditionalEncryption#compressor method. + class Encrypt < Base + include IO::Like + + # Creates a new instance of this class with the given argument using #new + # and then passes the instance to the given block. The #close method is + # guaranteed to be called after the block completes. + # + # Equivalent to #new if no block is given. + def self.open(io, password, mtime) + encrypt_io = new(io, password, mtime) + return encrypt_io unless block_given? + + begin + yield(encrypt_io) + ensure + encrypt_io.close unless encrypt_io.closed? + end + end + + # Creates a new instance of this class using _io_ as a data sink. _io_ + # must be writable and must provide a write method as IO does or errors + # will be raised when performing write operations. _password_ should be + # the encryption key. _mtime_ must be the last modified time of the entry + # to be encrypted/decrypted. + # + # The _flush_size_ attribute is set to 0 by default under the + # assumption that _io_ is already buffered. + def initialize(io, password, mtime) + # Keep track of the total number of bytes written. + # Set this here so that the call to #initialize_keys caused by the call + # to super below does not cause errors in #unbuffered_write due to this + # attribute being uninitialized. + @total_bytes_in = 0 + + # This buffer is used to hold the encrypted version of the string most + # recently sent to #unbuffered_write. + @encrypt_buffer = '' + + super(io, password, mtime) + + # Assume that the delegate IO object is already buffered. + self.flush_size = 0 + end + + # Closes the stream after flushing the encryption buffer to the delegate. + # If _close_delegate_ is +true+, the delegate object used as a data sink + # will also be closed using its close method. + # + # Raises IOError if called more than once. + def close(close_delegate = true) + flush() + begin + until @encrypt_buffer.empty? do + @encrypt_buffer.slice!(0, io.write(@encrypt_buffer)) + end + rescue Errno::EAGAIN, Errno::EINTR + retry if write_ready? + end + + super() + io.close if close_delegate + nil + end + + private + + # Extend the inherited initialize_keys method to further initialize the + # keys by encrypting and writing a 12 byte header to the delegate IO + # object. + def initialize_keys + super + + # Create and encrypt a 12 byte header to protect the encrypted file data + # from attack. The first 10 bytes are random, and the last 2 bytes are + # the low order word in little endian byte order of the last modified + # time of the entry in DOS format. + header = '' + 10.times do + header << rand(256).chr + end + header << mtime.to_dos_time.pack[0, 2] + + # Take care to ensure that all bytes in the header are written. + while header.size > 0 do + begin + header.slice!(0, unbuffered_write(header)) + rescue Errno::EAGAIN, Errno::EINTR + sleep(1) + end + end + + # Reset the total bytes written in order to disregard the header. + @total_bytes_in = 0 + + nil + end + + # Allows resetting this object and the delegate object back to the + # beginning of the stream or reporting the current position in the stream. + # + # Raises Errno::EINVAL unless _offset_ is 0 and _whence_ is + # either IO::SEEK_SET or IO::SEEK_CUR. Raises Errno::EINVAL if _whence_ + # is IO::SEEK_SEK and the delegate object does not respond to the _rewind_ + # method. + def unbuffered_seek(offset, whence = IO::SEEK_SET) + unless offset == 0 && + ((whence == IO::SEEK_SET && @io.respond_to?(:rewind)) || + whence == IO::SEEK_CUR) then + raise Errno::EINVAL + end + + case whence + when IO::SEEK_SET + io.rewind + @encrypt_buffer = '' + initialize_keys + @total_bytes_in = 0 + when IO::SEEK_CUR + @total_bytes_in + end + end + + # Encrypts and writes _string_ to the delegate IO object. Returns the + # number of bytes of _string_ written. + def unbuffered_write(string) + # First try to write out the contents of the encrypt buffer because if + # that raises a failure we can let that pass up the call stack without + # having polluted the encryption state. + until @encrypt_buffer.empty? do + @encrypt_buffer.slice!(0, io.write(@encrypt_buffer)) + end + # At this point we can encrypt the given string into a new buffer and + # behave as if it was written. + string.each_byte do |byte| + temp = decrypt_byte + @encrypt_buffer << (byte ^ temp).chr + update_keys(byte.chr) + end + @total_bytes_in += string.length + string.length + end + end + + # Archive::Zip::Codec::TraditionalEncryption::Decrypt is a readable, IO-like + # object which decrypts data data it reads from a delegate IO object using + # the traditional encryption algorithm as documented in the ZIP + # specification. A _close_ method is also provided which can optionally + # close the delegate object. + # + # Instances of this class should only be accessed via the + # Archive::Zip::Codec::TraditionalEncryption#decompressor method. + class Decrypt < Base + include IO::Like + + # Creates a new instance of this class with the given argument using #new + # and then passes the instance to the given block. The #close method is + # guaranteed to be called after the block completes. + # + # Equivalent to #new if no block is given. + def self.open(io, password, mtime) + decrypt_io = new(io, password, mtime) + return decrypt_io unless block_given? + + begin + yield(decrypt_io) + ensure + decrypt_io.close unless decrypt_io.closed? + end + end + + # Creates a new instance of this class using _io_ as a data source. _io_ + # must be readable and provide a _read_ method as an IO instance would or + # errors will be raised when performing read operations. _password_ + # should be the encryption key. _mtime_ must be the last modified time of + # the entry to be encrypted/decrypted. + # + # This class has extremely limited seek capabilities. It is possible to + # seek with an offset of 0 and a whence of IO::SEEK_CUR. + # As a result, the _pos_ and _tell_ methods also work as expected. + # + # Due to certain optimizations within IO::Like#seek and if there is data + # in the read buffer, the _seek_ method can be used to seek forward from + # the current stream position up to the end of the buffer. Unless it is + # known definitively how much data is in the buffer, it is best to avoid + # relying on this behavior. + # + # If _io_ also responds to _rewind_, then the _rewind_ method of this + # class can be used to reset the whole stream back to the beginning. Using + # _seek_ of this class to seek directly to offset 0 using + # IO::SEEK_SET for whence will also work in this case. + # + # Any other seeking attempts, will raise Errno::EINVAL exceptions. + # + # The _fill_size_ attribute is set to 0 by default under the + # assumption that _io_ is already buffered. + def initialize(io, password, mtime) + # Keep track of the total number of bytes read. + # Set this here so that the call to #initialize_keys caused by the call + # to super below does not cause errors in #unbuffered_read due to this + # attribute being uninitialized. + @total_bytes_out = 0 + + super(io, password, mtime) + + # Assume that the delegate IO object is already buffered. + self.fill_size = 0 + end + + # Closes this object so that further write operations will fail. If + # _close_delegate_ is +true+, the delegate object used as a data source + # will also be closed using its close method. + def close(close_delegate = true) + super() + io.close if close_delegate + end + + private + + # Extend the inherited initialize_keys method to further initialize the + # keys by encrypting and writing a 12 byte header to the delegate IO + # object. + def initialize_keys + super + + # Load the 12 byte header taking care to ensure that all bytes are read. + bytes_needed = 12 + while bytes_needed > 0 do + begin + bytes_read = unbuffered_read(bytes_needed) + bytes_needed -= bytes_read.size + rescue Errno::EAGAIN, Errno::EINTR + sleep(1) + end + end + + # Reset the total bytes read in order to disregard the header. + @total_bytes_out = 0 + + nil + end + + # Reads, decrypts, and returns at most _length_ bytes from the delegate IO + # object. + # + # Raises EOFError if there is no data to read. + def unbuffered_read(length) + buffer = io.read(length) + raise EOFError, 'end of file reached' if buffer.nil? + @total_bytes_out += buffer.length + + 0.upto(buffer.length - 1) do |i| + buffer[i] = (buffer[i].ord ^ decrypt_byte).chr + update_keys(buffer[i].chr) + end + buffer + end + + # Allows resetting this object and the delegate object back to the + # beginning of the stream or reporting the current position in the stream. + # + # Raises Errno::EINVAL unless _offset_ is 0 and _whence_ is + # either IO::SEEK_SET or IO::SEEK_CUR. Raises Errno::EINVAL if _whence_ + # is IO::SEEK_SEK and the delegate object does not respond to the _rewind_ + # method. + def unbuffered_seek(offset, whence = IO::SEEK_SET) + unless offset == 0 && + ((whence == IO::SEEK_SET && @io.respond_to?(:rewind)) || + whence == IO::SEEK_CUR) then + raise Errno::EINVAL + end + + case whence + when IO::SEEK_SET + io.rewind + initialize_keys + @total_bytes_out = 0 + when IO::SEEK_CUR + @total_bytes_out + end + end + end + + # The last modified time of the entry to be processed. Set this before + # calling #encryptor or #decryptor. + attr_accessor :mtime + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for encryption codec objects. + # + # A convenience method for creating an + # Archive::Zip::Codec::TraditionalEncryption::Encrypt object using that + # class' open method. + def encryptor(io, password, &b) + Encrypt.open(io, password, mtime, &b) + end + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for encryption codec objects. + # + # A convenience method for creating an + # Archive::Zip::Codec::TraditionalEncryption::Decrypt object using that + # class' open method. + def decryptor(io, password, &b) + Decrypt.open(io, password, mtime, &b) + end + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for encryption codec objects. + # + # Returns an integer which indicates the version of the official ZIP + # specification which introduced support for this encryption codec. + def version_needed_to_extract + 0x0014 + end + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for encryption codec objects. + # + # Returns an integer representing the general purpose flags of a ZIP archive + # entry using this encryption codec. + def general_purpose_flags + 0b0000000000000001 + end + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for encryption codec objects. + # + # Returns the size of the encryption header in bytes. + def header_size + 12 + end + end +end; end; end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/data_descriptor.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/data_descriptor.rb new file mode 100644 index 00000000..4bab53a6 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/data_descriptor.rb @@ -0,0 +1,58 @@ +# encoding: UTF-8 + +module Archive; class Zip + # Archive::Zip::DataDescriptor is a convenience class which bundles important + # information concerning the compressed data in a ZIP archive entry and allows + # easy comparisons between instances of itself. + class DataDescriptor + # Create a new instance of this class where crc32, + # _compressed_size_, and _uncompressed_size_ are all integers representing a + # CRC32 checksum of uncompressed data, the size of compressed data, and the + # size of uncompressed data respectively. + def initialize(crc32, compressed_size, uncompressed_size) + @crc32 = crc32 + @compressed_size = compressed_size + @uncompressed_size = uncompressed_size + end + + # A CRC32 checksum over some set of uncompressed data. + attr_reader :crc32 + # A count of the number of bytes of compressed data associated with a set of + # uncompressed data. + attr_reader :compressed_size + # A count of the number of bytes of a set of uncompressed data. + attr_reader :uncompressed_size + + # Compares the crc32 and uncompressed_size attributes of this object with + # like-named attributes of _other_ and raises Archive::Zip::Error for any + # mismatches. + # + # NOTE: The compressed_size attribute is not checked because + # encrypted entries may have misleading compressed sizes. Checking only the + # CRC32 and uncompressed size of the data should be sufficient to ensure + # that an entry has been successfully extracted. + def verify(other) + unless crc32 == other.crc32 then + raise Zip::Error, + "CRC32 mismatch: #{crc32.to_s(16)} vs. #{other.crc32.to_s(16)}" + end + unless uncompressed_size == other.uncompressed_size then + raise Zip::Error, + "uncompressed size mismatch: #{uncompressed_size} vs. #{other.uncompressed_size}" + end + end + + # Writes the data wrapped in this object to _io_ which must be a writable, + # IO-like object providing a _write_ method. Returns the number of bytes + # written. + def dump(io) + io.write( + [ + crc32, + compressed_size, + uncompressed_size + ].pack('VVV') + ) + end + end +end; end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/entry.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/entry.rb new file mode 100644 index 00000000..a014e7a0 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/entry.rb @@ -0,0 +1,1198 @@ +# encoding: UTF-8 + +require 'archive/support/ioextensions' +require 'archive/support/binary_stringio' +require 'archive/zip/codec/deflate' +require 'archive/zip/codec/null_encryption' +require 'archive/zip/codec/store' +require 'archive/zip/codec/traditional_encryption' +require 'archive/zip/data_descriptor' +require 'archive/zip/error' +require 'archive/zip/extra_field' + +module Archive; class Zip + # The Archive::Zip::Entry mixin provides classes with methods implementing + # many of the common features of all entry types. Some of these methods, such + # as _dump_local_file_record_ and _dump_central_file_record_, are required by + # Archive::Zip in order to store the entry into an archive. Those should be + # left alone. Others, such as _ftype_ and mode=, are expected to be + # overridden to provide sensible information for the new entry type. + # + # A class using this mixin must provide 2 methods: _extract_ and + # _dump_file_data_. _extract_ should be a public method with the following + # signature: + # + # def extract(options = {}) + # ... + # end + # + # This method should extract the contents of the entry to the filesystem. + # _options_ should be an optional Hash containing a mapping of option names to + # option values. Please refer to Archive::Zip::Entry::File#extract, + # Archive::Zip::Entry::Symlink#extract, and + # Archive::Zip::Entry::Directory#extract for examples of the options currently + # supported. + # + # _dump_file_data_ should be a private method with the following signature: + # + # def dump_file_data(io) + # ... + # end + # + # This method should use the _write_ method of _io_ to write all file data. + # _io_ will be a writable, IO-like object. + # + # The class methods from_file and parse are factories for creating the 3 kinds + # of concrete entries currently implemented: File, Directory, and Symlink. + # While it is possible to create new archives using custom entry + # implementations, it is not possible to load those same entries from the + # archive since the parse factory method does not know about them. Patches + # to support new entry types are welcome. + module Entry + CFHRecord = Struct.new( + :made_by_version, + :extraction_version, + :general_purpose_flags, + :compression_method, + :mtime, + :crc32, + :compressed_size, + :uncompressed_size, + :disk_number_start, + :internal_file_attributes, + :external_file_attributes, + :local_header_position, + :zip_path, + :extra_fields, + :comment + ) + + LFHRecord = Struct.new( + :extraction_version, + :general_purpose_flags, + :compression_method, + :mtime, + :crc32, + :compressed_size, + :uncompressed_size, + :zip_path, + :extra_fields, + :compressed_data + ) + + # When this flag is set in the general purpose flags, it indicates that the + # entry's file data is encrypted using the original (weak) algorithm. + FLAG_ENCRYPTED = 0b0001 + + # When this flag is set in the general purpose flags, it indicates that the + # read data descriptor record for a local file record is located after the + # entry's file data. + FLAG_DATA_DESCRIPTOR_FOLLOWS = 0b1000 + + # Cleans up and returns _zip_path_ by eliminating . and .. references, + # leading and trailing /'s, and runs of /'s. + def self.expand_path(zip_path) + result = [] + source = zip_path.split('/') + + source.each do |e| + next if e.empty? || e == '.' + + if e == '..' && ! (result.last.nil? || result.last == '..') then + result.pop + else + result.push(e) + end + end + result.shift while result.first == '..' + + result.join('/') + end + + # Creates a new Entry based upon a file, symlink, or directory. _file_path_ + # points to the source item. _options_ is a Hash optionally containing the + # following: + # :zip_path:: + # The path for the entry in the archive where `/' is the file separator + # character. This defaults to the basename of _file_path_ if unspecified. + # :follow_symlinks:: + # When set to +true+ (the default), symlinks are treated as the files or + # directories to which they point. + # :compression_codec:: + # Specifies a proc, lambda, or class. If a proc or lambda is used, it + # must take a single argument containing a zip entry and return a + # compression codec class to be instantiated and used with the entry. + # Otherwise, a compression codec class must be specified directly. When + # unset, the default compression codec for each entry type is used. + # :encryption_codec:: + # Specifies a proc, lambda, or class. If a proc or lambda is used, it + # must take a single argument containing a zip entry and return an + # encryption codec class to be instantiated and used with the entry. + # Otherwise, an encryption codec class must be specified directly. When + # unset, the default encryption codec for each entry type is used. + # + # Raises Archive::Zip::EntryError if processing the given file path results + # in a file not found error. + def self.from_file(file_path, options = {}) + zip_path = options.has_key?(:zip_path) ? + expand_path(options[:zip_path]) : + ::File.basename(file_path) + follow_symlinks = options.has_key?(:follow_symlinks) ? + options[:follow_symlinks] : + true + + # Avoid repeatedly stat'ing the file by storing the stat structure once. + begin + stat = follow_symlinks ? + ::File.stat(file_path) : + ::File.lstat(file_path) + rescue Errno::ENOENT + if ::File.symlink?(file_path) then + raise Zip::EntryError, + "symlink at `#{file_path}' points to a non-existent file `#{::File.readlink(file_path)}'" + else + raise Zip::EntryError, "no such file or directory `#{file_path}'" + end + end + + # Ensure that zip paths for directories end with '/'. + if stat.directory? then + zip_path += '/' + end + + # Instantiate the entry. + if stat.symlink? then + entry = Entry::Symlink.new(zip_path) + entry.link_target = ::File.readlink(file_path) + elsif stat.file? then + entry = Entry::File.new(zip_path) + entry.file_path = file_path + elsif stat.directory? then + entry = Entry::Directory.new(zip_path) + else + raise Zip::EntryError, + "unsupported file type `#{stat.ftype}' for file `#{file_path}'" + end + + # Set the compression and encryption codecs. + unless options[:compression_codec].nil? then + if options[:compression_codec].kind_of?(Proc) then + entry.compression_codec = options[:compression_codec][entry].new + else + entry.compression_codec = options[:compression_codec].new + end + end + unless options[:encryption_codec].nil? then + if options[:encryption_codec].kind_of?(Proc) then + entry.encryption_codec = options[:encryption_codec][entry].new + else + entry.encryption_codec = options[:encryption_codec].new + end + end + + # Set the entry's metadata. + entry.uid = stat.uid + entry.gid = stat.gid + entry.mtime = stat.mtime + entry.atime = stat.atime + entry.mode = stat.mode + + entry + end + + # Creates and returns a new entry object by parsing from the current + # position of _io_. _io_ must be a readable, IO-like object which is + # positioned at the start of a central file record following the signature + # for that record. + # + # NOTE: For now _io_ MUST be seekable. + # + # Currently, the only entry objects returned are instances of + # Archive::Zip::Entry::File, Archive::Zip::Entry::Directory, and + # Archive::Zip::Entry::Symlink. Any other kind of entry will be mapped into + # an instance of Archive::Zip::Entry::File. + # + # Raises Archive::Zip::EntryError for any other errors related to processing + # the entry. + def self.parse(io) + # Parse the central file record and then use the information found there + # to locate and parse the corresponding local file record. + cfr = parse_central_file_record(io) + next_record_position = io.pos + io.seek(cfr.local_header_position) + unless IOExtensions.read_exactly(io, 4) == LFH_SIGNATURE then + raise Zip::EntryError, 'bad local file header signature' + end + lfr = parse_local_file_record(io, cfr.compressed_size) + + # Check to ensure that the contents of the central file record and the + # local file record which are supposed to be duplicated are in fact the + # same. + compare_file_records(lfr, cfr) + + begin + # Load the correct compression codec. + compression_codec = Codec.create_compression_codec( + cfr.compression_method, + cfr.general_purpose_flags + ) + rescue Zip::Error => e + raise Zip::EntryError, "`#{cfr.zip_path}': #{e.message}" + end + + begin + # Load the correct encryption codec. + encryption_codec = Codec.create_encryption_codec( + cfr.general_purpose_flags + ) + rescue Zip::Error => e + raise Zip::EntryError, "`#{cfr.zip_path}': #{e.message}" + end + + # Set up a data descriptor with expected values for later comparison. + expected_data_descriptor = DataDescriptor.new( + cfr.crc32, + cfr.compressed_size, + cfr.uncompressed_size + ) + + # Create the entry. + expanded_path = expand_path(cfr.zip_path) + io_window = IOWindow.new(io, io.pos, cfr.compressed_size) + if cfr.zip_path[-1..-1] == '/' then + # This is a directory entry. + entry = Entry::Directory.new(expanded_path, io_window) + elsif (cfr.external_file_attributes >> 16) & 0770000 == 0120000 then + # This is a symlink entry. + entry = Entry::Symlink.new(expanded_path, io_window) + else + # Anything else is a file entry. + entry = Entry::File.new(expanded_path, io_window) + end + + # Set the expected data descriptor so that extraction can be verified. + entry.expected_data_descriptor = expected_data_descriptor + # Record the compression codec. + entry.compression_codec = compression_codec + # Record the encryption codec. + entry.encryption_codec = encryption_codec + # Set some entry metadata. + entry.mtime = cfr.mtime + # Only set mode bits for the entry if the external file attributes are + # Unix-compatible. + if cfr.made_by_version & 0xFF00 == 0x0300 then + entry.mode = cfr.external_file_attributes >> 16 + end + entry.comment = cfr.comment + cfr.extra_fields.each { |ef| entry.add_extra_field(ef) } + lfr.extra_fields.each { |ef| entry.add_extra_field(ef) } + + # Return to the beginning of the next central directory record. + io.seek(next_record_position) + + entry + end + + private + + # Parses a central file record and returns a CFHRecord instance containing + # the parsed data. _io_ must be a readable, IO-like object which is + # positioned at the start of a central file record following the signature + # for that record. + def self.parse_central_file_record(io) + cfr = CFHRecord.new + + cfr.made_by_version, + cfr.extraction_version, + cfr.general_purpose_flags, + cfr.compression_method, + dos_mtime, + cfr.crc32, + cfr.compressed_size, + cfr.uncompressed_size, + file_name_length, + extra_fields_length, + comment_length, + cfr.disk_number_start, + cfr.internal_file_attributes, + cfr.external_file_attributes, + cfr.local_header_position = + IOExtensions.read_exactly(io, 42).unpack('vvvvVVVVvvvvvVV') + + cfr.zip_path = IOExtensions.read_exactly(io, file_name_length) + cfr.extra_fields = parse_central_extra_fields( + IOExtensions.read_exactly(io, extra_fields_length) + ) + cfr.comment = IOExtensions.read_exactly(io, comment_length) + + # Convert from MSDOS time to Unix time. + cfr.mtime = DOSTime.new(dos_mtime).to_time + + cfr + rescue EOFError + raise Zip::EntryError, 'unexpected end of file' + end + + # Parses a local file record and returns a LFHRecord instance containing the + # parsed data. _io_ must be a readable, IO-like object which is positioned + # at the start of a local file record following the signature for that + # record. + # + # If the record to be parsed is flagged to have a trailing data descriptor + # record, _expected_compressed_size_ must be set to an integer counting the + # number of bytes of compressed data to skip in order to find the trailing + # data descriptor record, and _io_ must be seekable by providing _pos_ and + # pos= methods. + def self.parse_local_file_record(io, expected_compressed_size = nil) + lfr = LFHRecord.new + + lfr.extraction_version, + lfr.general_purpose_flags, + lfr.compression_method, + dos_mtime, + lfr.crc32, + lfr.compressed_size, + lfr.uncompressed_size, + file_name_length, + extra_fields_length = + IOExtensions.read_exactly(io, 26).unpack('vvvVVVVvv') + + lfr.zip_path = IOExtensions.read_exactly(io, file_name_length) + lfr.extra_fields = parse_local_extra_fields( + IOExtensions.read_exactly(io, extra_fields_length) + ) + + # Convert from MSDOS time to Unix time. + lfr.mtime = DOSTime.new(dos_mtime).to_time + + if lfr.general_purpose_flags & FLAG_DATA_DESCRIPTOR_FOLLOWS > 0 then + saved_pos = io.pos + io.pos += expected_compressed_size + # Because the ZIP specification has a history of murkiness, some + # libraries create trailing data descriptor records with a preceding + # signature while others do not. + # This handles both cases. + possible_signature = IOExtensions.read_exactly(io, 4) + if possible_signature == DD_SIGNATURE then + lfr.crc32, + lfr.compressed_size, + lfr.uncompressed_size = + IOExtensions.read_exactly(io, 12).unpack('VVV') + else + lfr.crc32 = possible_signature.unpack('V')[0] + lfr.compressed_size, + lfr.uncompressed_size = IOExtensions.read_exactly(io, 8).unpack('VV') + end + io.pos = saved_pos + end + + lfr + rescue EOFError + raise Zip::EntryError, 'unexpected end of file' + end + + # Parses the extra fields for central file records and returns an array of + # extra field objects. _bytes_ must be a String containing all of the extra + # field data to be parsed. + def self.parse_central_extra_fields(bytes) + BinaryStringIO.open(bytes) do |io| + extra_fields = [] + while ! io.eof? do + begin + header_id, data_size = IOExtensions.read_exactly(io, 4).unpack('vv') + data = IOExtensions.read_exactly(io, data_size) + rescue ::EOFError + raise EntryError, 'insufficient data available' + end + + extra_fields << ExtraField.parse_central(header_id, data) + end + extra_fields + end + end + + # Parses the extra fields for local file records and returns an array of + # extra field objects. _bytes_ must be a String containing all of the extra + # field data to be parsed. + def self.parse_local_extra_fields(bytes) + BinaryStringIO.open(bytes) do |io| + extra_fields = [] + while ! io.eof? do + begin + header_id, data_size = IOExtensions.read_exactly(io, 4).unpack('vv') + data = IOExtensions.read_exactly(io, data_size) + rescue ::EOFError + raise EntryError, 'insufficient data available' + end + + extra_fields << ExtraField.parse_local(header_id, data) + end + extra_fields + end + end + + # Compares the local and the central file records found in _lfr_ and _cfr + # respectively. Raises Archive::Zip::EntryError if the comparison fails. + def self.compare_file_records(lfr, cfr) + # Exclude the extra fields from the comparison since some implementations, + # such as InfoZip, are known to have differences in the extra fields used + # in local file records vs. central file records. + if lfr.zip_path != cfr.zip_path then + raise Zip::EntryError, "zip path differs between local and central file records: `#{lfr.zip_path}' != `#{cfr.zip_path}'" + end + if lfr.extraction_version != cfr.extraction_version then + raise Zip::EntryError, "`#{cfr.zip_path}': extraction version differs between local and central file records" + end + if lfr.crc32 != cfr.crc32 then + raise Zip::EntryError, "`#{cfr.zip_path}': CRC32 differs between local and central file records" + end + if lfr.compressed_size != cfr.compressed_size then + raise Zip::EntryError, "`#{cfr.zip_path}': compressed size differs between local and central file records" + end + if lfr.uncompressed_size != cfr.uncompressed_size then + raise Zip::EntryError, "`#{cfr.zip_path}': uncompressed size differs between local and central file records" + end + if lfr.general_purpose_flags != cfr.general_purpose_flags then + raise Zip::EntryError, "`#{cfr.zip_path}': general purpose flag differs between local and central file records" + end + if lfr.compression_method != cfr.compression_method then + raise Zip::EntryError, "`#{cfr.zip_path}': compression method differs between local and central file records" + end + if lfr.mtime != cfr.mtime then + raise Zip::EntryError, "`#{cfr.zip_path}': last modified time differs between local and central file records" + end + end + + public + + # Creates a new, uninitialized Entry instance using the Store compression + # method. The zip path is initialized to _zip_path_. _raw_data_, if + # specified, must be a readable, IO-like object containing possibly + # compressed/encrypted file data for the entry. It is intended to be used + # primarily by the parse class method. + def initialize(zip_path, raw_data = nil) + self.zip_path = zip_path + self.mtime = Time.now + self.atime = @mtime + self.uid = nil + self.gid = nil + self.mode = 0777 + self.comment = '' + self.expected_data_descriptor = nil + self.compression_codec = Zip::Codec::Store.new + self.encryption_codec = Zip::Codec::NullEncryption.new + @raw_data = raw_data + self.password = nil + @extra_fields = [] + end + + # The path for this entry in the ZIP archive. + attr_reader :zip_path + # The last accessed time. + attr_accessor :atime + # The last modified time. + attr_accessor :mtime + # The user ID of the owner of this entry. + attr_accessor :uid + # The group ID of the owner of this entry. + attr_accessor :gid + # The file mode/permission bits for this entry. + attr_accessor :mode + # The comment associated with this entry. + attr_accessor :comment + # An Archive::Zip::DataDescriptor instance which should contain the expected + # CRC32 checksum, compressed size, and uncompressed size for the file data. + # When not +nil+, this is used by #extract to confirm that the data + # extraction was successful. + attr_accessor :expected_data_descriptor + # The selected compression codec. + attr_accessor :compression_codec + # The selected encryption codec. + attr_accessor :encryption_codec + # The password used with the encryption codec to encrypt or decrypt the file + # data for an entry. + attr_accessor :password + # The raw, possibly compressed and/or encrypted file data for an entry. + attr_accessor :raw_data + + # Sets the path in the archive for this entry to _zip_path_ after passing it + # through Archive::Zip::Entry.expand_path and ensuring that the result is + # not empty. + def zip_path=(zip_path) + @zip_path = Archive::Zip::Entry.expand_path(zip_path) + if @zip_path.empty? then + raise ArgumentError, "zip path expands to empty string" + end + end + + # Returns the file type of this entry as the symbol :unknown. + # + # Override this in concrete subclasses to return an appropriate symbol. + def ftype + :unknown + end + + # Returns false. + def file? + false + end + + # Returns false. + def symlink? + false + end + + # Returns false. + def directory? + false + end + + # Adds _extra_field_ as an extra field specification to *both* the central + # file record and the local file record of this entry. + # + # If _extra_field_ is an instance of + # Archive::Zip::Entry::ExtraField::ExtendedTimestamp, the values of that + # field are used to set mtime and atime for this entry. If _extra_field_ is + # an instance of Archive::Zip::Entry::ExtraField::Unix, the values of that + # field are used to set mtime, atime, uid, and gid for this entry. + def add_extra_field(extra_field) + # Try to find an extra field with the same header ID already in the list + # and merge the new one with that if one exists; otherwise, add the new + # one to the list. + existing_extra_field = @extra_fields.find do |ef| + ef.header_id == extra_field.header_id + end + if existing_extra_field.nil? then + @extra_fields << extra_field + else + extra_field = existing_extra_field.merge(extra_field) + end + + # Set some attributes of this entry based on the settings in select types + # of extra fields. + if extra_field.kind_of?(ExtraField::ExtendedTimestamp) then + self.mtime = extra_field.mtime unless extra_field.mtime.nil? + self.atime = extra_field.atime unless extra_field.atime.nil? + elsif extra_field.kind_of?(ExtraField::Unix) then + self.mtime = extra_field.mtime unless extra_field.mtime.nil? + self.atime = extra_field.atime unless extra_field.atime.nil? + self.uid = extra_field.uid unless extra_field.uid.nil? + self.gid = extra_field.gid unless extra_field.uid.nil? + end + self + end + + # Writes the local file record for this entry to _io_, a writable, IO-like + # object which provides a _write_ method. _local_file_record_position_ is + # the offset within _io_ at which writing will begin. This is used so that + # when writing to a non-seekable IO object it is possible to avoid calling + # the _pos_ method of _io_. Returns the number of bytes written. + # + # NOTE: This method should only be called by Archive::Zip. + def dump_local_file_record(io, local_file_record_position) + @local_file_record_position = local_file_record_position + bytes_written = 0 + + # Assume that no trailing data descriptor will be necessary. + need_trailing_data_descriptor = false + begin + io.pos + rescue Errno::ESPIPE + # A trailing data descriptor is required for non-seekable IO. + need_trailing_data_descriptor = true + end + if encryption_codec.class == Codec::TraditionalEncryption then + # HACK: + # According to the ZIP specification, a trailing data descriptor should + # only be required when writing to non-seekable IO , but InfoZIP + # *always* does this when using traditional encryption even though it + # will also write the data descriptor in the usual place if possible. + # Failure to emulate InfoZIP in this behavior will prevent InfoZIP + # compatibility with traditionally encrypted entries. + need_trailing_data_descriptor = true + # HACK: + # The InfoZIP implementation of traditional encryption requires that the + # the last modified file time be used as part of the encryption header. + # This is a deviation from the ZIP specification. + encryption_codec.mtime = mtime + end + + # Set the general purpose flags. + general_purpose_flags = compression_codec.general_purpose_flags + general_purpose_flags |= encryption_codec.general_purpose_flags + if need_trailing_data_descriptor then + general_purpose_flags |= FLAG_DATA_DESCRIPTOR_FOLLOWS + end + + # Select the minimum ZIP specification version needed to extract this + # entry. + version_needed_to_extract = compression_codec.version_needed_to_extract + if encryption_codec.version_needed_to_extract > version_needed_to_extract then + version_needed_to_extract = encryption_codec.version_needed_to_extract + end + + # Write the data. + bytes_written += io.write(LFH_SIGNATURE) + extra_field_data = local_extra_field_data + bytes_written += io.write( + [ + version_needed_to_extract, + general_purpose_flags, + compression_codec.compression_method, + mtime.to_dos_time.to_i, + 0, + 0, + 0, + zip_path.bytesize, + extra_field_data.length + ].pack('vvvVVVVvv') + ) + bytes_written += io.write(zip_path) + bytes_written += io.write(extra_field_data) + + # Pipeline a compressor into an encryptor, write all the file data to the + # compressor, and get a data descriptor from it. + encryption_codec.encryptor(io, password) do |e| + compression_codec.compressor(e) do |c| + dump_file_data(c) + c.close(false) + @data_descriptor = DataDescriptor.new( + c.data_descriptor.crc32, + c.data_descriptor.compressed_size + encryption_codec.header_size, + c.data_descriptor.uncompressed_size + ) + end + e.close(false) + end + bytes_written += @data_descriptor.compressed_size + + # Write the trailing data descriptor if necessary. + if need_trailing_data_descriptor then + bytes_written += io.write(DD_SIGNATURE) + bytes_written += @data_descriptor.dump(io) + end + + begin + # Update the data descriptor located before the compressed data for the + # entry. + saved_position = io.pos + io.pos = @local_file_record_position + 14 + @data_descriptor.dump(io) + io.pos = saved_position + rescue Errno::ESPIPE + # Ignore a failed attempt to update the data descriptor. + end + + bytes_written + end + + # Writes the central file record for this entry to _io_, a writable, IO-like + # object which provides a _write_ method. Returns the number of bytes + # written. + # + # NOTE: This method should only be called by Archive::Zip. + def dump_central_file_record(io) + bytes_written = 0 + + # Assume that no trailing data descriptor will be necessary. + need_trailing_data_descriptor = false + begin + io.pos + rescue Errno::ESPIPE + # A trailing data descriptor is required for non-seekable IO. + need_trailing_data_descriptor = true + end + if encryption_codec.class == Codec::TraditionalEncryption then + # HACK: + # According to the ZIP specification, a trailing data descriptor should + # only be required when writing to non-seekable IO , but InfoZIP + # *always* does this when using traditional encryption even though it + # will also write the data descriptor in the usual place if possible. + # Failure to emulate InfoZIP in this behavior will prevent InfoZIP + # compatibility with traditionally encrypted entries. + need_trailing_data_descriptor = true + end + + # Set the general purpose flags. + general_purpose_flags = compression_codec.general_purpose_flags + general_purpose_flags |= encryption_codec.general_purpose_flags + if need_trailing_data_descriptor then + general_purpose_flags |= FLAG_DATA_DESCRIPTOR_FOLLOWS + end + + # Select the minimum ZIP specification version needed to extract this + # entry. + version_needed_to_extract = compression_codec.version_needed_to_extract + if encryption_codec.version_needed_to_extract > version_needed_to_extract then + version_needed_to_extract = encryption_codec.version_needed_to_extract + end + + # Write the data. + bytes_written += io.write(CFH_SIGNATURE) + bytes_written += io.write( + [ + version_made_by, + version_needed_to_extract, + general_purpose_flags, + compression_codec.compression_method, + mtime.to_dos_time.to_i + ].pack('vvvvV') + ) + bytes_written += @data_descriptor.dump(io) + extra_field_data = central_extra_field_data + bytes_written += io.write( + [ + zip_path.bytesize, + extra_field_data.length, + comment.length, + 0, + internal_file_attributes, + external_file_attributes, + @local_file_record_position + ].pack('vvvvvVV') + ) + bytes_written += io.write(zip_path) + bytes_written += io.write(extra_field_data) + bytes_written += io.write(comment) + + bytes_written + end + + private + + def version_made_by + 0x0314 + end + + def central_extra_field_data + @central_extra_field_data = @extra_fields.collect do |extra_field| + extra_field.dump_central + end.join + end + + def dummy + # Add fields for time data if available. + unless mtime.nil? && atime.nil? then + @central_extra_field_data += + ExtraField::ExtendedTimestamp.new(mtime, atime, nil).dump_central + end + + # Add fields for user and group ownerships if available. + unless uid.nil? || gid.nil? || mtime.nil? || atime.nil? then + @central_extra_field_data += ExtraField::Unix.new( + mtime, atime, uid, gid + ).dump_central + end + end + + def local_extra_field_data + @local_extra_field_data = @extra_fields.collect do |extra_field| + extra_field.dump_local + end.join + end + + def internal_file_attributes + 0x0000 + end + + def external_file_attributes + # Put Unix attributes into the high word and DOS attributes into the low + # word. + (mode << 16) + (directory? ? 0x10 : 0) + end + end +end; end + +module Archive; class Zip; module Entry + # Archive::Zip::Entry::Directory represents a directory entry within a Zip + # archive. + class Directory + include Archive::Zip::Entry + + # Inherits the behavior of Archive::Zip::Entry#zip_path= but ensures that + # there is a trailing slash (/) on the end of the path. + def zip_path=(zip_path) + super(zip_path) + @zip_path += '/' + end + + # Returns the file type of this entry as the symbol :directory. + def ftype + :directory + end + + # Returns +true+. + def directory? + true + end + + # Overridden in order to ensure that the proper mode bits are set for a + # directory. + def mode=(mode) + super(040000 | (mode & 07777)) + end + + # Extracts this entry. + # + # _options_ is a Hash optionally containing the following: + # :file_path:: + # Specifies the path to which this entry will be extracted. Defaults to + # the zip path of this entry. + # :permissions:: + # When set to +false+ (the default), POSIX mode/permission bits will be + # ignored. Otherwise, they will be restored if possible. + # :ownerships:: + # When set to +false+ (the default), user and group ownerships will be + # ignored. On most systems, only a superuser is able to change + # ownerships, so setting this option to +true+ as a regular user may have + # no effect. + # :times:: + # When set to +false+ (the default), last accessed and last modified times + # will be ignored. Otherwise, they will be restored if possible. + def extract(options = {}) + # Ensure that unspecified options have default values. + file_path = options.has_key?(:file_path) ? + options[:file_path].to_s : + @zip_path + restore_permissions = options.has_key?(:permissions) ? + options[:permissions] : + false + restore_ownerships = options.has_key?(:ownerships) ? + options[:ownerships] : + false + restore_times = options.has_key?(:times) ? + options[:times] : + false + + # Make the directory. + FileUtils.mkdir_p(file_path) + + # Restore the metadata. + ::File.chmod(mode, file_path) if restore_permissions + ::File.chown(uid, gid, file_path) if restore_ownerships + ::File.utime(atime, mtime, file_path) if restore_times + + nil + end + + private + + # Directory entries do not have file data to write, so do nothing. + def dump_file_data(io) + end + end +end; end; end + +module Archive; class Zip; module Entry + # Archive::Zip::Entry::Symlink represents a symlink entry withing a Zip + # archive. + class Symlink + include Archive::Zip::Entry + + # Returns the file type of this entry as the symbol :symlink. + def ftype + :symlink + end + + # Returns +true+. + def symlink? + true + end + + # Overridden in order to ensure that the proper mode bits are set for a + # symlink. + def mode=(mode) + super(0120000 | (mode & 07777)) + end + + # Returns the link target for this entry. + # + # Raises Archive::Zip::EntryError if decoding the link target from an + # archive is required but fails. + def link_target + return @link_target unless @link_target.nil? + + raw_data.rewind + encryption_codec.decryptor(raw_data, password) do |decryptor| + compression_codec.decompressor(decryptor) do |decompressor| + @link_target = decompressor.read + # Verify that the extracted data is good. + begin + unless expected_data_descriptor.nil? then + expected_data_descriptor.verify(decompressor.data_descriptor) + end + rescue => e + raise Zip::EntryError, "`#{zip_path}': #{e.message}" + end + end + end + @link_target + end + + # Sets the link target for this entry. As a side effect, the raw_data + # attribute is set to +nil+. + def link_target=(link_target) + self.raw_data = nil + @link_target = link_target + end + + # Extracts this entry. + # + # _options_ is a Hash optionally containing the following: + # :file_path:: + # Specifies the path to which this entry will be extracted. Defaults to + # the zip path of this entry. + # :permissions:: + # When set to +false+ (the default), POSIX mode/permission bits will be + # ignored. Otherwise, they will be restored if possible. Not supported + # on all platforms. + # :ownerships:: + # When set to +false+ (the default), user and group ownerships will be + # ignored. On most systems, only a superuser is able to change + # ownerships, so setting this option to +true+ as a regular user may have + # no effect. Not supported on all platforms. + # + # Raises Archive::Zip::EntryError if the link_target attribute is not + # specified. + def extract(options = {}) + raise Zip::EntryError, 'link_target is nil' if link_target.nil? + + # Ensure that unspecified options have default values. + file_path = options.has_key?(:file_path) ? + options[:file_path].to_s : + @zip_path + restore_permissions = options.has_key?(:permissions) ? + options[:permissions] : + false + restore_ownerships = options.has_key?(:ownerships) ? + options[:ownerships] : + false + + # Create the containing directory tree if necessary. + parent_dir = ::File.dirname(file_path) + FileUtils.mkdir_p(parent_dir) unless ::File.exist?(parent_dir) + + # Create the symlink. + ::File.symlink(link_target, file_path) + + # Restore the metadata. + # NOTE: Ruby does not have the ability to restore atime and mtime on + # symlinks at this time (version 1.8.6). + begin + ::File.lchmod(mode, file_path) if restore_permissions + rescue NotImplementedError + # Ignore on platforms that do not support lchmod. + end + begin + ::File.lchown(uid, gid, file_path) if restore_ownerships + rescue NotImplementedError + # Ignore on platforms that do not support lchown. + end + + nil + end + + private + + # Write the link target to _io_ as the file data for the entry. + def dump_file_data(io) + io.write(@link_target) + end + end +end; end; end + +module Archive; class Zip; module Entry + # Archive::Zip::Entry::File represents a file entry within a Zip archive. + class File + include Archive::Zip::Entry + + # Creates a new file entry where _zip_path_ is the path to the entry in the + # ZIP archive. The Archive::Zip::Codec::Deflate codec with the default + # compression level set (NORMAL) is used by default for compression. + # _raw_data_, if specified, must be a readable, IO-like object containing + # possibly compressed/encrypted file data for the entry. It is intended to + # be used primarily by the Archive::Zip::Entry.parse class method. + def initialize(zip_path, raw_data = nil) + super(zip_path, raw_data) + @file_path = nil + @file_data = nil + @compression_codec = Zip::Codec::Deflate.new + end + + # Returns the file type of this entry as the symbol :file. + def ftype + :file + end + + # Returns +true+. + def file? + true + end + + # Overridden in order to ensure that the proper mode bits are set for a + # file. + def mode=(mode) + super(0100000 | (mode & 07777)) + end + + # Sets the decryption password. + def password=(password) + unless @raw_data.nil? then + @file_data = nil + end + @password = password + end + + # The path to a file whose contents are to be used for uncompressed file + # data. This will be +nil+ if the +file_data+ attribute is set directly. + attr_reader :file_path + + # Sets the +file_path+ attribute to _file_path_ which should be a String + # usable with File#new to open a file for reading which will provide the + # IO-like object for the +file_data+ attribute. + def file_path=(file_path) + @file_data = nil + @raw_data = nil + @file_path = file_path + end + + # Returns a readable, IO-like object containing uncompressed file data. + # + # NOTE: It is the responsibility of the user of this attribute to + # ensure that the #close method of the returned IO-like object is called + # when the object is no longer needed. + def file_data + return @file_data unless @file_data.nil? || @file_data.closed? + + unless raw_data.nil? then + raw_data.rewind + @file_data = compression_codec.decompressor( + encryption_codec.decryptor(raw_data, password) + ) + else + if @file_path.nil? then + simulated_raw_data = BinaryStringIO.new + else + simulated_raw_data = ::File.new(@file_path, 'rb') + end + # Ensure that the IO-like object can return a data descriptor so that + # it's possible to verify extraction later if desired. + @file_data = Zip::Codec::Store.new.decompressor(simulated_raw_data) + end + @file_data + end + + # Sets the +file_data+ attribute of this object to _file_data_. _file_data_ + # must be a readable, IO-like object. + # + # NOTE: As a side effect, the +file_path+ and +raw_data+ attributes + # for this object will be set to +nil+. + def file_data=(file_data) + @file_path = nil + self.raw_data = nil + @file_data = file_data + # Ensure that the IO-like object can return CRC32 and data size + # information so that it's possible to verify extraction later if desired. + unless @file_data.respond_to?(:data_descriptor) then + @file_data = Zip::Codec::Store.new.decompressor(@file_data) + end + @file_data + end + + # Extracts this entry. + # + # _options_ is a Hash optionally containing the following: + # :file_path:: + # Specifies the path to which this entry will be extracted. Defaults to + # the zip path of this entry. + # :permissions:: + # When set to +false+ (the default), POSIX mode/permission bits will be + # ignored. Otherwise, they will be restored if possible. + # :ownerships:: + # When set to +false+ (the default), user and group ownerships will be + # ignored. On most systems, only a superuser is able to change + # ownerships, so setting this option to +true+ as a regular user may have + # no effect. + # :times:: + # When set to +false+ (the default), last accessed and last modified times + # will be ignored. Otherwise, they will be restored if possible. + # + # Raises Archive::Zip::EntryError if the extracted file data appears + # corrupt. + def extract(options = {}) + # Ensure that unspecified options have default values. + file_path = options.has_key?(:file_path) ? + options[:file_path].to_s : + @zip_path + restore_permissions = options.has_key?(:permissions) ? + options[:permissions] : + false + restore_ownerships = options.has_key?(:ownerships) ? + options[:ownerships] : + false + restore_times = options.has_key?(:times) ? + options[:times] : + false + + # Create the containing directory tree if necessary. + parent_dir = ::File.dirname(file_path) + FileUtils.mkdir_p(parent_dir) unless ::File.exist?(parent_dir) + + # Dump the file contents. + ::File.open(file_path, 'wb') do |f| + while buffer = file_data.read(4096) do + f.write(buffer) + end + end + + # Verify that the extracted data is good. + begin + unless expected_data_descriptor.nil? then + expected_data_descriptor.verify(file_data.data_descriptor) + end + rescue => e + raise Zip::EntryError, "`#{zip_path}': #{e.message}" + end + + # Restore the metadata. + ::File.chmod(mode, file_path) if restore_permissions + ::File.chown(uid, gid, file_path) if restore_ownerships + ::File.utime(atime, mtime, file_path) if restore_times + + # Attempt to rewind the file data back to the beginning, but ignore + # errors. + begin + file_data.rewind + rescue + # Ignore. + end + + nil + end + + private + + # Write the file data to _io_. + def dump_file_data(io) + while buffer = file_data.read(4096) do io.write(buffer) end + + # Attempt to ensure that the file data will still be in a readable state + # at the beginning of the data for the next user, but close it if possible + # in order to conserve resources. + if file_path.nil? then + # When the file_path attribute is not set, the file_data method cannot + # reinitialize the IO object it returns, so attempt to rewind the file + # data back to the beginning, but ignore errors. + begin + file_data.rewind + rescue + # Ignore. + end + else + # Since the file_path attribute is set, the file_data method will + # reinitialize the IO object it returns if we close the object here. + file_data.close + end + end + end +end; end; end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/error.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/error.rb new file mode 100644 index 00000000..8f7910f0 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/error.rb @@ -0,0 +1,24 @@ +# encoding: UTF-8 + +module Archive; class Zip; + # Archive::Zip::Error is the base class of all archive-related errors raised + # by the Archive::Zip library. + class Error < StandardError; end + + # Archive::Zip::EntryError is raised when there is an error while processing + # ZIP archive entries. + class EntryError < Error; end + + # Archive::Zip::ExtraFieldError is raised when there is an error while + # processing an extra field in a ZIP archive entry. + class ExtraFieldError < Error; end + + # Archive::Zip::IOError is raised in various places where either an IOError is + # raised or an operation on an Archive::Zip object is disallowed because of + # the state of the object. + class IOError < Error; end + + # Archive::Zip::UnzipError is raised when attempting to extract an archive + # fails but no more exact error class exists for reporting the error. + class UnzipError < Error; end +end; end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/extra_field.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/extra_field.rb new file mode 100644 index 00000000..168c85a0 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/extra_field.rb @@ -0,0 +1,37 @@ +# encoding: UTF-8 + +module Archive; class Zip + module ExtraField + # A Hash used to map extra field header identifiers to extra field classes. + EXTRA_FIELDS = {} + + # Returns an instance of an extra field class by selecting the class using + # _header_id_ and passing _data_ to the class' _parse_central_ method. If + # there is no mapping from a given value of _header_id_ to an extra field + # class, an instance of Archive::Zip::Entry::ExtraField::Raw is returned. + def self.parse_central(header_id, data) + if EXTRA_FIELDS.has_key?(header_id) then + EXTRA_FIELDS[header_id].parse_central(data) + else + Raw.parse_central(header_id, data) + end + end + + # Returns an instance of an extra field class by selecting the class using + # _header_id_ and passing _data_ to the class' _parse_local_ method. If + # there is no mapping from a given value of _header_id_ to an extra field + # class, an instance of Archive::Zip::Entry::ExtraField::Raw is returned. + def self.parse_local(header_id, data) + if EXTRA_FIELDS.has_key?(header_id) then + EXTRA_FIELDS[header_id].parse_local(data) + else + Raw.parse_local(header_id, data) + end + end + end +end; end + +# Load the standard extra field classes. +require 'archive/zip/extra_field/extended_timestamp' +require 'archive/zip/extra_field/raw' +require 'archive/zip/extra_field/unix' diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/extra_field/extended_timestamp.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/extra_field/extended_timestamp.rb new file mode 100644 index 00000000..7484e57b --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/extra_field/extended_timestamp.rb @@ -0,0 +1,182 @@ +# encoding: UTF-8 + +require 'archive/zip/error' + +module Archive; class Zip; module ExtraField + # Archive::Zip::Entry::ExtraField::ExtendedTimestamp represents an extra field + # which optionally contains the last modified time, last accessed time, and + # file creation time for a ZIP archive entry and stored in a Unix time format + # (seconds since the epoc). + class ExtendedTimestamp + # The identifier reserved for this extra field type. + ID = 0x5455 + + # Register this extra field for use. + EXTRA_FIELDS[ID] = self + + class << self + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for extra field objects. + # + # Parses _data_ which is expected to be a String formatted according to + # the documentation provided with InfoZip's sources. + # + # Raises Archive::Zip::ExtraFieldError if _data_ contains invalid data. + def parse_central(data) + unless data.size == 5 || data.size == 9 || data.size == 13 then + raise Zip::ExtraFieldError, + "invalid size for extended timestamp: #{data.size}" + end + flags, *times = data.unpack('CV*') + mtime = nil + atime = nil + crtime = nil + if flags & 0b001 != 0 then + if times.size == 0 then + # Report an error if the flags indicate that the last modified time + # field should be present when it is not. + raise Zip::ExtraFieldError, + 'corrupt extended timestamp: last modified time field not present' + end + mtime = Time.at(times.shift) + end + if flags & 0b010 != 0 then + # If parsing the central file record version of this field, this flag + # may be set without having the corresponding time value. + # Use the time value if available, but ignore it if it's missing. + if times.size > 0 then + atime = Time.at(times.shift) + end + end + if flags & 0b100 != 0 then + # If parsing the central file record version of this field, this flag + # may be set without having the corresponding time value. + # Use the time value if available, but ignore it if it's missing. + if times.size > 0 then + crtime = Time.at(times.shift) + end + end + new(mtime, atime, crtime) + end + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for extra field objects. + # + # Parses _data_ which is expected to be a String formatted according to + # the documentation provided with InfoZip's sources. + # + # Raises Archive::Zip::ExtraFieldError if _data_ contains invalid data. + def parse_local(data) + unless data.size == 5 || data.size == 9 || data.size == 13 then + raise Zip::ExtraFieldError, + "invalid size for extended timestamp: #{data.size}" + end + flags, *times = data.unpack('CV*') + mtime = nil + atime = nil + crtime = nil + if flags & 0b001 != 0 then + if times.size == 0 then + # Report an error if the flags indicate that the last modified time + # field should be present when it is not. + raise Zip::ExtraFieldError, + 'corrupt extended timestamp: last modified time field not present' + end + mtime = Time.at(times.shift) + end + if flags & 0b010 != 0 then + if times.size == 0 then + # Report an error if the flags indicate that the last modified time + # field should be present when it is not. + raise Zip::ExtraFieldError, + 'corrupt extended timestamp: last accessed time field not present' + end + atime = Time.at(times.shift) + end + if flags & 0b100 != 0 then + if times.size == 0 then + # Report an error if the flags indicate that the file creation time + # field should be present when it is not. + raise Zip::ExtraFieldError, + 'corrupt extended timestamp: file creation time field not present' + end + crtime = Time.at(times.shift) + end + new(mtime, atime, crtime) + end + end + + # Creates a new instance of this class. _mtime_, _atime_, and _crtime_ + # should be Time instances or +nil+. When set to +nil+ the field is + # considered to be unset and will not be stored in the archive. + def initialize(mtime, atime, crtime) + @header_id = ID + self.mtime = mtime unless mtime.nil? + self.atime = atime unless atime.nil? + self.crtime = crtime unless crtime.nil? + end + + # Returns the header ID for this ExtraField. + attr_reader :header_id + # The last modified time for an entry. Set to either a Time instance or + # +nil+. + attr_accessor :mtime + # The last accessed time for an entry. Set to either a Time instance or + # +nil+. + attr_accessor :atime + # The creation time for an entry. Set to either a Time instance or +nil+. + attr_accessor :crtime + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for extra field objects. + # + # Merges the attributes of _other_ into this object and returns +self+. + # + # Raises ArgumentError if _other_ is not the same class as this object. + def merge(other) + if self.class != other.class then + raise ArgumentError, "#{self.class} is not the same as #{other.class}" + end + + @mtime = other.mtime unless other.mtime.nil? + @atime = other.atime unless other.atime.nil? + @crtime = other.crtime unless other.crtime.nil? + + self + end + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for extra field objects. + # + # Returns a String suitable to writing to a central file record in a ZIP + # archive file which contains the data for this object. + def dump_central + times = [] + times << mtime.to_i unless mtime.nil? + ([ID, 4 * times.size + 1, flags] + times).pack('vvC' + 'V' * times.size) + end + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for extra field objects. + # + # Returns a String suitable to writing to a local file record in a ZIP + # archive file which contains the data for this object. + def dump_local + times = [] + times << mtime.to_i unless mtime.nil? + times << atime.to_i unless atime.nil? + times << crtime.to_i unless crtime.nil? + ([ID, 4 * times.size + 1, flags] + times).pack('vvC' + 'V' * times.size) + end + + private + + def flags + flags = 0 + flags |= 0b001 unless mtime.nil? + flags |= 0b010 unless atime.nil? + flags |= 0b100 unless crtime.nil? + flags + end + end +end; end; end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/extra_field/raw.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/extra_field/raw.rb new file mode 100644 index 00000000..99681dd3 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/extra_field/raw.rb @@ -0,0 +1,90 @@ +# encoding: UTF-8 + +module Archive; class Zip; module ExtraField + # Archive::Zip::Entry::ExtraField::Raw represents an unknown extra field. It + # is used to store extra fields the Archive::Zip library does not directly + # support. + # + # Do not use this class directly. Define a new class which supports the extra + # field of interest directly instead. + class Raw + class << self + # Simply stores _header_id_ and _data_ for later reproduction by + # #dump_central. + # This is essentially and alias for #new. + def parse_central(header_id, data) + new(header_id, data, true) + end + + # Simply stores _header_id_ and _data_ for later reproduction by + # #dump_local. + # This is essentially and alias for #new. + def parse_local(header_id, data) + new(header_id, data, false) + end + end + + # Simply stores _header_id_ and _data_ for later reproduction by + # #dump_central or #dump_local. _central_record_ indicates that this field + # resides in the central file record for an entry when +true+. When + # +false+, it indicates that this field resides in the local file record for + # an entry. + def initialize(header_id, data, central_record) + @header_id = header_id + @central_record_data = [] + @local_record_data = [] + if central_record then + @central_record_data << data + else + @local_record_data << data + end + end + + # Returns the header ID for this ExtraField. + attr_reader :header_id + # Returns the data contained within this ExtraField. + attr_reader :central_record_data + attr_reader :local_record_data + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for extra field objects. + # + # Merges the attributes of _other_ into this object and returns +self+. + # + # Raises ArgumentError if _other_ does not have the same header ID as this + # object. + def merge(other) + if header_id != other.header_id then + raise ArgumentError, + "Header ID mismatch: #{header_id} != #{other.header_id}" + end + + @central_record_data += other.central_record_data + @local_record_data += other.local_record_data + + self + end + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for extra field objects. + # + # Returns a String suitable to writing to a central file record in a ZIP + # archive file which contains the data for this object. + def dump_central + @central_record_data.collect do |data| + [header_id, data.size].pack('vv') + data + end + end + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for extra field objects. + # + # Returns a String suitable to writing to a local file record in a ZIP + # archive file which contains the data for this object. + def dump_local + @local_record_data.collect do |data| + [header_id, data.size].pack('vv') + data + end + end + end +end; end; end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/extra_field/unix.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/extra_field/unix.rb new file mode 100644 index 00000000..b0f2f3b6 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/extra_field/unix.rb @@ -0,0 +1,140 @@ +# encoding: UTF-8 + +require 'archive/zip/error' + +module Archive; class Zip; module ExtraField + # Archive::Zip::Entry::ExtraField::Unix represents an extra field which + # contains the last modified time, last accessed time, user name, and group + # name for a ZIP archive entry. Times are in Unix time format (seconds since + # the epoc). + # + # This class also optionally stores either major and minor numbers for devices + # or a link target for either hard or soft links. Which is in use when given + # and instance of this class depends upon the external file attributes for the + # ZIP archive entry associated with this extra field. + class Unix + # The identifier reserved for this extra field type. + ID = 0x000d + + # Register this extra field for use. + EXTRA_FIELDS[ID] = self + + class << self + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for extra field objects. + # + # Parses _data_ which is expected to be a String formatted according to + # the official ZIP specification. + # + # Raises Archive::Zip::ExtraFieldError if _data_ contains invalid data. + def parse_central(data) + unless data.length >= 12 then + raise Zip::ExtraFieldError, "invalid size for Unix data: #{data.size}" + end + atime, mtime, uid, gid, rest = data.unpack('VVvva') + new(Time.at(mtime), Time.at(atime), uid, gid, rest) + end + alias :parse_local :parse_central + end + + # Creates a new instance of this class. _mtime_ and _atime_ should be Time + # instances. _uid_ and _gid_ should be user and group IDs as Integers + # respectively. _data_ should be a string containing either major and minor + # device numbers consecutively packed as little endian, 4-byte, unsigned + # integers (see the _V_ directive of Array#pack) or a path to use as a link + # target. + def initialize(mtime, atime, uid, gid, data = '') + @header_id = ID + @mtime = mtime + @atime = atime + @uid = uid + @gid = gid + @data = data + end + + # Returns the header ID for this ExtraField. + attr_reader :header_id + # A Time object representing the last accessed time for an entry. + attr_accessor :atime + # A Time object representing the last modified time for an entry. + attr_accessor :mtime + # An integer representing the user ownership for an entry. + attr_accessor :uid + # An integer representing the group ownership for an entry. + attr_accessor :gid + + # Attempts to return a two element array representing the major and minor + # device numbers which may be stored in the variable data section of this + # object. + def device_numbers + @data.unpack('VV') + end + + # Takes a two element array containing major and minor device numbers and + # stores the numbers into the variable data section of this object. + def device_numbers=(major_minor) + @data = major_minor.pack('VV') + end + + # Attempts to return a string representing the path of a file which is + # either a symlink or hard link target which may be stored in the variable + # data section of this object. + def link_target + @data + end + + # Takes a string containing the path to a file which is either a symlink or + # a hardlink target and stores it in the variable data section of this + # object. + def link_target=(link_target) + @data = link_target + end + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for extra field objects. + # + # Merges the attributes of _other_ into this object and returns +self+. + # + # Raises ArgumentError if _other_ is not the same class as this object. + def merge(other) + if self.class != other.class then + raise ArgumentError, "#{self.class} is not the same as #{other.class}" + end + + @atime = other.atime + @mtime = other.mtime + @uid = other.uid + @gid = other.gid + @data = other.data + + self + end + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for extra field objects. + # + # Returns a String suitable to writing to a local file record in a ZIP + # archive file which contains the data for this object. + def dump_local + [ + ID, + 12 + @data.size, + @atime.to_i, + @mtime.to_i, + @uid, + @gid + ].pack('vvVVvv') + @data + end + + # This method signature is part of the interface contract expected by + # Archive::Zip::Entry for extra field objects. + # + # Returns a String suitable to writing to a central file record in a ZIP + # archive file which contains the data for this object. + alias :dump_central :dump_local + + protected + + attr_reader :data + end +end; end; end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/version.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/version.rb new file mode 100644 index 00000000..dabacc76 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/lib/archive/zip/version.rb @@ -0,0 +1,6 @@ +# encoding: UTF-8 + +module Archive; class Zip + # The current version of this gem. + VERSION = '0.12.0' +end; end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/dos_time_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/dos_time_spec.rb new file mode 100644 index 00000000..0d734801 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/dos_time_spec.rb @@ -0,0 +1,113 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require 'archive/support/time' + +describe 'Archive::DOSTime.new' do + let(:epoc) { 0b0000000_0001_00001_00000_000000_00000 } + let(:end_times) { 0b1110111_1100_11111_11000_111011_11101 } + + it 'uses the current time when no structure is given' do + now = Time.now.localtime + dos_time = Archive::DOSTime.new.to_time + + now.year.must_be_close_to(dos_time.year, 1) + now.month.must_be_close_to(dos_time.month, 1) + now.day.must_be_close_to(dos_time.day, 1) + now.hour.must_be_close_to(dos_time.hour, 1) + now.min.must_be_close_to(dos_time.min, 1) + now.sec.must_be_close_to(dos_time.sec, 3) + end + + it 'accepts valid Integer structures' do + Archive::DOSTime.new(epoc) + Archive::DOSTime.new(end_times) + end + + it 'accepts valid String structures' do + Archive::DOSTime.new([epoc].pack('V')) + Archive::DOSTime.new([end_times].pack('V')) + end + + it 'rejects invalid Integer structures' do + # Second must not be greater than 29. + proc { + Archive::DOSTime.new(epoc | 0b0000000_0000_00000_00000_000000_11110) + }.must_raise(ArgumentError) + + # Minute must not be greater than 59. + proc { + Archive::DOSTime.new(epoc | 0b0000000_0000_00000_00000_111100_00000) + }.must_raise(ArgumentError) + + # Hour must not be greater than 24. + proc { + Archive::DOSTime.new(epoc | 0b0000000_0000_00000_11001_000000_00000) + }.must_raise(ArgumentError) + + # Day must not be zero. + proc { + Archive::DOSTime.new(epoc & 0b1111111_1111_00000_11111_111111_11111) + }.must_raise(ArgumentError) + + # Month must not be zero. + proc { + Archive::DOSTime.new(epoc & 0b1111111_0000_11111_11111_111111_11111) + }.must_raise(ArgumentError) + + # Month must not be greater than 12. + proc { + Archive::DOSTime.new(epoc | 0b0000000_1101_00000_00000_000000_00000) + }.must_raise(ArgumentError) + + # Year must not be greater than 119. + proc { + Archive::DOSTime.new(epoc | 0b1111000_0000_00000_00000_000000_00000) + }.must_raise(ArgumentError) + end + + it 'rejects invalid String structures' do + # Second must not be greater than 29. + proc { + packed = [epoc | 0b0000000_0000_00000_00000_000000_11110].pack('V') + Archive::DOSTime.new(packed) + }.must_raise(ArgumentError) + + # Minute must not be greater than 59. + proc { + packed = [epoc | 0b0000000_0000_00000_00000_111100_00000].pack('V') + Archive::DOSTime.new(packed) + }.must_raise(ArgumentError) + + # Hour must not be greater than 24. + proc { + packed = [epoc | 0b0000000_0000_00000_11001_000000_00000].pack('V') + Archive::DOSTime.new(packed) + }.must_raise(ArgumentError) + + # Day must not be zero. + proc { + packed = [epoc & 0b1111111_1111_00000_11111_111111_11111].pack('V') + Archive::DOSTime.new(packed) + }.must_raise(ArgumentError) + + # Month must not be zero. + proc { + packed = [epoc & 0b1111111_0000_11111_11111_111111_11111].pack('V') + Archive::DOSTime.new(packed) + }.must_raise(ArgumentError) + + # Month must not be greater than 12. + proc { + packed = [epoc | 0b0000000_1101_00000_00000_000000_00000].pack('V') + Archive::DOSTime.new(packed) + }.must_raise(ArgumentError) + + # Year must not be greater than 119. + proc { + packed = [epoc | 0b1111000_0000_00000_00000_000000_00000].pack('V') + Archive::DOSTime.new(packed) + }.must_raise(ArgumentError) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/archive_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/archive_spec.rb new file mode 100644 index 00000000..17c924e6 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/archive_spec.rb @@ -0,0 +1,54 @@ +# encoding: UTF-8 + +require 'minitest/autorun' +require 'tmpdir' + +require 'archive/zip' + +describe 'Archive::Zip#archive' do + it 'adds file entries' do + file_name = 'file' + Dir.mktmpdir('archive_zip#archive') do |dir| + file_path = File.join(dir, file_name) + File.open(file_path, 'wb') { |f| f.write('data') } + archive_file_path = File.join(dir, 'archive.zip') + + Archive::Zip.open(archive_file_path, 'w') do |a| + a.archive(file_path) + end + + Archive::Zip.open(archive_file_path, 'r') do |a| + entry = a.first + entry.wont_be_nil + entry.zip_path.must_equal(file_name) + entry.file?.must_equal(true) + entry.file_data.read.must_equal('data') + end + end + end + + it 'adds entries with multibyte names' do + unless Object.const_defined?(:Encoding) + skip("String encodings are not supported on current Ruby (#{RUBY_DESCRIPTION})") + end + + mb_file_name = '☂file☄' + Dir.mktmpdir('archive_zip#archive') do |dir| + mb_file_path = File.join(dir, mb_file_name) + File.open(mb_file_path, 'wb') { |f| f.write('data') } + archive_file_path = File.join(dir, 'archive.zip') + + Archive::Zip.open(archive_file_path, 'w') do |a| + a.archive(mb_file_path) + end + + Archive::Zip.open(archive_file_path, 'r') do |a| + entry = a.first + entry.wont_be_nil + entry.zip_path.must_equal(mb_file_name.dup.force_encoding('binary')) + entry.file?.must_equal(true) + entry.file_data.read.must_equal('data') + end + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/compress/checksum_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/compress/checksum_spec.rb new file mode 100644 index 00000000..fd2d2ff8 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/compress/checksum_spec.rb @@ -0,0 +1,44 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/deflate' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::Deflate::Compress#checksum" do + it "computes the CRC32 checksum" do + compressed_data = BinaryStringIO.new + closed_compressor = Archive::Zip::Codec::Deflate::Compress.open( + compressed_data, Zlib::DEFAULT_COMPRESSION + ) do |compressor| + compressor.write(DeflateSpecs.test_data) + compressor.flush + compressor.checksum.must_equal Zlib.crc32(DeflateSpecs.test_data) + compressor + end + closed_compressor.checksum.must_equal Zlib.crc32(DeflateSpecs.test_data) + end + + it "computes the CRC32 checksum even when the delegate performs partial writes" do + compressed_data = BinaryStringIO.new + # Override compressed_data.write to perform writes 1 byte at a time. + class << compressed_data + alias :write_orig :write + def write(buffer) + write_orig(buffer.slice(0, 1)) + end + end + + closed_compressor = Archive::Zip::Codec::Deflate::Compress.open( + compressed_data, Zlib::DEFAULT_COMPRESSION + ) do |compressor| + compressor.write(DeflateSpecs.test_data) + compressor.flush + compressor.checksum.must_equal Zlib.crc32(DeflateSpecs.test_data) + compressor + end + closed_compressor.checksum.must_equal Zlib.crc32(DeflateSpecs.test_data) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/compress/close_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/compress/close_spec.rb new file mode 100644 index 00000000..dc022802 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/compress/close_spec.rb @@ -0,0 +1,45 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/deflate' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::Deflate::Compress#close" do + it "closes the stream" do + c = Archive::Zip::Codec::Deflate::Compress.new( + BinaryStringIO.new, Zlib::DEFAULT_COMPRESSION + ) + c.close + c.closed?.must_equal true + end + + it "closes the delegate stream by default" do + delegate = MiniTest::Mock.new + delegate.expect(:write, 8, [String]) + delegate.expect(:close, nil) + c = Archive::Zip::Codec::Deflate::Compress.new( + delegate, Zlib::DEFAULT_COMPRESSION + ) + c.close + end + + it "optionally leaves the delegate stream open" do + delegate = MiniTest::Mock.new + delegate.expect(:write, 8, [String]) + delegate.expect(:close, nil) + c = Archive::Zip::Codec::Deflate::Compress.new( + delegate, Zlib::DEFAULT_COMPRESSION + ) + c.close(true) + + delegate = MiniTest::Mock.new + delegate.expect(:write, 8, [String]) + c = Archive::Zip::Codec::Deflate::Compress.new( + delegate, Zlib::DEFAULT_COMPRESSION + ) + c.close(false) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/compress/crc32_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/compress/crc32_spec.rb new file mode 100644 index 00000000..99a41b52 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/compress/crc32_spec.rb @@ -0,0 +1,23 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/deflate' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::Deflate::Compress#crc32" do + it "computes the CRC32 checksum" do + compressed_data = BinaryStringIO.new + closed_compressor = Archive::Zip::Codec::Deflate::Compress.open( + compressed_data, Zlib::DEFAULT_COMPRESSION + ) do |compressor| + compressor.write(DeflateSpecs.test_data) + compressor.flush + compressor.crc32.must_equal Zlib.crc32(DeflateSpecs.test_data) + compressor + end + closed_compressor.crc32.must_equal Zlib.crc32(DeflateSpecs.test_data) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/compress/data_descriptor_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/compress/data_descriptor_spec.rb new file mode 100644 index 00000000..ed89befb --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/compress/data_descriptor_spec.rb @@ -0,0 +1,74 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/deflate' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::Deflate::Compress#data_descriptor" do + it "is an instance of Archive::Zip::DataDescriptor" do + test_data = DeflateSpecs.test_data + compressed_data = BinaryStringIO.new + closed_compressor = Archive::Zip::Codec::Deflate::Compress.open( + compressed_data, Zlib::DEFAULT_COMPRESSION + ) do |compressor| + compressor.write(test_data) + compressor.flush + compressor.data_descriptor.class.must_equal(Archive::Zip::DataDescriptor) + compressor + end + closed_compressor.data_descriptor.class.must_equal( + Archive::Zip::DataDescriptor + ) + end + + it "has a crc32 attribute containing the CRC32 checksum" do + test_data = DeflateSpecs.test_data + compressed_data = BinaryStringIO.new + closed_compressor = Archive::Zip::Codec::Deflate::Compress.open( + compressed_data, Zlib::DEFAULT_COMPRESSION + ) do |compressor| + compressor.write(test_data) + compressor.flush + compressor.data_descriptor.crc32.must_equal(Zlib.crc32(test_data)) + compressor + end + closed_compressor.data_descriptor.crc32.must_equal(Zlib.crc32(test_data)) + end + + it "has a compressed_size attribute containing the size of the compressed data" do + test_data = DeflateSpecs.test_data + compressed_data = BinaryStringIO.new + closed_compressor = Archive::Zip::Codec::Deflate::Compress.open( + compressed_data, Zlib::DEFAULT_COMPRESSION + ) do |compressor| + compressor.write(test_data) + compressor.flush + compressor.data_descriptor.compressed_size.must_equal( + compressed_data.size + ) + compressor + end + closed_compressor.data_descriptor.compressed_size.must_equal( + compressed_data.size + ) + end + + it "has an uncompressed_size attribute containing the size of the input data" do + test_data = DeflateSpecs.test_data + compressed_data = BinaryStringIO.new + closed_compressor = Archive::Zip::Codec::Deflate::Compress.open( + compressed_data, Zlib::DEFAULT_COMPRESSION + ) do |compressor| + compressor.write(test_data) + compressor.flush + compressor.data_descriptor.uncompressed_size.must_equal(test_data.size) + compressor + end + closed_compressor.data_descriptor.uncompressed_size.must_equal( + test_data.size + ) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/compress/new_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/compress/new_spec.rb new file mode 100644 index 00000000..e28a168b --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/compress/new_spec.rb @@ -0,0 +1,39 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/deflate' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::Deflate::Compress.new" do + it "returns a new instance" do + c = Archive::Zip::Codec::Deflate::Compress.new( + BinaryStringIO.new, Zlib::DEFAULT_COMPRESSION + ) + c.must_be_instance_of(Archive::Zip::Codec::Deflate::Compress) + c.close + end + + it "allows level to be set" do + data = DeflateSpecs.test_data + compressed_data = BinaryStringIO.new + c = Archive::Zip::Codec::Deflate::Compress.new( + compressed_data, Zlib::DEFAULT_COMPRESSION + ) + c.write(data) + c.close + + compressed_data.string.must_equal(DeflateSpecs.compressed_data) + + compressed_data = BinaryStringIO.new + c = Archive::Zip::Codec::Deflate::Compress.new( + compressed_data, Zlib::NO_COMPRESSION + ) + c.write(data) + c.close + + compressed_data.string.must_equal(DeflateSpecs.compressed_data_nocomp) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/compress/open_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/compress/open_spec.rb new file mode 100644 index 00000000..fc5f2a02 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/compress/open_spec.rb @@ -0,0 +1,48 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/deflate' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::Deflate::Compress.open" do + it "returns a new instance when run without a block" do + c = Archive::Zip::Codec::Deflate::Compress.open( + BinaryStringIO.new, Zlib::DEFAULT_COMPRESSION + ) + c.must_be_instance_of(Archive::Zip::Codec::Deflate::Compress) + c.close + end + + it "executes a block with a new instance as an argument" do + Archive::Zip::Codec::Deflate::Compress.open( + BinaryStringIO.new, Zlib::DEFAULT_COMPRESSION + ) { |c| c.must_be_instance_of(Archive::Zip::Codec::Deflate::Compress) } + end + + it "closes the object after executing a block" do + Archive::Zip::Codec::Deflate::Compress.open( + BinaryStringIO.new, Zlib::DEFAULT_COMPRESSION + ) { |c| c }.closed?.must_equal(true) + end + + it "allows level to be set" do + data = DeflateSpecs.test_data + compressed_data = BinaryStringIO.new + Archive::Zip::Codec::Deflate::Compress.open( + compressed_data, Zlib::DEFAULT_COMPRESSION + ) { |c| c.write(data) } + + compressed_data.string.must_equal(DeflateSpecs.compressed_data) + + data = DeflateSpecs.test_data + compressed_data = BinaryStringIO.new + Archive::Zip::Codec::Deflate::Compress.open( + compressed_data, Zlib::NO_COMPRESSION + ) { |c| c.write(data) } + + compressed_data.string.must_equal(DeflateSpecs.compressed_data_nocomp) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/compress/write_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/compress/write_spec.rb new file mode 100644 index 00000000..f898b84f --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/compress/write_spec.rb @@ -0,0 +1,111 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/deflate' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::Deflate::Compress#write" do + it "calls the write method of the delegate" do + delegate = MiniTest::Mock.new + delegate.expect( + :write, DeflateSpecs.compressed_data.size, [DeflateSpecs.compressed_data] + ) + delegate.expect(:close, nil) + Archive::Zip::Codec::Deflate::Compress.open( + delegate, Zlib::DEFAULT_COMPRESSION + ) do |compressor| + compressor.write(DeflateSpecs.test_data) + end + end + + it "writes compressed data to the delegate" do + compressed_data = BinaryStringIO.new + Archive::Zip::Codec::Deflate::Compress.open( + compressed_data, Zlib::DEFAULT_COMPRESSION + ) do |compressor| + compressor.write(DeflateSpecs.test_data) + end + compressed_data.string.must_equal(DeflateSpecs.compressed_data) + end + + it "writes compressed data to a delegate that only performs partial writes" do + compressed_data = BinaryStringIO.new + # Override compressed_data.write to perform writes 1 byte at a time. + class << compressed_data + alias :write_orig :write + def write(buffer) + write_orig(buffer.slice(0, 1)) + end + end + + Archive::Zip::Codec::Deflate::Compress.open( + compressed_data, Zlib::DEFAULT_COMPRESSION + ) do |compressor| + compressor.write(DeflateSpecs.test_data) + end + compressed_data.string.must_equal(DeflateSpecs.compressed_data) + end + + it "writes compressed data to a delegate that raises Errno::EAGAIN" do + compressed_data = BinaryStringIO.new + # Override compressed_data.write to raise Errno::EAGAIN every other time + # it's called. + class << compressed_data + alias :write_orig :write + def write(buffer) + @error_raised ||= false + if @error_raised then + @error_raised = false + write_orig(buffer) + else + @error_raised = true + raise Errno::EAGAIN + end + end + end + + Archive::Zip::Codec::Deflate::Compress.open( + compressed_data, Zlib::DEFAULT_COMPRESSION + ) do |compressor| + begin + compressor.write(DeflateSpecs.test_data) + rescue Errno::EAGAIN + retry + end + end + compressed_data.string.must_equal(DeflateSpecs.compressed_data) + end + + it "writes compressed data to a delegate that raises Errno::EINTR" do + compressed_data = BinaryStringIO.new + # Override compressed_data.write to raise Errno::EINTR every other time it's + # called. + class << compressed_data + alias :write_orig :write + def write(buffer) + @error_raised ||= false + if @error_raised then + @error_raised = false + write_orig(buffer) + else + @error_raised = true + raise Errno::EINTR + end + end + end + + Archive::Zip::Codec::Deflate::Compress.open( + compressed_data, Zlib::DEFAULT_COMPRESSION + ) do |compressor| + begin + compressor.write(DeflateSpecs.test_data) + rescue Errno::EINTR + retry + end + end + compressed_data.string.must_equal(DeflateSpecs.compressed_data) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/decompress/checksum_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/decompress/checksum_spec.rb new file mode 100644 index 00000000..6a68b84e --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/decompress/checksum_spec.rb @@ -0,0 +1,20 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/deflate' + +describe "Archive::Zip::Deflate::Decompress#checksum" do + it "computes a CRC32 checksum" do + closed_decompressor = DeflateSpecs.compressed_data do |f| + Archive::Zip::Codec::Deflate::Decompress.open(f) do |decompressor| + decompressor.read + decompressor.checksum.must_equal(Zlib.crc32(DeflateSpecs.test_data)) + decompressor + end + end + closed_decompressor.checksum.must_equal(Zlib.crc32(DeflateSpecs.test_data)) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/decompress/close_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/decompress/close_spec.rb new file mode 100644 index 00000000..a245bcc3 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/decompress/close_spec.rb @@ -0,0 +1,34 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/deflate' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::Deflate::Decompress#close" do + it "closes the stream" do + c = Archive::Zip::Codec::Deflate::Decompress.new(BinaryStringIO.new) + c.close + c.closed?.must_equal(true) + end + + it "closes the delegate stream by default" do + delegate = MiniTest::Mock.new + delegate.expect(:close, nil) + c = Archive::Zip::Codec::Deflate::Decompress.new(delegate) + c.close + end + + it "optionally leaves the delegate stream open" do + delegate = MiniTest::Mock.new + delegate.expect(:close, nil) + c = Archive::Zip::Codec::Deflate::Decompress.new(delegate) + c.close(true) + + delegate = MiniTest::Mock.new + c = Archive::Zip::Codec::Deflate::Decompress.new(delegate) + c.close(false) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/decompress/crc32_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/decompress/crc32_spec.rb new file mode 100644 index 00000000..8ef61aa8 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/decompress/crc32_spec.rb @@ -0,0 +1,20 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/deflate' + +describe "Archive::Zip::Deflate::Decompress#crc32" do + it "computes a CRC32 checksum" do + closed_decompressor = DeflateSpecs.compressed_data do |f| + Archive::Zip::Codec::Deflate::Decompress.open(f) do |decompressor| + decompressor.read + decompressor.crc32.must_equal(Zlib.crc32(DeflateSpecs.test_data)) + decompressor + end + end + closed_decompressor.crc32.must_equal(Zlib.crc32(DeflateSpecs.test_data)) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/decompress/data_descriptor_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/decompress/data_descriptor_spec.rb new file mode 100644 index 00000000..ab4d239b --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/decompress/data_descriptor_spec.rb @@ -0,0 +1,74 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/deflate' + +describe "Archive::Zip::Codec::Deflate::Decompress#data_descriptor" do + it "is an instance of Archive::Zip::DataDescriptor" do + DeflateSpecs.compressed_data do |cd| + closed_decompressor = Archive::Zip::Codec::Deflate::Decompress.open( + cd + ) do |decompressor| + decompressor.read + decompressor.data_descriptor.must_be_instance_of( + Archive::Zip::DataDescriptor + ) + decompressor + end + closed_decompressor.data_descriptor.must_be_instance_of( + Archive::Zip::DataDescriptor + ) + end + end + + it "has a crc32 attribute containing the CRC32 checksum" do + crc32 = Zlib.crc32(DeflateSpecs.test_data) + DeflateSpecs.compressed_data do |cd| + closed_decompressor = Archive::Zip::Codec::Deflate::Decompress.open( + cd + ) do |decompressor| + decompressor.read + decompressor.data_descriptor.crc32.must_equal(crc32) + decompressor + end + closed_decompressor.data_descriptor.crc32.must_equal(crc32) + end + end + + it "has a compressed_size attribute containing the size of the compressed data" do + compressed_size = DeflateSpecs.compressed_data.size + DeflateSpecs.compressed_data do |cd| + closed_decompressor = Archive::Zip::Codec::Deflate::Decompress.open( + cd + ) do |decompressor| + decompressor.read + decompressor.data_descriptor.compressed_size.must_equal(compressed_size) + decompressor + end + closed_decompressor.data_descriptor.compressed_size.must_equal( + compressed_size + ) + end + end + + it "has an uncompressed_size attribute containing the size of the input data" do + uncompressed_size = DeflateSpecs.test_data.size + DeflateSpecs.compressed_data do |cd| + closed_decompressor = Archive::Zip::Codec::Deflate::Decompress.open( + cd + ) do |decompressor| + decompressor.read + decompressor.data_descriptor.uncompressed_size.must_equal( + uncompressed_size + ) + decompressor + end + closed_decompressor.data_descriptor.uncompressed_size.must_equal( + uncompressed_size + ) + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/decompress/new_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/decompress/new_spec.rb new file mode 100644 index 00000000..09c77640 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/decompress/new_spec.rb @@ -0,0 +1,16 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/deflate' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::Deflate::Decompress.new" do + it "returns a new instance" do + d = Archive::Zip::Codec::Deflate::Decompress.new(BinaryStringIO.new) + d.must_be_instance_of(Archive::Zip::Codec::Deflate::Decompress) + d.close + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/decompress/open_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/decompress/open_spec.rb new file mode 100644 index 00000000..c63a43e3 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/decompress/open_spec.rb @@ -0,0 +1,29 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/deflate' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::Deflate::Decompress.open" do + it "returns a new instance when run without a block" do + d = Archive::Zip::Codec::Deflate::Decompress.open(BinaryStringIO.new) + d.must_be_instance_of(Archive::Zip::Codec::Deflate::Decompress) + d.close + end + + it "executes a block with a new instance as an argument" do + Archive::Zip::Codec::Deflate::Decompress.open(BinaryStringIO.new) do |decompressor| + decompressor.must_be_instance_of(Archive::Zip::Codec::Deflate::Decompress) + end + end + + it "closes the object after executing a block" do + d = Archive::Zip::Codec::Deflate::Decompress.open(BinaryStringIO.new) do |decompressor| + decompressor + end + d.closed?.must_equal(true) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/fixtures/classes.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/fixtures/classes.rb new file mode 100644 index 00000000..8a793ee0 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/fixtures/classes.rb @@ -0,0 +1,25 @@ +# encoding: UTF-8 + +class DeflateSpecs + def self.compressed_data_nocomp(&b) + File.open( + File.join(File.dirname(__FILE__), 'compressed_file_nocomp.bin'), 'rb' + ) do |f| + f.read + end + end + + def self.compressed_data + File.open( + File.join(File.dirname(__FILE__), 'compressed_file.bin'), 'rb' + ) do |f| + block_given? ? yield(f) : f.read + end + end + + def self.test_data + File.open(File.join(File.dirname(__FILE__), 'raw_file.txt'), 'rb') do |f| + block_given? ? yield(f) : f.read + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/fixtures/compressed_file.bin b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/fixtures/compressed_file.bin new file mode 100644 index 00000000..05bd6dea --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/fixtures/compressed_file.bin @@ -0,0 +1 @@ +%A0E9?A\kjE5K o)+S.- ZJ[0u~*wC‘Dm%c$QT-ecxpX"m[K\ٝdƋ/nɁr)>e=`T \ No newline at end of file diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/fixtures/compressed_file_nocomp.bin b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/fixtures/compressed_file_nocomp.bin new file mode 100644 index 00000000..803d664b Binary files /dev/null and b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/fixtures/compressed_file_nocomp.bin differ diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/fixtures/raw_file.txt b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/fixtures/raw_file.txt new file mode 100644 index 00000000..60bb924e --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/deflate/fixtures/raw_file.txt @@ -0,0 +1,10 @@ +test +This is a file with test data. +The quick brown fox jumps over the lazy dog. + +Mary had a little lamb +Whose fleece was white as snow +And everywhere that Mary went +The lamb was sure to go + +She sells sea shells down by the sea shore. diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/decrypt/close_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/decrypt/close_spec.rb new file mode 100644 index 00000000..dc5d0a96 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/decrypt/close_spec.rb @@ -0,0 +1,34 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/null_encryption' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::NullEncryption::Decrypt#close" do + it "closes the stream" do + d = Archive::Zip::Codec::NullEncryption::Decrypt.new(BinaryStringIO.new) + d.close + d.closed?.must_equal(true) + end + + it "closes the delegate stream by default" do + delegate = MiniTest::Mock.new + delegate.expect(:close, nil) + d = Archive::Zip::Codec::NullEncryption::Decrypt.new(delegate) + d.close + end + + it "optionally leaves the delegate stream open" do + delegate = MiniTest::Mock.new + delegate.expect(:close, nil) + d = Archive::Zip::Codec::NullEncryption::Decrypt.new(delegate) + d.close(true) + + delegate = MiniTest::Mock.new + d = Archive::Zip::Codec::NullEncryption::Decrypt.new(delegate) + d.close(false) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/decrypt/new_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/decrypt/new_spec.rb new file mode 100644 index 00000000..96e5775e --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/decrypt/new_spec.rb @@ -0,0 +1,16 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/null_encryption' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::NullEncryption::Decrypt.new" do + it "returns a new instance" do + d = Archive::Zip::Codec::NullEncryption::Decrypt.new(BinaryStringIO.new) + d.must_be_instance_of(Archive::Zip::Codec::NullEncryption::Decrypt) + d.close + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/decrypt/open_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/decrypt/open_spec.rb new file mode 100644 index 00000000..9cb373cf --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/decrypt/open_spec.rb @@ -0,0 +1,29 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/null_encryption' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::NullEncryption::Decrypt.open" do + it "returns a new instance when run without a block" do + d = Archive::Zip::Codec::NullEncryption::Decrypt.open(BinaryStringIO.new) + d.must_be_instance_of(Archive::Zip::Codec::NullEncryption::Decrypt) + d.close + end + + it "executes a block with a new instance as an argument" do + Archive::Zip::Codec::NullEncryption::Decrypt.open(BinaryStringIO.new) do |decryptor| + decryptor.must_be_instance_of(Archive::Zip::Codec::NullEncryption::Decrypt) + end + end + + it "closes the object after executing a block" do + d = Archive::Zip::Codec::NullEncryption::Decrypt.open(BinaryStringIO.new) do |decryptor| + decryptor + end + d.closed?.must_equal(true) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/decrypt/read_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/decrypt/read_spec.rb new file mode 100644 index 00000000..cf46d88d --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/decrypt/read_spec.rb @@ -0,0 +1,26 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/null_encryption' + +describe "Archive::Zip::Codec::NullEncryption::Decrypt#read" do + it "calls the read method of the delegate" do + delegate = MiniTest::Mock.new + delegate.expect(:read, nil, [Integer]) + delegate.expect(:close, nil) + Archive::Zip::Codec::NullEncryption::Decrypt.open(delegate) do |d| + d.read + end + end + + it "passes data through unmodified" do + NullEncryptionSpecs.encrypted_data do |ed| + Archive::Zip::Codec::NullEncryption::Decrypt.open(ed) do |d| + d.read.must_equal(NullEncryptionSpecs.test_data) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/decrypt/rewind_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/decrypt/rewind_spec.rb new file mode 100644 index 00000000..f2d2d83e --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/decrypt/rewind_spec.rb @@ -0,0 +1,27 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/null_encryption' + +describe "Archive::Zip::Codec::NullEncryption::Decrypt#rewind" do + it "can rewind the stream when the delegate responds to rewind" do + NullEncryptionSpecs.encrypted_data do |ed| + Archive::Zip::Codec::NullEncryption::Decrypt.open(ed) do |d| + d.read(4) + d.rewind + d.read.must_equal(NullEncryptionSpecs.test_data) + end + end + end + + it "raises Errno::EINVAL when attempting to rewind the stream when the delegate does not respond to rewind" do + delegate = MiniTest::Mock.new + delegate.expect(:close, nil) + Archive::Zip::Codec::NullEncryption::Decrypt.open(delegate) do |d| + lambda { d.rewind }.must_raise(Errno::EINVAL) + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/decrypt/seek_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/decrypt/seek_spec.rb new file mode 100644 index 00000000..81a79866 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/decrypt/seek_spec.rb @@ -0,0 +1,59 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/null_encryption' + +describe "Archive::Zip::Codec::NullEncryption::Decrypt#seek" do + it "can seek to the beginning of the stream when the delegate responds to rewind" do + NullEncryptionSpecs.encrypted_data do |ed| + Archive::Zip::Codec::NullEncryption::Decrypt.open(ed) do |d| + d.read(4) + d.seek(0).must_equal(0) + end + end + end + + it "raises Errno::EINVAL when attempting to seek to the beginning of the stream when the delegate does not respond to rewind" do + delegate = MiniTest::Mock.new + delegate.expect(:close, nil) + Archive::Zip::Codec::NullEncryption::Decrypt.open(delegate) do |d| + lambda { d.seek(0) }.must_raise(Errno::EINVAL) + end + end + + it "raises Errno::EINVAL when seeking forward or backward from the current position of the stream" do + NullEncryptionSpecs.encrypted_data do |ed| + Archive::Zip::Codec::NullEncryption::Decrypt.open(ed) do |d| + # Disable read buffering to avoid some seeking optimizations implemented + # by IO::Like which allow seeking forward within the buffer. + d.fill_size = 0 + + d.read(4) + lambda { d.seek(1, IO::SEEK_CUR) }.must_raise(Errno::EINVAL) + lambda { d.seek(-1, IO::SEEK_CUR) }.must_raise(Errno::EINVAL) + end + end + end + + it "raises Errno::EINVAL when seeking a non-zero offset relative to the beginning of the stream" do + NullEncryptionSpecs.encrypted_data do |ed| + Archive::Zip::Codec::NullEncryption::Decrypt.open(ed) do |d| + lambda { d.seek(-1, IO::SEEK_SET) }.must_raise(Errno::EINVAL) + lambda { d.seek(1, IO::SEEK_SET) }.must_raise(Errno::EINVAL) + end + end + end + + it "raises Errno::EINVAL when seeking relative to the end of the stream" do + NullEncryptionSpecs.encrypted_data do |ed| + Archive::Zip::Codec::NullEncryption::Decrypt.open(ed) do |d| + lambda { d.seek(0, IO::SEEK_END) }.must_raise(Errno::EINVAL) + lambda { d.seek(-1, IO::SEEK_END) }.must_raise(Errno::EINVAL) + lambda { d.seek(1, IO::SEEK_END) }.must_raise(Errno::EINVAL) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/decrypt/tell_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/decrypt/tell_spec.rb new file mode 100644 index 00000000..d039ac43 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/decrypt/tell_spec.rb @@ -0,0 +1,23 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/null_encryption' + +describe "Archive::Zip::Codec::NullEncryption::Decrypt#tell" do + it "returns the current position of the stream" do + NullEncryptionSpecs.encrypted_data do |ed| + Archive::Zip::Codec::NullEncryption::Decrypt.open(ed) do |d| + d.tell.must_equal(0) + d.read(4) + d.tell.must_equal(4) + d.read + d.tell.must_equal(235) + d.rewind + d.tell.must_equal(0) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/encrypt/close_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/encrypt/close_spec.rb new file mode 100644 index 00000000..8291b853 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/encrypt/close_spec.rb @@ -0,0 +1,34 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/null_encryption' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::NullEncryption::Encrypt#close" do + it "closes the stream" do + e = Archive::Zip::Codec::NullEncryption::Encrypt.new(BinaryStringIO.new) + e.close + e.closed?.must_equal(true) + end + + it "closes the delegate stream by default" do + delegate = MiniTest::Mock.new + delegate.expect(:close, nil) + e = Archive::Zip::Codec::NullEncryption::Encrypt.new(delegate) + e.close + end + + it "optionally leaves the delegate stream open" do + delegate = MiniTest::Mock.new + delegate.expect(:close, nil) + e = Archive::Zip::Codec::NullEncryption::Encrypt.new(delegate) + e.close(true) + + delegate = MiniTest::Mock.new + e = Archive::Zip::Codec::NullEncryption::Encrypt.new(delegate) + e.close(false) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/encrypt/new_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/encrypt/new_spec.rb new file mode 100644 index 00000000..d0e5b8af --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/encrypt/new_spec.rb @@ -0,0 +1,16 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/null_encryption' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::NullEncryption::Encrypt.new" do + it "returns a new instance" do + e = Archive::Zip::Codec::NullEncryption::Encrypt.new(BinaryStringIO.new) + e.must_be_instance_of(Archive::Zip::Codec::NullEncryption::Encrypt) + e.close + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/encrypt/open_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/encrypt/open_spec.rb new file mode 100644 index 00000000..d086b3dc --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/encrypt/open_spec.rb @@ -0,0 +1,31 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/null_encryption' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::NullEncryption::Encrypt.open" do + it "returns a new instance when run without a block" do + e = Archive::Zip::Codec::NullEncryption::Encrypt.open(BinaryStringIO.new) + e.must_be_instance_of(Archive::Zip::Codec::NullEncryption::Encrypt) + e.close + end + + it "executes a block with a new instance as an argument" do + Archive::Zip::Codec::NullEncryption::Encrypt.open(BinaryStringIO.new) do |encryptor| + encryptor.must_be_instance_of( + Archive::Zip::Codec::NullEncryption::Encrypt + ) + end + end + + it "closes the object after executing a block" do + e = Archive::Zip::Codec::NullEncryption::Encrypt.open(BinaryStringIO.new) do |encryptor| + encryptor + end + e.closed?.must_equal(true) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/encrypt/rewind_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/encrypt/rewind_spec.rb new file mode 100644 index 00000000..b7b1fa26 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/encrypt/rewind_spec.rb @@ -0,0 +1,28 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/null_encryption' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::NullEncryption::Encrypt#rewind" do + it "can rewind the stream when the delegate responds to rewind" do + encrypted_data = BinaryStringIO.new + Archive::Zip::Codec::NullEncryption::Encrypt.open(encrypted_data) do |e| + e.write('test') + e.rewind + e.write(NullEncryptionSpecs.test_data) + end + encrypted_data.string.must_equal(NullEncryptionSpecs.encrypted_data) + end + + it "raises Errno::EINVAL when attempting to rewind the stream when the delegate does not respond to rewind" do + delegate = MiniTest::Mock.new + delegate.expect(:close, nil) + Archive::Zip::Codec::NullEncryption::Encrypt.open(delegate) do |e| + lambda { e.rewind }.must_raise(Errno::EINVAL) + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/encrypt/seek_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/encrypt/seek_spec.rb new file mode 100644 index 00000000..1c625e1e --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/encrypt/seek_spec.rb @@ -0,0 +1,52 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/null_encryption' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::NullEncryption::Encrypt#seek" do + it "can seek to the beginning of the stream when the delegate responds to rewind" do + encrypted_data = BinaryStringIO.new + Archive::Zip::Codec::NullEncryption::Encrypt.open(encrypted_data) do |e| + e.write('test') + e.seek(0).must_equal(0) + end + end + + it "raises Errno::EINVAL when attempting to seek to the beginning of the stream when the delegate does not respond to rewind" do + delegate = MiniTest::Mock.new + delegate.expect(:close, nil) + Archive::Zip::Codec::NullEncryption::Encrypt.open(delegate) do |e| + lambda { e.seek(0) }.must_raise(Errno::EINVAL) + end + end + + it "raises Errno::EINVAL when seeking forward or backward from the current position of the stream" do + encrypted_data = BinaryStringIO.new + Archive::Zip::Codec::NullEncryption::Encrypt.open(encrypted_data) do |e| + e.write('test') + lambda { e.seek(1, IO::SEEK_CUR) }.must_raise(Errno::EINVAL) + lambda { e.seek(-1, IO::SEEK_CUR) }.must_raise(Errno::EINVAL) + end + end + + it "raises Errno::EINVAL when seeking a non-zero offset relative to the beginning of the stream" do + encrypted_data = BinaryStringIO.new + Archive::Zip::Codec::NullEncryption::Encrypt.open(encrypted_data) do |e| + lambda { e.seek(-1, IO::SEEK_SET) }.must_raise(Errno::EINVAL) + lambda { e.seek(1, IO::SEEK_SET) }.must_raise(Errno::EINVAL) + end + end + + it "raises Errno::EINVAL when seeking relative to the end of the stream" do + encrypted_data = BinaryStringIO.new + Archive::Zip::Codec::NullEncryption::Encrypt.open(encrypted_data) do |e| + lambda { e.seek(0, IO::SEEK_END) }.must_raise(Errno::EINVAL) + lambda { e.seek(-1, IO::SEEK_END) }.must_raise(Errno::EINVAL) + lambda { e.seek(1, IO::SEEK_END) }.must_raise(Errno::EINVAL) + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/encrypt/tell_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/encrypt/tell_spec.rb new file mode 100644 index 00000000..a13e7e0b --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/encrypt/tell_spec.rb @@ -0,0 +1,31 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/null_encryption' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::NullEncryption::Encrypt#tell" do + it "returns the current position of the stream" do + sio = BinaryStringIO.new + Archive::Zip::Codec::NullEncryption::Encrypt.open(sio) do |e| + e.tell.must_equal(0) + e.write('test1') + e.tell.must_equal(5) + e.write('test2') + e.tell.must_equal(10) + e.rewind + e.tell.must_equal(0) + end + end + + it "raises IOError on closed stream" do + delegate = MiniTest::Mock.new + delegate.expect(:close, nil) + lambda do + Archive::Zip::Codec::NullEncryption::Encrypt.open(delegate) { |e| e }.tell + end.must_raise(IOError) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/encrypt/write_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/encrypt/write_spec.rb new file mode 100644 index 00000000..d703a5ae --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/encrypt/write_spec.rb @@ -0,0 +1,31 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/null_encryption' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::NullEncryption::Encrypt#write" do + it "calls the write method of the delegate" do + delegate = MiniTest::Mock.new + delegate.expect( + :write, + NullEncryptionSpecs.encrypted_data.size, + [NullEncryptionSpecs.encrypted_data] + ) + delegate.expect(:close, nil) + Archive::Zip::Codec::NullEncryption::Encrypt.open(delegate) do |e| + e.write(NullEncryptionSpecs.test_data) + end + end + + it "passes data through unmodified" do + encrypted_data = BinaryStringIO.new + Archive::Zip::Codec::NullEncryption::Encrypt.open(encrypted_data) do |e| + e.write(NullEncryptionSpecs.test_data) + end + encrypted_data.string.must_equal(NullEncryptionSpecs.encrypted_data) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/fixtures/classes.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/fixtures/classes.rb new file mode 100644 index 00000000..7b90348d --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/fixtures/classes.rb @@ -0,0 +1,12 @@ +# encoding: UTF-8 + +class NullEncryptionSpecs + class << self + def test_data + File.open(File.join(File.dirname(__FILE__), 'raw_file.txt'), 'rb') do |f| + block_given? ? yield(f) : f.read + end + end + alias :encrypted_data :test_data + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/fixtures/raw_file.txt b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/fixtures/raw_file.txt new file mode 100644 index 00000000..60bb924e --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/null_encryption/fixtures/raw_file.txt @@ -0,0 +1,10 @@ +test +This is a file with test data. +The quick brown fox jumps over the lazy dog. + +Mary had a little lamb +Whose fleece was white as snow +And everywhere that Mary went +The lamb was sure to go + +She sells sea shells down by the sea shore. diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/compress/close_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/compress/close_spec.rb new file mode 100644 index 00000000..048d2a6f --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/compress/close_spec.rb @@ -0,0 +1,34 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/store' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::Store::Compress#close" do + it "closes the stream" do + c = Archive::Zip::Codec::Store::Compress.new(BinaryStringIO.new) + c.close + c.closed?.must_equal(true) + end + + it "closes the delegate stream by default" do + delegate = MiniTest::Mock.new + delegate.expect(:close, nil) + c = Archive::Zip::Codec::Store::Compress.new(delegate) + c.close + end + + it "optionally leaves the delegate stream open" do + delegate = MiniTest::Mock.new + delegate.expect(:close, nil) + c = Archive::Zip::Codec::Store::Compress.new(delegate) + c.close(true) + + delegate = MiniTest::Mock.new + c = Archive::Zip::Codec::Store::Compress.new(delegate) + c.close(false) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/compress/data_descriptor_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/compress/data_descriptor_spec.rb new file mode 100644 index 00000000..b1363c46 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/compress/data_descriptor_spec.rb @@ -0,0 +1,77 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/support/zlib' +require 'archive/zip/codec/store' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::Store::Compress#data_descriptor" do + it "is an instance of Archive::Zip::DataDescriptor" do + test_data = StoreSpecs.test_data + compressed_data = BinaryStringIO.new + closed_compressor = Archive::Zip::Codec::Store::Compress.open( + compressed_data + ) do |compressor| + compressor.write(test_data) + compressor.flush + compressor.data_descriptor.must_be_instance_of( + Archive::Zip::DataDescriptor + ) + compressor + end + closed_compressor.data_descriptor.must_be_instance_of( + Archive::Zip::DataDescriptor + ) + end + + it "has a crc32 attribute containing the CRC32 checksum" do + test_data = StoreSpecs.test_data + compressed_data = BinaryStringIO.new + closed_compressor = Archive::Zip::Codec::Store::Compress.open( + compressed_data + ) do |compressor| + compressor.write(test_data) + compressor.flush + compressor.data_descriptor.crc32.must_equal(Zlib.crc32(test_data)) + compressor + end + closed_compressor.data_descriptor.crc32.must_equal(Zlib.crc32(test_data)) + end + + it "has a compressed_size attribute containing the size of the compressed data" do + test_data = StoreSpecs.test_data + compressed_data = BinaryStringIO.new + closed_compressor = Archive::Zip::Codec::Store::Compress.open( + compressed_data + ) do |compressor| + compressor.write(test_data) + compressor.flush + compressor.data_descriptor.compressed_size.must_equal( + compressed_data.string.size + ) + compressor + end + closed_compressor.data_descriptor.compressed_size.must_equal( + compressed_data.string.size + ) + end + + it "has an uncompressed_size attribute containing the size of the input data" do + test_data = StoreSpecs.test_data + compressed_data = BinaryStringIO.new + closed_compressor = Archive::Zip::Codec::Store::Compress.open( + compressed_data + ) do |compressor| + compressor.write(test_data) + compressor.flush + compressor.data_descriptor.uncompressed_size.must_equal(test_data.size) + compressor + end + closed_compressor.data_descriptor.uncompressed_size.must_equal( + test_data.size + ) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/compress/new_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/compress/new_spec.rb new file mode 100644 index 00000000..244bbf35 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/compress/new_spec.rb @@ -0,0 +1,16 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/store' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::Store::Compress.new" do + it "returns a new instance" do + c = Archive::Zip::Codec::Store::Compress.new(BinaryStringIO.new) + c.must_be_instance_of(Archive::Zip::Codec::Store::Compress) + c.close + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/compress/open_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/compress/open_spec.rb new file mode 100644 index 00000000..c2e96dbe --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/compress/open_spec.rb @@ -0,0 +1,29 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/store' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::Store::Compress.open" do + it "returns a new instance when run without a block" do + c = Archive::Zip::Codec::Store::Compress.open(BinaryStringIO.new) + c.must_be_instance_of(Archive::Zip::Codec::Store::Compress) + c.close + end + + it "executes a block with a new instance as an argument" do + Archive::Zip::Codec::Store::Compress.open(BinaryStringIO.new) do |compressor| + compressor.must_be_instance_of(Archive::Zip::Codec::Store::Compress) + end + end + + it "closes the object after executing a block" do + c = Archive::Zip::Codec::Store::Compress.open(BinaryStringIO.new) do |compressor| + compressor + end + c.closed?.must_equal(true) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/compress/rewind_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/compress/rewind_spec.rb new file mode 100644 index 00000000..8833fd37 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/compress/rewind_spec.rb @@ -0,0 +1,28 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/store' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::Store::Compress#rewind" do + it "can rewind the stream when the delegate responds to rewind" do + sio = BinaryStringIO.new + Archive::Zip::Codec::Store::Compress.open(sio) do |c| + c.write('test') + c.rewind + c.write(StoreSpecs.test_data) + end + sio.string.must_equal(StoreSpecs.compressed_data) + end + + it "raises Errno::EINVAL when attempting to rewind the stream when the delegate does not respond to rewind" do + delegate = MiniTest::Mock.new + delegate.expect(:close, nil) + Archive::Zip::Codec::Store::Compress.open(delegate) do |c| + lambda { c.rewind }.must_raise(Errno::EINVAL) + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/compress/seek_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/compress/seek_spec.rb new file mode 100644 index 00000000..3e21e32c --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/compress/seek_spec.rb @@ -0,0 +1,52 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/store' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::Store::Compress#seek" do + it "can seek to the beginning of the stream when the delegate responds to rewind" do + compressed_data = BinaryStringIO.new + Archive::Zip::Codec::Store::Compress.open(compressed_data) do |c| + c.write('test') + c.seek(0).must_equal(0) + end + end + + it "raises Errno::EINVAL when attempting to seek to the beginning of the stream when the delegate does not respond to rewind" do + delegate = MiniTest::Mock.new + delegate.expect(:close, nil) + Archive::Zip::Codec::Store::Compress.open(delegate) do |c| + lambda { c.seek(0) }.must_raise(Errno::EINVAL) + end + end + + it "raises Errno::EINVAL when seeking forward or backward from the current position of the stream" do + compressed_data = BinaryStringIO.new + Archive::Zip::Codec::Store::Compress.open(compressed_data) do |c| + c.write('test') + lambda { c.seek(1, IO::SEEK_CUR) }.must_raise(Errno::EINVAL) + lambda { c.seek(-1, IO::SEEK_CUR) }.must_raise(Errno::EINVAL) + end + end + + it "raises Errno::EINVAL when seeking a non-zero offset relative to the beginning of the stream" do + compressed_data = BinaryStringIO.new + Archive::Zip::Codec::Store::Compress.open(compressed_data) do |c| + lambda { c.seek(-1, IO::SEEK_SET) }.must_raise(Errno::EINVAL) + lambda { c.seek(1, IO::SEEK_SET) }.must_raise(Errno::EINVAL) + end + end + + it "raises Errno::EINVAL when seeking relative to the end of the stream" do + compressed_data = BinaryStringIO.new + Archive::Zip::Codec::Store::Compress.open(compressed_data) do |c| + lambda { c.seek(0, IO::SEEK_END) }.must_raise(Errno::EINVAL) + lambda { c.seek(-1, IO::SEEK_END) }.must_raise(Errno::EINVAL) + lambda { c.seek(1, IO::SEEK_END) }.must_raise(Errno::EINVAL) + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/compress/tell_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/compress/tell_spec.rb new file mode 100644 index 00000000..3eb30eed --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/compress/tell_spec.rb @@ -0,0 +1,31 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/store' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::Store::Compress#tell" do + it "returns the current position of the stream" do + sio = BinaryStringIO.new + Archive::Zip::Codec::Store::Compress.open(sio) do |c| + c.tell.must_equal(0) + c.write('test1') + c.tell.must_equal(5) + c.write('test2') + c.tell.must_equal(10) + c.rewind + c.tell.must_equal(0) + end + end + + it "raises IOError on closed stream" do + delegate = MiniTest::Mock.new + delegate.expect(:close, nil) + lambda do + Archive::Zip::Codec::Store::Compress.open(delegate) { |c| c }.tell + end.must_raise(IOError) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/compress/write_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/compress/write_spec.rb new file mode 100644 index 00000000..9cb6287c --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/compress/write_spec.rb @@ -0,0 +1,29 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/store' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::Store::Compress#write" do + it "calls the write method of the delegate" do + delegate = MiniTest::Mock.new + delegate.expect( + :write, StoreSpecs.compressed_data.size, [StoreSpecs.compressed_data] + ) + delegate.expect(:close, nil) + Archive::Zip::Codec::Store::Compress.open(delegate) do |c| + c.write(StoreSpecs.test_data) + end + end + + it "passes data through unmodified" do + compressed_data = BinaryStringIO.new + Archive::Zip::Codec::Store::Compress.open(compressed_data) do |c| + c.write(StoreSpecs.test_data) + end + compressed_data.string.must_equal(StoreSpecs.compressed_data) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/decompress/close_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/decompress/close_spec.rb new file mode 100644 index 00000000..873443c8 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/decompress/close_spec.rb @@ -0,0 +1,34 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/store' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::Store::Decompress#close" do + it "closes the stream" do + c = Archive::Zip::Codec::Store::Decompress.new(BinaryStringIO.new) + c.close + c.closed?.must_equal(true) + end + + it "closes the delegate stream by default" do + delegate = MiniTest::Mock.new + delegate.expect(:close, nil) + c = Archive::Zip::Codec::Store::Decompress.new(delegate) + c.close + end + + it "optionally leaves the delegate stream open" do + delegate = MiniTest::Mock.new + delegate.expect(:close, nil) + c = Archive::Zip::Codec::Store::Decompress.new(delegate) + c.close(true) + + delegate = MiniTest::Mock.new + c = Archive::Zip::Codec::Store::Decompress.new(delegate) + c.close(false) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/decompress/data_descriptor_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/decompress/data_descriptor_spec.rb new file mode 100644 index 00000000..2b0f364f --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/decompress/data_descriptor_spec.rb @@ -0,0 +1,75 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/support/zlib' +require 'archive/zip/codec/store' + +describe "Archive::Zip::Codec::Store::Decompress#data_descriptor" do + it "is an instance of Archive::Zip::DataDescriptor" do + StoreSpecs.compressed_data do |cd| + closed_decompressor = Archive::Zip::Codec::Store::Decompress.open( + cd + ) do |decompressor| + decompressor.read + decompressor.data_descriptor.must_be_instance_of( + Archive::Zip::DataDescriptor + ) + decompressor + end + closed_decompressor.data_descriptor.must_be_instance_of( + Archive::Zip::DataDescriptor + ) + end + end + + it "has a crc32 attribute containing the CRC32 checksum" do + crc32 = Zlib.crc32(StoreSpecs.test_data) + StoreSpecs.compressed_data do |cd| + closed_decompressor = Archive::Zip::Codec::Store::Decompress.open( + cd + ) do |decompressor| + decompressor.read + decompressor.data_descriptor.crc32.must_equal(crc32) + decompressor + end + closed_decompressor.data_descriptor.crc32.must_equal(crc32) + end + end + + it "has a compressed_size attribute containing the size of the compressed data" do + compressed_size = StoreSpecs.compressed_data.size + StoreSpecs.compressed_data do |cd| + closed_decompressor = Archive::Zip::Codec::Store::Decompress.open( + cd + ) do |decompressor| + decompressor.read + decompressor.data_descriptor.compressed_size.must_equal(compressed_size) + decompressor + end + closed_decompressor.data_descriptor.compressed_size.must_equal( + compressed_size + ) + end + end + + it "has an uncompressed_size attribute containing the size of the input data" do + uncompressed_size = StoreSpecs.test_data.size + StoreSpecs.compressed_data do |cd| + closed_decompressor = Archive::Zip::Codec::Store::Decompress.open( + cd + ) do |decompressor| + decompressor.read + decompressor.data_descriptor.uncompressed_size.must_equal( + uncompressed_size + ) + decompressor + end + closed_decompressor.data_descriptor.uncompressed_size.must_equal( + uncompressed_size + ) + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/decompress/new_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/decompress/new_spec.rb new file mode 100644 index 00000000..2bb3a7b9 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/decompress/new_spec.rb @@ -0,0 +1,16 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/store' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::Store::Decompress.new" do + it "returns a new instance" do + d = Archive::Zip::Codec::Store::Decompress.new(BinaryStringIO.new) + d.must_be_instance_of(Archive::Zip::Codec::Store::Decompress) + d.close + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/decompress/open_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/decompress/open_spec.rb new file mode 100644 index 00000000..d2b8aeb7 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/decompress/open_spec.rb @@ -0,0 +1,29 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/store' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::Store::Decompress.open" do + it "returns a new instance when run without a block" do + d = Archive::Zip::Codec::Store::Decompress.open(BinaryStringIO.new) + d.must_be_instance_of(Archive::Zip::Codec::Store::Decompress) + d.close + end + + it "executes a block with a new instance as an argument" do + Archive::Zip::Codec::Store::Decompress.open(BinaryStringIO.new) do |decompressor| + decompressor.must_be_instance_of(Archive::Zip::Codec::Store::Decompress) + end + end + + it "closes the object after executing a block" do + d = Archive::Zip::Codec::Store::Decompress.open(BinaryStringIO.new) do |decompressor| + decompressor + end + d.closed?.must_equal(true) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/decompress/read_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/decompress/read_spec.rb new file mode 100644 index 00000000..bb500b86 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/decompress/read_spec.rb @@ -0,0 +1,26 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/store' + +describe "Archive::Zip::Codec::Store::Decompress#read" do + it "calls the read method of the delegate" do + delegate = MiniTest::Mock.new + delegate.expect(:read, nil, [Integer]) + delegate.expect(:close, nil) + Archive::Zip::Codec::Store::Decompress.open(delegate) do |d| + d.read + end + end + + it "passes data through unmodified" do + StoreSpecs.compressed_data do |cd| + Archive::Zip::Codec::Store::Decompress.open(cd) do |d| + d.read.must_equal(StoreSpecs.test_data) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/decompress/rewind_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/decompress/rewind_spec.rb new file mode 100644 index 00000000..19ff22b8 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/decompress/rewind_spec.rb @@ -0,0 +1,27 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/store' + +describe "Archive::Zip::Codec::Store::Decompress#rewind" do + it "can rewind the stream when the delegate responds to rewind" do + StoreSpecs.compressed_data do |cd| + Archive::Zip::Codec::Store::Decompress.open(cd) do |d| + d.read(4) + d.rewind + d.read.must_equal(StoreSpecs.test_data) + end + end + end + + it "raises Errno::EINVAL when attempting to rewind the stream when the delegate does not respond to rewind" do + delegate = MiniTest::Mock.new + delegate.expect(:close, nil) + Archive::Zip::Codec::Store::Decompress.open(delegate) do |d| + lambda { d.rewind }.must_raise(Errno::EINVAL) + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/decompress/seek_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/decompress/seek_spec.rb new file mode 100644 index 00000000..87890e00 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/decompress/seek_spec.rb @@ -0,0 +1,59 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/store' + +describe "Archive::Zip::Codec::Store::Decompress#seek" do + it "can seek to the beginning of the stream when the delegate responds to rewind" do + StoreSpecs.compressed_data do |cd| + Archive::Zip::Codec::Store::Decompress.open(cd) do |d| + d.read(4) + d.seek(0).must_equal(0) + end + end + end + + it "raises Errno::EINVAL when attempting to seek to the beginning of the stream when the delegate does not respond to rewind" do + delegate = MiniTest::Mock.new + delegate.expect(:close, nil) + Archive::Zip::Codec::Store::Decompress.open(delegate) do |d| + lambda { d.seek(0) }.must_raise(Errno::EINVAL) + end + end + + it "raises Errno::EINVAL when seeking forward or backward from the current position of the stream" do + StoreSpecs.compressed_data do |cd| + Archive::Zip::Codec::Store::Decompress.open(cd) do |d| + # Disable read buffering to avoid some seeking optimizations implemented + # by IO::Like which allow seeking forward within the buffer. + d.fill_size = 0 + + d.read(4) + lambda { d.seek(1, IO::SEEK_CUR) }.must_raise(Errno::EINVAL) + lambda { d.seek(-1, IO::SEEK_CUR) }.must_raise(Errno::EINVAL) + end + end + end + + it "raises Errno::EINVAL when seeking a non-zero offset relative to the beginning of the stream" do + StoreSpecs.compressed_data do |cd| + Archive::Zip::Codec::Store::Decompress.open(cd) do |d| + lambda { d.seek(-1, IO::SEEK_SET) }.must_raise(Errno::EINVAL) + lambda { d.seek(1, IO::SEEK_SET) }.must_raise(Errno::EINVAL) + end + end + end + + it "raises Errno::EINVAL when seeking relative to the end of the stream" do + StoreSpecs.compressed_data do |cd| + Archive::Zip::Codec::Store::Decompress.open(cd) do |d| + lambda { d.seek(0, IO::SEEK_END) }.must_raise(Errno::EINVAL) + lambda { d.seek(-1, IO::SEEK_END) }.must_raise(Errno::EINVAL) + lambda { d.seek(1, IO::SEEK_END) }.must_raise(Errno::EINVAL) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/decompress/tell_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/decompress/tell_spec.rb new file mode 100644 index 00000000..481ec3ef --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/decompress/tell_spec.rb @@ -0,0 +1,23 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/store' + +describe "Archive::Zip::Codec::Store::Decompress#tell" do + it "returns the current position of the stream" do + StoreSpecs.compressed_data do |cd| + Archive::Zip::Codec::Store::Decompress.open(cd) do |d| + d.tell.must_equal(0) + d.read(4) + d.tell.must_equal(4) + d.read + d.tell.must_equal(235) + d.rewind + d.tell.must_equal(0) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/fixtures/classes.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/fixtures/classes.rb new file mode 100644 index 00000000..3663af74 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/fixtures/classes.rb @@ -0,0 +1,12 @@ +# encoding: UTF-8 + +class StoreSpecs + class << self + def test_data + File.open(File.join(File.dirname(__FILE__), 'raw_file.txt'), 'rb') do |f| + block_given? ? yield(f) : f.read + end + end + alias :compressed_data :test_data + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/fixtures/raw_file.txt b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/fixtures/raw_file.txt new file mode 100644 index 00000000..60bb924e --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/store/fixtures/raw_file.txt @@ -0,0 +1,10 @@ +test +This is a file with test data. +The quick brown fox jumps over the lazy dog. + +Mary had a little lamb +Whose fleece was white as snow +And everywhere that Mary went +The lamb was sure to go + +She sells sea shells down by the sea shore. diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/decrypt/close_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/decrypt/close_spec.rb new file mode 100644 index 00000000..a3957529 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/decrypt/close_spec.rb @@ -0,0 +1,53 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/traditional_encryption' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::TraditionalEncryption::Decrypt#close" do + it "closes the stream" do + d = Archive::Zip::Codec::TraditionalEncryption::Decrypt.new( + BinaryStringIO.new("\000" * 12), + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) + d.close + d.closed?.must_equal(true) + end + + it "closes the delegate stream by default" do + delegate = MiniTest::Mock.new + delegate.expect(:read, "\000" * 12, [Integer]) + delegate.expect(:close, nil) + d = Archive::Zip::Codec::TraditionalEncryption::Decrypt.new( + delegate, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) + d.close + end + + it "optionally leaves the delegate stream open" do + delegate = MiniTest::Mock.new + delegate.expect(:read, "\000" * 12, [Integer]) + delegate.expect(:close, nil) + d = Archive::Zip::Codec::TraditionalEncryption::Decrypt.new( + delegate, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) + d.close(true) + + delegate = MiniTest::Mock.new + delegate.expect(:read, "\000" * 12, [Integer]) + d = Archive::Zip::Codec::TraditionalEncryption::Decrypt.new( + delegate, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) + d.close(false) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/decrypt/new_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/decrypt/new_spec.rb new file mode 100644 index 00000000..15aa8f6f --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/decrypt/new_spec.rb @@ -0,0 +1,20 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/traditional_encryption' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::TraditionalEncryption::Decrypt.new" do + it "returns a new instance" do + d = Archive::Zip::Codec::TraditionalEncryption::Decrypt.new( + BinaryStringIO.new("\000" * 12), + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) + d.must_be_instance_of(Archive::Zip::Codec::TraditionalEncryption::Decrypt) + d.close + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/decrypt/open_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/decrypt/open_spec.rb new file mode 100644 index 00000000..0f5cb460 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/decrypt/open_spec.rb @@ -0,0 +1,43 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/traditional_encryption' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::TraditionalEncryption::Decrypt.open" do + it "returns a new instance when run without a block" do + d = Archive::Zip::Codec::TraditionalEncryption::Decrypt.open( + BinaryStringIO.new("\000" * 12), + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) + d.must_be_instance_of(Archive::Zip::Codec::TraditionalEncryption::Decrypt) + d.close + end + + it "executes a block with a new instance as an argument" do + Archive::Zip::Codec::TraditionalEncryption::Decrypt.open( + BinaryStringIO.new("\000" * 12), + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |decryptor| + decryptor.must_be_instance_of( + Archive::Zip::Codec::TraditionalEncryption::Decrypt + ) + end + end + + it "closes the object after executing a block" do + d = Archive::Zip::Codec::TraditionalEncryption::Decrypt.open( + BinaryStringIO.new("\000" * 12), + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |decryptor| + decryptor + end + d.closed?.must_equal(true) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/decrypt/read_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/decrypt/read_spec.rb new file mode 100644 index 00000000..9bb6a81f --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/decrypt/read_spec.rb @@ -0,0 +1,127 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/traditional_encryption' + +describe "Archive::Zip::Codec::TraditionalEncryption::Decrypt#read" do + it "calls the read method of the delegate" do + delegate = MiniTest::Mock.new + delegate.expect(:read, "\000" * 12, [Integer]) + delegate.expect(:read, nil, [Integer]) + delegate.expect(:close, nil) + Archive::Zip::Codec::TraditionalEncryption::Decrypt.open( + delegate, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |d| + d.read + end + end + + it "decrypts data read from the delegate" do + TraditionalEncryptionSpecs.encrypted_data do |ed| + Archive::Zip::Codec::TraditionalEncryption::Decrypt.open( + ed, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |d| + d.read.must_equal(TraditionalEncryptionSpecs.test_data) + end + end + end + + it "decrypts data read from a delegate that only returns 1 byte at a time" do + TraditionalEncryptionSpecs.encrypted_data do |ed| + # Override ed.read to raise Errno::EAGAIN every other time it's called. + class << ed + alias :read_orig :read + def read(length = nil, buffer = nil) + read_orig(1, buffer) + end + end + + Archive::Zip::Codec::TraditionalEncryption::Decrypt.open( + ed, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |d| + buffer = '' + begin + buffer << d.read + rescue Errno::EAGAIN + retry + end + buffer.must_equal(TraditionalEncryptionSpecs.test_data) + end + end + end + + it "decrypts data read from a delegate that raises Errno::EAGAIN" do + TraditionalEncryptionSpecs.encrypted_data do |ed| + # Override ed.read to raise Errno::EAGAIN every other time it's called. + class << ed + alias :read_orig :read + def read(length = nil, buffer = nil) + @error_raised ||= false + if @error_raised then + @error_raised = false + read_orig(length, buffer) + else + @error_raised = true + raise Errno::EAGAIN + end + end + end + + Archive::Zip::Codec::TraditionalEncryption::Decrypt.open( + ed, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |d| + buffer = '' + begin + buffer << d.read + rescue Errno::EAGAIN + retry + end + buffer.must_equal(TraditionalEncryptionSpecs.test_data) + end + end + end + + it "decrypts data read from a delegate that raises Errno::EINTR" do + TraditionalEncryptionSpecs.encrypted_data do |ed| + # Override ed.read to raise Errno::EINTR every other time it's called. + class << ed + alias :read_orig :read + def read(length = nil, buffer = nil) + @error_raised ||= false + if @error_raised then + @error_raised = false + read_orig(length, buffer) + else + @error_raised = true + raise Errno::EINTR + end + end + end + + Archive::Zip::Codec::TraditionalEncryption::Decrypt.open( + ed, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |d| + buffer = '' + begin + buffer << d.read + rescue Errno::EINTR + retry + end + buffer.must_equal(TraditionalEncryptionSpecs.test_data) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/decrypt/rewind_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/decrypt/rewind_spec.rb new file mode 100644 index 00000000..59d1df2d --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/decrypt/rewind_spec.rb @@ -0,0 +1,36 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/traditional_encryption' + +describe "Archive::Zip::Codec::TraditionalEncryption::Decrypt#rewind" do + it "can rewind the stream when the delegate responds to rewind" do + TraditionalEncryptionSpecs.encrypted_data do |ed| + Archive::Zip::Codec::TraditionalEncryption::Decrypt.open( + ed, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |d| + d.read(4) + d.rewind + d.read.must_equal(TraditionalEncryptionSpecs.test_data) + end + end + end + + it "raises Errno::EINVAL when attempting to rewind the stream when the delegate does not respond to rewind" do + delegate = MiniTest::Mock.new + delegate.expect(:read, "\000" * 12, [Integer]) + delegate.expect(:close, nil) + Archive::Zip::Codec::TraditionalEncryption::Decrypt.open( + delegate, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |d| + lambda { d.rewind }.must_raise(Errno::EINVAL) + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/decrypt/seek_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/decrypt/seek_spec.rb new file mode 100644 index 00000000..1455139f --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/decrypt/seek_spec.rb @@ -0,0 +1,80 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/traditional_encryption' + +describe "Archive::Zip::Codec::TraditionalEncryption::Decrypt#seek" do + it "can seek to the beginning of the stream when the delegate responds to rewind" do + TraditionalEncryptionSpecs.encrypted_data do |ed| + Archive::Zip::Codec::TraditionalEncryption::Decrypt.open( + ed, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |d| + d.read(4) + d.seek(0).must_equal(0) + end + end + end + + it "raises Errno::EINVAL when attempting to seek to the beginning of the stream when the delegate does not respond to rewind" do + delegate = MiniTest::Mock.new + delegate.expect(:read, "\000" * 12, [Integer]) + delegate.expect(:close, nil) + Archive::Zip::Codec::TraditionalEncryption::Decrypt.open( + delegate, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |d| + lambda { d.seek(0) }.must_raise(Errno::EINVAL) + end + end + + it "raises Errno::EINVAL when seeking forward or backward from the current position of the stream" do + TraditionalEncryptionSpecs.encrypted_data do |ed| + Archive::Zip::Codec::TraditionalEncryption::Decrypt.open( + ed, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |d| + # Disable read buffering to avoid some seeking optimizations implemented + # by IO::Like which allow seeking forward within the buffer. + d.fill_size = 0 + + d.read(4) + lambda { d.seek(1, IO::SEEK_CUR) }.must_raise(Errno::EINVAL) + lambda { d.seek(-1, IO::SEEK_CUR) }.must_raise(Errno::EINVAL) + end + end + end + + it "raises Errno::EINVAL when seeking a non-zero offset relative to the beginning of the stream" do + TraditionalEncryptionSpecs.encrypted_data do |ed| + Archive::Zip::Codec::TraditionalEncryption::Decrypt.open( + ed, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |d| + lambda { d.seek(-1, IO::SEEK_SET) }.must_raise(Errno::EINVAL) + lambda { d.seek(1, IO::SEEK_SET) }.must_raise(Errno::EINVAL) + end + end + end + + it "raises Errno::EINVAL when seeking relative to the end of the stream" do + TraditionalEncryptionSpecs.encrypted_data do |ed| + Archive::Zip::Codec::TraditionalEncryption::Decrypt.open( + ed, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |d| + lambda { d.seek(0, IO::SEEK_END) }.must_raise(Errno::EINVAL) + lambda { d.seek(-1, IO::SEEK_END) }.must_raise(Errno::EINVAL) + lambda { d.seek(1, IO::SEEK_END) }.must_raise(Errno::EINVAL) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/decrypt/tell_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/decrypt/tell_spec.rb new file mode 100644 index 00000000..010ee269 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/decrypt/tell_spec.rb @@ -0,0 +1,27 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/traditional_encryption' + +describe "Archive::Zip::Codec::TraditionalEncryption::Decrypt#tell" do + it "returns the current position of the stream" do + TraditionalEncryptionSpecs.encrypted_data do |ed| + Archive::Zip::Codec::TraditionalEncryption::Decrypt.open( + ed, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |d| + d.tell.must_equal(0) + d.read(4) + d.tell.must_equal(4) + d.read + d.tell.must_equal(235) + d.rewind + d.tell.must_equal(0) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/encrypt/close_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/encrypt/close_spec.rb new file mode 100644 index 00000000..492734f4 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/encrypt/close_spec.rb @@ -0,0 +1,53 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/traditional_encryption' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::TraditionalEncryption::Encrypt#close" do + it "closes the stream" do + e = Archive::Zip::Codec::TraditionalEncryption::Encrypt.new( + BinaryStringIO.new, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) + e.close + e.closed?.must_equal(true) + end + + it "closes the delegate stream by default" do + delegate = MiniTest::Mock.new + delegate.expect(:write, 12, [String]) + delegate.expect(:close, nil) + e = Archive::Zip::Codec::TraditionalEncryption::Encrypt.new( + delegate, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) + e.close + end + + it "optionally leaves the delegate stream open" do + delegate = MiniTest::Mock.new + delegate.expect(:write, 12, [String]) + delegate.expect(:close, nil) + e = Archive::Zip::Codec::TraditionalEncryption::Encrypt.new( + delegate, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) + e.close(true) + + delegate = MiniTest::Mock.new + delegate.expect(:write, 12, [String]) + e = Archive::Zip::Codec::TraditionalEncryption::Encrypt.new( + delegate, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) + e.close(false) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/encrypt/new_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/encrypt/new_spec.rb new file mode 100644 index 00000000..f34e42c6 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/encrypt/new_spec.rb @@ -0,0 +1,20 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/traditional_encryption' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::TraditionalEncryption::Encrypt.new" do + it "returns a new instance" do + e = Archive::Zip::Codec::TraditionalEncryption::Encrypt.new( + BinaryStringIO.new, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) + e.must_be_instance_of(Archive::Zip::Codec::TraditionalEncryption::Encrypt) + e.close + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/encrypt/open_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/encrypt/open_spec.rb new file mode 100644 index 00000000..53a312a9 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/encrypt/open_spec.rb @@ -0,0 +1,41 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/traditional_encryption' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::TraditionalEncryption::Encrypt.open" do + it "returns a new instance when run without a block" do + e = Archive::Zip::Codec::TraditionalEncryption::Encrypt.open( + BinaryStringIO.new, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) + e.must_be_instance_of(Archive::Zip::Codec::TraditionalEncryption::Encrypt) + e.close + end + + it "executes a block with a new instance as an argument" do + Archive::Zip::Codec::TraditionalEncryption::Encrypt.open( + BinaryStringIO.new, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |encryptor| + encryptor.must_be_instance_of(Archive::Zip::Codec::TraditionalEncryption::Encrypt) + end + end + + it "closes the object after executing a block" do + e = Archive::Zip::Codec::TraditionalEncryption::Encrypt.open( + BinaryStringIO.new, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |encryptor| + encryptor + end + e.closed?.must_equal(true) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/encrypt/rewind_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/encrypt/rewind_spec.rb new file mode 100644 index 00000000..ff714d83 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/encrypt/rewind_spec.rb @@ -0,0 +1,39 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/traditional_encryption' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::TraditionalEncryption::Encrypt#rewind" do + it "can rewind the stream when the delegate responds to rewind" do + encrypted_data = BinaryStringIO.new + Archive::Zip::Codec::TraditionalEncryption::Encrypt.open( + encrypted_data, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |e| + e.write('test') + # Ensure repeatable test data is used for encryption header. + srand(0) + e.rewind + e.write(TraditionalEncryptionSpecs.test_data) + end + encrypted_data.string.must_equal(TraditionalEncryptionSpecs.encrypted_data) + end + + it "raises Errno::EINVAL when attempting to rewind the stream when the delegate does not respond to rewind" do + delegate = MiniTest::Mock.new + delegate.expect(:write, 12, [String]) + delegate.expect(:close, nil) + Archive::Zip::Codec::TraditionalEncryption::Encrypt.open( + delegate, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |e| + lambda { e.rewind }.must_raise(Errno::EINVAL) + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/encrypt/seek_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/encrypt/seek_spec.rb new file mode 100644 index 00000000..b6290fbd --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/encrypt/seek_spec.rb @@ -0,0 +1,73 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/traditional_encryption' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::TraditionalEncryption::Encrypt#seek" do + it "can seek to the beginning of the stream when the delegate responds to rewind" do + encrypted_data = BinaryStringIO.new + Archive::Zip::Codec::TraditionalEncryption::Encrypt.open( + encrypted_data, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |e| + e.write('test') + e.seek(0).must_equal(0) + end + end + + it "raises Errno::EINVAL when attempting to seek to the beginning of the stream when the delegate does not respond to rewind" do + delegate = MiniTest::Mock.new + delegate.expect(:write, 12, [String]) + delegate.expect(:close, nil) + Archive::Zip::Codec::TraditionalEncryption::Encrypt.open( + delegate, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |e| + lambda { e.seek(0) }.must_raise(Errno::EINVAL) + end + end + + it "raises Errno::EINVAL when seeking forward or backward from the current position of the stream" do + encrypted_data = BinaryStringIO.new + Archive::Zip::Codec::TraditionalEncryption::Encrypt.open( + encrypted_data, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |e| + e.write('test') + lambda { e.seek(1, IO::SEEK_CUR) }.must_raise(Errno::EINVAL) + lambda { e.seek(-1, IO::SEEK_CUR) }.must_raise(Errno::EINVAL) + end + end + + it "raises Errno::EINVAL when seeking a non-zero offset relative to the beginning of the stream" do + encrypted_data = BinaryStringIO.new + Archive::Zip::Codec::TraditionalEncryption::Encrypt.open( + encrypted_data, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |e| + lambda { e.seek(-1, IO::SEEK_SET) }.must_raise(Errno::EINVAL) + lambda { e.seek(1, IO::SEEK_SET) }.must_raise(Errno::EINVAL) + end + end + + it "raises Errno::EINVAL when seeking relative to the end of the stream" do + encrypted_data = BinaryStringIO.new + Archive::Zip::Codec::TraditionalEncryption::Encrypt.open( + encrypted_data, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |e| + lambda { e.seek(0, IO::SEEK_END) }.must_raise(Errno::EINVAL) + lambda { e.seek(-1, IO::SEEK_END) }.must_raise(Errno::EINVAL) + lambda { e.seek(1, IO::SEEK_END) }.must_raise(Errno::EINVAL) + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/encrypt/tell_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/encrypt/tell_spec.rb new file mode 100644 index 00000000..4f522852 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/encrypt/tell_spec.rb @@ -0,0 +1,40 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/traditional_encryption' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::TraditionalEncryption::Encrypt#tell" do + it "returns the current position of the stream" do + encrypted_data = BinaryStringIO.new + Archive::Zip::Codec::TraditionalEncryption::Encrypt.open( + encrypted_data, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |e| + e.tell.must_equal(0) + e.write('test1') + e.tell.must_equal(5) + e.write('test2') + e.tell.must_equal(10) + e.rewind + e.tell.must_equal(0) + end + end + + it "raises IOError on closed stream" do + delegate = MiniTest::Mock.new + delegate.expect(:write, 12, [String]) + delegate.expect(:close, nil) + lambda do + Archive::Zip::Codec::TraditionalEncryption::Encrypt.open( + delegate, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) { |e| e }.tell + end.must_raise(IOError) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/encrypt/write_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/encrypt/write_spec.rb new file mode 100644 index 00000000..d886d759 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/encrypt/write_spec.rb @@ -0,0 +1,114 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/zip/codec/traditional_encryption' +require 'archive/support/binary_stringio' + +describe "Archive::Zip::Codec::TraditionalEncryption::Encrypt#write" do + it "writes encrypted data to the delegate" do + # Ensure repeatable test data is used for encryption header. + srand(0) + encrypted_data = BinaryStringIO.new + Archive::Zip::Codec::TraditionalEncryption::Encrypt.open( + encrypted_data, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |e| + e.write(TraditionalEncryptionSpecs.test_data) + end + encrypted_data.string.must_equal(TraditionalEncryptionSpecs.encrypted_data) + end + + it "writes encrypted data to a delegate that only performs partial writes" do + # Ensure repeatable test data is used for encryption header. + srand(0) + encrypted_data = BinaryStringIO.new + # Override encrypted_data.write to perform writes 1 byte at a time. + class << encrypted_data + alias :write_orig :write + def write(buffer) + write_orig(buffer.slice(0, 1)) + end + end + + Archive::Zip::Codec::TraditionalEncryption::Encrypt.open( + encrypted_data, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |e| + e.write(TraditionalEncryptionSpecs.test_data) + end + encrypted_data.string.must_equal(TraditionalEncryptionSpecs.encrypted_data) + end + + it "writes encrypted data to a delegate that raises Errno::EAGAIN" do + # Ensure repeatable test data is used for encryption header. + srand(0) + encrypted_data = BinaryStringIO.new + # Override encrypted_data.write to raise Errno::EAGAIN every other time it's + # called. + class << encrypted_data + alias :write_orig :write + def write(buffer) + @error_raised ||= false + if @error_raised then + @error_raised = false + write_orig(buffer) + else + @error_raised = true + raise Errno::EAGAIN + end + end + end + + Archive::Zip::Codec::TraditionalEncryption::Encrypt.open( + encrypted_data, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |e| + begin + e.write(TraditionalEncryptionSpecs.test_data) + rescue Errno::EAGAIN + retry + end + end + encrypted_data.string.must_equal(TraditionalEncryptionSpecs.encrypted_data) + end + + it "writes encrypted data to a delegate that raises Errno::EINTR" do + # Ensure repeatable test data is used for encryption header. + srand(0) + encrypted_data = BinaryStringIO.new + # Override encrypted_data.write to raise Errno::EINTR every other time it's + # called. + class << encrypted_data + alias :write_orig :write + def write(buffer) + @error_raised ||= false + if @error_raised then + @error_raised = false + write_orig(buffer) + else + @error_raised = true + raise Errno::EINTR + end + end + end + + Archive::Zip::Codec::TraditionalEncryption::Encrypt.open( + encrypted_data, + TraditionalEncryptionSpecs.password, + TraditionalEncryptionSpecs.mtime + ) do |e| + begin + e.write(TraditionalEncryptionSpecs.test_data) + rescue Errno::EINTR + retry + end + end + encrypted_data.string.must_equal(TraditionalEncryptionSpecs.encrypted_data) + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/fixtures/classes.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/fixtures/classes.rb new file mode 100644 index 00000000..11b0c52c --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/fixtures/classes.rb @@ -0,0 +1,27 @@ +# encoding: UTF-8 + +class TraditionalEncryptionSpecs + class << self + def password + 'p455w0rd' + end + + def mtime + Time.local(1979, 12, 31, 18, 0, 0) + end + + def encrypted_data + File.open( + File.join(File.dirname(__FILE__), 'encrypted_file.bin'), 'rb' + ) do |f| + block_given? ? yield(f) : f.read + end + end + + def test_data + File.open(File.join(File.dirname(__FILE__), 'raw_file.txt'), 'rb') do |f| + block_given? ? yield(f) : f.read + end + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/fixtures/encrypted_file.bin b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/fixtures/encrypted_file.bin new file mode 100644 index 00000000..d4ff7c3d Binary files /dev/null and b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/fixtures/encrypted_file.bin differ diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/fixtures/raw_file.txt b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/fixtures/raw_file.txt new file mode 100644 index 00000000..60bb924e --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/archive/zip/codec/traditional_encryption/fixtures/raw_file.txt @@ -0,0 +1,10 @@ +test +This is a file with test data. +The quick brown fox jumps over the lazy dog. + +Mary had a little lamb +Whose fleece was white as snow +And everywhere that Mary went +The lamb was sure to go + +She sells sea shells down by the sea shore. diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/binary_stringio/new_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/binary_stringio/new_spec.rb new file mode 100644 index 00000000..7abde89c --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/binary_stringio/new_spec.rb @@ -0,0 +1,40 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require 'archive/support/binary_stringio' + +describe "BinaryStringIO.new" do + it "returns a new instance" do + io = BinaryStringIO.new + io.must_be_instance_of BinaryStringIO + io.close + end + + it "creates a decendent of StringIO" do + io = BinaryStringIO.new + io.must_be_kind_of StringIO + io.close + end + + # TODO: + # This is lame. Break this out as augmentation for the "returns a new + # instance" test. + it "takes the same arguments as StringIO.new" do + BinaryStringIO.new.must_be_instance_of BinaryStringIO + BinaryStringIO.new('').must_be_instance_of BinaryStringIO + BinaryStringIO.new('', 'r').must_be_instance_of BinaryStringIO + BinaryStringIO.new('', 'w').must_be_instance_of BinaryStringIO + + lambda { BinaryStringIO.new('', 'w', 42) }.must_raise ArgumentError + end + + it "sets the external encoding to binary" do + unless Object.const_defined?(:Encoding) + skip("Encoding methods are not supported on current Ruby (#{RUBY_DESCRIPTION})") + end + + io = BinaryStringIO.new + io.external_encoding.must_equal Encoding::ASCII_8BIT + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/binary_stringio/set_encoding_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/binary_stringio/set_encoding_spec.rb new file mode 100644 index 00000000..6a0e859b --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/binary_stringio/set_encoding_spec.rb @@ -0,0 +1,17 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require 'archive/support/binary_stringio' + +describe "BinaryStringIO#set_encoding" do + it "raises an exception when called" do + unless Object.const_defined?(:Encoding) + skip("Encoding methods are not supported on current Ruby (#{RUBY_DESCRIPTION})") + end + + lambda do + BinaryStringIO.new.set_encoding('utf-8') + end.must_raise RuntimeError + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/ioextensions/read_exactly_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/ioextensions/read_exactly_spec.rb new file mode 100644 index 00000000..3cec4ce7 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/ioextensions/read_exactly_spec.rb @@ -0,0 +1,52 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require 'archive/support/ioextensions.rb' +require 'archive/support/binary_stringio' + +describe "IOExtensions.read_exactly" do + it "reads and returns length bytes from a given IO object" do + io = BinaryStringIO.new('This is test data') + IOExtensions.read_exactly(io, 4).must_equal 'This' + IOExtensions.read_exactly(io, 13).must_equal ' is test data' + end + + it "raises an error when too little data is available" do + io = BinaryStringIO.new('This is test data') + lambda do + IOExtensions.read_exactly(io, 18) + end.must_raise EOFError + end + + it "takes an optional buffer argument and fills it" do + io = BinaryStringIO.new('This is test data') + buffer = '' + IOExtensions.read_exactly(io, 4, buffer) + buffer.must_equal 'This' + buffer = '' + IOExtensions.read_exactly(io, 13, buffer) + buffer.must_equal ' is test data' + end + + it "empties the optional buffer before filling it" do + io = BinaryStringIO.new('This is test data') + buffer = '' + IOExtensions.read_exactly(io, 4, buffer) + buffer.must_equal 'This' + IOExtensions.read_exactly(io, 13, buffer) + buffer.must_equal ' is test data' + end + + it "can read 0 bytes" do + io = BinaryStringIO.new('This is test data') + IOExtensions.read_exactly(io, 0).must_equal '' + end + + it "retries partial reads" do + io = MiniTest::Mock.new + io.expect(:read, 'hello', [10]) + io.expect(:read, 'hello', [5]) + IOExtensions.read_exactly(io, 10).must_equal 'hellohello' + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/fixtures/classes.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/fixtures/classes.rb new file mode 100644 index 00000000..bfba3bf2 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/fixtures/classes.rb @@ -0,0 +1,65 @@ +# encoding: UTF-8 + +class ZlibSpecs + def self.compressed_data + File.open( + File.join(File.dirname(__FILE__), 'compressed_file.bin'), 'rb' + ) do |f| + block_given? ? yield(f) : f.read + end + end + + def self.compressed_data_nocomp + File.open( + File.join(File.dirname(__FILE__), 'compressed_file_nocomp.bin'), 'rb' + ) do |f| + block_given? ? yield(f) : f.read + end + end + + def self.compressed_data_minwin + File.open( + File.join(File.dirname(__FILE__), 'compressed_file_minwin.bin'), 'rb' + ) do |f| + block_given? ? yield(f) : f.read + end + end + + def self.compressed_data_minmem + File.open( + File.join(File.dirname(__FILE__), 'compressed_file_minmem.bin'), 'rb' + ) do |f| + block_given? ? yield(f) : f.read + end + end + + def self.compressed_data_huffman + File.open( + File.join(File.dirname(__FILE__), 'compressed_file_huffman.bin'), 'rb' + ) do |f| + block_given? ? yield(f) : f.read + end + end + + def self.compressed_data_gzip + File.open( + File.join(File.dirname(__FILE__), 'compressed_file_gzip.bin'), 'rb' + ) do |f| + block_given? ? yield(f) : f.read + end + end + + def self.compressed_data_raw + File.open( + File.join(File.dirname(__FILE__), 'compressed_file_raw.bin'), 'rb' + ) do |f| + block_given? ? yield(f) : f.read + end + end + + def self.test_data + File.open(File.join(File.dirname(__FILE__), 'raw_file.txt'), 'rb') do |f| + f.read + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/fixtures/compressed_file.bin b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/fixtures/compressed_file.bin new file mode 100644 index 00000000..4d5ec0bd --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/fixtures/compressed_file.bin @@ -0,0 +1 @@ +x%A0E9?A\kjE5K o)+S.- ZJ[0u~*wC‘Dm%c$QT-ecxpX"m[K\ٝdƋ/nɁr)>e=`TCQ \ No newline at end of file diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/fixtures/compressed_file_gzip.bin b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/fixtures/compressed_file_gzip.bin new file mode 100644 index 00000000..1f64d913 Binary files /dev/null and b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/fixtures/compressed_file_gzip.bin differ diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/fixtures/compressed_file_huffman.bin b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/fixtures/compressed_file_huffman.bin new file mode 100644 index 00000000..a32a9dc7 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/fixtures/compressed_file_huffman.bin @@ -0,0 +1,2 @@ +xq0 ? +ԃ +NcH S}veQ5H쒲U>^غu#0r>C`5s+AF>!5 DQnms(;*eU0$W'R>Pv D*zRCQ \ No newline at end of file diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/fixtures/compressed_file_minmem.bin b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/fixtures/compressed_file_minmem.bin new file mode 100644 index 00000000..2b0bffe9 Binary files /dev/null and b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/fixtures/compressed_file_minmem.bin differ diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/fixtures/compressed_file_minwin.bin b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/fixtures/compressed_file_minwin.bin new file mode 100644 index 00000000..6d297d72 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/fixtures/compressed_file_minwin.bin @@ -0,0 +1 @@ +%A0E9?A\kjE5K o)+S.- ZJ[0u~*wC‘Dm%c$QT-ecxpX"m[K\ٝdƋ/nɁr)>e=`TCQ \ No newline at end of file diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/fixtures/compressed_file_nocomp.bin b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/fixtures/compressed_file_nocomp.bin new file mode 100644 index 00000000..efcf6084 Binary files /dev/null and b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/fixtures/compressed_file_nocomp.bin differ diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/fixtures/compressed_file_raw.bin b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/fixtures/compressed_file_raw.bin new file mode 100644 index 00000000..05bd6dea --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/fixtures/compressed_file_raw.bin @@ -0,0 +1 @@ +%A0E9?A\kjE5K o)+S.- ZJ[0u~*wC‘Dm%c$QT-ecxpX"m[K\ٝdƋ/nɁr)>e=`T \ No newline at end of file diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/fixtures/raw_file.txt b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/fixtures/raw_file.txt new file mode 100644 index 00000000..60bb924e --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/fixtures/raw_file.txt @@ -0,0 +1,10 @@ +test +This is a file with test data. +The quick brown fox jumps over the lazy dog. + +Mary had a little lamb +Whose fleece was white as snow +And everywhere that Mary went +The lamb was sure to go + +She sells sea shells down by the sea shore. diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/checksum_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/checksum_spec.rb new file mode 100644 index 00000000..614eb87d --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/checksum_spec.rb @@ -0,0 +1,42 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/support/zlib' + +describe "Zlib::ZReader#checksum" do + it "computes the ADLER32 checksum of zlib formatted data" do + closed_zr = ZlibSpecs.compressed_data do |f| + Zlib::ZReader.open(f, 15) do |zr| + zr.read + zr.checksum.must_equal Zlib.adler32(ZlibSpecs.test_data) + zr + end + end + closed_zr.checksum.must_equal Zlib.adler32(ZlibSpecs.test_data) + end + + it "computes the CRC32 checksum of gzip formatted data" do + closed_zr = ZlibSpecs.compressed_data_gzip do |f| + Zlib::ZReader.open(f, 31) do |zr| + zr.read + zr.checksum.must_equal Zlib.crc32(ZlibSpecs.test_data) + zr + end + end + closed_zr.checksum.must_equal Zlib.crc32(ZlibSpecs.test_data) + end + + it "does not compute a checksum for raw zlib data" do + closed_zr = ZlibSpecs.compressed_data_raw do |f| + Zlib::ZReader.open(f, -15) do |zr| + zr.read + zr.checksum.must_be_nil + zr + end + end + closed_zr.checksum.must_be_nil + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/close_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/close_spec.rb new file mode 100644 index 00000000..38851e3f --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/close_spec.rb @@ -0,0 +1,16 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/support/zlib' +require 'archive/support/binary_stringio' + +describe "Zlib::ZReader.close" do + it "closes the stream" do + zr = Zlib::ZReader.new(BinaryStringIO.new) + zr.close + zr.closed?.must_equal true + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/compressed_size_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/compressed_size_spec.rb new file mode 100644 index 00000000..8cd6325b --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/compressed_size_spec.rb @@ -0,0 +1,20 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/support/zlib' + +describe "Zlib::ZReader#compressed_size" do + it "returns the number of bytes of compressed data" do + closed_zr = ZlibSpecs.compressed_data_raw do |compressed_data| + Zlib::ZReader.open(compressed_data, -15) do |zr| + zr.read + zr.compressed_size.must_equal 160 + zr + end + end + closed_zr.compressed_size.must_equal 160 + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/new_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/new_spec.rb new file mode 100644 index 00000000..42e5d8da --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/new_spec.rb @@ -0,0 +1,43 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/support/zlib' +require 'archive/support/binary_stringio' + +describe "Zlib::ZReader.new" do + it "returns a new instance" do + Zlib::ZReader.new(BinaryStringIO.new).class.must_equal Zlib::ZReader + end + + it "does not require window_bits to be set" do + data = ZlibSpecs.test_data + compressed_data = BinaryStringIO.new + Zlib::ZWriter.open(compressed_data) do |zw| + zw.write(data) + end + compressed_data.rewind + + zr = Zlib::ZReader.new(compressed_data) + zr.read.must_equal data + zr.close + end + + it "allows window_bits to be set" do + data = ZlibSpecs.test_data + compressed_data = BinaryStringIO.new + window_bits = -Zlib::MAX_WBITS + Zlib::ZWriter.open( + compressed_data, Zlib::DEFAULT_COMPRESSION, window_bits + ) do |zw| + zw.write(data) + end + compressed_data.rewind + + zr = Zlib::ZReader.new(compressed_data, window_bits) + zr.read.must_equal data + zr.close + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/open_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/open_spec.rb new file mode 100644 index 00000000..7174623a --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/open_spec.rb @@ -0,0 +1,51 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/support/zlib' +require 'archive/support/binary_stringio' + +describe "Zlib::ZReader.open" do + it "returns a new instance when run without a block" do + Zlib::ZReader.open(BinaryStringIO.new).class.must_equal Zlib::ZReader + end + + it "executes a block with a new instance as an argument" do + Zlib::ZReader.open(BinaryStringIO.new) { |zr| zr.class.must_equal Zlib::ZReader } + end + + it "closes the object after executing a block" do + Zlib::ZReader.open(BinaryStringIO.new) { |zr| zr }.closed?.must_equal true + end + + it "does not require window_bits to be set" do + data = ZlibSpecs.test_data + compressed_data = BinaryStringIO.new + Zlib::ZWriter.open(compressed_data) do |zw| + zw.write(data) + end + compressed_data.rewind + + Zlib::ZReader.open(compressed_data) do |zr| + zr.read.must_equal data + end + end + + it "allows window_bits to be set" do + data = ZlibSpecs.test_data + compressed_data = BinaryStringIO.new + window_bits = -Zlib::MAX_WBITS + Zlib::ZWriter.open( + compressed_data, Zlib::DEFAULT_COMPRESSION, window_bits + ) do |zw| + zw.write(data) + end + compressed_data.rewind + + Zlib::ZReader.open(compressed_data, window_bits) do |zr| + zr.read.must_equal data + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/read_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/read_spec.rb new file mode 100644 index 00000000..161b83a3 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/read_spec.rb @@ -0,0 +1,58 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/support/zlib' +require 'archive/support/binary_stringio' + +describe "Zlib::ZReader#read" do + it "calls the read method of the delegate" do + delegate = MiniTest::Mock.new + delegate.expect(:read, nil, [Integer]) + Zlib::ZReader.open(delegate) do |zr| + # Capture and ignore the Zlib::BufError which is generated due to mocking. + begin + zr.read + rescue Zlib::BufError + end + + # Avoid warnings from zlib caused by closing the un-finished inflater. + def zr.close; end + end + end + + it "decompresses compressed data" do + ZlibSpecs.compressed_data do |cd| + Zlib::ZReader.open(cd) do |zr| + zr.read.must_equal ZlibSpecs.test_data + end + end + end + + it "raises Zlib::DataError when reading invalid data" do + Zlib::ZReader.open(BinaryStringIO.new('This is not compressed data')) do |zr| + lambda { zr.read }.must_raise Zlib::DataError + end + end + + it "raises Zlib::BufError when reading truncated data" do + truncated_data = ZlibSpecs.compressed_data { |cd| cd.read(100) } + Zlib::ZReader.open(BinaryStringIO.new(truncated_data)) do |zr| + lambda { zr.read }.must_raise Zlib::BufError + + # Avoid warnings from zlib caused by closing the un-finished inflater. + def zr.close; end + end + end + + it "raises Zlib::BufError when reading empty data" do + Zlib::ZReader.open(BinaryStringIO.new()) do |zr| + lambda { zr.read }.must_raise Zlib::BufError + + # Avoid warnings from zlib caused by closing the un-finished inflater. + def zr.close; end + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/rewind_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/rewind_spec.rb new file mode 100644 index 00000000..b6fa0c2b --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/rewind_spec.rb @@ -0,0 +1,25 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/support/zlib' + +describe "Zlib::ZReader#rewind" do + it "can rewind the stream when the delegate responds to rewind" do + ZlibSpecs.compressed_data do |cd| + Zlib::ZReader.open(cd) do |zr| + zr.read(4) + zr.rewind + zr.read.must_equal ZlibSpecs.test_data + end + end + end + + it "raises Errno::EINVAL when attempting to rewind the stream when the delegate does not respond to rewind" do + Zlib::ZReader.open(Object.new) do |zr| + lambda { zr.rewind }.must_raise Errno::EINVAL + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/seek_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/seek_spec.rb new file mode 100644 index 00000000..e1f27a80 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/seek_spec.rb @@ -0,0 +1,57 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/support/zlib' + +describe "Zlib::ZReader#seek" do + it "can seek to the beginning of the stream when the delegate responds to rewind" do + ZlibSpecs.compressed_data do |cd| + Zlib::ZReader.open(cd) do |zr| + zr.read(4) + zr.seek(0).must_equal 0 + end + end + end + + it "raises Errno::EINVAL when attempting to seek to the beginning of the stream when the delegate does not respond to rewind" do + Zlib::ZReader.open(Object.new) do |zr| + lambda { zr.seek(0) }.must_raise Errno::EINVAL + end + end + + it "raises Errno::EINVAL when seeking forward or backward from the current position of the stream" do + ZlibSpecs.compressed_data do |cd| + Zlib::ZReader.open(cd) do |zr| + # Disable read buffering to avoid some seeking optimizations implemented + # by IO::Like which allow seeking forward within the buffer. + zr.fill_size = 0 + + zr.read(4) + lambda { zr.seek(1, IO::SEEK_CUR) }.must_raise Errno::EINVAL + lambda { zr.seek(-1, IO::SEEK_CUR) }.must_raise Errno::EINVAL + end + end + end + + it "raises Errno::EINVAL when seeking a non-zero offset relative to the beginning of the stream" do + ZlibSpecs.compressed_data do |cd| + Zlib::ZReader.open(cd) do |zr| + lambda { zr.seek(-1, IO::SEEK_SET) }.must_raise Errno::EINVAL + lambda { zr.seek(1, IO::SEEK_SET) }.must_raise Errno::EINVAL + end + end + end + + it "raises Errno::EINVAL when seeking relative to the end of the stream" do + ZlibSpecs.compressed_data do |cd| + Zlib::ZReader.open(cd) do |zr| + lambda { zr.seek(0, IO::SEEK_END) }.must_raise Errno::EINVAL + lambda { zr.seek(-1, IO::SEEK_END) }.must_raise Errno::EINVAL + lambda { zr.seek(1, IO::SEEK_END) }.must_raise Errno::EINVAL + end + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/tell_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/tell_spec.rb new file mode 100644 index 00000000..75de235d --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/tell_spec.rb @@ -0,0 +1,23 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/support/zlib' + +describe "Zlib::ZReader#tell" do + it "returns the current position of the stream" do + ZlibSpecs.compressed_data do |cd| + Zlib::ZReader.open(cd) do |zr| + zr.tell.must_equal 0 + zr.read(4) + zr.tell.must_equal 4 + zr.read + zr.tell.must_equal 235 + zr.rewind + zr.tell.must_equal 0 + end + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/uncompressed_size_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/uncompressed_size_spec.rb new file mode 100644 index 00000000..7eb396e5 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zreader/uncompressed_size_spec.rb @@ -0,0 +1,20 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/support/zlib' + +describe "Zlib::ZReader#uncompressed_size" do + it "returns the number of bytes of uncompressed data" do + closed_zr = ZlibSpecs.compressed_data_raw do |compressed_data| + Zlib::ZReader.open(compressed_data, -15) do |zr| + zr.read + zr.uncompressed_size.must_equal 235 + zr + end + end + closed_zr.uncompressed_size.must_equal 235 + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/checksum_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/checksum_spec.rb new file mode 100644 index 00000000..9506c8e4 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/checksum_spec.rb @@ -0,0 +1,43 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/support/zlib' +require 'archive/support/binary_stringio' + +describe "Zlib::ZWriter#checksum" do + it "computes the ADLER32 checksum of zlib formatted data" do + compressed_data = BinaryStringIO.new + closed_zw = Zlib::ZWriter.open(compressed_data, nil, 15) do |zw| + zw.write(ZlibSpecs.test_data) + zw.flush + zw.checksum.must_equal Zlib.adler32(ZlibSpecs.test_data) + zw + end + closed_zw.checksum.must_equal Zlib.adler32(ZlibSpecs.test_data) + end + + it "computes the CRC32 checksum of gzip formatted data" do + compressed_data = BinaryStringIO.new + closed_zw = Zlib::ZWriter.open(compressed_data, nil, 31) do |zw| + zw.write(ZlibSpecs.test_data) + zw.flush + zw.checksum.must_equal Zlib.crc32(ZlibSpecs.test_data) + zw + end + closed_zw.checksum.must_equal Zlib.crc32(ZlibSpecs.test_data) + end + + it "does not compute a checksum for raw zlib data" do + compressed_data = BinaryStringIO.new + closed_zw = Zlib::ZWriter.open(compressed_data, nil, -15) do |zw| + zw.write(ZlibSpecs.test_data) + zw.flush + zw.checksum.must_be_nil + zw + end + closed_zw.checksum.must_be_nil + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/close_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/close_spec.rb new file mode 100644 index 00000000..455b8496 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/close_spec.rb @@ -0,0 +1,16 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/support/zlib' +require 'archive/support/binary_stringio' + +describe "Zlib::ZWriter.close" do + it "closes the stream" do + zw = Zlib::ZWriter.new(BinaryStringIO.new) + zw.close + zw.closed?.must_equal true + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/compressed_size_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/compressed_size_spec.rb new file mode 100644 index 00000000..b0c9590f --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/compressed_size_spec.rb @@ -0,0 +1,21 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/support/zlib' +require 'archive/support/binary_stringio' + +describe "Zlib::ZWriter#compressed_size" do + it "returns the number of bytes of compressed data" do + compressed_data = BinaryStringIO.new + closed_zw = Zlib::ZWriter.open(compressed_data, nil, -15) do |zw| + zw.sync = true + zw.write(ZlibSpecs.test_data) + zw.compressed_size.must_be :>=, 0 + zw + end + closed_zw.compressed_size.must_equal 160 + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/new_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/new_spec.rb new file mode 100644 index 00000000..d8c28377 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/new_spec.rb @@ -0,0 +1,66 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/support/zlib' +require 'archive/support/binary_stringio' + +describe "Zlib::ZWriter.new" do + it "returns a new instance" do + zw = Zlib::ZWriter.new(BinaryStringIO.new) + zw.class.must_equal Zlib::ZWriter + zw.close + end + + it "provides default settings for level, window_bits, mem_level, and strategy" do + data = ZlibSpecs.test_data + compressed_data = BinaryStringIO.new + zw = Zlib::ZWriter.new(compressed_data) + zw.write(data) + zw.close + + compressed_data.string.must_equal ZlibSpecs.compressed_data + end + + it "allows level to be set" do + data = ZlibSpecs.test_data + compressed_data = BinaryStringIO.new + zw = Zlib::ZWriter.new(compressed_data, Zlib::NO_COMPRESSION) + zw.write(data) + zw.close + + compressed_data.string.must_equal ZlibSpecs.compressed_data_nocomp + end + + it "allows window_bits to be set" do + data = ZlibSpecs.test_data + compressed_data = BinaryStringIO.new + zw = Zlib::ZWriter.new(compressed_data, nil, 8) + zw.write(data) + zw.close + + compressed_data.string.must_equal ZlibSpecs.compressed_data_minwin + end + + it "allows mem_level to be set" do + data = ZlibSpecs.test_data + compressed_data = BinaryStringIO.new + zw = Zlib::ZWriter.new(compressed_data, nil, nil, 1) + zw.write(data) + zw.close + + compressed_data.string.must_equal ZlibSpecs.compressed_data_minmem + end + + it "allows strategy to be set" do + data = ZlibSpecs.test_data + compressed_data = BinaryStringIO.new + zw = Zlib::ZWriter.new(compressed_data, nil, nil, nil, Zlib::HUFFMAN_ONLY) + zw.write(data) + zw.close + + compressed_data.string.must_equal ZlibSpecs.compressed_data_huffman + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/open_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/open_spec.rb new file mode 100644 index 00000000..0e7225c8 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/open_spec.rb @@ -0,0 +1,70 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/support/zlib' +require 'archive/support/binary_stringio' + +describe "Zlib::ZWriter.open" do + it "returns a new instance when run without a block" do + zw = Zlib::ZWriter.open(BinaryStringIO.new) + zw.class.must_equal Zlib::ZWriter + zw.close + end + + it "executes a block with a new instance as an argument" do + Zlib::ZWriter.open(BinaryStringIO.new) { |zr| zr.class.must_equal Zlib::ZWriter } + end + + it "closes the object after executing a block" do + Zlib::ZWriter.open(BinaryStringIO.new) { |zr| zr }.closed?.must_equal true + end + + it "provides default settings for level, window_bits, mem_level, and strategy" do + data = ZlibSpecs.test_data + compressed_data = BinaryStringIO.new + Zlib::ZWriter.open(compressed_data) { |zw| zw.write(data) } + + compressed_data.string.must_equal ZlibSpecs.compressed_data + end + + it "allows level to be set" do + data = ZlibSpecs.test_data + compressed_data = BinaryStringIO.new + Zlib::ZWriter.open(compressed_data, Zlib::NO_COMPRESSION) do |zw| + zw.write(data) + end + + compressed_data.string.must_equal ZlibSpecs.compressed_data_nocomp + end + + it "allows window_bits to be set" do + data = ZlibSpecs.test_data + compressed_data = BinaryStringIO.new + Zlib::ZWriter.open(compressed_data, nil, 8) { |zw| zw.write(data) } + + compressed_data.string.must_equal ZlibSpecs.compressed_data_minwin + end + + it "allows mem_level to be set" do + data = ZlibSpecs.test_data + compressed_data = BinaryStringIO.new + Zlib::ZWriter.open(compressed_data, nil, nil, 1) { |zw| zw.write(data) } + + compressed_data.string.must_equal ZlibSpecs.compressed_data_minmem + end + + it "allows strategy to be set" do + data = ZlibSpecs.test_data + compressed_data = BinaryStringIO.new + Zlib::ZWriter.open( + compressed_data, nil, nil, nil, Zlib::HUFFMAN_ONLY + ) do |zw| + zw.write(data) + end + + compressed_data.string.must_equal ZlibSpecs.compressed_data_huffman + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/rewind_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/rewind_spec.rb new file mode 100644 index 00000000..e7e522f8 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/rewind_spec.rb @@ -0,0 +1,28 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/support/zlib' +require 'archive/support/binary_stringio' + +describe "Zlib::ZWriter#rewind" do + it "can rewind the stream when the delegate responds to rewind" do + sio = BinaryStringIO.new + Zlib::ZWriter.open(sio) do |zw| + zw.write('test') + zw.rewind + zw.write(ZlibSpecs.test_data) + end + sio.string.must_equal ZlibSpecs.compressed_data + end + + it "raises Errno::EINVAL when attempting to rewind the stream when the delegate does not respond to rewind" do + delegate = MiniTest::Mock.new + delegate.expect(:write, 8, [String]) + Zlib::ZWriter.open(delegate) do |zw| + lambda { zw.rewind }.must_raise Errno::EINVAL + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/seek_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/seek_spec.rb new file mode 100644 index 00000000..ccae70f7 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/seek_spec.rb @@ -0,0 +1,52 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/support/zlib' +require 'archive/support/binary_stringio' + +describe "Zlib::ZWriter#seek" do + it "can seek to the beginning of the stream when the delegate responds to rewind" do + compressed_data = BinaryStringIO.new + Zlib::ZWriter.open(compressed_data) do |c| + c.write('test') + c.seek(0).must_equal 0 + end + end + + it "raises Errno::EINVAL when attempting to seek to the beginning of the stream when the delegate does not respond to rewind" do + delegate = MiniTest::Mock.new + delegate.expect(:write, 8, [String]) + Zlib::ZWriter.open(delegate) do |c| + lambda { c.seek(0) }.must_raise Errno::EINVAL + end + end + + it "raises Errno::EINVAL when seeking forward or backward from the current position of the stream" do + compressed_data = BinaryStringIO.new + Zlib::ZWriter.open(compressed_data) do |c| + c.write('test') + lambda { c.seek(1, IO::SEEK_CUR) }.must_raise Errno::EINVAL + lambda { c.seek(-1, IO::SEEK_CUR) }.must_raise Errno::EINVAL + end + end + + it "raises Errno::EINVAL when seeking a non-zero offset relative to the beginning of the stream" do + compressed_data = BinaryStringIO.new + Zlib::ZWriter.open(compressed_data) do |c| + lambda { c.seek(-1, IO::SEEK_SET) }.must_raise Errno::EINVAL + lambda { c.seek(1, IO::SEEK_SET) }.must_raise Errno::EINVAL + end + end + + it "raises Errno::EINVAL when seeking relative to the end of the stream" do + compressed_data = BinaryStringIO.new + Zlib::ZWriter.open(compressed_data) do |c| + lambda { c.seek(0, IO::SEEK_END) }.must_raise Errno::EINVAL + lambda { c.seek(-1, IO::SEEK_END) }.must_raise Errno::EINVAL + lambda { c.seek(1, IO::SEEK_END) }.must_raise Errno::EINVAL + end + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/tell_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/tell_spec.rb new file mode 100644 index 00000000..fdcc2e33 --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/tell_spec.rb @@ -0,0 +1,31 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/support/zlib' +require 'archive/support/binary_stringio' + +describe "Zlib::ZWriter#tell" do + it "returns the current position of the stream" do + sio = BinaryStringIO.new + Zlib::ZWriter.open(sio) do |zw| + zw.tell.must_equal 0 + zw.write('test1') + zw.tell.must_equal 5 + zw.write('test2') + zw.tell.must_equal 10 + zw.rewind + zw.tell.must_equal 0 + end + end + + it "raises IOError on closed stream" do + delegate = MiniTest::Mock.new + delegate.expect(:write, 8, [String]) + lambda do + Zlib::ZWriter.open(delegate) { |zw| zw }.tell + end.must_raise IOError + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/uncompressed_size_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/uncompressed_size_spec.rb new file mode 100644 index 00000000..9bb2005e --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/uncompressed_size_spec.rb @@ -0,0 +1,21 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/support/zlib' +require 'archive/support/binary_stringio' + +describe "Zlib::ZWriter#uncompressed_size" do + it "returns the number of bytes of uncompressed data" do + compressed_data = BinaryStringIO.new + closed_zw = Zlib::ZWriter.open(compressed_data, nil, -15) do |zw| + zw.sync = true + zw.write(ZlibSpecs.test_data) + zw.uncompressed_size.must_equal 235 + zw + end + closed_zw.uncompressed_size.must_equal 235 + end +end diff --git a/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/write_spec.rb b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/write_spec.rb new file mode 100644 index 00000000..2e94d7fa --- /dev/null +++ b/path/ruby/2.6.0/gems/archive-zip-0.12.0/spec/zlib/zwriter/write_spec.rb @@ -0,0 +1,28 @@ +# encoding: UTF-8 + +require 'minitest/autorun' + +require File.expand_path('../../fixtures/classes', __FILE__) + +require 'archive/support/zlib' +require 'archive/support/binary_stringio' + +describe "Zlib::ZWriter#write" do + it "calls the write method of the delegate" do + delegate = MiniTest::Mock.new + delegate.expect( + :write, ZlibSpecs.compressed_data.size, [ZlibSpecs.compressed_data] + ) + Zlib::ZWriter.open(delegate) do |zw| + zw.write(ZlibSpecs.test_data) + end + end + + it "compresses data" do + compressed_data = BinaryStringIO.new + Zlib::ZWriter.open(compressed_data) do |zw| + zw.write(ZlibSpecs.test_data) + end + compressed_data.string.must_equal ZlibSpecs.compressed_data + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/History.txt b/path/ruby/2.6.0/gems/arel-9.0.0/History.txt new file mode 100644 index 00000000..11ca9e86 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/History.txt @@ -0,0 +1,332 @@ +=== 9.0.0 / 2017-11-14 + +* Enhancements + * `InsertManager#insert` is now chainable + * Support multiple inserts + +=== 8.0.0 / 2017-02-21 + +* Enhancements + + * Remove deprecated type casting support in Arel + * Frozen all string literals in Arel + +=== 7.1.1 / 2016-07-27 + +* Bug Fixes + + * Fix warning in `Casted#hash` + +=== 7.1.0 / 2016-07-19 + +* Enhancements + + * Support Ruby 2.4 unified Integer class + * Implement `CASE` conditional expression + * Support for Bitwise Operations as `InfixOperations` + +=== 7.0.0 / 2015-12-17 + +* Enhancements + + * Remove deprecated method `Table#primary_key` + * Remove engine from the constructor arguments `Arel::Table` + * Deprecate automatic type casting within Arel + +=== 6.0.0 / 2014-11-25 + +* Enhancements + + * Remove deprecated `Arel::Expression` + * Remove deprecated `Arel::SqlLiteral` + * Remove deprecated `SelectManager#joins` + * Remove deprecated `SelectManager#to_a` + * Remove deprecated `Arel::Sql::Engine` + * Remove deprecated `Arel::InnerJoin` constant + * Remove deprecated `Arel::OuterJoin` constant + +== 5.0.0 / 2013-12-04 + +* Enhancements + + * Remove deprecated code + +* Bug Fixes + + * Fix serializing a relation when calling `to_yaml` + +=== 4.0.2 / 2014-02-05 + + * Bug Fixes + + * Fix `SqlLiteral` YAML serialization + * PostgreSQL bugfix for invalid SQL in subqueries + +== 4.0.1 / 2013-10-22 + +* Enhancements + + * Cache visitor dispatch on a per-visitor basis + * Improve performance of #uniq across a large number of nodes + +* Bug Fixes + + * Make visitors threadsafe by removing @last_column + * Support `columns_for_distinct` with Oracle adapter + +== 3.0.3 / 2013-11-12 + +* Enhancements + + * Support ANSI 2003 window functions + +* Bug Fixes + + * Fix joins in Informix + +== 3.0.2 / 2012-02-21 + +* Enhancements + + * Added a module for visiting and transforming bind values + * Fix in [] to be false, not in [] to be true + +* Bug Fixes + + * Revert fix for LIMIT / OFFSET when query is ordered in Oracle + +== 3.0.1 / 2012-02-17 + +* Bug Fixes + + * Fixed LIMIT / OFFSET when query is ordered in Oracle + +== 3.0.0 / 2012-01-12 + +* Enhancements + + * Support connection pool and schema cache + +* Bug Fixes + + * Conditions with no column can be followed by other conditions in Postgres + +== 2.2.3 / 2012-02-21 + +* Enhancements + + * Added a module for visiting and transforming bind values + +== 2.2.2 / 2012-02-20 + +* Enhancements + + * Support LOCK + * Allow using non-table alias as a right-hand relation name + * Added SelectManager#distinct + +== 2.2.1 / 2011-09-15 + +* Enhancements + + * Added UpdateManager#key to access the key value + * Added SelectManager#projections= to override any existing projections + * Added SelectManager#source to get the source of the last select core in the AST + +== 2.2.0 / 2011-08-09 + +* Bug Fixes + + * The database connection caches visitors for generating SQL. + * FALSE and TRUE nodes can be constructed. + * Fixed ORDER BY / LIMIT clauses for UPDATE statements in Oracle. + +== 2.1.4 / 2011-07-25 + +* Bug Fixes + + * Fix depth-first traversal to understand ascending / descending nodes. + * Parenthesis are suppressed with nested unions in MySQL. Thanks jhtwong! + +== 2.1.3 / 2011-06-27 + +* Bug Fixes + + * Fixed broken gem build. + +== 2.1.2 / 2011-06-27 + +* Bug Fixes + + * Visitors can define their own cache strategy so caches are not shared. + Fixes #57 + * Informix support fixed. Thanks Khronos. + * Ordering nodes broken to subclasses. Thanks Ernie Miller! + * Reversal supported in ordering nodes. Thanks Ernie Miller! + +== 2.1.1 / 2011/05/14 + +* Bug fixes + + * Fixed thread safety bug in ToSql visitor. Thanks Damon McCormick and + Cameron Walters! + +== 2.1.0 / 2011/04/30 + +* Enhancements + + * AST is now Enumerable + * AND nodes are now n-ary nodes + * SQL Literals may be used as Attribute names + * Added Arel::Nodes::NamedFunction for representing generic SQL functions + * Add Arel::SelectManager#limit= + * Add Arel::SelectManager#offset + * Add Arel::SelectManager#offset= + * Added Arel::SelectManager#create_insert for building an insert manager. + * SQL Literals are allowed for values in INSERT statements. + * Math operations have been added to attributes, thanks to + Vladimir Meremyanin. + +* Bug fixes + + * MSSQL adds TOP to sub selects + * Assigning nil to take() removes LIMIT from statement. + * Assigning nil to offset() removes OFFSET from statement. + * TableAlias leg ordering fixed + +* Deprecations + + * Calls to `insert` are deprecated. Please use `compile_insert` then call + `to_sql` on the resulting object and execute that SQL. + + * Calls to `update` are deprecated. Please use `compile_update` then call + `to_sql` on the resulting object and execute that SQL. + + * Calls to `delete` are deprecated. Please use `compile_delete` then call + `to_sql` on the resulting object and execute that SQL. + + * Arel::Table#joins is deprecated and will be removed in 3.0.0 with no + replacement. + + * Arel::Table#columns is deprecated and will be removed in 3.0.0 with no + replacement. + + * Arel::Table.table_cache is deprecated and will be removed in 3.0.0 with no + replacement. + + * Arel::Nodes::And.new takes a single list instead of left and right. + + * Arel::Table#primary_key is deprecated and will be removed in 3.0.0 with no + replacement. + + * Arel::SelectManager#where_clauses is deprecated and will be removed in + 3.0.0 with no replacement. + + * Arel::SelectManager#wheres is deprecated and will be removed in + 3.0.0 with no replacement. + +== 2.0.9 / 2010/02/25 + +* Bug Fixes + + * Custom LOCK strings are allowed. Fixes LH # 6399 + https://rails.lighthouseapp.com/projects/8994/tickets/6399-allow-database-specific-locking-clauses-to-be-used + + * Strings passed to StringManager#on will be automatically tagged as SQL + literals. Fixes Rails LH #6384 + https://rails.lighthouseapp.com/projects/8994/tickets/6384-activerecord-303-and-3-0-stable-generate-invalid-sql-for-has_many-through-association-with-conditions + +== 2.0.8 / 2010/02/08 + +* Bug Fixes + + * Added set operation support + * Fixed problems with *_any / *_all methods. + +== 2.0.7 + +* Bug Fixes + + * Limit members are visited + * Fixing MSSQL TOP support + +== 2.0.6 12/01/2010 + +* Bug Fixes + + * Rails 3.0.x does not like that Node is Enumerable, so removing for now. + +== 2.0.5 11/30/2010 + +* Enhancements + + * Arel::Visitors::DepthFirst can walk your AST depth first + * Arel::Nodes::Node is enumerable, depth first + +* Bug fixes + + * #lock will lock SELECT statements "FOR UPDATE" on mysql + * Nodes::Node#not factory method added for creating Nodes::Not nodes + * Added an As node + +* Deprecations + + * Support for Subclasses of core classes will be removed in Arel version + 2.2.0 + +== 2.0.4 + +* Bug fixes + + * Speed improvements for Range queries. Thanks Rolf Timmermans! + +== 2.0.3 + +* Bug fixes + + * Fixing Oracle support + * Added a visitor for "Class" objects + +== 2.0.2 + +* Bug fixes + + * MySQL selects from DUAL on empty FROM + * Visitor translates nil to NULL + * Visitor translates Bignum properly + +== 2.0.1 + +* Bug fixes + +== 2.0.0 / 2010-08-01 +* Enhancements + + * Recreate library using the Visitor pattern. + http://en.wikipedia.org/wiki/Visitor_pattern + +== 0.3.0 / 2010-03-10 + +* Enhancements + + * Introduced "SQL compilers" for query generation. + * Added support for Oracle (Raimonds Simanovskis) and IBM/DB (Praveen Devarao). + * Improvements to give better support to Active Record. + +== 0.2.1 / 2010-02-05 + +* Enhancements + + * Bump dependency version of activesupport to 3.0.0.beta + +== 0.2.0 / 2010-01-31 + + * Ruby 1.9 compatibility + * Many improvements to support the Arel integration into Active Record (see `git log v0.1.0..v0.2.0`) + * Thanks to Emilio Tagua and Pratik Naik for many significant contributions! + +== 0.1.0 / 2009-08-06 + +* 1 major enhancement + + * Birthday! diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/MIT-LICENSE.txt b/path/ruby/2.6.0/gems/arel-9.0.0/MIT-LICENSE.txt new file mode 100644 index 00000000..f7a59c3c --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/MIT-LICENSE.txt @@ -0,0 +1,21 @@ +Copyright (c) 2007-2016 Nick Kallen, Bryan Helmkamp, Emilio Tagua, Aaron Patterson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/README.md b/path/ruby/2.6.0/gems/arel-9.0.0/README.md new file mode 100644 index 00000000..065bfea2 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/README.md @@ -0,0 +1,295 @@ +# Arel + +* http://github.com/rails/arel +* [API Documentation](http://www.rubydoc.info/github/rails/arel) + +## DESCRIPTION + +Arel Really Exasperates Logicians + +Arel is a SQL AST manager for Ruby. It + +1. simplifies the generation of complex SQL queries, and +2. adapts to various RDBMSes. + +It is intended to be a framework framework; that is, you can build your own ORM +with it, focusing on innovative object and collection modeling as opposed to +database compatibility and query generation. + +## Status + +For the moment, Arel uses Active Record's connection adapters to connect to the various engines and perform connection pooling, quoting, and type conversion. + +## A Gentle Introduction + +Generating a query with Arel is simple. For example, in order to produce + +```sql +SELECT * FROM users +``` + +you construct a table relation and convert it to SQL: + +```ruby +users = Arel::Table.new(:users) +query = users.project(Arel.sql('*')) +query.to_sql +``` + +### More Sophisticated Queries + +Here is a whirlwind tour through the most common SQL operators. These will probably cover 80% of all interaction with the database. + +First is the 'restriction' operator, `where`: + +```ruby +users.where(users[:name].eq('amy')) +# => SELECT * FROM users WHERE users.name = 'amy' +``` + +What would, in SQL, be part of the `SELECT` clause is called in Arel a `projection`: + +```ruby +users.project(users[:id]) +# => SELECT users.id FROM users +``` + +Comparison operators `=`, `!=`, `<`, `>`, `<=`, `>=`, `IN`: + +```ruby +users.where(users[:age].eq(10)).project(Arel.sql('*')) +# => SELECT * FROM "users" WHERE "users"."age" = 10 + +users.where(users[:age].not_eq(10)).project(Arel.sql('*')) +# => SELECT * FROM "users" WHERE "users"."age" != 10 + +users.where(users[:age].lt(10)).project(Arel.sql('*')) +# => SELECT * FROM "users" WHERE "users"."age" < 10 + +users.where(users[:age].gt(10)).project(Arel.sql('*')) +# => SELECT * FROM "users" WHERE "users"."age" > 10 + +users.where(users[:age].lteq(10)).project(Arel.sql('*')) +# => SELECT * FROM "users" WHERE "users"."age" <= 10 + +users.where(users[:age].gteq(10)).project(Arel.sql('*')) +# => SELECT * FROM "users" WHERE "users"."age" >= 10 + +users.where(users[:age].in([20, 16, 17])).project(Arel.sql('*')) +# => SELECT * FROM "users" WHERE "users"."age" IN (20, 16, 17) +``` + +Bitwise operators `&`, `|`, `^`, `<<`, `>>`: + +```ruby +users.where((users[:bitmap] & 16).gt(0)).project(Arel.sql('*')) +# => SELECT * FROM "users" WHERE ("users"."bitmap" & 16) > 0 + +users.where((users[:bitmap] | 16).gt(0)).project(Arel.sql('*')) +# => SELECT * FROM "users" WHERE ("users"."bitmap" | 16) > 0 + +users.where((users[:bitmap] ^ 16).gt(0)).project(Arel.sql('*')) +# => SELECT * FROM "users" WHERE ("users"."bitmap" ^ 16) > 0 + +users.where((users[:bitmap] << 1).gt(0)).project(Arel.sql('*')) +# => SELECT * FROM "users" WHERE ("users"."bitmap" << 1) > 0 + +users.where((users[:bitmap] >> 1).gt(0)).project(Arel.sql('*')) +# => SELECT * FROM "users" WHERE ("users"."bitmap" >> 1) > 0 + +users.where((~ users[:bitmap]).gt(0)).project(Arel.sql('*')) +# => SELECT * FROM "users" WHERE ~ "users"."bitmap" > 0 +``` + +Joins resemble SQL strongly: + +```ruby +users.join(photos).on(users[:id].eq(photos[:user_id])) +# => SELECT * FROM users INNER JOIN photos ON users.id = photos.user_id +``` + +Left joins: + +```ruby +users.join(photos, Arel::Nodes::OuterJoin).on(users[:id].eq(photos[:user_id])) +# => SELECT FROM users LEFT OUTER JOIN photos ON users.id = photos.user_id +``` + +What are called `LIMIT` and `OFFSET` in SQL are called `take` and `skip` in Arel: + +```ruby +users.take(5) # => SELECT * FROM users LIMIT 5 +users.skip(4) # => SELECT * FROM users OFFSET 4 +``` + +`GROUP BY` is called `group`: + +```ruby +users.project(users[:name]).group(users[:name]) +# => SELECT users.name FROM users GROUP BY users.name +``` + +The best property of Arel is its "composability," or closure under all operations. For example, to restrict AND project, just "chain" the method invocations: + +```ruby +users \ + .where(users[:name].eq('amy')) \ + .project(users[:id]) \ +# => SELECT users.id FROM users WHERE users.name = 'amy' +``` + +All operators are chainable in this way, and they are chainable any number of times, in any order. + +```ruby +users.where(users[:name].eq('bob')).where(users[:age].lt(25)) +``` + +The `OR` operator works like this: + +```ruby +users.where(users[:name].eq('bob').or(users[:age].lt(25))) +``` + +The `AND` operator behaves similarly. Here is an example of the `DISTINCT` operator: + +```ruby +posts = Arel::Table.new(:posts) +posts.project(posts[:title]) +posts.distinct +posts.to_sql # => 'SELECT DISTINCT "posts"."title" FROM "posts"' +``` + +Aggregate functions `AVG`, `SUM`, `COUNT`, `MIN`, `MAX`, `HAVING`: + +```ruby +photos.group(photos[:user_id]).having(photos[:id].count.gt(5)) +# => SELECT FROM photos GROUP BY photos.user_id HAVING COUNT(photos.id) > 5 + +users.project(users[:age].sum) +# => SELECT SUM(users.age) FROM users + +users.project(users[:age].average) +# => SELECT AVG(users.age) FROM users + +users.project(users[:age].maximum) +# => SELECT MAX(users.age) FROM users + +users.project(users[:age].minimum) +# => SELECT MIN(users.age) FROM users + +users.project(users[:age].count) +# => SELECT COUNT(users.age) FROM users +``` + +Aliasing Aggregate Functions: + +```ruby +users.project(users[:age].average.as("mean_age")) +# => SELECT AVG(users.age) AS mean_age FROM users +``` + +### The Advanced Features + +The examples above are fairly simple and other libraries match or come close to matching the expressiveness of Arel (e.g. `Sequel` in Ruby). + +#### Inline math operations + +Suppose we have a table `products` with prices in different currencies. And we have a table `currency_rates`, of constantly changing currency rates. In Arel: + +```ruby +products = Arel::Table.new(:products) +# Attributes: [:id, :name, :price, :currency_id] + +currency_rates = Arel::Table.new(:currency_rates) +# Attributes: [:from_id, :to_id, :date, :rate] +``` + +Now, to order products by price in user preferred currency simply call: + +```ruby +products. + join(:currency_rates).on(products[:currency_id].eq(currency_rates[:from_id])). + where(currency_rates[:to_id].eq(user_preferred_currency), currency_rates[:date].eq(Date.today)). + order(products[:price] * currency_rates[:rate]) +``` + +#### Complex Joins + +##### Alias +Where Arel really shines is in its ability to handle complex joins and aggregations. As a first example, let's consider an "adjacency list", a tree represented in a table. Suppose we have a table `comments`, representing a threaded discussion: + +```ruby +comments = Arel::Table.new(:comments) +``` + +And this table has the following attributes: + +```ruby +# [:id, :body, :parent_id] +``` + +The `parent_id` column is a foreign key from the `comments` table to itself. +Joining a table to itself requires aliasing in SQL. This aliasing can be handled from Arel as below: + +```ruby +replies = comments.alias +comments_with_replies = \ + comments.join(replies).on(replies[:parent_id].eq(comments[:id])).where(comments[:id].eq(1)) +# => SELECT * FROM comments INNER JOIN comments AS comments_2 +# WHERE comments_2.parent_id = comments.id AND comments.id = 1 +``` + +This will return the reply for the first comment. + +##### CTE +[Common Table Expressions (CTE)](https://en.wikipedia.org/wiki/Common_table_expressions#Common_table_expression) support via: + +Create a `CTE` + +```ruby +cte_table = Arel::Table.new(:cte_table) +composed_cte = Arel::Nodes::As.new(cte_table, photos.where(photos[:created_at].gt(Date.current))) +``` + +Use the created `CTE`: + +```ruby +users. + join(cte_table).on(users[:id].eq(cte_table[:user_id])). + project(users[:id], cte_table[:click].sum). + with(composed_cte) + +# => WITH cte_table AS (SELECT FROM photos WHERE photos.created_at > '2014-05-02') +# SELECT users.id, SUM(cte_table.click) +# FROM users INNER JOIN cte_table ON users.id = cte_table.user_id +``` + +#### Write SQL strings +When your query is too complex for `Arel`, you can use `Arel::SqlLiteral`: + +```ruby +photo_clicks = Arel::Nodes::SqlLiteral.new(<<-SQL + CASE WHEN condition1 THEN calculation1 + WHEN condition2 THEN calculation2 + WHEN condition3 THEN calculation3 + ELSE default_calculation END +SQL +) + +photos.project(photo_clicks.as("photo_clicks")) +# => SELECT CASE WHEN condition1 THEN calculation1 +# WHEN condition2 THEN calculation2 +# WHEN condition3 THEN calculation3 +# ELSE default_calculation END +# FROM "photos" +``` + +## Contributing to Arel + +Arel is the work of many contributors. You're encouraged to submit pull requests, propose +features and discuss issues. + +See [CONTRIBUTING](CONTRIBUTING.md). + +## License +Arel is released under the [MIT License](http://www.opensource.org/licenses/MIT). diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel.rb new file mode 100644 index 00000000..e7c6fc7f --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true +require 'arel/errors' + +require 'arel/crud' +require 'arel/factory_methods' + +require 'arel/expressions' +require 'arel/predications' +require 'arel/window_predications' +require 'arel/math' +require 'arel/alias_predication' +require 'arel/order_predications' +require 'arel/table' +require 'arel/attributes' +require 'arel/compatibility/wheres' + +require 'arel/visitors' + +require 'arel/tree_manager' +require 'arel/insert_manager' +require 'arel/select_manager' +require 'arel/update_manager' +require 'arel/delete_manager' +require 'arel/nodes' + +module Arel + VERSION = '9.0.0' + + def self.sql raw_sql + Arel::Nodes::SqlLiteral.new raw_sql + end + + def self.star + sql '*' + end + ## Convenience Alias + Node = Arel::Nodes::Node +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/alias_predication.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/alias_predication.rb new file mode 100644 index 00000000..cb50fb95 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/alias_predication.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true +module Arel + module AliasPredication + def as other + Nodes::As.new self, Nodes::SqlLiteral.new(other) + end + end +end \ No newline at end of file diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/attributes.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/attributes.rb new file mode 100644 index 00000000..ed4739eb --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/attributes.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true +require 'arel/attributes/attribute' + +module Arel + module Attributes + ### + # Factory method to wrap a raw database +column+ to an Arel Attribute. + def self.for column + case column.type + when :string, :text, :binary then String + when :integer then Integer + when :float then Float + when :decimal then Decimal + when :date, :datetime, :timestamp, :time then Time + when :boolean then Boolean + else + Undefined + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/attributes/attribute.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/attributes/attribute.rb new file mode 100644 index 00000000..41bc0c32 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/attributes/attribute.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true +module Arel + module Attributes + class Attribute < Struct.new :relation, :name + include Arel::Expressions + include Arel::Predications + include Arel::AliasPredication + include Arel::OrderPredications + include Arel::Math + + ### + # Create a node for lowering this attribute + def lower + relation.lower self + end + + def type_cast_for_database(value) + relation.type_cast_for_database(name, value) + end + + def able_to_type_cast? + relation.able_to_type_cast? + end + end + + class String < Attribute; end + class Time < Attribute; end + class Boolean < Attribute; end + class Decimal < Attribute; end + class Float < Attribute; end + class Integer < Attribute; end + class Undefined < Attribute; end + end + + Attribute = Attributes::Attribute +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/collectors/bind.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/collectors/bind.rb new file mode 100644 index 00000000..d816aed9 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/collectors/bind.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Arel + module Collectors + class Bind + def initialize + @binds = [] + end + + def << str + self + end + + def add_bind bind + @binds << bind + self + end + + def value + @binds + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/collectors/composite.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/collectors/composite.rb new file mode 100644 index 00000000..4f6156fe --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/collectors/composite.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Arel + module Collectors + class Composite + def initialize(left, right) + @left = left + @right = right + end + + def << str + left << str + right << str + self + end + + def add_bind bind, &block + left.add_bind bind, &block + right.add_bind bind, &block + self + end + + def value + [left.value, right.value] + end + + protected + + attr_reader :left, :right + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/collectors/plain_string.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/collectors/plain_string.rb new file mode 100644 index 00000000..1e8d2a21 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/collectors/plain_string.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true +module Arel + module Collectors + class PlainString + def initialize + @str = ''.dup + end + + def value + @str + end + + def << str + @str << str + self + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/collectors/sql_string.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/collectors/sql_string.rb new file mode 100644 index 00000000..bcb941f6 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/collectors/sql_string.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'arel/collectors/plain_string' + +module Arel + module Collectors + class SQLString < PlainString + def initialize(*) + super + @bind_index = 1 + end + + def add_bind bind + self << yield(@bind_index) + @bind_index += 1 + self + end + + def compile bvs + value + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/collectors/substitute_binds.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/collectors/substitute_binds.rb new file mode 100644 index 00000000..99d2215a --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/collectors/substitute_binds.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true +module Arel + module Collectors + class SubstituteBinds + def initialize(quoter, delegate_collector) + @quoter = quoter + @delegate = delegate_collector + end + + def << str + delegate << str + self + end + + def add_bind bind + self << quoter.quote(bind) + end + + def value + delegate.value + end + + protected + + attr_reader :quoter, :delegate + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/compatibility/wheres.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/compatibility/wheres.rb new file mode 100644 index 00000000..3e60894b --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/compatibility/wheres.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true +module Arel + module Compatibility # :nodoc: + class Wheres # :nodoc: + include Enumerable + + module Value # :nodoc: + attr_accessor :visitor + def value + visitor.accept self + end + + def name + super.to_sym + end + end + + def initialize engine, collection + @engine = engine + @collection = collection + end + + def each + to_sql = Visitors::ToSql.new @engine + + @collection.each { |c| + c.extend(Value) + c.visitor = to_sql + yield c + } + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/crud.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/crud.rb new file mode 100644 index 00000000..2d104322 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/crud.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true +module Arel + ### + # FIXME hopefully we can remove this + module Crud + def compile_update values, pk + um = UpdateManager.new + + if Nodes::SqlLiteral === values + relation = @ctx.from + else + relation = values.first.first.relation + end + um.key = pk + um.table relation + um.set values + um.take @ast.limit.expr if @ast.limit + um.order(*@ast.orders) + um.wheres = @ctx.wheres + um + end + + def compile_insert values + im = create_insert + im.insert values + im + end + + def create_insert + InsertManager.new + end + + def compile_delete + dm = DeleteManager.new + dm.take @ast.limit.expr if @ast.limit + dm.wheres = @ctx.wheres + dm.from @ctx.froms + dm + end + + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/delete_manager.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/delete_manager.rb new file mode 100644 index 00000000..aee45112 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/delete_manager.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true +module Arel + class DeleteManager < Arel::TreeManager + def initialize + super + @ast = Nodes::DeleteStatement.new + @ctx = @ast + end + + def from relation + @ast.relation = relation + self + end + + def take limit + @ast.limit = Nodes::Limit.new(Nodes.build_quoted(limit)) if limit + self + end + + def wheres= list + @ast.wheres = list + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/errors.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/errors.rb new file mode 100644 index 00000000..86fbb804 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/errors.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true +module Arel + class ArelError < StandardError + end + + class EmptyJoinError < ArelError + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/expressions.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/expressions.rb new file mode 100644 index 00000000..612a0942 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/expressions.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true +module Arel + module Expressions + def count distinct = false + Nodes::Count.new [self], distinct + end + + def sum + Nodes::Sum.new [self] + end + + def maximum + Nodes::Max.new [self] + end + + def minimum + Nodes::Min.new [self] + end + + def average + Nodes::Avg.new [self] + end + + def extract field + Nodes::Extract.new [self], field + end + + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/factory_methods.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/factory_methods.rb new file mode 100644 index 00000000..6647c5ac --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/factory_methods.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true +module Arel + ### + # Methods for creating various nodes + module FactoryMethods + def create_true + Arel::Nodes::True.new + end + + def create_false + Arel::Nodes::False.new + end + + def create_table_alias relation, name + Nodes::TableAlias.new(relation, name) + end + + def create_join to, constraint = nil, klass = Nodes::InnerJoin + klass.new(to, constraint) + end + + def create_string_join to + create_join to, nil, Nodes::StringJoin + end + + def create_and clauses + Nodes::And.new clauses + end + + def create_on expr + Nodes::On.new expr + end + + def grouping expr + Nodes::Grouping.new expr + end + + ### + # Create a LOWER() function + def lower column + Nodes::NamedFunction.new 'LOWER', [Nodes.build_quoted(column)] + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/insert_manager.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/insert_manager.rb new file mode 100644 index 00000000..dcbac6cb --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/insert_manager.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true +module Arel + class InsertManager < Arel::TreeManager + def initialize + super + @ast = Nodes::InsertStatement.new + end + + def into table + @ast.relation = table + self + end + + def columns; @ast.columns end + def values= val; @ast.values = val; end + + def select select + @ast.select = select + end + + def insert fields + return if fields.empty? + + if String === fields + @ast.values = Nodes::SqlLiteral.new(fields) + else + @ast.relation ||= fields.first.first.relation + + values = [] + + fields.each do |column, value| + @ast.columns << column + values << value + end + @ast.values = create_values values, @ast.columns + end + self + end + + def create_values values, columns + Nodes::Values.new values, columns + end + + def create_values_list(rows) + Nodes::ValuesList.new(rows) + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/math.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/math.rb new file mode 100644 index 00000000..9e6549b5 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/math.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true +module Arel + module Math + def *(other) + Arel::Nodes::Multiplication.new(self, other) + end + + def +(other) + Arel::Nodes::Grouping.new(Arel::Nodes::Addition.new(self, other)) + end + + def -(other) + Arel::Nodes::Grouping.new(Arel::Nodes::Subtraction.new(self, other)) + end + + def /(other) + Arel::Nodes::Division.new(self, other) + end + + def &(other) + Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseAnd.new(self, other)) + end + + def |(other) + Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseOr.new(self, other)) + end + + def ^(other) + Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseXor.new(self, other)) + end + + def <<(other) + Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseShiftLeft.new(self, other)) + end + + def >>(other) + Arel::Nodes::Grouping.new(Arel::Nodes::BitwiseShiftRight.new(self, other)) + end + + def ~@ + Arel::Nodes::BitwiseNot.new(self) + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes.rb new file mode 100644 index 00000000..bb8ad650 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true +# node +require 'arel/nodes/node' +require 'arel/nodes/select_statement' +require 'arel/nodes/select_core' +require 'arel/nodes/insert_statement' +require 'arel/nodes/update_statement' +require 'arel/nodes/bind_param' + +# terminal + +require 'arel/nodes/terminal' +require 'arel/nodes/true' +require 'arel/nodes/false' + +# unary +require 'arel/nodes/unary' +require 'arel/nodes/grouping' +require 'arel/nodes/ascending' +require 'arel/nodes/descending' +require 'arel/nodes/unqualified_column' +require 'arel/nodes/with' + +# binary +require 'arel/nodes/binary' +require 'arel/nodes/equality' +require 'arel/nodes/in' # Why is this subclassed from equality? +require 'arel/nodes/join_source' +require 'arel/nodes/delete_statement' +require 'arel/nodes/table_alias' +require 'arel/nodes/infix_operation' +require 'arel/nodes/unary_operation' +require 'arel/nodes/over' +require 'arel/nodes/matches' +require 'arel/nodes/regexp' + +# nary +require 'arel/nodes/and' + +# function +# FIXME: Function + Alias can be rewritten as a Function and Alias node. +# We should make Function a Unary node and deprecate the use of "aliaz" +require 'arel/nodes/function' +require 'arel/nodes/count' +require 'arel/nodes/extract' +require 'arel/nodes/values' +require 'arel/nodes/values_list' +require 'arel/nodes/named_function' + +# windows +require 'arel/nodes/window' + +# conditional expressions +require 'arel/nodes/case' + +# joins +require 'arel/nodes/full_outer_join' +require 'arel/nodes/inner_join' +require 'arel/nodes/outer_join' +require 'arel/nodes/right_outer_join' +require 'arel/nodes/string_join' + +require 'arel/nodes/sql_literal' + +require 'arel/nodes/casted' diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/and.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/and.rb new file mode 100644 index 00000000..1e2f61cf --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/and.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true +module Arel + module Nodes + class And < Arel::Nodes::Node + attr_reader :children + + def initialize children + super() + @children = children + end + + def left + children.first + end + + def right + children[1] + end + + def hash + children.hash + end + + def eql? other + self.class == other.class && + self.children == other.children + end + alias :== :eql? + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/ascending.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/ascending.rb new file mode 100644 index 00000000..adadab55 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/ascending.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true +module Arel + module Nodes + class Ascending < Ordering + + def reverse + Descending.new(expr) + end + + def direction + :asc + end + + def ascending? + true + end + + def descending? + false + end + + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/binary.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/binary.rb new file mode 100644 index 00000000..30017887 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/binary.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true +module Arel + module Nodes + class Binary < Arel::Nodes::Node + attr_accessor :left, :right + + def initialize left, right + super() + @left = left + @right = right + end + + def initialize_copy other + super + @left = @left.clone if @left + @right = @right.clone if @right + end + + def hash + [self.class, @left, @right].hash + end + + def eql? other + self.class == other.class && + self.left == other.left && + self.right == other.right + end + alias :== :eql? + end + + %w{ + As + Assignment + Between + GreaterThan + GreaterThanOrEqual + Join + LessThan + LessThanOrEqual + NotEqual + NotIn + Or + Union + UnionAll + Intersect + Except + }.each do |name| + const_set name, Class.new(Binary) + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/bind_param.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/bind_param.rb new file mode 100644 index 00000000..efa4f452 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/bind_param.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true +module Arel + module Nodes + class BindParam < Node + attr_accessor :value + + def initialize(value) + @value = value + super() + end + + def hash + [self.class, self.value].hash + end + + def eql?(other) + other.is_a?(BindParam) && + value == other.value + end + alias :== :eql? + + def nil? + value.nil? + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/case.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/case.rb new file mode 100644 index 00000000..1edca400 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/case.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true +module Arel + module Nodes + class Case < Arel::Nodes::Node + include Arel::OrderPredications + include Arel::Predications + include Arel::AliasPredication + + attr_accessor :case, :conditions, :default + + def initialize expression = nil, default = nil + @case = expression + @conditions = [] + @default = default + end + + def when condition, expression = nil + @conditions << When.new(Nodes.build_quoted(condition), expression) + self + end + + def then expression + @conditions.last.right = Nodes.build_quoted(expression) + self + end + + def else expression + @default = Else.new Nodes.build_quoted(expression) + self + end + + def initialize_copy other + super + @case = @case.clone if @case + @conditions = @conditions.map { |x| x.clone } + @default = @default.clone if @default + end + + def hash + [@case, @conditions, @default].hash + end + + def eql? other + self.class == other.class && + self.case == other.case && + self.conditions == other.conditions && + self.default == other.default + end + alias :== :eql? + end + + class When < Binary # :nodoc: + end + + class Else < Unary # :nodoc: + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/casted.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/casted.rb new file mode 100644 index 00000000..1d7385d0 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/casted.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true +module Arel + module Nodes + class Casted < Arel::Nodes::Node # :nodoc: + attr_reader :val, :attribute + def initialize val, attribute + @val = val + @attribute = attribute + super() + end + + def nil?; @val.nil?; end + + def hash + [self.class, val, attribute].hash + end + + def eql? other + self.class == other.class && + self.val == other.val && + self.attribute == other.attribute + end + alias :== :eql? + end + + class Quoted < Arel::Nodes::Unary # :nodoc: + alias :val :value + def nil?; val.nil?; end + end + + def self.build_quoted other, attribute = nil + case other + when Arel::Nodes::Node, Arel::Attributes::Attribute, Arel::Table, Arel::Nodes::BindParam, Arel::SelectManager, Arel::Nodes::Quoted, Arel::Nodes::SqlLiteral + other + else + case attribute + when Arel::Attributes::Attribute + Casted.new other, attribute + else + Quoted.new other + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/count.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/count.rb new file mode 100644 index 00000000..4dd9be45 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/count.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true +module Arel + module Nodes + class Count < Arel::Nodes::Function + include Math + + def initialize expr, distinct = false, aliaz = nil + super(expr, aliaz) + @distinct = distinct + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/delete_statement.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/delete_statement.rb new file mode 100644 index 00000000..593ce9bd --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/delete_statement.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true +module Arel + module Nodes + class DeleteStatement < Arel::Nodes::Binary + attr_accessor :limit + + alias :relation :left + alias :relation= :left= + alias :wheres :right + alias :wheres= :right= + + def initialize relation = nil, wheres = [] + super + end + + def initialize_copy other + super + @right = @right.clone + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/descending.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/descending.rb new file mode 100644 index 00000000..d7261ab5 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/descending.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true +module Arel + module Nodes + class Descending < Ordering + + def reverse + Ascending.new(expr) + end + + def direction + :desc + end + + def ascending? + false + end + + def descending? + true + end + + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/equality.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/equality.rb new file mode 100644 index 00000000..ef44725e --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/equality.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true +module Arel + module Nodes + class Equality < Arel::Nodes::Binary + def operator; :== end + alias :operand1 :left + alias :operand2 :right + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/extract.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/extract.rb new file mode 100644 index 00000000..4e797b67 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/extract.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true +module Arel + module Nodes + class Extract < Arel::Nodes::Unary + include Arel::AliasPredication + include Arel::Predications + + attr_accessor :field + + def initialize expr, field + super(expr) + @field = field + end + + def hash + super ^ @field.hash + end + + def eql? other + super && + self.field == other.field + end + alias :== :eql? + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/false.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/false.rb new file mode 100644 index 00000000..fb821dd5 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/false.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true +module Arel + module Nodes + class False < Arel::Nodes::Node + def hash + self.class.hash + end + + def eql? other + self.class == other.class + end + alias :== :eql? + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/full_outer_join.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/full_outer_join.rb new file mode 100644 index 00000000..12a02d8c --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/full_outer_join.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true +module Arel + module Nodes + class FullOuterJoin < Arel::Nodes::Join + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/function.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/function.rb new file mode 100644 index 00000000..b2b89ee9 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/function.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true +module Arel + module Nodes + class Function < Arel::Nodes::Node + include Arel::Predications + include Arel::WindowPredications + include Arel::OrderPredications + attr_accessor :expressions, :alias, :distinct + + def initialize expr, aliaz = nil + super() + @expressions = expr + @alias = aliaz && SqlLiteral.new(aliaz) + @distinct = false + end + + def as aliaz + self.alias = SqlLiteral.new(aliaz) + self + end + + def hash + [@expressions, @alias, @distinct].hash + end + + def eql? other + self.class == other.class && + self.expressions == other.expressions && + self.alias == other.alias && + self.distinct == other.distinct + end + alias :== :eql? + + end + + %w{ + Sum + Exists + Max + Min + Avg + }.each do |name| + const_set(name, Class.new(Function)) + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/grouping.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/grouping.rb new file mode 100644 index 00000000..16911eb3 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/grouping.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true +module Arel + module Nodes + class Grouping < Unary + include Arel::Predications + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/in.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/in.rb new file mode 100644 index 00000000..30cd771c --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/in.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true +module Arel + module Nodes + class In < Equality + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/infix_operation.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/infix_operation.rb new file mode 100644 index 00000000..4eb7c535 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/infix_operation.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true +module Arel + module Nodes + + class InfixOperation < Binary + include Arel::Expressions + include Arel::Predications + include Arel::OrderPredications + include Arel::AliasPredication + include Arel::Math + + attr_reader :operator + + def initialize operator, left, right + super(left, right) + @operator = operator + end + end + + class Multiplication < InfixOperation + def initialize left, right + super(:*, left, right) + end + end + + class Division < InfixOperation + def initialize left, right + super(:/, left, right) + end + end + + class Addition < InfixOperation + def initialize left, right + super(:+, left, right) + end + end + + class Subtraction < InfixOperation + def initialize left, right + super(:-, left, right) + end + end + + class Concat < InfixOperation + def initialize left, right + super('||', left, right) + end + end + + class BitwiseAnd < InfixOperation + def initialize left, right + super(:&, left, right) + end + end + + class BitwiseOr < InfixOperation + def initialize left, right + super(:|, left, right) + end + end + + class BitwiseXor < InfixOperation + def initialize left, right + super(:^, left, right) + end + end + + class BitwiseShiftLeft < InfixOperation + def initialize left, right + super(:<<, left, right) + end + end + + class BitwiseShiftRight < InfixOperation + def initialize left, right + super(:>>, left, right) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/inner_join.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/inner_join.rb new file mode 100644 index 00000000..4e398267 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/inner_join.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true +module Arel + module Nodes + class InnerJoin < Arel::Nodes::Join + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/insert_statement.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/insert_statement.rb new file mode 100644 index 00000000..72793bc1 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/insert_statement.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true +module Arel + module Nodes + class InsertStatement < Arel::Nodes::Node + attr_accessor :relation, :columns, :values, :select + + def initialize + super() + @relation = nil + @columns = [] + @values = nil + @select = nil + end + + def initialize_copy other + super + @columns = @columns.clone + @values = @values.clone if @values + @select = @select.clone if @select + end + + def hash + [@relation, @columns, @values, @select].hash + end + + def eql? other + self.class == other.class && + self.relation == other.relation && + self.columns == other.columns && + self.select == other.select && + self.values == other.values + end + alias :== :eql? + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/join_source.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/join_source.rb new file mode 100644 index 00000000..428ce818 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/join_source.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true +module Arel + module Nodes + ### + # Class that represents a join source + # + # http://www.sqlite.org/syntaxdiagrams.html#join-source + + class JoinSource < Arel::Nodes::Binary + def initialize single_source, joinop = [] + super + end + + def empty? + !left && right.empty? + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/matches.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/matches.rb new file mode 100644 index 00000000..3ad3850a --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/matches.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true +module Arel + module Nodes + class Matches < Binary + attr_reader :escape + attr_accessor :case_sensitive + + def initialize(left, right, escape = nil, case_sensitive = false) + super(left, right) + @escape = escape && Nodes.build_quoted(escape) + @case_sensitive = case_sensitive + end + end + + class DoesNotMatch < Matches; end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/named_function.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/named_function.rb new file mode 100644 index 00000000..173838a7 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/named_function.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true +module Arel + module Nodes + class NamedFunction < Arel::Nodes::Function + attr_accessor :name + + def initialize name, expr, aliaz = nil + super(expr, aliaz) + @name = name + end + + def hash + super ^ @name.hash + end + + def eql? other + super && self.name == other.name + end + alias :== :eql? + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/node.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/node.rb new file mode 100644 index 00000000..34e71063 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/node.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true +require 'arel/collectors/sql_string' + +module Arel + module Nodes + ### + # Abstract base class for all AST nodes + class Node + include Arel::FactoryMethods + include Enumerable + + if $DEBUG + def _caller + @caller + end + + def initialize + @caller = caller.dup + end + end + + ### + # Factory method to create a Nodes::Not node that has the recipient of + # the caller as a child. + def not + Nodes::Not.new self + end + + ### + # Factory method to create a Nodes::Grouping node that has an Nodes::Or + # node as a child. + def or right + Nodes::Grouping.new Nodes::Or.new(self, right) + end + + ### + # Factory method to create an Nodes::And node. + def and right + Nodes::And.new [self, right] + end + + # FIXME: this method should go away. I don't like people calling + # to_sql on non-head nodes. This forces us to walk the AST until we + # can find a node that has a "relation" member. + # + # Maybe we should just use `Table.engine`? :'( + def to_sql engine = Table.engine + collector = Arel::Collectors::SQLString.new + collector = engine.connection.visitor.accept self, collector + collector.value + end + + # Iterate through AST, nodes will be yielded depth-first + def each &block + return enum_for(:each) unless block_given? + + ::Arel::Visitors::DepthFirst.new(block).accept self + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/outer_join.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/outer_join.rb new file mode 100644 index 00000000..c568655f --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/outer_join.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true +module Arel + module Nodes + class OuterJoin < Arel::Nodes::Join + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/over.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/over.rb new file mode 100644 index 00000000..47a34e69 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/over.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true +module Arel + module Nodes + + class Over < Binary + include Arel::AliasPredication + + def initialize(left, right = nil) + super(left, right) + end + + def operator; 'OVER' end + end + + end +end \ No newline at end of file diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/regexp.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/regexp.rb new file mode 100644 index 00000000..8a76185e --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/regexp.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true +module Arel + module Nodes + class Regexp < Binary + attr_accessor :case_sensitive + + def initialize(left, right, case_sensitive = true) + super(left, right) + @case_sensitive = case_sensitive + end + end + + class NotRegexp < Regexp; end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/right_outer_join.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/right_outer_join.rb new file mode 100644 index 00000000..04ab31eb --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/right_outer_join.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true +module Arel + module Nodes + class RightOuterJoin < Arel::Nodes::Join + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/select_core.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/select_core.rb new file mode 100644 index 00000000..264fa465 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/select_core.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true +module Arel + module Nodes + class SelectCore < Arel::Nodes::Node + attr_accessor :top, :projections, :wheres, :groups, :windows + attr_accessor :havings, :source, :set_quantifier + + def initialize + super() + @source = JoinSource.new nil + @top = nil + + # http://savage.net.au/SQL/sql-92.bnf.html#set%20quantifier + @set_quantifier = nil + @projections = [] + @wheres = [] + @groups = [] + @havings = [] + @windows = [] + end + + def from + @source.left + end + + def from= value + @source.left = value + end + + alias :froms= :from= + alias :froms :from + + def initialize_copy other + super + @source = @source.clone if @source + @projections = @projections.clone + @wheres = @wheres.clone + @groups = @groups.clone + @havings = @havings.clone + @windows = @windows.clone + end + + def hash + [ + @source, @top, @set_quantifier, @projections, + @wheres, @groups, @havings, @windows + ].hash + end + + def eql? other + self.class == other.class && + self.source == other.source && + self.top == other.top && + self.set_quantifier == other.set_quantifier && + self.projections == other.projections && + self.wheres == other.wheres && + self.groups == other.groups && + self.havings == other.havings && + self.windows == other.windows + end + alias :== :eql? + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/select_statement.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/select_statement.rb new file mode 100644 index 00000000..641a0840 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/select_statement.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true +module Arel + module Nodes + class SelectStatement < Arel::Nodes::Node + attr_reader :cores + attr_accessor :limit, :orders, :lock, :offset, :with + + def initialize cores = [SelectCore.new] + super() + @cores = cores + @orders = [] + @limit = nil + @lock = nil + @offset = nil + @with = nil + end + + def initialize_copy other + super + @cores = @cores.map { |x| x.clone } + @orders = @orders.map { |x| x.clone } + end + + def hash + [@cores, @orders, @limit, @lock, @offset, @with].hash + end + + def eql? other + self.class == other.class && + self.cores == other.cores && + self.orders == other.orders && + self.limit == other.limit && + self.lock == other.lock && + self.offset == other.offset && + self.with == other.with + end + alias :== :eql? + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/sql_literal.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/sql_literal.rb new file mode 100644 index 00000000..73575a7d --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/sql_literal.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true +module Arel + module Nodes + class SqlLiteral < String + include Arel::Expressions + include Arel::Predications + include Arel::AliasPredication + include Arel::OrderPredications + + def encode_with(coder) + coder.scalar = self.to_s + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/string_join.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/string_join.rb new file mode 100644 index 00000000..21d6845c --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/string_join.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true +module Arel + module Nodes + class StringJoin < Arel::Nodes::Join + def initialize left, right = nil + super + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/table_alias.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/table_alias.rb new file mode 100644 index 00000000..78deb175 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/table_alias.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true +module Arel + module Nodes + class TableAlias < Arel::Nodes::Binary + alias :name :right + alias :relation :left + alias :table_alias :name + + def [] name + Attribute.new(self, name) + end + + def table_name + relation.respond_to?(:name) ? relation.name : name + end + + def type_cast_for_database(*args) + relation.type_cast_for_database(*args) + end + + def able_to_type_cast? + relation.respond_to?(:able_to_type_cast?) && relation.able_to_type_cast? + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/terminal.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/terminal.rb new file mode 100644 index 00000000..421f0399 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/terminal.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true +module Arel + module Nodes + class Distinct < Arel::Nodes::Node + def hash + self.class.hash + end + + def eql? other + self.class == other.class + end + alias :== :eql? + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/true.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/true.rb new file mode 100644 index 00000000..bb9d8c14 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/true.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true +module Arel + module Nodes + class True < Arel::Nodes::Node + def hash + self.class.hash + end + + def eql? other + self.class == other.class + end + alias :== :eql? + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/unary.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/unary.rb new file mode 100644 index 00000000..a42744b1 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/unary.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true +module Arel + module Nodes + class Unary < Arel::Nodes::Node + attr_accessor :expr + alias :value :expr + + def initialize expr + super() + @expr = expr + end + + def hash + @expr.hash + end + + def eql? other + self.class == other.class && + self.expr == other.expr + end + alias :== :eql? + end + + %w{ + Bin + Cube + DistinctOn + Group + GroupingElement + GroupingSet + Limit + Lock + Not + Offset + On + Ordering + RollUp + Top + }.each do |name| + const_set(name, Class.new(Unary)) + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/unary_operation.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/unary_operation.rb new file mode 100644 index 00000000..3c56ef20 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/unary_operation.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true +module Arel + module Nodes + + class UnaryOperation < Unary + include Arel::Expressions + include Arel::Predications + include Arel::OrderPredications + include Arel::AliasPredication + include Arel::Math + + attr_reader :operator + + def initialize operator, operand + super(operand) + @operator = operator + end + end + + class BitwiseNot < UnaryOperation + def initialize operand + super(:~, operand) + end + end + end +end \ No newline at end of file diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/unqualified_column.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/unqualified_column.rb new file mode 100644 index 00000000..f9017238 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/unqualified_column.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true +module Arel + module Nodes + class UnqualifiedColumn < Arel::Nodes::Unary + alias :attribute :expr + alias :attribute= :expr= + + def relation + @expr.relation + end + + def column + @expr.column + end + + def name + @expr.name + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/update_statement.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/update_statement.rb new file mode 100644 index 00000000..286f0bd3 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/update_statement.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true +module Arel + module Nodes + class UpdateStatement < Arel::Nodes::Node + attr_accessor :relation, :wheres, :values, :orders, :limit + attr_accessor :key + + def initialize + @relation = nil + @wheres = [] + @values = [] + @orders = [] + @limit = nil + @key = nil + end + + def initialize_copy other + super + @wheres = @wheres.clone + @values = @values.clone + end + + def hash + [@relation, @wheres, @values, @orders, @limit, @key].hash + end + + def eql? other + self.class == other.class && + self.relation == other.relation && + self.wheres == other.wheres && + self.values == other.values && + self.orders == other.orders && + self.limit == other.limit && + self.key == other.key + end + alias :== :eql? + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/values.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/values.rb new file mode 100644 index 00000000..b32d5063 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/values.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true +module Arel + module Nodes + class Values < Arel::Nodes::Binary + alias :expressions :left + alias :expressions= :left= + alias :columns :right + alias :columns= :right= + + def initialize exprs, columns = [] + super + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/values_list.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/values_list.rb new file mode 100644 index 00000000..89cea179 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/values_list.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true +module Arel + module Nodes + class ValuesList < Node + attr_reader :rows + + def initialize(rows) + @rows = rows + super() + end + + def hash + @rows.hash + end + + def eql? other + self.class == other.class && + self.rows == other.rows + end + alias :== :eql? + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/window.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/window.rb new file mode 100644 index 00000000..23a005da --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/window.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true +module Arel + module Nodes + class Window < Arel::Nodes::Node + attr_accessor :orders, :framing, :partitions + + def initialize + @orders = [] + @partitions = [] + @framing = nil + end + + def order *expr + # FIXME: We SHOULD NOT be converting these to SqlLiteral automatically + @orders.concat expr.map { |x| + String === x || Symbol === x ? Nodes::SqlLiteral.new(x.to_s) : x + } + self + end + + def partition *expr + # FIXME: We SHOULD NOT be converting these to SqlLiteral automatically + @partitions.concat expr.map { |x| + String === x || Symbol === x ? Nodes::SqlLiteral.new(x.to_s) : x + } + self + end + + def frame(expr) + @framing = expr + end + + def rows(expr = nil) + if @framing + Rows.new(expr) + else + frame(Rows.new(expr)) + end + end + + def range(expr = nil) + if @framing + Range.new(expr) + else + frame(Range.new(expr)) + end + end + + def initialize_copy other + super + @orders = @orders.map { |x| x.clone } + end + + def hash + [@orders, @framing].hash + end + + def eql? other + self.class == other.class && + self.orders == other.orders && + self.framing == other.framing && + self.partitions == other.partitions + end + alias :== :eql? + end + + class NamedWindow < Window + attr_accessor :name + + def initialize name + super() + @name = name + end + + def initialize_copy other + super + @name = other.name.clone + end + + def hash + super ^ @name.hash + end + + def eql? other + super && self.name == other.name + end + alias :== :eql? + end + + class Rows < Unary + def initialize(expr = nil) + super(expr) + end + end + + class Range < Unary + def initialize(expr = nil) + super(expr) + end + end + + class CurrentRow < Node + def hash + self.class.hash + end + + def eql? other + self.class == other.class + end + alias :== :eql? + end + + class Preceding < Unary + def initialize(expr = nil) + super(expr) + end + end + + class Following < Unary + def initialize(expr = nil) + super(expr) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/with.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/with.rb new file mode 100644 index 00000000..def7840e --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/nodes/with.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true +module Arel + module Nodes + class With < Arel::Nodes::Unary + alias children expr + end + + class WithRecursive < With; end + end +end + diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/order_predications.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/order_predications.rb new file mode 100644 index 00000000..d84be82b --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/order_predications.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true +module Arel + module OrderPredications + + def asc + Nodes::Ascending.new self + end + + def desc + Nodes::Descending.new self + end + + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/predications.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/predications.rb new file mode 100644 index 00000000..799c7c67 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/predications.rb @@ -0,0 +1,240 @@ +# frozen_string_literal: true +module Arel + module Predications + def not_eq other + Nodes::NotEqual.new self, quoted_node(other) + end + + def not_eq_any others + grouping_any :not_eq, others + end + + def not_eq_all others + grouping_all :not_eq, others + end + + def eq other + Nodes::Equality.new self, quoted_node(other) + end + + def eq_any others + grouping_any :eq, others + end + + def eq_all others + grouping_all :eq, quoted_array(others) + end + + def between other + if equals_quoted?(other.begin, -Float::INFINITY) + if equals_quoted?(other.end, Float::INFINITY) + not_in([]) + elsif other.exclude_end? + lt(other.end) + else + lteq(other.end) + end + elsif equals_quoted?(other.end, Float::INFINITY) + gteq(other.begin) + elsif other.exclude_end? + gteq(other.begin).and(lt(other.end)) + else + left = quoted_node(other.begin) + right = quoted_node(other.end) + Nodes::Between.new(self, left.and(right)) + end + end + + def in other + case other + when Arel::SelectManager + Arel::Nodes::In.new(self, other.ast) + when Range + if $VERBOSE + warn <<-eowarn +Passing a range to `#in` is deprecated. Call `#between`, instead. + eowarn + end + between(other) + when Enumerable + Nodes::In.new self, quoted_array(other) + else + Nodes::In.new self, quoted_node(other) + end + end + + def in_any others + grouping_any :in, others + end + + def in_all others + grouping_all :in, others + end + + def not_between other + if equals_quoted?(other.begin, -Float::INFINITY) + if equals_quoted?(other.end, Float::INFINITY) + self.in([]) + elsif other.exclude_end? + gteq(other.end) + else + gt(other.end) + end + elsif equals_quoted?(other.end, Float::INFINITY) + lt(other.begin) + else + left = lt(other.begin) + right = if other.exclude_end? + gteq(other.end) + else + gt(other.end) + end + left.or(right) + end + end + + def not_in other + case other + when Arel::SelectManager + Arel::Nodes::NotIn.new(self, other.ast) + when Range + if $VERBOSE + warn <<-eowarn +Passing a range to `#not_in` is deprecated. Call `#not_between`, instead. + eowarn + end + not_between(other) + when Enumerable + Nodes::NotIn.new self, quoted_array(other) + else + Nodes::NotIn.new self, quoted_node(other) + end + end + + def not_in_any others + grouping_any :not_in, others + end + + def not_in_all others + grouping_all :not_in, others + end + + def matches other, escape = nil, case_sensitive = false + Nodes::Matches.new self, quoted_node(other), escape, case_sensitive + end + + def matches_regexp other, case_sensitive = true + Nodes::Regexp.new self, quoted_node(other), case_sensitive + end + + def matches_any others, escape = nil, case_sensitive = false + grouping_any :matches, others, escape, case_sensitive + end + + def matches_all others, escape = nil, case_sensitive = false + grouping_all :matches, others, escape, case_sensitive + end + + def does_not_match other, escape = nil, case_sensitive = false + Nodes::DoesNotMatch.new self, quoted_node(other), escape, case_sensitive + end + + def does_not_match_regexp other, case_sensitive = true + Nodes::NotRegexp.new self, quoted_node(other), case_sensitive + end + + def does_not_match_any others, escape = nil + grouping_any :does_not_match, others, escape + end + + def does_not_match_all others, escape = nil + grouping_all :does_not_match, others, escape + end + + def gteq right + Nodes::GreaterThanOrEqual.new self, quoted_node(right) + end + + def gteq_any others + grouping_any :gteq, others + end + + def gteq_all others + grouping_all :gteq, others + end + + def gt right + Nodes::GreaterThan.new self, quoted_node(right) + end + + def gt_any others + grouping_any :gt, others + end + + def gt_all others + grouping_all :gt, others + end + + def lt right + Nodes::LessThan.new self, quoted_node(right) + end + + def lt_any others + grouping_any :lt, others + end + + def lt_all others + grouping_all :lt, others + end + + def lteq right + Nodes::LessThanOrEqual.new self, quoted_node(right) + end + + def lteq_any others + grouping_any :lteq, others + end + + def lteq_all others + grouping_all :lteq, others + end + + def when right + Nodes::Case.new(self).when quoted_node(right) + end + + def concat other + Nodes::Concat.new self, other + end + + private + + def grouping_any method_id, others, *extras + nodes = others.map {|expr| send(method_id, expr, *extras)} + Nodes::Grouping.new nodes.inject { |memo,node| + Nodes::Or.new(memo, node) + } + end + + def grouping_all method_id, others, *extras + nodes = others.map {|expr| send(method_id, expr, *extras)} + Nodes::Grouping.new Nodes::And.new(nodes) + end + + def quoted_node(other) + Nodes.build_quoted(other, self) + end + + def quoted_array(others) + others.map { |v| quoted_node(v) } + end + + def equals_quoted?(maybe_quoted, value) + if maybe_quoted.is_a?(Nodes::Quoted) + maybe_quoted.val == value + else + maybe_quoted == value + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/select_manager.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/select_manager.rb new file mode 100644 index 00000000..0b351768 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/select_manager.rb @@ -0,0 +1,269 @@ +# frozen_string_literal: true +require 'arel/collectors/sql_string' + +module Arel + class SelectManager < Arel::TreeManager + include Arel::Crud + + STRING_OR_SYMBOL_CLASS = [Symbol, String] + + def initialize table = nil + super() + @ast = Nodes::SelectStatement.new + @ctx = @ast.cores.last + from table + end + + def initialize_copy other + super + @ctx = @ast.cores.last + end + + def limit + @ast.limit && @ast.limit.expr + end + alias :taken :limit + + def constraints + @ctx.wheres + end + + def offset + @ast.offset && @ast.offset.expr + end + + def skip amount + if amount + @ast.offset = Nodes::Offset.new(amount) + else + @ast.offset = nil + end + self + end + alias :offset= :skip + + ### + # Produces an Arel::Nodes::Exists node + def exists + Arel::Nodes::Exists.new @ast + end + + def as other + create_table_alias grouping(@ast), Nodes::SqlLiteral.new(other) + end + + def lock locking = Arel.sql('FOR UPDATE') + case locking + when true + locking = Arel.sql('FOR UPDATE') + when Arel::Nodes::SqlLiteral + when String + locking = Arel.sql locking + end + + @ast.lock = Nodes::Lock.new(locking) + self + end + + def locked + @ast.lock + end + + def on *exprs + @ctx.source.right.last.right = Nodes::On.new(collapse(exprs)) + self + end + + def group *columns + columns.each do |column| + # FIXME: backwards compat + column = Nodes::SqlLiteral.new(column) if String === column + column = Nodes::SqlLiteral.new(column.to_s) if Symbol === column + + @ctx.groups.push Nodes::Group.new column + end + self + end + + def from table + table = Nodes::SqlLiteral.new(table) if String === table + + case table + when Nodes::Join + @ctx.source.right << table + else + @ctx.source.left = table + end + + self + end + + def froms + @ast.cores.map { |x| x.from }.compact + end + + def join relation, klass = Nodes::InnerJoin + return self unless relation + + case relation + when String, Nodes::SqlLiteral + raise EmptyJoinError if relation.empty? + klass = Nodes::StringJoin + end + + @ctx.source.right << create_join(relation, nil, klass) + self + end + + def outer_join relation + join(relation, Nodes::OuterJoin) + end + + def having expr + @ctx.havings << expr + self + end + + def window name + window = Nodes::NamedWindow.new(name) + @ctx.windows.push window + window + end + + def project *projections + # FIXME: converting these to SQLLiterals is probably not good, but + # rails tests require it. + @ctx.projections.concat projections.map { |x| + STRING_OR_SYMBOL_CLASS.include?(x.class) ? Nodes::SqlLiteral.new(x.to_s) : x + } + self + end + + def projections + @ctx.projections + end + + def projections= projections + @ctx.projections = projections + end + + def distinct(value = true) + if value + @ctx.set_quantifier = Arel::Nodes::Distinct.new + else + @ctx.set_quantifier = nil + end + self + end + + def distinct_on(value) + if value + @ctx.set_quantifier = Arel::Nodes::DistinctOn.new(value) + else + @ctx.set_quantifier = nil + end + self + end + + def order *expr + # FIXME: We SHOULD NOT be converting these to SqlLiteral automatically + @ast.orders.concat expr.map { |x| + STRING_OR_SYMBOL_CLASS.include?(x.class) ? Nodes::SqlLiteral.new(x.to_s) : x + } + self + end + + def orders + @ast.orders + end + + def where_sql engine = Table.engine + return if @ctx.wheres.empty? + + viz = Visitors::WhereSql.new(engine.connection.visitor, engine.connection) + Nodes::SqlLiteral.new viz.accept(@ctx, Collectors::SQLString.new).value + end + + def union operation, other = nil + if other + node_class = Nodes.const_get("Union#{operation.to_s.capitalize}") + else + other = operation + node_class = Nodes::Union + end + + node_class.new self.ast, other.ast + end + + def intersect other + Nodes::Intersect.new ast, other.ast + end + + def except other + Nodes::Except.new ast, other.ast + end + alias :minus :except + + def with *subqueries + if subqueries.first.is_a? Symbol + node_class = Nodes.const_get("With#{subqueries.shift.to_s.capitalize}") + else + node_class = Nodes::With + end + @ast.with = node_class.new(subqueries.flatten) + + self + end + + def take limit + if limit + @ast.limit = Nodes::Limit.new(limit) + @ctx.top = Nodes::Top.new(limit) + else + @ast.limit = nil + @ctx.top = nil + end + self + end + alias limit= take + + def join_sources + @ctx.source.right + end + + def source + @ctx.source + end + + class Row < Struct.new(:data) # :nodoc: + def id + data['id'] + end + + def method_missing(name, *args) + name = name.to_s + return data[name] if data.key?(name) + super + end + end + + private + def collapse exprs, existing = nil + exprs = exprs.unshift(existing.expr) if existing + exprs = exprs.compact.map { |expr| + if String === expr + # FIXME: Don't do this automatically + Arel.sql(expr) + else + expr + end + } + + if exprs.length == 1 + exprs.first + else + create_and exprs + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/table.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/table.rb new file mode 100644 index 00000000..b3f2d79e --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/table.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true +module Arel + class Table + include Arel::Crud + include Arel::FactoryMethods + + @engine = nil + class << self; attr_accessor :engine; end + + attr_accessor :name, :table_alias + + # TableAlias and Table both have a #table_name which is the name of the underlying table + alias :table_name :name + + def initialize(name, as: nil, type_caster: nil) + @name = name.to_s + @type_caster = type_caster + + # Sometime AR sends an :as parameter to table, to let the table know + # that it is an Alias. We may want to override new, and return a + # TableAlias node? + if as.to_s == @name + as = nil + end + @table_alias = as + end + + def alias name = "#{self.name}_2" + Nodes::TableAlias.new(self, name) + end + + def from + SelectManager.new(self) + end + + def join relation, klass = Nodes::InnerJoin + return from unless relation + + case relation + when String, Nodes::SqlLiteral + raise EmptyJoinError if relation.empty? + klass = Nodes::StringJoin + end + + from.join(relation, klass) + end + + def outer_join relation + join(relation, Nodes::OuterJoin) + end + + def group *columns + from.group(*columns) + end + + def order *expr + from.order(*expr) + end + + def where condition + from.where condition + end + + def project *things + from.project(*things) + end + + def take amount + from.take amount + end + + def skip amount + from.skip amount + end + + def having expr + from.having expr + end + + def [] name + ::Arel::Attribute.new self, name + end + + def hash + # Perf note: aliases and table alias is excluded from the hash + # aliases can have a loop back to this table breaking hashes in parent + # relations, for the vast majority of cases @name is unique to a query + @name.hash + end + + def eql? other + self.class == other.class && + self.name == other.name && + self.table_alias == other.table_alias + end + alias :== :eql? + + def type_cast_for_database(attribute_name, value) + type_caster.type_cast_for_database(attribute_name, value) + end + + def able_to_type_cast? + !type_caster.nil? + end + + protected + + attr_reader :type_caster + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/tree_manager.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/tree_manager.rb new file mode 100644 index 00000000..05424424 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/tree_manager.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true +require 'arel/collectors/sql_string' + +module Arel + class TreeManager + include Arel::FactoryMethods + + attr_reader :ast + + def initialize + @ctx = nil + end + + def to_dot + collector = Arel::Collectors::PlainString.new + collector = Visitors::Dot.new.accept @ast, collector + collector.value + end + + def to_sql engine = Table.engine + collector = Arel::Collectors::SQLString.new + collector = engine.connection.visitor.accept @ast, collector + collector.value + end + + def initialize_copy other + super + @ast = @ast.clone + end + + def where expr + if Arel::TreeManager === expr + expr = expr.ast + end + @ctx.wheres << expr + self + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/update_manager.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/update_manager.rb new file mode 100644 index 00000000..eac414ea --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/update_manager.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true +module Arel + class UpdateManager < Arel::TreeManager + def initialize + super + @ast = Nodes::UpdateStatement.new + @ctx = @ast + end + + def take limit + @ast.limit = Nodes::Limit.new(Nodes.build_quoted(limit)) if limit + self + end + + def key= key + @ast.key = Nodes.build_quoted(key) + end + + def key + @ast.key + end + + def order *expr + @ast.orders = expr + self + end + + ### + # UPDATE +table+ + def table table + @ast.relation = table + self + end + + def wheres= exprs + @ast.wheres = exprs + end + + def where expr + @ast.wheres << expr + self + end + + def set values + if String === values + @ast.values = [values] + else + @ast.values = values.map { |column,value| + Nodes::Assignment.new( + Nodes::UnqualifiedColumn.new(column), + value + ) + } + end + self + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors.rb new file mode 100644 index 00000000..a3404cf9 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true +require 'arel/visitors/visitor' +require 'arel/visitors/depth_first' +require 'arel/visitors/to_sql' +require 'arel/visitors/sqlite' +require 'arel/visitors/postgresql' +require 'arel/visitors/mysql' +require 'arel/visitors/mssql' +require 'arel/visitors/oracle' +require 'arel/visitors/oracle12' +require 'arel/visitors/where_sql' +require 'arel/visitors/dot' +require 'arel/visitors/ibm_db' +require 'arel/visitors/informix' + +module Arel + module Visitors + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/depth_first.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/depth_first.rb new file mode 100644 index 00000000..5416a285 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/depth_first.rb @@ -0,0 +1,198 @@ +# frozen_string_literal: true +module Arel + module Visitors + class DepthFirst < Arel::Visitors::Visitor + def initialize block = nil + @block = block || Proc.new + super() + end + + private + + def visit o + super + @block.call o + end + + def unary o + visit o.expr + end + alias :visit_Arel_Nodes_Else :unary + alias :visit_Arel_Nodes_Group :unary + alias :visit_Arel_Nodes_Cube :unary + alias :visit_Arel_Nodes_RollUp :unary + alias :visit_Arel_Nodes_GroupingSet :unary + alias :visit_Arel_Nodes_GroupingElement :unary + alias :visit_Arel_Nodes_Grouping :unary + alias :visit_Arel_Nodes_Having :unary + alias :visit_Arel_Nodes_Limit :unary + alias :visit_Arel_Nodes_Not :unary + alias :visit_Arel_Nodes_Offset :unary + alias :visit_Arel_Nodes_On :unary + alias :visit_Arel_Nodes_Ordering :unary + alias :visit_Arel_Nodes_Ascending :unary + alias :visit_Arel_Nodes_Descending :unary + alias :visit_Arel_Nodes_Top :unary + alias :visit_Arel_Nodes_UnqualifiedColumn :unary + + def function o + visit o.expressions + visit o.alias + visit o.distinct + end + alias :visit_Arel_Nodes_Avg :function + alias :visit_Arel_Nodes_Exists :function + alias :visit_Arel_Nodes_Max :function + alias :visit_Arel_Nodes_Min :function + alias :visit_Arel_Nodes_Sum :function + + def visit_Arel_Nodes_NamedFunction o + visit o.name + visit o.expressions + visit o.distinct + visit o.alias + end + + def visit_Arel_Nodes_Count o + visit o.expressions + visit o.alias + visit o.distinct + end + + def visit_Arel_Nodes_Case o + visit o.case + visit o.conditions + visit o.default + end + + def nary o + o.children.each { |child| visit child} + end + alias :visit_Arel_Nodes_And :nary + + def binary o + visit o.left + visit o.right + end + alias :visit_Arel_Nodes_As :binary + alias :visit_Arel_Nodes_Assignment :binary + alias :visit_Arel_Nodes_Between :binary + alias :visit_Arel_Nodes_Concat :binary + alias :visit_Arel_Nodes_DeleteStatement :binary + alias :visit_Arel_Nodes_DoesNotMatch :binary + alias :visit_Arel_Nodes_Equality :binary + alias :visit_Arel_Nodes_FullOuterJoin :binary + alias :visit_Arel_Nodes_GreaterThan :binary + alias :visit_Arel_Nodes_GreaterThanOrEqual :binary + alias :visit_Arel_Nodes_In :binary + alias :visit_Arel_Nodes_InfixOperation :binary + alias :visit_Arel_Nodes_JoinSource :binary + alias :visit_Arel_Nodes_InnerJoin :binary + alias :visit_Arel_Nodes_LessThan :binary + alias :visit_Arel_Nodes_LessThanOrEqual :binary + alias :visit_Arel_Nodes_Matches :binary + alias :visit_Arel_Nodes_NotEqual :binary + alias :visit_Arel_Nodes_NotIn :binary + alias :visit_Arel_Nodes_NotRegexp :binary + alias :visit_Arel_Nodes_Or :binary + alias :visit_Arel_Nodes_OuterJoin :binary + alias :visit_Arel_Nodes_Regexp :binary + alias :visit_Arel_Nodes_RightOuterJoin :binary + alias :visit_Arel_Nodes_TableAlias :binary + alias :visit_Arel_Nodes_Values :binary + alias :visit_Arel_Nodes_When :binary + + def visit_Arel_Nodes_StringJoin o + visit o.left + end + + def visit_Arel_Attribute o + visit o.relation + visit o.name + end + alias :visit_Arel_Attributes_Integer :visit_Arel_Attribute + alias :visit_Arel_Attributes_Float :visit_Arel_Attribute + alias :visit_Arel_Attributes_String :visit_Arel_Attribute + alias :visit_Arel_Attributes_Time :visit_Arel_Attribute + alias :visit_Arel_Attributes_Boolean :visit_Arel_Attribute + alias :visit_Arel_Attributes_Attribute :visit_Arel_Attribute + alias :visit_Arel_Attributes_Decimal :visit_Arel_Attribute + + def visit_Arel_Table o + visit o.name + end + + def terminal o + end + alias :visit_ActiveSupport_Multibyte_Chars :terminal + alias :visit_ActiveSupport_StringInquirer :terminal + alias :visit_Arel_Nodes_Lock :terminal + alias :visit_Arel_Nodes_Node :terminal + alias :visit_Arel_Nodes_SqlLiteral :terminal + alias :visit_Arel_Nodes_BindParam :terminal + alias :visit_Arel_Nodes_Window :terminal + alias :visit_Arel_Nodes_True :terminal + alias :visit_Arel_Nodes_False :terminal + alias :visit_BigDecimal :terminal + alias :visit_Bignum :terminal + alias :visit_Class :terminal + alias :visit_Date :terminal + alias :visit_DateTime :terminal + alias :visit_FalseClass :terminal + alias :visit_Fixnum :terminal + alias :visit_Float :terminal + alias :visit_Integer :terminal + alias :visit_NilClass :terminal + alias :visit_String :terminal + alias :visit_Symbol :terminal + alias :visit_Time :terminal + alias :visit_TrueClass :terminal + + def visit_Arel_Nodes_InsertStatement o + visit o.relation + visit o.columns + visit o.values + end + + def visit_Arel_Nodes_SelectCore o + visit o.projections + visit o.source + visit o.wheres + visit o.groups + visit o.windows + visit o.havings + end + + def visit_Arel_Nodes_SelectStatement o + visit o.cores + visit o.orders + visit o.limit + visit o.lock + visit o.offset + end + + def visit_Arel_Nodes_UpdateStatement o + visit o.relation + visit o.values + visit o.wheres + visit o.orders + visit o.limit + end + + def visit_Array o + o.each { |i| visit i } + end + alias :visit_Set :visit_Array + + def visit_Hash o + o.each { |k,v| visit(k); visit(v) } + end + + DISPATCH = dispatch_cache + + def get_dispatch_cache + DISPATCH + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/dot.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/dot.rb new file mode 100644 index 00000000..9aa22d33 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/dot.rb @@ -0,0 +1,291 @@ +# frozen_string_literal: true +module Arel + module Visitors + class Dot < Arel::Visitors::Visitor + class Node # :nodoc: + attr_accessor :name, :id, :fields + + def initialize name, id, fields = [] + @name = name + @id = id + @fields = fields + end + end + + class Edge < Struct.new :name, :from, :to # :nodoc: + end + + def initialize + super() + @nodes = [] + @edges = [] + @node_stack = [] + @edge_stack = [] + @seen = {} + end + + def accept object, collector + visit object + collector << to_dot + end + + private + + def visit_Arel_Nodes_Ordering o + visit_edge o, "expr" + end + + def visit_Arel_Nodes_TableAlias o + visit_edge o, "name" + visit_edge o, "relation" + end + + def visit_Arel_Nodes_Count o + visit_edge o, "expressions" + visit_edge o, "distinct" + end + + def visit_Arel_Nodes_Values o + visit_edge o, "expressions" + end + + def visit_Arel_Nodes_StringJoin o + visit_edge o, "left" + end + + def visit_Arel_Nodes_InnerJoin o + visit_edge o, "left" + visit_edge o, "right" + end + alias :visit_Arel_Nodes_FullOuterJoin :visit_Arel_Nodes_InnerJoin + alias :visit_Arel_Nodes_OuterJoin :visit_Arel_Nodes_InnerJoin + alias :visit_Arel_Nodes_RightOuterJoin :visit_Arel_Nodes_InnerJoin + + def visit_Arel_Nodes_DeleteStatement o + visit_edge o, "relation" + visit_edge o, "wheres" + end + + def unary o + visit_edge o, "expr" + end + alias :visit_Arel_Nodes_Group :unary + alias :visit_Arel_Nodes_Cube :unary + alias :visit_Arel_Nodes_RollUp :unary + alias :visit_Arel_Nodes_GroupingSet :unary + alias :visit_Arel_Nodes_GroupingElement :unary + alias :visit_Arel_Nodes_Grouping :unary + alias :visit_Arel_Nodes_Having :unary + alias :visit_Arel_Nodes_Limit :unary + alias :visit_Arel_Nodes_Not :unary + alias :visit_Arel_Nodes_Offset :unary + alias :visit_Arel_Nodes_On :unary + alias :visit_Arel_Nodes_Top :unary + alias :visit_Arel_Nodes_UnqualifiedColumn :unary + alias :visit_Arel_Nodes_Preceding :unary + alias :visit_Arel_Nodes_Following :unary + alias :visit_Arel_Nodes_Rows :unary + alias :visit_Arel_Nodes_Range :unary + + def window o + visit_edge o, "partitions" + visit_edge o, "orders" + visit_edge o, "framing" + end + alias :visit_Arel_Nodes_Window :window + + def named_window o + visit_edge o, "partitions" + visit_edge o, "orders" + visit_edge o, "framing" + visit_edge o, "name" + end + alias :visit_Arel_Nodes_NamedWindow :named_window + + def function o + visit_edge o, "expressions" + visit_edge o, "distinct" + visit_edge o, "alias" + end + alias :visit_Arel_Nodes_Exists :function + alias :visit_Arel_Nodes_Min :function + alias :visit_Arel_Nodes_Max :function + alias :visit_Arel_Nodes_Avg :function + alias :visit_Arel_Nodes_Sum :function + + def extract o + visit_edge o, "expressions" + visit_edge o, "alias" + end + alias :visit_Arel_Nodes_Extract :extract + + def visit_Arel_Nodes_NamedFunction o + visit_edge o, "name" + visit_edge o, "expressions" + visit_edge o, "distinct" + visit_edge o, "alias" + end + + def visit_Arel_Nodes_InsertStatement o + visit_edge o, "relation" + visit_edge o, "columns" + visit_edge o, "values" + end + + def visit_Arel_Nodes_SelectCore o + visit_edge o, "source" + visit_edge o, "projections" + visit_edge o, "wheres" + visit_edge o, "windows" + end + + def visit_Arel_Nodes_SelectStatement o + visit_edge o, "cores" + visit_edge o, "limit" + visit_edge o, "orders" + visit_edge o, "offset" + end + + def visit_Arel_Nodes_UpdateStatement o + visit_edge o, "relation" + visit_edge o, "wheres" + visit_edge o, "values" + end + + def visit_Arel_Table o + visit_edge o, "name" + end + + def visit_Arel_Nodes_Casted o + visit_edge o, 'val' + visit_edge o, 'attribute' + end + + def visit_Arel_Attribute o + visit_edge o, "relation" + visit_edge o, "name" + end + alias :visit_Arel_Attributes_Integer :visit_Arel_Attribute + alias :visit_Arel_Attributes_Float :visit_Arel_Attribute + alias :visit_Arel_Attributes_String :visit_Arel_Attribute + alias :visit_Arel_Attributes_Time :visit_Arel_Attribute + alias :visit_Arel_Attributes_Boolean :visit_Arel_Attribute + alias :visit_Arel_Attributes_Attribute :visit_Arel_Attribute + + def nary o + o.children.each_with_index do |x,i| + edge(i) { visit x } + end + end + alias :visit_Arel_Nodes_And :nary + + def binary o + visit_edge o, "left" + visit_edge o, "right" + end + alias :visit_Arel_Nodes_As :binary + alias :visit_Arel_Nodes_Assignment :binary + alias :visit_Arel_Nodes_Between :binary + alias :visit_Arel_Nodes_Concat :binary + alias :visit_Arel_Nodes_DoesNotMatch :binary + alias :visit_Arel_Nodes_Equality :binary + alias :visit_Arel_Nodes_GreaterThan :binary + alias :visit_Arel_Nodes_GreaterThanOrEqual :binary + alias :visit_Arel_Nodes_In :binary + alias :visit_Arel_Nodes_JoinSource :binary + alias :visit_Arel_Nodes_LessThan :binary + alias :visit_Arel_Nodes_LessThanOrEqual :binary + alias :visit_Arel_Nodes_Matches :binary + alias :visit_Arel_Nodes_NotEqual :binary + alias :visit_Arel_Nodes_NotIn :binary + alias :visit_Arel_Nodes_Or :binary + alias :visit_Arel_Nodes_Over :binary + + def visit_String o + @node_stack.last.fields << o + end + alias :visit_Time :visit_String + alias :visit_Date :visit_String + alias :visit_DateTime :visit_String + alias :visit_NilClass :visit_String + alias :visit_TrueClass :visit_String + alias :visit_FalseClass :visit_String + alias :visit_Integer :visit_String + alias :visit_Fixnum :visit_String + alias :visit_BigDecimal :visit_String + alias :visit_Float :visit_String + alias :visit_Symbol :visit_String + alias :visit_Arel_Nodes_SqlLiteral :visit_String + + def visit_Arel_Nodes_BindParam o; end + + def visit_Hash o + o.each_with_index do |pair, i| + edge("pair_#{i}") { visit pair } + end + end + + def visit_Array o + o.each_with_index do |x,i| + edge(i) { visit x } + end + end + alias :visit_Set :visit_Array + + def visit_edge o, method + edge(method) { visit o.send(method) } + end + + def visit o + if node = @seen[o.object_id] + @edge_stack.last.to = node + return + end + + node = Node.new(o.class.name, o.object_id) + @seen[node.id] = node + @nodes << node + with_node node do + super + end + end + + def edge name + edge = Edge.new(name, @node_stack.last) + @edge_stack.push edge + @edges << edge + yield + @edge_stack.pop + end + + def with_node node + if edge = @edge_stack.last + edge.to = node + end + + @node_stack.push node + yield + @node_stack.pop + end + + def quote string + string.to_s.gsub('"', '\"') + end + + def to_dot + "digraph \"Arel\" {\nnode [width=0.375,height=0.25,shape=record];\n" + + @nodes.map { |node| + label = "#{node.name}" + + node.fields.each_with_index do |field, i| + label += "|#{quote field}" + end + + "#{node.id} [label=\"#{label}\"];" + }.join("\n") + "\n" + @edges.map { |edge| + "#{edge.from.id} -> #{edge.to.id} [label=\"#{edge.name}\"];" + }.join("\n") + "\n}" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/ibm_db.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/ibm_db.rb new file mode 100644 index 00000000..e85a5a08 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/ibm_db.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true +module Arel + module Visitors + class IBM_DB < Arel::Visitors::ToSql + private + + def visit_Arel_Nodes_Limit o, collector + collector << "FETCH FIRST " + collector = visit o.expr, collector + collector << " ROWS ONLY" + end + + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/informix.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/informix.rb new file mode 100644 index 00000000..44b18b55 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/informix.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true +module Arel + module Visitors + class Informix < Arel::Visitors::ToSql + private + def visit_Arel_Nodes_SelectStatement o, collector + collector << "SELECT " + collector = maybe_visit o.offset, collector + collector = maybe_visit o.limit, collector + collector = o.cores.inject(collector) { |c,x| + visit_Arel_Nodes_SelectCore x, c + } + if o.orders.any? + collector << "ORDER BY " + collector = inject_join o.orders, collector, ", " + end + collector = maybe_visit o.lock, collector + end + def visit_Arel_Nodes_SelectCore o, collector + collector = inject_join o.projections, collector, ", " + if o.source && !o.source.empty? + collector << " FROM " + collector = visit o.source, collector + end + + if o.wheres.any? + collector << " WHERE " + collector = inject_join o.wheres, collector, " AND " + end + + if o.groups.any? + collector << "GROUP BY " + collector = inject_join o.groups, collector, ", " + end + + if o.havings.any? + collector << " HAVING " + collector = inject_join o.havings, collector, " AND " + end + collector + end + + def visit_Arel_Nodes_Offset o, collector + collector << "SKIP " + visit o.expr, collector + end + def visit_Arel_Nodes_Limit o, collector + collector << "FIRST " + visit o.expr, collector + collector << " " + end + end + end +end + diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/mssql.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/mssql.rb new file mode 100644 index 00000000..8347d05d --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/mssql.rb @@ -0,0 +1,124 @@ +# frozen_string_literal: true +module Arel + module Visitors + class MSSQL < Arel::Visitors::ToSql + RowNumber = Struct.new :children + + def initialize(*) + @primary_keys = {} + super + end + + private + + # `top` wouldn't really work here. I.e. User.select("distinct first_name").limit(10) would generate + # "select top 10 distinct first_name from users", which is invalid query! it should be + # "select distinct top 10 first_name from users" + def visit_Arel_Nodes_Top o + "" + end + + def visit_Arel_Visitors_MSSQL_RowNumber o, collector + collector << "ROW_NUMBER() OVER (ORDER BY " + inject_join(o.children, collector, ', ') << ") as _row_num" + end + + def visit_Arel_Nodes_SelectStatement o, collector + if !o.limit && !o.offset + return super + end + + is_select_count = false + o.cores.each { |x| + core_order_by = row_num_literal determine_order_by(o.orders, x) + if select_count? x + x.projections = [core_order_by] + is_select_count = true + else + x.projections << core_order_by + end + } + + if is_select_count + # fixme count distinct wouldn't work with limit or offset + collector << "SELECT COUNT(1) as count_id FROM (" + end + + collector << "SELECT _t.* FROM (" + collector = o.cores.inject(collector) { |c,x| + visit_Arel_Nodes_SelectCore x, c + } + collector << ") as _t WHERE #{get_offset_limit_clause(o)}" + + if is_select_count + collector << ") AS subquery" + else + collector + end + end + + def get_offset_limit_clause o + first_row = o.offset ? o.offset.expr.to_i + 1 : 1 + last_row = o.limit ? o.limit.expr.to_i - 1 + first_row : nil + if last_row + " _row_num BETWEEN #{first_row} AND #{last_row}" + else + " _row_num >= #{first_row}" + end + end + + def visit_Arel_Nodes_DeleteStatement o, collector + collector << 'DELETE ' + if o.limit + collector << 'TOP (' + visit o.limit.expr, collector + collector << ') ' + end + collector << 'FROM ' + collector = visit o.relation, collector + if o.wheres.any? + collector << ' WHERE ' + inject_join o.wheres, collector, AND + else + collector + end + end + + def determine_order_by orders, x + if orders.any? + orders + elsif x.groups.any? + x.groups + else + pk = find_left_table_pk(x.froms) + pk ? [pk] : [] + end + end + + def row_num_literal order_by + RowNumber.new order_by + end + + def select_count? x + x.projections.length == 1 && Arel::Nodes::Count === x.projections.first + end + + # FIXME raise exception of there is no pk? + def find_left_table_pk o + if o.kind_of?(Arel::Nodes::Join) + find_left_table_pk(o.left) + elsif o.instance_of?(Arel::Table) + find_primary_key(o) + end + end + + def find_primary_key(o) + @primary_keys[o.name] ||= begin + primary_key_name = @connection.primary_key(o.name) + # some tables might be without primary key + primary_key_name && o[primary_key_name] + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/mysql.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/mysql.rb new file mode 100644 index 00000000..4c734f62 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/mysql.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true +module Arel + module Visitors + class MySQL < Arel::Visitors::ToSql + private + def visit_Arel_Nodes_Union o, collector, suppress_parens = false + unless suppress_parens + collector << "( " + end + + collector = case o.left + when Arel::Nodes::Union + visit_Arel_Nodes_Union o.left, collector, true + else + visit o.left, collector + end + + collector << " UNION " + + collector = case o.right + when Arel::Nodes::Union + visit_Arel_Nodes_Union o.right, collector, true + else + visit o.right, collector + end + + if suppress_parens + collector + else + collector << " )" + end + end + + def visit_Arel_Nodes_Bin o, collector + collector << "BINARY " + visit o.expr, collector + end + + ### + # :'( + # http://dev.mysql.com/doc/refman/5.0/en/select.html#id3482214 + def visit_Arel_Nodes_SelectStatement o, collector + if o.offset && !o.limit + o.limit = Arel::Nodes::Limit.new(18446744073709551615) + end + super + end + + def visit_Arel_Nodes_SelectCore o, collector + o.froms ||= Arel.sql('DUAL') + super + end + + def visit_Arel_Nodes_UpdateStatement o, collector + collector << "UPDATE " + collector = visit o.relation, collector + + unless o.values.empty? + collector << " SET " + collector = inject_join o.values, collector, ', ' + end + + unless o.wheres.empty? + collector << " WHERE " + collector = inject_join o.wheres, collector, ' AND ' + end + + unless o.orders.empty? + collector << " ORDER BY " + collector = inject_join o.orders, collector, ', ' + end + + maybe_visit o.limit, collector + end + + def visit_Arel_Nodes_Concat o, collector + collector << " CONCAT(" + visit o.left, collector + collector << ", " + visit o.right, collector + collector << ") " + collector + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/oracle.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/oracle.rb new file mode 100644 index 00000000..d4749bba --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/oracle.rb @@ -0,0 +1,153 @@ +# frozen_string_literal: true +module Arel + module Visitors + class Oracle < Arel::Visitors::ToSql + private + + def visit_Arel_Nodes_SelectStatement o, collector + o = order_hacks(o) + + # if need to select first records without ORDER BY and GROUP BY and without DISTINCT + # then can use simple ROWNUM in WHERE clause + if o.limit && o.orders.empty? && o.cores.first.groups.empty? && !o.offset && o.cores.first.set_quantifier.class.to_s !~ /Distinct/ + o.cores.last.wheres.push Nodes::LessThanOrEqual.new( + Nodes::SqlLiteral.new('ROWNUM'), o.limit.expr + ) + return super + end + + if o.limit && o.offset + o = o.dup + limit = o.limit.expr + offset = o.offset + o.offset = nil + collector << " + SELECT * FROM ( + SELECT raw_sql_.*, rownum raw_rnum_ + FROM (" + + collector = super(o, collector) + + if offset.expr.is_a? Nodes::BindParam + collector << ') raw_sql_ WHERE rownum <= (' + collector = visit offset.expr, collector + collector << ' + ' + collector = visit limit, collector + collector << ") ) WHERE raw_rnum_ > " + collector = visit offset.expr, collector + return collector + else + collector << ") raw_sql_ + WHERE rownum <= #{offset.expr.to_i + limit} + ) + WHERE " + return visit(offset, collector) + end + end + + if o.limit + o = o.dup + limit = o.limit.expr + collector << "SELECT * FROM (" + collector = super(o, collector) + collector << ") WHERE ROWNUM <= " + return visit limit, collector + end + + if o.offset + o = o.dup + offset = o.offset + o.offset = nil + collector << "SELECT * FROM ( + SELECT raw_sql_.*, rownum raw_rnum_ + FROM (" + collector = super(o, collector) + collector << ") raw_sql_ + ) + WHERE " + return visit offset, collector + end + + super + end + + def visit_Arel_Nodes_Limit o, collector + collector + end + + def visit_Arel_Nodes_Offset o, collector + collector << "raw_rnum_ > " + visit o.expr, collector + end + + def visit_Arel_Nodes_Except o, collector + collector << "( " + collector = infix_value o, collector, " MINUS " + collector << " )" + end + + def visit_Arel_Nodes_UpdateStatement o, collector + # Oracle does not allow ORDER BY/LIMIT in UPDATEs. + if o.orders.any? && o.limit.nil? + # However, there is no harm in silently eating the ORDER BY clause if no LIMIT has been provided, + # otherwise let the user deal with the error + o = o.dup + o.orders = [] + end + + super + end + + ### + # Hacks for the order clauses specific to Oracle + def order_hacks o + return o if o.orders.empty? + return o unless o.cores.any? do |core| + core.projections.any? do |projection| + /FIRST_VALUE/ === projection + end + end + # Previous version with join and split broke ORDER BY clause + # if it contained functions with several arguments (separated by ','). + # + # orders = o.orders.map { |x| visit x }.join(', ').split(',') + orders = o.orders.map do |x| + string = visit(x, Arel::Collectors::SQLString.new).value + if string.include?(',') + split_order_string(string) + else + string + end + end.flatten + o.orders = [] + orders.each_with_index do |order, i| + o.orders << + Nodes::SqlLiteral.new("alias_#{i}__#{' DESC' if /\bdesc$/i === order}") + end + o + end + + # Split string by commas but count opening and closing brackets + # and ignore commas inside brackets. + def split_order_string(string) + array = [] + i = 0 + string.split(',').each do |part| + if array[i] + array[i] << ',' << part + else + # to ensure that array[i] will be String and not Arel::Nodes::SqlLiteral + array[i] = part.to_s + end + i += 1 if array[i].count('(') == array[i].count(')') + end + array + end + + def visit_Arel_Nodes_BindParam o, collector + collector.add_bind(o.value) { |i| ":a#{i}" } + end + + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/oracle12.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/oracle12.rb new file mode 100644 index 00000000..648047ae --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/oracle12.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true +module Arel + module Visitors + class Oracle12 < Arel::Visitors::ToSql + private + + def visit_Arel_Nodes_SelectStatement o, collector + # Oracle does not allow LIMIT clause with select for update + if o.limit && o.lock + raise ArgumentError, <<-MSG + 'Combination of limit and lock is not supported. + because generated SQL statements + `SELECT FOR UPDATE and FETCH FIRST n ROWS` generates ORA-02014.` + MSG + end + super + end + + def visit_Arel_Nodes_SelectOptions o, collector + collector = maybe_visit o.offset, collector + collector = maybe_visit o.limit, collector + collector = maybe_visit o.lock, collector + end + + def visit_Arel_Nodes_Limit o, collector + collector << "FETCH FIRST " + collector = visit o.expr, collector + collector << " ROWS ONLY" + end + + def visit_Arel_Nodes_Offset o, collector + collector << "OFFSET " + visit o.expr, collector + collector << " ROWS" + end + + def visit_Arel_Nodes_Except o, collector + collector << "( " + collector = infix_value o, collector, " MINUS " + collector << " )" + end + + def visit_Arel_Nodes_UpdateStatement o, collector + # Oracle does not allow ORDER BY/LIMIT in UPDATEs. + if o.orders.any? && o.limit.nil? + # However, there is no harm in silently eating the ORDER BY clause if no LIMIT has been provided, + # otherwise let the user deal with the error + o = o.dup + o.orders = [] + end + + super + end + + def visit_Arel_Nodes_BindParam o, collector + collector.add_bind(o.value) { |i| ":a#{i}" } + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/postgresql.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/postgresql.rb new file mode 100644 index 00000000..2a935e43 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/postgresql.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true +module Arel + module Visitors + class PostgreSQL < Arel::Visitors::ToSql + CUBE = 'CUBE' + ROLLUP = 'ROLLUP' + GROUPING_SET = 'GROUPING SET' + + private + + def visit_Arel_Nodes_Matches o, collector + op = o.case_sensitive ? ' LIKE ' : ' ILIKE ' + collector = infix_value o, collector, op + if o.escape + collector << ' ESCAPE ' + visit o.escape, collector + else + collector + end + end + + def visit_Arel_Nodes_DoesNotMatch o, collector + op = o.case_sensitive ? ' NOT LIKE ' : ' NOT ILIKE ' + collector = infix_value o, collector, op + if o.escape + collector << ' ESCAPE ' + visit o.escape, collector + else + collector + end + end + + def visit_Arel_Nodes_Regexp o, collector + op = o.case_sensitive ? ' ~ ' : ' ~* ' + infix_value o, collector, op + end + + def visit_Arel_Nodes_NotRegexp o, collector + op = o.case_sensitive ? ' !~ ' : ' !~* ' + infix_value o, collector, op + end + + def visit_Arel_Nodes_DistinctOn o, collector + collector << "DISTINCT ON ( " + visit(o.expr, collector) << " )" + end + + def visit_Arel_Nodes_BindParam o, collector + collector.add_bind(o.value) { |i| "$#{i}" } + end + + def visit_Arel_Nodes_GroupingElement o, collector + collector << "( " + visit(o.expr, collector) << " )" + end + + def visit_Arel_Nodes_Cube o, collector + collector << CUBE + grouping_array_or_grouping_element o, collector + end + + def visit_Arel_Nodes_RollUp o, collector + collector << ROLLUP + grouping_array_or_grouping_element o, collector + end + + def visit_Arel_Nodes_GroupingSet o, collector + collector << GROUPING_SET + grouping_array_or_grouping_element o, collector + end + + # Utilized by GroupingSet, Cube & RollUp visitors to + # handle grouping aggregation semantics + def grouping_array_or_grouping_element o, collector + if o.expr.is_a? Array + collector << "( " + visit o.expr, collector + collector << " )" + else + visit o.expr, collector + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/reduce.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/reduce.rb new file mode 100644 index 00000000..1156b780 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/reduce.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true +require 'arel/visitors/visitor' + +module Arel + module Visitors + class Reduce < Arel::Visitors::Visitor + def accept object, collector + visit object, collector + end + + private + + def visit object, collector + dispatch_method = dispatch[object.class] + send dispatch_method, object, collector + rescue NoMethodError => e + raise e if respond_to?(dispatch_method, true) + superklass = object.class.ancestors.find { |klass| + respond_to?(dispatch[klass], true) + } + raise(TypeError, "Cannot visit #{object.class}") unless superklass + dispatch[object.class] = dispatch[superklass] + retry + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/sqlite.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/sqlite.rb new file mode 100644 index 00000000..4ae09396 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/sqlite.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true +module Arel + module Visitors + class SQLite < Arel::Visitors::ToSql + private + + # Locks are not supported in SQLite + def visit_Arel_Nodes_Lock o, collector + collector + end + + def visit_Arel_Nodes_SelectStatement o, collector + o.limit = Arel::Nodes::Limit.new(-1) if o.offset && !o.limit + super + end + + def visit_Arel_Nodes_True o, collector + collector << "1" + end + + def visit_Arel_Nodes_False o, collector + collector << "0" + end + + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/to_sql.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/to_sql.rb new file mode 100644 index 00000000..6aaaa19e --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/to_sql.rb @@ -0,0 +1,850 @@ +# frozen_string_literal: true +require 'bigdecimal' +require 'date' +require 'arel/visitors/reduce' + +module Arel + module Visitors + class UnsupportedVisitError < StandardError + def initialize(object) + super "Unsupported argument type: #{object.class.name}. Construct an Arel node instead." + end + end + + class ToSql < Arel::Visitors::Reduce + ## + # This is some roflscale crazy stuff. I'm roflscaling this because + # building SQL queries is a hotspot. I will explain the roflscale so that + # others will not rm this code. + # + # In YARV, string literals in a method body will get duped when the byte + # code is executed. Let's take a look: + # + # > puts RubyVM::InstructionSequence.new('def foo; "bar"; end').disasm + # + # == disasm: >===== + # 0000 trace 8 + # 0002 trace 1 + # 0004 putstring "bar" + # 0006 trace 16 + # 0008 leave + # + # The `putstring` bytecode will dup the string and push it on the stack. + # In many cases in our SQL visitor, that string is never mutated, so there + # is no need to dup the literal. + # + # If we change to a constant lookup, the string will not be duped, and we + # can reduce the objects in our system: + # + # > puts RubyVM::InstructionSequence.new('BAR = "bar"; def foo; BAR; end').disasm + # + # == disasm: >======== + # 0000 trace 8 + # 0002 trace 1 + # 0004 getinlinecache 11, + # 0007 getconstant :BAR + # 0009 setinlinecache + # 0011 trace 16 + # 0013 leave + # + # `getconstant` should be a hash lookup, and no object is duped when the + # value of the constant is pushed on the stack. Hence the crazy + # constants below. + # + # `matches` and `doesNotMatch` operate case-insensitively via Visitor subclasses + # specialized for specific databases when necessary. + # + + WHERE = ' WHERE ' # :nodoc: + SPACE = ' ' # :nodoc: + COMMA = ', ' # :nodoc: + GROUP_BY = ' GROUP BY ' # :nodoc: + ORDER_BY = ' ORDER BY ' # :nodoc: + WINDOW = ' WINDOW ' # :nodoc: + AND = ' AND ' # :nodoc: + + DISTINCT = 'DISTINCT' # :nodoc: + + def initialize connection + super() + @connection = connection + end + + def compile node, &block + accept(node, Arel::Collectors::SQLString.new, &block).value + end + + private + + def visit_Arel_Nodes_DeleteStatement o, collector + collector << 'DELETE FROM ' + collector = visit o.relation, collector + if o.wheres.any? + collector << WHERE + collector = inject_join o.wheres, collector, AND + end + + maybe_visit o.limit, collector + end + + # FIXME: we should probably have a 2-pass visitor for this + def build_subselect key, o + stmt = Nodes::SelectStatement.new + core = stmt.cores.first + core.froms = o.relation + core.wheres = o.wheres + core.projections = [key] + stmt.limit = o.limit + stmt.orders = o.orders + stmt + end + + def visit_Arel_Nodes_UpdateStatement o, collector + if o.orders.empty? && o.limit.nil? + wheres = o.wheres + else + wheres = [Nodes::In.new(o.key, [build_subselect(o.key, o)])] + end + + collector << "UPDATE " + collector = visit o.relation, collector + unless o.values.empty? + collector << " SET " + collector = inject_join o.values, collector, ", " + end + + unless wheres.empty? + collector << " WHERE " + collector = inject_join wheres, collector, " AND " + end + + collector + end + + def visit_Arel_Nodes_InsertStatement o, collector + collector << "INSERT INTO " + collector = visit o.relation, collector + if o.columns.any? + collector << " (#{o.columns.map { |x| + quote_column_name x.name + }.join ', '})" + end + + if o.values + maybe_visit o.values, collector + elsif o.select + maybe_visit o.select, collector + else + collector + end + end + + def visit_Arel_Nodes_Exists o, collector + collector << "EXISTS (" + collector = visit(o.expressions, collector) << ")" + if o.alias + collector << " AS " + visit o.alias, collector + else + collector + end + end + + def visit_Arel_Nodes_Casted o, collector + collector << quoted(o.val, o.attribute).to_s + end + + def visit_Arel_Nodes_Quoted o, collector + collector << quoted(o.expr, nil).to_s + end + + def visit_Arel_Nodes_True o, collector + collector << "TRUE" + end + + def visit_Arel_Nodes_False o, collector + collector << "FALSE" + end + + def visit_Arel_Nodes_ValuesList o, collector + collector << "VALUES " + + len = o.rows.length - 1 + o.rows.each_with_index { |row, i| + collector << '(' + row_len = row.length - 1 + row.each_with_index do |value, k| + case value + when Nodes::SqlLiteral, Nodes::BindParam + collector = visit(value, collector) + else + collector << quote(value) + end + collector << COMMA unless k == row_len + end + collector << ')' + collector << COMMA unless i == len + } + collector + end + + def visit_Arel_Nodes_Values o, collector + collector << "VALUES (" + + len = o.expressions.length - 1 + o.expressions.each_with_index { |value, i| + case value + when Nodes::SqlLiteral, Nodes::BindParam + collector = visit value, collector + else + collector << quote(value).to_s + end + unless i == len + collector << COMMA + end + } + + collector << ")" + end + + def visit_Arel_Nodes_SelectStatement o, collector + if o.with + collector = visit o.with, collector + collector << SPACE + end + + collector = o.cores.inject(collector) { |c,x| + visit_Arel_Nodes_SelectCore(x, c) + } + + unless o.orders.empty? + collector << ORDER_BY + len = o.orders.length - 1 + o.orders.each_with_index { |x, i| + collector = visit(x, collector) + collector << COMMA unless len == i + } + end + + visit_Arel_Nodes_SelectOptions(o, collector) + + collector + end + + def visit_Arel_Nodes_SelectOptions o, collector + collector = maybe_visit o.limit, collector + collector = maybe_visit o.offset, collector + collector = maybe_visit o.lock, collector + end + + def visit_Arel_Nodes_SelectCore o, collector + collector << "SELECT" + + collector = maybe_visit o.top, collector + + collector = maybe_visit o.set_quantifier, collector + + collect_nodes_for o.projections, collector, SPACE + + if o.source && !o.source.empty? + collector << " FROM " + collector = visit o.source, collector + end + + collect_nodes_for o.wheres, collector, WHERE, AND + collect_nodes_for o.groups, collector, GROUP_BY + unless o.havings.empty? + collector << " HAVING " + inject_join o.havings, collector, AND + end + collect_nodes_for o.windows, collector, WINDOW + + collector + end + + def collect_nodes_for nodes, collector, spacer, connector = COMMA + unless nodes.empty? + collector << spacer + len = nodes.length - 1 + nodes.each_with_index do |x, i| + collector = visit(x, collector) + collector << connector unless len == i + end + end + end + + def visit_Arel_Nodes_Bin o, collector + visit o.expr, collector + end + + def visit_Arel_Nodes_Distinct o, collector + collector << DISTINCT + end + + def visit_Arel_Nodes_DistinctOn o, collector + raise NotImplementedError, 'DISTINCT ON not implemented for this db' + end + + def visit_Arel_Nodes_With o, collector + collector << "WITH " + inject_join o.children, collector, COMMA + end + + def visit_Arel_Nodes_WithRecursive o, collector + collector << "WITH RECURSIVE " + inject_join o.children, collector, COMMA + end + + def visit_Arel_Nodes_Union o, collector + collector << "( " + infix_value(o, collector, " UNION ") << " )" + end + + def visit_Arel_Nodes_UnionAll o, collector + collector << "( " + infix_value(o, collector, " UNION ALL ") << " )" + end + + def visit_Arel_Nodes_Intersect o, collector + collector << "( " + infix_value(o, collector, " INTERSECT ") << " )" + end + + def visit_Arel_Nodes_Except o, collector + collector << "( " + infix_value(o, collector, " EXCEPT ") << " )" + end + + def visit_Arel_Nodes_NamedWindow o, collector + collector << quote_column_name(o.name) + collector << " AS " + visit_Arel_Nodes_Window o, collector + end + + def visit_Arel_Nodes_Window o, collector + collector << "(" + + if o.partitions.any? + collector << "PARTITION BY " + collector = inject_join o.partitions, collector, ", " + end + + if o.orders.any? + collector << SPACE if o.partitions.any? + collector << "ORDER BY " + collector = inject_join o.orders, collector, ", " + end + + if o.framing + collector << SPACE if o.partitions.any? or o.orders.any? + collector = visit o.framing, collector + end + + collector << ")" + end + + def visit_Arel_Nodes_Rows o, collector + if o.expr + collector << "ROWS " + visit o.expr, collector + else + collector << "ROWS" + end + end + + def visit_Arel_Nodes_Range o, collector + if o.expr + collector << "RANGE " + visit o.expr, collector + else + collector << "RANGE" + end + end + + def visit_Arel_Nodes_Preceding o, collector + collector = if o.expr + visit o.expr, collector + else + collector << "UNBOUNDED" + end + + collector << " PRECEDING" + end + + def visit_Arel_Nodes_Following o, collector + collector = if o.expr + visit o.expr, collector + else + collector << "UNBOUNDED" + end + + collector << " FOLLOWING" + end + + def visit_Arel_Nodes_CurrentRow o, collector + collector << "CURRENT ROW" + end + + def visit_Arel_Nodes_Over o, collector + case o.right + when nil + visit(o.left, collector) << " OVER ()" + when Arel::Nodes::SqlLiteral + infix_value o, collector, " OVER " + when String, Symbol + visit(o.left, collector) << " OVER #{quote_column_name o.right.to_s}" + else + infix_value o, collector, " OVER " + end + end + + def visit_Arel_Nodes_Offset o, collector + collector << "OFFSET " + visit o.expr, collector + end + + def visit_Arel_Nodes_Limit o, collector + collector << "LIMIT " + visit o.expr, collector + end + + # FIXME: this does nothing on most databases, but does on MSSQL + def visit_Arel_Nodes_Top o, collector + collector + end + + def visit_Arel_Nodes_Lock o, collector + visit o.expr, collector + end + + def visit_Arel_Nodes_Grouping o, collector + if o.expr.is_a? Nodes::Grouping + visit(o.expr, collector) + else + collector << "(" + visit(o.expr, collector) << ")" + end + end + + def visit_Arel_SelectManager o, collector + collector << '(' + visit(o.ast, collector) << ')' + end + + def visit_Arel_Nodes_Ascending o, collector + visit(o.expr, collector) << " ASC" + end + + def visit_Arel_Nodes_Descending o, collector + visit(o.expr, collector) << " DESC" + end + + def visit_Arel_Nodes_Group o, collector + visit o.expr, collector + end + + def visit_Arel_Nodes_NamedFunction o, collector + collector << o.name + collector << "(" + collector << "DISTINCT " if o.distinct + collector = inject_join(o.expressions, collector, ", ") << ")" + if o.alias + collector << " AS " + visit o.alias, collector + else + collector + end + end + + def visit_Arel_Nodes_Extract o, collector + collector << "EXTRACT(#{o.field.to_s.upcase} FROM " + visit(o.expr, collector) << ")" + end + + def visit_Arel_Nodes_Count o, collector + aggregate "COUNT", o, collector + end + + def visit_Arel_Nodes_Sum o, collector + aggregate "SUM", o, collector + end + + def visit_Arel_Nodes_Max o, collector + aggregate "MAX", o, collector + end + + def visit_Arel_Nodes_Min o, collector + aggregate "MIN", o, collector + end + + def visit_Arel_Nodes_Avg o, collector + aggregate "AVG", o, collector + end + + def visit_Arel_Nodes_TableAlias o, collector + collector = visit o.relation, collector + collector << " " + collector << quote_table_name(o.name) + end + + def visit_Arel_Nodes_Between o, collector + collector = visit o.left, collector + collector << " BETWEEN " + visit o.right, collector + end + + def visit_Arel_Nodes_GreaterThanOrEqual o, collector + collector = visit o.left, collector + collector << " >= " + visit o.right, collector + end + + def visit_Arel_Nodes_GreaterThan o, collector + collector = visit o.left, collector + collector << " > " + visit o.right, collector + end + + def visit_Arel_Nodes_LessThanOrEqual o, collector + collector = visit o.left, collector + collector << " <= " + visit o.right, collector + end + + def visit_Arel_Nodes_LessThan o, collector + collector = visit o.left, collector + collector << " < " + visit o.right, collector + end + + def visit_Arel_Nodes_Matches o, collector + collector = visit o.left, collector + collector << " LIKE " + collector = visit o.right, collector + if o.escape + collector << ' ESCAPE ' + visit o.escape, collector + else + collector + end + end + + def visit_Arel_Nodes_DoesNotMatch o, collector + collector = visit o.left, collector + collector << " NOT LIKE " + collector = visit o.right, collector + if o.escape + collector << ' ESCAPE ' + visit o.escape, collector + else + collector + end + end + + def visit_Arel_Nodes_JoinSource o, collector + if o.left + collector = visit o.left, collector + end + if o.right.any? + collector << SPACE if o.left + collector = inject_join o.right, collector, SPACE + end + collector + end + + def visit_Arel_Nodes_Regexp o, collector + raise NotImplementedError, '~ not implemented for this db' + end + + def visit_Arel_Nodes_NotRegexp o, collector + raise NotImplementedError, '!~ not implemented for this db' + end + + def visit_Arel_Nodes_StringJoin o, collector + visit o.left, collector + end + + def visit_Arel_Nodes_FullOuterJoin o, collector + collector << "FULL OUTER JOIN " + collector = visit o.left, collector + collector << SPACE + visit o.right, collector + end + + def visit_Arel_Nodes_OuterJoin o, collector + collector << "LEFT OUTER JOIN " + collector = visit o.left, collector + collector << " " + visit o.right, collector + end + + def visit_Arel_Nodes_RightOuterJoin o, collector + collector << "RIGHT OUTER JOIN " + collector = visit o.left, collector + collector << SPACE + visit o.right, collector + end + + def visit_Arel_Nodes_InnerJoin o, collector + collector << "INNER JOIN " + collector = visit o.left, collector + if o.right + collector << SPACE + visit(o.right, collector) + else + collector + end + end + + def visit_Arel_Nodes_On o, collector + collector << "ON " + visit o.expr, collector + end + + def visit_Arel_Nodes_Not o, collector + collector << "NOT (" + visit(o.expr, collector) << ")" + end + + def visit_Arel_Table o, collector + if o.table_alias + collector << "#{quote_table_name o.name} #{quote_table_name o.table_alias}" + else + collector << quote_table_name(o.name) + end + end + + def visit_Arel_Nodes_In o, collector + if Array === o.right && o.right.empty? + collector << '1=0' + else + collector = visit o.left, collector + collector << " IN (" + visit(o.right, collector) << ")" + end + end + + def visit_Arel_Nodes_NotIn o, collector + if Array === o.right && o.right.empty? + collector << '1=1' + else + collector = visit o.left, collector + collector << " NOT IN (" + collector = visit o.right, collector + collector << ")" + end + end + + def visit_Arel_Nodes_And o, collector + inject_join o.children, collector, " AND " + end + + def visit_Arel_Nodes_Or o, collector + collector = visit o.left, collector + collector << " OR " + visit o.right, collector + end + + def visit_Arel_Nodes_Assignment o, collector + case o.right + when Arel::Nodes::UnqualifiedColumn, Arel::Attributes::Attribute, Arel::Nodes::BindParam + collector = visit o.left, collector + collector << " = " + visit o.right, collector + else + collector = visit o.left, collector + collector << " = " + collector << quote(o.right).to_s + end + end + + def visit_Arel_Nodes_Equality o, collector + right = o.right + + collector = visit o.left, collector + + if right.nil? + collector << " IS NULL" + else + collector << " = " + visit right, collector + end + end + + def visit_Arel_Nodes_NotEqual o, collector + right = o.right + + collector = visit o.left, collector + + if right.nil? + collector << " IS NOT NULL" + else + collector << " != " + visit right, collector + end + end + + def visit_Arel_Nodes_As o, collector + collector = visit o.left, collector + collector << " AS " + visit o.right, collector + end + + def visit_Arel_Nodes_Case o, collector + collector << "CASE " + if o.case + visit o.case, collector + collector << " " + end + o.conditions.each do |condition| + visit condition, collector + collector << " " + end + if o.default + visit o.default, collector + collector << " " + end + collector << "END" + end + + def visit_Arel_Nodes_When o, collector + collector << "WHEN " + visit o.left, collector + collector << " THEN " + visit o.right, collector + end + + def visit_Arel_Nodes_Else o, collector + collector << "ELSE " + visit o.expr, collector + end + + def visit_Arel_Nodes_UnqualifiedColumn o, collector + collector << "#{quote_column_name o.name}" + collector + end + + def visit_Arel_Attributes_Attribute o, collector + join_name = o.relation.table_alias || o.relation.name + collector << "#{quote_table_name join_name}.#{quote_column_name o.name}" + end + alias :visit_Arel_Attributes_Integer :visit_Arel_Attributes_Attribute + alias :visit_Arel_Attributes_Float :visit_Arel_Attributes_Attribute + alias :visit_Arel_Attributes_Decimal :visit_Arel_Attributes_Attribute + alias :visit_Arel_Attributes_String :visit_Arel_Attributes_Attribute + alias :visit_Arel_Attributes_Time :visit_Arel_Attributes_Attribute + alias :visit_Arel_Attributes_Boolean :visit_Arel_Attributes_Attribute + + def literal o, collector; collector << o.to_s; end + + def visit_Arel_Nodes_BindParam o, collector + collector.add_bind(o.value) { "?" } + end + + alias :visit_Arel_Nodes_SqlLiteral :literal + alias :visit_Bignum :literal + alias :visit_Fixnum :literal + alias :visit_Integer :literal + + def quoted o, a + if a && a.able_to_type_cast? + quote(a.type_cast_for_database(o)) + else + quote(o) + end + end + + def unsupported o, collector + raise UnsupportedVisitError.new(o) + end + + alias :visit_ActiveSupport_Multibyte_Chars :unsupported + alias :visit_ActiveSupport_StringInquirer :unsupported + alias :visit_BigDecimal :unsupported + alias :visit_Class :unsupported + alias :visit_Date :unsupported + alias :visit_DateTime :unsupported + alias :visit_FalseClass :unsupported + alias :visit_Float :unsupported + alias :visit_Hash :unsupported + alias :visit_NilClass :unsupported + alias :visit_String :unsupported + alias :visit_Symbol :unsupported + alias :visit_Time :unsupported + alias :visit_TrueClass :unsupported + + def visit_Arel_Nodes_InfixOperation o, collector + collector = visit o.left, collector + collector << " #{o.operator} " + visit o.right, collector + end + + alias :visit_Arel_Nodes_Addition :visit_Arel_Nodes_InfixOperation + alias :visit_Arel_Nodes_Subtraction :visit_Arel_Nodes_InfixOperation + alias :visit_Arel_Nodes_Multiplication :visit_Arel_Nodes_InfixOperation + alias :visit_Arel_Nodes_Division :visit_Arel_Nodes_InfixOperation + + def visit_Arel_Nodes_UnaryOperation o, collector + collector << " #{o.operator} " + visit o.expr, collector + end + + def visit_Array o, collector + inject_join o, collector, ", " + end + alias :visit_Set :visit_Array + + def quote value + return value if Arel::Nodes::SqlLiteral === value + @connection.quote value + end + + def quote_table_name name + return name if Arel::Nodes::SqlLiteral === name + @connection.quote_table_name(name) + end + + def quote_column_name name + return name if Arel::Nodes::SqlLiteral === name + @connection.quote_column_name(name) + end + + def maybe_visit thing, collector + return collector unless thing + collector << " " + visit thing, collector + end + + def inject_join list, collector, join_str + len = list.length - 1 + list.each_with_index.inject(collector) { |c, (x,i)| + if i == len + visit x, c + else + visit(x, c) << join_str + end + } + end + + def infix_value o, collector, value + collector = visit o.left, collector + collector << value + visit o.right, collector + end + + def aggregate name, o, collector + collector << "#{name}(" + if o.distinct + collector << "DISTINCT " + end + collector = inject_join(o.expressions, collector, ", ") << ")" + if o.alias + collector << " AS " + visit o.alias, collector + else + collector + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/visitor.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/visitor.rb new file mode 100644 index 00000000..2690c98e --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/visitor.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true +module Arel + module Visitors + class Visitor + def initialize + @dispatch = get_dispatch_cache + end + + def accept object + visit object + end + + private + + def self.dispatch_cache + Hash.new do |hash, klass| + hash[klass] = "visit_#{(klass.name || '').gsub('::', '_')}" + end + end + + def get_dispatch_cache + self.class.dispatch_cache + end + + def dispatch + @dispatch + end + + def visit object + dispatch_method = dispatch[object.class] + send dispatch_method, object + rescue NoMethodError => e + raise e if respond_to?(dispatch_method, true) + superklass = object.class.ancestors.find { |klass| + respond_to?(dispatch[klass], true) + } + raise(TypeError, "Cannot visit #{object.class}") unless superklass + dispatch[object.class] = dispatch[superklass] + retry + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/where_sql.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/where_sql.rb new file mode 100644 index 00000000..55e6ca9a --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/visitors/where_sql.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true +module Arel + module Visitors + class WhereSql < Arel::Visitors::ToSql + def initialize(inner_visitor, *args, &block) + @inner_visitor = inner_visitor + super(*args, &block) + end + + private + + def visit_Arel_Nodes_SelectCore o, collector + collector << "WHERE " + wheres = o.wheres.map do |where| + Nodes::SqlLiteral.new(@inner_visitor.accept(where, collector.class.new).value) + end + + inject_join wheres, collector, ' AND ' + end + end + end +end diff --git a/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/window_predications.rb b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/window_predications.rb new file mode 100644 index 00000000..f93dede0 --- /dev/null +++ b/path/ruby/2.6.0/gems/arel-9.0.0/lib/arel/window_predications.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true +module Arel + module WindowPredications + + def over(expr = nil) + Nodes::Over.new(self, expr) + end + + end +end \ No newline at end of file diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/.gitignore b/path/ruby/2.6.0/gems/bcrypt-3.1.13/.gitignore new file mode 100644 index 00000000..3c40c7fa --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/.gitignore @@ -0,0 +1,9 @@ +doc +pkg +tmp +*.o +*.bundle +*.so +*.jar +.DS_Store +.rbenv-gemsets diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/.rspec b/path/ruby/2.6.0/gems/bcrypt-3.1.13/.rspec new file mode 100644 index 00000000..ff90c031 --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/.rspec @@ -0,0 +1,3 @@ +--color +--backtrace +--format documentation diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/.travis.yml b/path/ruby/2.6.0/gems/bcrypt-3.1.13/.travis.yml new file mode 100644 index 00000000..621ca7f4 --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/.travis.yml @@ -0,0 +1,17 @@ +language: ruby +before_install: + - "echo 'gem: --no-rdoc --no-ri' > ~/.gemrc" + - gem update --system 2.7.8 + - gem install bundler -v 1.17.3 +rvm: + - 2.0 + - 2.1 + - 2.2 + - 2.3 + - 2.4 + - 2.5 + - 2.6 + - ruby-head + - jruby-head + - rbx-3 +script: bundle exec rake diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/CHANGELOG b/path/ruby/2.6.0/gems/bcrypt-3.1.13/CHANGELOG new file mode 100644 index 00000000..8bead602 --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/CHANGELOG @@ -0,0 +1,94 @@ +1.0.0 Feb 27 2007 + - Initial release. + +2.0.0 Mar 07 2007 + - Removed BCrypt::Password#exactly_equals -- use BCrypt::Password#eql? instead. + - Added BCrypt::Password#is_password?. + - Refactored out BCrypt::Internals into more useful BCrypt::Engine. + - Added validation of secrets -- nil is not healthy. + +2.0.1 Mar 09 2007 + - Fixed load path issues + - Fixed crashes when hashing weird values (e.g., false, etc.) + +2.0.2 Jun 06 2007 + - Fixed example code in the README [Winson] + - Fixed Solaris compatibility [Jeremy LaTrasse, Twitter crew] + +2.0.3 May 07 2008 + - Made exception classes descend from StandardError, not Exception [Dan42] + - Changed BCrypt::Engine.hash to BCrypt::Engine.hash_secret to avoid Merb + sorting issues. [Lee Pope] + +2.0.4 Mar 09 2009 + - Added Ruby 1.9 compatibility. [Genki Takiuchi] + - Fixed segfaults on some different types of empty strings. [Mike Pomraning] + +2.0.5 Mar 11 2009 + - Fixed Ruby 1.8.5 compatibility. [Mike Pomraning] + +2.1.0 Aug 12 2009 + - Improved code coverage, unit tests, and build chain. [Hongli Lai] + - Ruby 1.9 compatibility fixes. [Hongli Lai] + - JRuby support, using Damien Miller's jBCrypt. [Hongli Lai] + - Ruby 1.9 GIL releasing for high-cost hashes. [Hongli Lai] + +2.1.1 Aug 14 2009 + - JVM 1.4/1.5 compatibility [Hongli Lai] + +2.1.2 Sep 16 2009 + - Fixed support for Solaris, OpenSolaris. + +3.0.0 Aug 24 2011 + - Bcrypt C implementation replaced with a public domain implementation. + - License changed to MIT + +3.0.1 Sep 12 2011 + - create raises an exception if the cost is higher than 31. GH #27 + +3.1.0 May 07 2013 + - Add BCrypt::Password.valid_hash?(str) to check if a string is a valid bcrypt password hash + - BCrypt::Password cost should be set to DEFAULT_COST if nil + - Add BCrypt::Engine.cost attribute for getting/setting a default cost externally + +3.1.1 Jul 10 2013 + - Remove support for Ruby 1.8 in compiled win32 binaries + +3.1.2 Aug 26 2013 + - Add support for Ruby 1.8 and 2.0 (in addition to 1.9) in compiled Windows binaries + - Add support for 64-bit Windows + +3.1.3 Feb 21 2014 + - Add support for Ruby 2.1 in compiled Windows binaries + - Rename gem from "bcrypt-ruby" to just "bcrypt". [GH #86 by @sferik] + +3.1.6 Feb 21 2014 + - Dummy version of "bcrypt-ruby" needed a couple version bumps to fix some + bugs. It felt wrong to have that at a higher version than the real gem, so + the real gem is getting bumped to 3.1.6. + +3.1.7 Feb 24 2014 + - Rebuild corrupt Java binary version of gem [GH #90] + - The 2.1 support for Windows binaries alleged in 3.1.3 was a lie -- documentation removed + +3.1.8 Oct 23 2014 + - Add support for Ruby 2.1 in compiled Windows binaries [GH #102] + +3.1.9 Oct 23 2014 + - Rebuild corrupt binaries + +3.1.10 Jan 28 2015 + - Fix issue with dumping a BCrypt::Password instance to YAML in Ruby 2.2 [GH #107 by @mattwildig] + +3.1.11 Mar 06 2016 + - Add support for Ruby 2.2 in compiled Windows binaries + +3.1.12 May 16 2018 + - Add support for Ruby 2.3, 2.4, and 2.5 in compiled Windows binaries + - Fix compatibility with libxcrypt [GH #164 by @besser82] + +[DRAFT] 4.0.0 MMM DD YYYY + - No longer include compiled binaries for Windows. See GH #173. + - Update C and Java implementations to latest versions [GH #182 by @fonica] + - Bump default cost to 12 [GH #181 by @bdewater] + - Remove explicit support for Rubies 1.8 and 1.9 diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/COPYING b/path/ruby/2.6.0/gems/bcrypt-3.1.13/COPYING new file mode 100644 index 00000000..8b4922d6 --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/COPYING @@ -0,0 +1,28 @@ +(The MIT License) + +Copyright 2007-2011: + +* Coda Hale + +C implementation of the BCrypt algorithm by Solar Designer and placed in the +public domain. +jBCrypt is Copyright (c) 2006 Damien Miller . + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/Gemfile b/path/ruby/2.6.0/gems/bcrypt-3.1.13/Gemfile new file mode 100644 index 00000000..851fabc2 --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/Gemfile @@ -0,0 +1,2 @@ +source 'https://rubygems.org' +gemspec diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/Gemfile.lock b/path/ruby/2.6.0/gems/bcrypt-3.1.13/Gemfile.lock new file mode 100644 index 00000000..99811580 --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/Gemfile.lock @@ -0,0 +1,37 @@ +PATH + remote: . + specs: + bcrypt (3.1.13) + +GEM + remote: https://rubygems.org/ + specs: + diff-lcs (1.3) + rake (12.3.2) + rake-compiler (0.9.9) + rake + rspec (3.8.0) + rspec-core (~> 3.8.0) + rspec-expectations (~> 3.8.0) + rspec-mocks (~> 3.8.0) + rspec-core (3.8.0) + rspec-support (~> 3.8.0) + rspec-expectations (3.8.3) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-mocks (3.8.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-support (3.8.0) + +PLATFORMS + java + ruby + +DEPENDENCIES + bcrypt! + rake-compiler (~> 0.9.2) + rspec (>= 3) + +BUNDLED WITH + 1.16.1 diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/README.md b/path/ruby/2.6.0/gems/bcrypt-3.1.13/README.md new file mode 100644 index 00000000..9bbc7391 --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/README.md @@ -0,0 +1,194 @@ +# bcrypt-ruby + +An easy way to keep your users' passwords secure. + +* https://github.com/codahale/bcrypt-ruby/tree/master + +[![Travis Build Status](https://travis-ci.org/codahale/bcrypt-ruby.svg?branch=master)](https://travis-ci.org/codahale/bcrypt-ruby) +[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/6fplerx9lnaf0hyo?svg=true)](https://ci.appveyor.com/project/TJSchuck35975/bcrypt-ruby) + + +## Why you should use `bcrypt()` + +If you store user passwords in the clear, then an attacker who steals a copy of your database has a giant list of emails +and passwords. Some of your users will only have one password -- for their email account, for their banking account, for +your application. A simple hack could escalate into massive identity theft. + +It's your responsibility as a web developer to make your web application secure -- blaming your users for not being +security experts is not a professional response to risk. + +`bcrypt()` allows you to easily harden your application against these kinds of attacks. + +*Note*: JRuby versions of the bcrypt gem `<= 2.1.3` had a [security +vulnerability](https://www.mindrot.org/files/jBCrypt/internat.adv) that +was fixed in `>= 2.1.4`. If you used a vulnerable version to hash +passwords with international characters in them, you will need to +re-hash those passwords. This vulnerability only affected the JRuby gem. + +## How to install bcrypt + + gem install bcrypt + +The bcrypt gem is available on the following Ruby platforms: + +* JRuby +* RubyInstaller 2.0 – 2.5 builds on Windows with the DevKit +* Any 2.0 – 2.5 Ruby on a BSD/OS X/Linux system with a compiler + +## How to use `bcrypt()` in your Rails application + +*Note*: Rails versions >= 3 ship with `ActiveModel::SecurePassword` which uses bcrypt-ruby. +`has_secure_password` [docs](https://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html#method-i-has_secure_password) +implements a similar authentication strategy to the code below. + +### The _User_ model +```ruby +require 'bcrypt' + +class User < ActiveRecord::Base + # users.password_hash in the database is a :string + include BCrypt + + def password + @password ||= Password.new(password_hash) + end + + def password=(new_password) + @password = Password.create(new_password) + self.password_hash = @password + end +end +``` +### Creating an account +```ruby +def create + @user = User.new(params[:user]) + @user.password = params[:password] + @user.save! +end +``` +### Authenticating a user +```ruby +def login + @user = User.find_by_email(params[:email]) + if @user.password == params[:password] + give_token + else + redirect_to home_url + end +end +``` +## How to use bcrypt-ruby in general +```ruby +require 'bcrypt' + +my_password = BCrypt::Password.create("my password") +#=> "$2a$12$K0ByB.6YI2/OYrB4fQOYLe6Tv0datUVf6VZ/2Jzwm879BW5K1cHey" + +my_password.version #=> "2a" +my_password.cost #=> 12 +my_password == "my password" #=> true +my_password == "not my password" #=> false + +my_password = BCrypt::Password.new("$2a$12$K0ByB.6YI2/OYrB4fQOYLe6Tv0datUVf6VZ/2Jzwm879BW5K1cHey") +my_password == "my password" #=> true +my_password == "not my password" #=> false +``` +Check the rdocs for more details -- BCrypt, BCrypt::Password. + +## How `bcrypt()` works + +`bcrypt()` is a hashing algorithm designed by Niels Provos and David Mazières of the OpenBSD Project. + +### Background + +Hash algorithms take a chunk of data (e.g., your user's password) and create a "digital fingerprint," or hash, of it. +Because this process is not reversible, there's no way to go from the hash back to the password. + +In other words: + + hash(p) #=> + +You can store the hash and check it against a hash made of a potentially valid password: + + =? hash(just_entered_password) + +### Rainbow Tables + +But even this has weaknesses -- attackers can just run lists of possible passwords through the same algorithm, store the +results in a big database, and then look up the passwords by their hash: + + PrecomputedPassword.find_by_hash().password #=> "secret1" + +### Salts + +The solution to this is to add a small chunk of random data -- called a salt -- to the password before it's hashed: + + hash(salt + p) #=> + +The salt is then stored along with the hash in the database, and used to check potentially valid passwords: + + =? hash(salt + just_entered_password) + +bcrypt-ruby automatically handles the storage and generation of these salts for you. + +Adding a salt means that an attacker has to have a gigantic database for each unique salt -- for a salt made of 4 +letters, that's 456,976 different databases. Pretty much no one has that much storage space, so attackers try a +different, slower method -- throw a list of potential passwords at each individual password: + + hash(salt + "aadvark") =? + hash(salt + "abacus") =? + etc. + +This is much slower than the big database approach, but most hash algorithms are pretty quick -- and therein lies the +problem. Hash algorithms aren't usually designed to be slow, they're designed to turn gigabytes of data into secure +fingerprints as quickly as possible. `bcrypt()`, though, is designed to be computationally expensive: + + Ten thousand iterations: + user system total real + md5 0.070000 0.000000 0.070000 ( 0.070415) + bcrypt 22.230000 0.080000 22.310000 ( 22.493822) + +If an attacker was using Ruby to check each password, they could check ~140,000 passwords a second with MD5 but only +~450 passwords a second with `bcrypt()`. + +### Cost Factors + +In addition, `bcrypt()` allows you to increase the amount of work required to hash a password as computers get faster. Old +passwords will still work fine, but new passwords can keep up with the times. + +The default cost factor used by bcrypt-ruby is 12, which is fine for session-based authentication. If you are using a +stateless authentication architecture (e.g., HTTP Basic Auth), you will want to lower the cost factor to reduce your +server load and keep your request times down. This will lower the security provided you, but there are few alternatives. + +To change the default cost factor used by bcrypt-ruby, use `BCrypt::Engine.cost = new_value`: +```ruby +BCrypt::Password.create('secret').cost + #=> 12, the default provided by bcrypt-ruby + +# set a new default cost +BCrypt::Engine.cost = 8 +BCrypt::Password.create('secret').cost + #=> 8 +``` +The default cost can be overridden as needed by passing an options hash with a different cost: + + BCrypt::Password.create('secret', :cost => 6).cost #=> 6 + +## More Information + +`bcrypt()` is currently used as the default password storage hash in OpenBSD, widely regarded as the most secure operating +system available. + +For a more technical explanation of the algorithm and its design criteria, please read Niels Provos and David Mazières' +Usenix99 paper: +https://www.usenix.org/events/usenix99/provos.html + +If you'd like more down-to-earth advice regarding cryptography, I suggest reading Practical Cryptography by Niels +Ferguson and Bruce Schneier: +https://www.schneier.com/book-practical.html + +# Etc + +* Author :: Coda Hale +* Website :: https://codahale.com diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/Rakefile b/path/ruby/2.6.0/gems/bcrypt-3.1.13/Rakefile new file mode 100644 index 00000000..98ed63b5 --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/Rakefile @@ -0,0 +1,70 @@ +require 'rspec/core/rake_task' +require 'rubygems/package_task' +require 'rake/extensiontask' +require 'rake/javaextensiontask' +require 'rake/clean' +require 'rdoc/task' +require 'benchmark' + +CLEAN.include( + "tmp", + "lib/bcrypt_ext.jar", + "lib/bcrypt_ext.so" +) +CLOBBER.include( + "doc", + "pkg" +) + +GEMSPEC = Gem::Specification.load("bcrypt.gemspec") + +task :default => [:compile, :spec] + +desc "Run all specs" +RSpec::Core::RakeTask.new do |t| + t.pattern = 'spec/**/*_spec.rb' + t.ruby_opts = '-w' +end + +desc "Run all specs, with coverage testing" +RSpec::Core::RakeTask.new(:rcov) do |t| + t.pattern = 'spec/**/*_spec.rb' + t.rcov = true + t.rcov_path = 'doc/coverage' + t.rcov_opts = ['--exclude', 'rspec,diff-lcs,rcov,_spec,_helper'] +end + +desc 'Generate RDoc' +RDoc::Task.new do |rdoc| + rdoc.rdoc_dir = 'doc/rdoc' + rdoc.options += GEMSPEC.rdoc_options + rdoc.template = ENV['TEMPLATE'] if ENV['TEMPLATE'] + rdoc.rdoc_files.include(*GEMSPEC.extra_rdoc_files) +end + +Gem::PackageTask.new(GEMSPEC) do |pkg| + pkg.need_zip = true + pkg.need_tar = true +end + +if RUBY_PLATFORM =~ /java/ + Rake::JavaExtensionTask.new('bcrypt_ext', GEMSPEC) do |ext| + ext.ext_dir = 'ext/jruby' + end +else + Rake::ExtensionTask.new("bcrypt_ext", GEMSPEC) do |ext| + ext.ext_dir = 'ext/mri' + end +end + +desc "Run a set of benchmarks on the compiled extension." +task :benchmark do + TESTS = 100 + TEST_PWD = "this is a test" + require File.expand_path(File.join(File.dirname(__FILE__), "lib", "bcrypt")) + Benchmark.bmbm do |results| + 4.upto(10) do |n| + results.report("cost #{n}:") { TESTS.times { BCrypt::Password.create(TEST_PWD, :cost => n) } } + end + end +end diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/appveyor.yml b/path/ruby/2.6.0/gems/bcrypt-3.1.13/appveyor.yml new file mode 100644 index 00000000..e832e22d --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/appveyor.yml @@ -0,0 +1,50 @@ +version: "{branch}-{build}" +build: off +clone_depth: 1 + +init: + # Install Ruby head + - if %RUBY_VERSION%==head ( + appveyor DownloadFile https://github.com/oneclick/rubyinstaller2/releases/download/rubyinstaller-head/rubyinstaller-head-x86.exe -FileName C:\head_x86.exe & + C:\head_x86.exe /verysilent /dir=C:\Ruby%RUBY_VERSION% + ) + - if %RUBY_VERSION%==head-x64 ( + appveyor DownloadFile https://github.com/oneclick/rubyinstaller2/releases/download/rubyinstaller-head/rubyinstaller-head-x64.exe -FileName C:\head_x64.exe & + C:\head_x64.exe /verysilent /dir=C:\Ruby%RUBY_VERSION% + ) + + # Add Ruby to the path + - set PATH=C:\Ruby%RUBY_VERSION%\bin;%PATH% + +environment: + matrix: + - RUBY_VERSION: "head" + - RUBY_VERSION: "head-x64" + - RUBY_VERSION: "25" + - RUBY_VERSION: "25-x64" + - RUBY_VERSION: "24" + - RUBY_VERSION: "24-x64" + - RUBY_VERSION: "23" + - RUBY_VERSION: "23-x64" + - RUBY_VERSION: "22" + - RUBY_VERSION: "22-x64" + - RUBY_VERSION: "21" + - RUBY_VERSION: "21-x64" + - RUBY_VERSION: "200" + - RUBY_VERSION: "200-x64" + +install: + - ps: "Set-Content -Value 'gem: --no-ri --no-rdoc ' -Path C:\\ProgramData\\gemrc" + - if %RUBY_VERSION%==head ( gem install bundler -v'< 2' ) + - if %RUBY_VERSION%==head-x64 ( gem install bundler -v'< 2' ) + - bundle install + +before_build: + - ruby -v + - gem -v + +build_script: + - bundle exec rake compile -rdevkit + +test_script: + - bundle exec rake spec diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/bcrypt.gemspec b/path/ruby/2.6.0/gems/bcrypt-3.1.13/bcrypt.gemspec new file mode 100644 index 00000000..e67bad22 --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/bcrypt.gemspec @@ -0,0 +1,27 @@ +Gem::Specification.new do |s| + s.name = 'bcrypt' + s.version = '3.1.13' + + s.summary = "OpenBSD's bcrypt() password hashing algorithm." + s.description = <<-EOF + bcrypt() is a sophisticated and secure hash algorithm designed by The OpenBSD project + for hashing passwords. The bcrypt Ruby gem provides a simple wrapper for safely handling + passwords. + EOF + + s.files = `git ls-files`.split("\n") + s.require_path = 'lib' + + s.add_development_dependency 'rake-compiler', '~> 0.9.2' + s.add_development_dependency 'rspec', '>= 3' + + s.rdoc_options += ['--title', 'bcrypt-ruby', '--line-numbers', '--inline-source', '--main', 'README.md'] + s.extra_rdoc_files += ['README.md', 'COPYING', 'CHANGELOG', *Dir['lib/**/*.rb']] + + s.extensions = 'ext/mri/extconf.rb' + + s.authors = ["Coda Hale"] + s.email = "coda.hale@gmail.com" + s.homepage = "https://github.com/codahale/bcrypt-ruby" + s.license = "MIT" +end diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/jruby/bcrypt_jruby/BCrypt.java b/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/jruby/bcrypt_jruby/BCrypt.java new file mode 100644 index 00000000..86db91b4 --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/jruby/bcrypt_jruby/BCrypt.java @@ -0,0 +1,925 @@ +// Copyright (c) 2006 Damien Miller +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +package bcrypt_jruby; + +import java.io.UnsupportedEncodingException; +import java.util.Arrays; +import java.security.SecureRandom; + +/** + * BCrypt implements OpenBSD-style Blowfish password hashing using + * the scheme described in "A Future-Adaptable Password Scheme" by + * Niels Provos and David Mazieres. + *

    + * This password hashing system tries to thwart off-line password + * cracking using a computationally-intensive hashing algorithm, + * based on Bruce Schneier's Blowfish cipher. The work factor of + * the algorithm is parameterised, so it can be increased as + * computers get faster. + *

    + * Usage is really simple. To hash a password for the first time, + * call the hashpw method with a random salt, like this: + *

    + * + * String pw_hash = BCrypt.hashpw(plain_password, BCrypt.gensalt());
    + *
    + *

    + * To check whether a plaintext password matches one that has been + * hashed previously, use the checkpw method: + *

    + * + * if (BCrypt.checkpw(candidate_password, stored_hash))
    + *     System.out.println("It matches");
    + * else
    + *     System.out.println("It does not match");
    + *
    + *

    + * The gensalt() method takes an optional parameter (log_rounds) + * that determines the computational complexity of the hashing: + *

    + * + * String strong_salt = BCrypt.gensalt(10)
    + * String stronger_salt = BCrypt.gensalt(12)
    + *
    + *

    + * The amount of work increases exponentially (2**log_rounds), so + * each increment is twice as much work. The default log_rounds is + * 10, and the valid range is 4 to 31. + * + * @author Damien Miller + * @version 0.3 + */ +public class BCrypt { + // BCrypt parameters + private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10; + private static final int BCRYPT_SALT_LEN = 16; + + // Blowfish parameters + private static final int BLOWFISH_NUM_ROUNDS = 16; + + // Initial contents of key schedule + private static final int P_orig[] = { + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, + 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, + 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, + 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, + 0x9216d5d9, 0x8979fb1b + }; + private static final int S_orig[] = { + 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, + 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, + 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, + 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, + 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, + 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, + 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, + 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, + 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, + 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, + 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, + 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, + 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, + 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, + 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, + 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, + 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, + 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, + 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, + 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, + 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, + 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, + 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, + 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, + 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, + 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, + 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, + 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, + 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, + 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, + 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, + 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, + 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, + 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, + 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, + 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, + 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, + 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, + 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, + 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, + 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, + 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, + 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, + 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, + 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, + 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, + 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, + 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, + 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, + 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, + 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, + 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, + 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, + 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, + 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, + 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, + 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, + 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, + 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, + 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, + 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, + 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, + 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, + 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a, + 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, + 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, + 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, + 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, + 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, + 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, + 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, + 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, + 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, + 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, + 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, + 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, + 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, + 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, + 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, + 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, + 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, + 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, + 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, + 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, + 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, + 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, + 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, + 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, + 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, + 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, + 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, + 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, + 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, + 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, + 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, + 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, + 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, + 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, + 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, + 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, + 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, + 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, + 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, + 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, + 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, + 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, + 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, + 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, + 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, + 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, + 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, + 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, + 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, + 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, + 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, + 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, + 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, + 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, + 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, + 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, + 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, + 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, + 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, + 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, + 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, + 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, + 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, + 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7, + 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, + 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, + 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, + 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, + 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, + 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, + 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, + 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, + 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, + 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, + 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, + 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, + 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, + 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, + 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, + 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, + 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, + 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, + 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, + 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, + 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, + 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, + 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, + 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, + 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, + 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, + 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, + 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, + 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, + 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, + 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, + 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, + 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, + 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, + 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, + 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, + 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, + 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, + 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, + 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, + 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, + 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, + 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, + 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, + 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, + 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, + 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, + 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, + 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, + 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, + 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, + 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, + 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, + 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, + 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, + 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, + 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, + 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, + 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, + 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, + 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, + 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, + 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, + 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0, + 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, + 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, + 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, + 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, + 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, + 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, + 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, + 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, + 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, + 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, + 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, + 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, + 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, + 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, + 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, + 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, + 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, + 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, + 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, + 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, + 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, + 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, + 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, + 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, + 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, + 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, + 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, + 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, + 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, + 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, + 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, + 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, + 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, + 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, + 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, + 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, + 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, + 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, + 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, + 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, + 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, + 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, + 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, + 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, + 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, + 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, + 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, + 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, + 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, + 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, + 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, + 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, + 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, + 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, + 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, + 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, + 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, + 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, + 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, + 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, + 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, + 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, + 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, + 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 + }; + + // bcrypt IV: "OrpheanBeholderScryDoubt" + static private final int bf_crypt_ciphertext[] = { + 0x4f727068, 0x65616e42, 0x65686f6c, + 0x64657253, 0x63727944, 0x6f756274 + }; + + // Table for Base64 encoding + static private final char base64_code[] = { + '.', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9' + }; + + // Table for Base64 decoding + static private final byte index_64[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 0, 1, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, -1, -1, + -1, -1, -1, -1, -1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, + -1, -1, -1, -1, -1, -1, 28, 29, 30, + 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, -1, -1, -1, -1, -1 + }; + static final int MIN_LOG_ROUNDS = 4; + static final int MAX_LOG_ROUNDS = 31; + + // Expanded Blowfish key + private int P[]; + private int S[]; + + /** + * Encode a byte array using bcrypt's slightly-modified base64 encoding scheme. Note + * that this is not compatible with the standard MIME-base64 + * encoding. + * + * @param d the byte array to encode + * @param len the number of bytes to encode + * @param rs the destination buffer for the base64-encoded string + * @exception IllegalArgumentException if the length is invalid + */ + static void encode_base64(byte d[], int len, StringBuilder rs) + throws IllegalArgumentException { + int off = 0; + int c1, c2; + + if (len <= 0 || len > d.length) { + throw new IllegalArgumentException("Invalid len"); + } + + while (off < len) { + c1 = d[off++] & 0xff; + rs.append(base64_code[(c1 >> 2) & 0x3f]); + c1 = (c1 & 0x03) << 4; + if (off >= len) { + rs.append(base64_code[c1 & 0x3f]); + break; + } + c2 = d[off++] & 0xff; + c1 |= (c2 >> 4) & 0x0f; + rs.append(base64_code[c1 & 0x3f]); + c1 = (c2 & 0x0f) << 2; + if (off >= len) { + rs.append(base64_code[c1 & 0x3f]); + break; + } + c2 = d[off++] & 0xff; + c1 |= (c2 >> 6) & 0x03; + rs.append(base64_code[c1 & 0x3f]); + rs.append(base64_code[c2 & 0x3f]); + } + } + + /** + * Look up the 3 bits base64-encoded by the specified character, + * range-checking againt conversion table + * @param x the base64-encoded value + * @return the decoded value of x + */ + private static byte char64(char x) { + if ((int) x < 0 || (int) x >= index_64.length) + return -1; + return index_64[(int) x]; + } + + /** + * Decode a string encoded using bcrypt's base64 scheme to a + * byte array. Note that this is *not* compatible with + * the standard MIME-base64 encoding. + * @param s the string to decode + * @param maxolen the maximum number of bytes to decode + * @return an array containing the decoded bytes + * @throws IllegalArgumentException if maxolen is invalid + */ + static byte[] decode_base64(String s, int maxolen) + throws IllegalArgumentException { + StringBuilder rs = new StringBuilder(); + int off = 0, slen = s.length(), olen = 0; + byte ret[]; + byte c1, c2, c3, c4, o; + + if (maxolen <= 0) + throw new IllegalArgumentException ("Invalid maxolen"); + + while (off < slen - 1 && olen < maxolen) { + c1 = char64(s.charAt(off++)); + c2 = char64(s.charAt(off++)); + if (c1 == -1 || c2 == -1) + break; + o = (byte) (c1 << 2); + o |= (c2 & 0x30) >> 4; + rs.append((char) o); + if (++olen >= maxolen || off >= slen) + break; + c3 = char64(s.charAt(off++)); + if (c3 == -1) + break; + o = (byte) ((c2 & 0x0f) << 4); + o |= (c3 & 0x3c) >> 2; + rs.append((char) o); + if (++olen >= maxolen || off >= slen) + break; + c4 = char64(s.charAt(off++)); + o = (byte) ((c3 & 0x03) << 6); + o |= c4; + rs.append((char) o); + ++olen; + } + + ret = new byte[olen]; + for (off = 0; off < olen; off++) + ret[off] = (byte) rs.charAt(off); + return ret; + } + + /** + * Blowfish encipher a single 64-bit block encoded as + * two 32-bit halves + * @param lr an array containing the two 32-bit half blocks + * @param off the position in the array of the blocks + */ + private final void encipher(int lr[], int off) { + int i, n, l = lr[off], r = lr[off + 1]; + + l ^= P[0]; + for (i = 0; i <= BLOWFISH_NUM_ROUNDS - 2;) { + // Feistel substitution on left word + n = S[(l >> 24) & 0xff]; + n += S[0x100 | ((l >> 16) & 0xff)]; + n ^= S[0x200 | ((l >> 8) & 0xff)]; + n += S[0x300 | (l & 0xff)]; + r ^= n ^ P[++i]; + + // Feistel substitution on right word + n = S[(r >> 24) & 0xff]; + n += S[0x100 | ((r >> 16) & 0xff)]; + n ^= S[0x200 | ((r >> 8) & 0xff)]; + n += S[0x300 | (r & 0xff)]; + l ^= n ^ P[++i]; + } + lr[off] = r ^ P[BLOWFISH_NUM_ROUNDS + 1]; + lr[off + 1] = l; + } + + /** + * Cycically extract a word of key material + * @param data the string to extract the data from + * @param offp a "pointer" (as a one-entry array) to the + * current offset into data + * @param signp a "pointer" (as a one-entry array) to the + * cumulative flag for non-benign sign extension + * @return correct and buggy next word of material from data as int[2] + */ + private static int[] streamtowords(byte data[], int offp[], int signp[]) { + int i; + int words[] = { 0, 0 }; + int off = offp[0]; + int sign = signp[0]; + + for (i = 0; i < 4; i++) { + words[0] = (words[0] << 8) | (data[off] & 0xff); + words[1] = (words[1] << 8) | (int) data[off]; // sign extension bug + if (i > 0) sign |= words[1] & 0x80; + off = (off + 1) % data.length; + } + + offp[0] = off; + signp[0] = sign; + return words; + } + + /** + * Cycically extract a word of key material + * @param data the string to extract the data from + * @param offp a "pointer" (as a one-entry array) to the + * current offset into data + * @return the next word of material from data + */ + private static int streamtoword(byte data[], int offp[]) { + int signp[] = { 0 }; + return streamtowords(data, offp, signp)[0]; + } + + /** + * Cycically extract a word of key material, with sign-extension bug + * @param data the string to extract the data from + * @param offp a "pointer" (as a one-entry array) to the + * current offset into data + * @return the next word of material from data + */ + private static int streamtoword_bug(byte data[], int offp[]) { + int signp[] = { 0 }; + return streamtowords(data, offp, signp)[1]; + } + + /** + * Initialise the Blowfish key schedule + */ + private void init_key() { + P = P_orig.clone(); + S = S_orig.clone(); + } + + /** + * Key the Blowfish cipher + * @param key an array containing the key + * @param sign_ext_bug true to implement the 2x bug + * @param safety bit 16 is set when the safety measure is requested + */ + private void key(byte key[], boolean sign_ext_bug, int safety) { + int i; + int koffp[] = { 0 }; + int lr[] = { 0, 0 }; + int plen = P.length, slen = S.length; + + for (i = 0; i < plen; i++) + if (!sign_ext_bug) + P[i] = P[i] ^ streamtoword(key, koffp); + else + P[i] = P[i] ^ streamtoword_bug(key, koffp); + + for (i = 0; i < plen; i += 2) { + encipher(lr, 0); + P[i] = lr[0]; + P[i + 1] = lr[1]; + } + + for (i = 0; i < slen; i += 2) { + encipher(lr, 0); + S[i] = lr[0]; + S[i + 1] = lr[1]; + } + } + + /** + * Perform the "enhanced key schedule" step described by + * Provos and Mazieres in "A Future-Adaptable Password Scheme" + * http://www.openbsd.org/papers/bcrypt-paper.ps + * @param data salt information + * @param key password information + * @param sign_ext_bug true to implement the 2x bug + * @param safety bit 16 is set when the safety measure is requested + */ + private void ekskey(byte data[], byte key[], + boolean sign_ext_bug, int safety) { + int i; + int koffp[] = { 0 }, doffp[] = { 0 }; + int lr[] = { 0, 0 }; + int plen = P.length, slen = S.length; + int signp[] = { 0 }; // non-benign sign-extension flag + int diff = 0; // zero iff correct and buggy are same + + for (i = 0; i < plen; i++) { + int words[] = streamtowords(key, koffp, signp); + diff |= words[0] ^ words[1]; + P[i] = P[i] ^ words[sign_ext_bug ? 1 : 0]; + } + + int sign = signp[0]; + + /* + * At this point, "diff" is zero iff the correct and buggy algorithms produced + * exactly the same result. If so and if "sign" is non-zero, which indicates + * that there was a non-benign sign extension, this means that we have a + * collision between the correctly computed hash for this password and a set of + * passwords that could be supplied to the buggy algorithm. Our safety measure + * is meant to protect from such many-buggy to one-correct collisions, by + * deviating from the correct algorithm in such cases. Let's check for this. + */ + diff |= diff >> 16; /* still zero iff exact match */ + diff &= 0xffff; /* ditto */ + diff += 0xffff; /* bit 16 set iff "diff" was non-zero (on non-match) */ + sign <<= 9; /* move the non-benign sign extension flag to bit 16 */ + sign &= ~diff & safety; /* action needed? */ + + /* + * If we have determined that we need to deviate from the correct algorithm, + * flip bit 16 in initial expanded key. (The choice of 16 is arbitrary, but + * let's stick to it now. It came out of the approach we used above, and it's + * not any worse than any other choice we could make.) + * + * It is crucial that we don't do the same to the expanded key used in the main + * Eksblowfish loop. By doing it to only one of these two, we deviate from a + * state that could be directly specified by a password to the buggy algorithm + * (and to the fully correct one as well, but that's a side-effect). + */ + P[0] ^= sign; + + for (i = 0; i < plen; i += 2) { + lr[0] ^= streamtoword(data, doffp); + lr[1] ^= streamtoword(data, doffp); + encipher(lr, 0); + P[i] = lr[0]; + P[i + 1] = lr[1]; + } + + for (i = 0; i < slen; i += 2) { + lr[0] ^= streamtoword(data, doffp); + lr[1] ^= streamtoword(data, doffp); + encipher(lr, 0); + S[i] = lr[0]; + S[i + 1] = lr[1]; + } + } + + static long roundsForLogRounds(int log_rounds) { + if (log_rounds < 4 || log_rounds > 31) { + throw new IllegalArgumentException("Bad number of rounds"); + } + return 1L << log_rounds; + } + + /** + * Perform the central password hashing step in the + * bcrypt scheme + * @param password the password to hash + * @param salt the binary salt to hash with the password + * @param log_rounds the binary logarithm of the number + * of rounds of hashing to apply + * @param sign_ext_bug true to implement the 2x bug + * @param safety bit 16 is set when the safety measure is requested + * @return an array containing the binary hashed password + */ + private byte[] crypt_raw(byte password[], byte salt[], int log_rounds, + boolean sign_ext_bug, int safety) { + int rounds, i, j; + int cdata[] = bf_crypt_ciphertext.clone(); + int clen = cdata.length; + byte ret[]; + + if (log_rounds < 4 || log_rounds > 31) + throw new IllegalArgumentException ("Bad number of rounds"); + rounds = 1 << log_rounds; + if (salt.length != BCRYPT_SALT_LEN) + throw new IllegalArgumentException ("Bad salt length"); + + init_key(); + ekskey(salt, password, sign_ext_bug, safety); + for (i = 0; i < rounds; i++) { + key(password, sign_ext_bug, safety); + key(salt, false, safety); + } + + for (i = 0; i < 64; i++) { + for (j = 0; j < (clen >> 1); j++) + encipher(cdata, j << 1); + } + + ret = new byte[clen * 4]; + for (i = 0, j = 0; i < clen; i++) { + ret[j++] = (byte) ((cdata[i] >> 24) & 0xff); + ret[j++] = (byte) ((cdata[i] >> 16) & 0xff); + ret[j++] = (byte) ((cdata[i] >> 8) & 0xff); + ret[j++] = (byte) (cdata[i] & 0xff); + } + return ret; + } + + /** + * Hash a password using the OpenBSD bcrypt scheme + * @param password the password to hash + * @param salt the salt to hash with (perhaps generated + * using BCrypt.gensalt) + * @return the hashed password + */ + public static String hashpw(String password, String salt) { + byte passwordb[]; + + try { + passwordb = password.getBytes("UTF-8"); + } catch (UnsupportedEncodingException uee) { + throw new AssertionError("UTF-8 is not supported"); + } + + return hashpw(passwordb, salt); + } + + /** + * Hash a password using the OpenBSD bcrypt scheme + * @param passwordb the password to hash, as a byte array + * @param salt the salt to hash with (perhaps generated + * using BCrypt.gensalt) + * @return the hashed password + */ + public static String hashpw(byte passwordb[], String salt) { + BCrypt B; + String real_salt; + byte saltb[], hashed[]; + char minor = (char) 0; + int rounds, off; + StringBuilder rs = new StringBuilder(); + + if (salt == null) { + throw new IllegalArgumentException("salt cannot be null"); + } + + int saltLength = salt.length(); + + if (saltLength < 28) { + throw new IllegalArgumentException("Invalid salt"); + } + + if (salt.charAt(0) != '$' || salt.charAt(1) != '2') + throw new IllegalArgumentException ("Invalid salt version"); + if (salt.charAt(2) == '$') + off = 3; + else { + minor = salt.charAt(2); + if ((minor != 'a' && minor != 'x' && minor != 'y' && minor != 'b') + || salt.charAt(3) != '$') + throw new IllegalArgumentException ("Invalid salt revision"); + off = 4; + } + + // Extract number of rounds + if (salt.charAt(off + 2) > '$') + throw new IllegalArgumentException ("Missing salt rounds"); + rounds = Integer.parseInt(salt.substring(off, off + 2)); + + real_salt = salt.substring(off + 3, off + 25); + saltb = decode_base64(real_salt, BCRYPT_SALT_LEN); + + if (minor >= 'a') // add null terminator + passwordb = Arrays.copyOf(passwordb, passwordb.length + 1); + + B = new BCrypt(); + hashed = B.crypt_raw(passwordb, saltb, rounds, minor == 'x', minor == 'a' ? 0x10000 : 0); + + rs.append("$2"); + if (minor >= 'a') + rs.append(minor); + rs.append("$"); + if (rounds < 10) + rs.append("0"); + rs.append(rounds); + rs.append("$"); + encode_base64(saltb, saltb.length, rs); + encode_base64(hashed, bf_crypt_ciphertext.length * 4 - 1, rs); + return rs.toString(); + } + + /** + * Generate a salt for use with the BCrypt.hashpw() method + * @param prefix the prefix value (default $2a) + * @param log_rounds the log2 of the number of rounds of + * hashing to apply - the work factor therefore increases as + * 2**log_rounds. + * @param random an instance of SecureRandom to use + * @return an encoded salt value + * @exception IllegalArgumentException if prefix or log_rounds is invalid + */ + public static String gensalt(String prefix, int log_rounds, SecureRandom random) + throws IllegalArgumentException { + StringBuilder rs = new StringBuilder(); + byte rnd[] = new byte[BCRYPT_SALT_LEN]; + + if (!prefix.startsWith("$2") || + (prefix.charAt(2) != 'a' && prefix.charAt(2) != 'y' && + prefix.charAt(2) != 'b')) { + throw new IllegalArgumentException ("Invalid prefix"); + } + if (log_rounds < 4 || log_rounds > 31) { + throw new IllegalArgumentException ("Invalid log_rounds"); + } + + random.nextBytes(rnd); + + rs.append("$2"); + rs.append(prefix.charAt(2)); + rs.append("$"); + if (log_rounds < 10) + rs.append("0"); + rs.append(log_rounds); + rs.append("$"); + encode_base64(rnd, rnd.length, rs); + return rs.toString(); + } + + /** + * Generate a salt for use with the BCrypt.hashpw() method + * @param prefix the prefix value (default $2a) + * @param log_rounds the log2 of the number of rounds of + * hashing to apply - the work factor therefore increases as + * 2**log_rounds. + * @return an encoded salt value + * @exception IllegalArgumentException if prefix or log_rounds is invalid + */ + public static String gensalt(String prefix, int log_rounds) + throws IllegalArgumentException { + return gensalt(prefix, log_rounds, new SecureRandom()); + } + + /** + * Generate a salt for use with the BCrypt.hashpw() method + * @param log_rounds the log2 of the number of rounds of + * hashing to apply - the work factor therefore increases as + * 2**log_rounds. + * @param random an instance of SecureRandom to use + * @return an encoded salt value + * @exception IllegalArgumentException if log_rounds is invalid + */ + public static String gensalt(int log_rounds, SecureRandom random) + throws IllegalArgumentException { + return gensalt("$2a", log_rounds, random); + } + + /** + * Generate a salt for use with the BCrypt.hashpw() method + * @param log_rounds the log2 of the number of rounds of + * hashing to apply - the work factor therefore increases as + * 2**log_rounds. + * @return an encoded salt value + * @exception IllegalArgumentException if log_rounds is invalid + */ + public static String gensalt(int log_rounds) + throws IllegalArgumentException { + return gensalt(log_rounds, new SecureRandom()); + } + + public static String gensalt(String prefix) { + return gensalt(prefix, GENSALT_DEFAULT_LOG2_ROUNDS); + } + + /** + * Generate a salt for use with the BCrypt.hashpw() method, + * selecting a reasonable default for the number of hashing + * rounds to apply + * @return an encoded salt value + */ + public static String gensalt() { + return gensalt(GENSALT_DEFAULT_LOG2_ROUNDS); + } + + /** + * Check that a plaintext password matches a previously hashed + * one + * @param plaintext the plaintext password to verify + * @param hashed the previously-hashed password + * @return true if the passwords match, false otherwise + */ + public static boolean checkpw(String plaintext, String hashed) { + return equalsNoEarlyReturn(hashed, hashpw(plaintext, hashed)); + } + + static boolean equalsNoEarlyReturn(String a, String b) { + char[] caa = a.toCharArray(); + char[] cab = b.toCharArray(); + + if (caa.length != cab.length) { + return false; + } + + byte ret = 0; + for (int i = 0; i < caa.length; i++) { + ret |= caa[i] ^ cab[i]; + } + return ret == 0; + } +} + diff --git a/tutor-virtual-2/test/system/.keep b/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/.sitearchdir.time similarity index 100% rename from tutor-virtual-2/test/system/.keep rename to path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/.sitearchdir.time diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/Makefile b/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/Makefile new file mode 100644 index 00000000..1cf30383 --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/Makefile @@ -0,0 +1,266 @@ + +SHELL = /bin/sh + +# V=0 quiet, V=1 verbose. other values don't work. +V = 0 +Q1 = $(V:1=) +Q = $(Q1:0=@) +ECHO1 = $(V:1=@ :) +ECHO = $(ECHO1:0=@ echo) +NULLCMD = : + +#### Start of system configuration section. #### + +srcdir = . +topdir = /Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 +hdrdir = $(topdir) +arch_hdrdir = /Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 +PATH_SEPARATOR = : +VPATH = $(srcdir):$(arch_hdrdir)/ruby:$(hdrdir)/ruby +prefix = $(DESTDIR)/Users/sergio/.rvm/rubies/ruby-2.6.3 +rubysitearchprefix = $(rubylibprefix)/$(sitearch) +rubyarchprefix = $(rubylibprefix)/$(arch) +rubylibprefix = $(libdir)/$(RUBY_BASE_NAME) +exec_prefix = $(prefix) +vendorarchhdrdir = $(vendorhdrdir)/$(sitearch) +sitearchhdrdir = $(sitehdrdir)/$(sitearch) +rubyarchhdrdir = $(rubyhdrdir)/$(arch) +vendorhdrdir = $(rubyhdrdir)/vendor_ruby +sitehdrdir = $(rubyhdrdir)/site_ruby +rubyhdrdir = $(includedir)/$(RUBY_VERSION_NAME) +vendorarchdir = $(vendorlibdir)/$(sitearch) +vendorlibdir = $(vendordir)/$(ruby_version) +vendordir = $(rubylibprefix)/vendor_ruby +sitearchdir = $(DESTDIR)./.gem.20191018-47188-1cv438l +sitelibdir = $(DESTDIR)./.gem.20191018-47188-1cv438l +sitedir = $(rubylibprefix)/site_ruby +rubyarchdir = $(rubylibdir)/$(arch) +rubylibdir = $(rubylibprefix)/$(ruby_version) +sitearchincludedir = $(includedir)/$(sitearch) +archincludedir = $(includedir)/$(arch) +sitearchlibdir = $(libdir)/$(sitearch) +archlibdir = $(libdir)/$(arch) +ridir = $(datarootdir)/$(RI_BASE_NAME) +mandir = $(datarootdir)/man +localedir = $(datarootdir)/locale +libdir = $(exec_prefix)/lib +psdir = $(docdir) +pdfdir = $(docdir) +dvidir = $(docdir) +htmldir = $(docdir) +infodir = $(datarootdir)/info +docdir = $(datarootdir)/doc/$(PACKAGE) +oldincludedir = $(SDKROOT)/usr/include +includedir = $(prefix)/include +localstatedir = $(prefix)/var +sharedstatedir = $(prefix)/com +sysconfdir = $(prefix)/etc +datadir = $(datarootdir) +datarootdir = $(prefix)/share +libexecdir = $(exec_prefix)/libexec +sbindir = $(exec_prefix)/sbin +bindir = $(exec_prefix)/bin +archdir = $(rubyarchdir) + + +CC_WRAPPER = +CC = gcc +CXX = g++ +LIBRUBY = $(LIBRUBY_SO) +LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a +LIBRUBYARG_SHARED = -l$(RUBY_SO_NAME) +LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static -framework Security -framework Foundation $(MAINLIBS) +empty = +OUTFLAG = -o $(empty) +COUTFLAG = -o $(empty) +CSRCFLAG = $(empty) + +RUBY_EXTCONF_H = +cflags = $(optflags) $(debugflags) $(warnflags) +cxxflags = $(optflags) $(debugflags) $(warnflags) +optflags = -O3 +debugflags = -ggdb3 +warnflags = -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens +cppflags = +CCDLFLAGS = -fno-common +CFLAGS = $(CCDLFLAGS) $(cflags) -fno-common -pipe $(ARCH_FLAG) +INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir)/ruby/backward -I$(hdrdir) -I$(srcdir) +DEFS = +CPPFLAGS = -D__SKIP_GNU -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT $(DEFS) $(cppflags) +CXXFLAGS = $(CCDLFLAGS) $(cxxflags) $(ARCH_FLAG) +ldflags = -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib +dldflags = -Wl,-undefined,dynamic_lookup -Wl,-multiply_defined,suppress -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib +ARCH_FLAG = +DLDFLAGS = $(ldflags) $(dldflags) $(ARCH_FLAG) +LDSHARED = $(CC) -dynamic -bundle +LDSHAREDXX = $(CXX) -dynamic -bundle +AR = libtool -static +EXEEXT = + +RUBY_INSTALL_NAME = $(RUBY_BASE_NAME) +RUBY_SO_NAME = ruby.2.6 +RUBYW_INSTALL_NAME = +RUBY_VERSION_NAME = $(RUBY_BASE_NAME)-$(ruby_version) +RUBYW_BASE_NAME = rubyw +RUBY_BASE_NAME = ruby + +arch = x86_64-darwin18 +sitearch = $(arch) +ruby_version = 2.6.0 +ruby = $(bindir)/$(RUBY_BASE_NAME) +RUBY = $(ruby) +ruby_headers = $(hdrdir)/ruby.h $(hdrdir)/ruby/backward.h $(hdrdir)/ruby/ruby.h $(hdrdir)/ruby/defines.h $(hdrdir)/ruby/missing.h $(hdrdir)/ruby/intern.h $(hdrdir)/ruby/st.h $(hdrdir)/ruby/subst.h $(arch_hdrdir)/ruby/config.h + +RM = rm -f +RM_RF = $(RUBY) -run -e rm -- -rf +RMDIRS = rmdir -p +MAKEDIRS = /usr/local/opt/coreutils/bin/gmkdir -p +INSTALL = /usr/local/opt/coreutils/bin/ginstall -c +INSTALL_PROG = $(INSTALL) -m 0755 +INSTALL_DATA = $(INSTALL) -m 644 +COPY = cp +TOUCH = exit > + +#### End of system configuration section. #### + +preload = +libpath = . $(libdir) /usr/local/opt/libyaml/lib /usr/local/opt/libksba/lib /usr/local/opt/readline/lib /usr/local/opt/zlib/lib /usr/local/opt/openssl@1.1/lib +LIBPATH = -L. -L$(libdir) -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib +DEFFILE = + +CLEANFILES = mkmf.log +DISTCLEANFILES = +DISTCLEANDIRS = + +extout = +extout_prefix = +target_prefix = +LOCAL_LIBS = +LIBS = $(LIBRUBYARG_SHARED) +ORIG_SRCS = bcrypt_ext.c crypt.c crypt_blowfish.c crypt_gensalt.c wrapper.c +SRCS = $(ORIG_SRCS) bcrypt_ext.c crypt_blowfish.c x86.c crypt_gensalt.c wrapper.c +OBJS = bcrypt_ext.o crypt_blowfish.o x86.o crypt_gensalt.o wrapper.o +HDRS = $(srcdir)/crypt_blowfish.h $(srcdir)/crypt.h $(srcdir)/crypt_gensalt.h $(srcdir)/ow-crypt.h +LOCAL_HDRS = +TARGET = bcrypt_ext +TARGET_NAME = bcrypt_ext +TARGET_ENTRY = Init_$(TARGET_NAME) +DLLIB = $(TARGET).bundle +EXTSTATIC = +STATIC_LIB = + +TIMESTAMP_DIR = . +BINDIR = $(bindir) +RUBYCOMMONDIR = $(sitedir)$(target_prefix) +RUBYLIBDIR = $(sitelibdir)$(target_prefix) +RUBYARCHDIR = $(sitearchdir)$(target_prefix) +HDRDIR = $(rubyhdrdir)/ruby$(target_prefix) +ARCHHDRDIR = $(rubyhdrdir)/$(arch)/ruby$(target_prefix) +TARGET_SO_DIR = +TARGET_SO = $(TARGET_SO_DIR)$(DLLIB) +CLEANLIBS = $(TARGET_SO) +CLEANOBJS = *.o *.bak + +all: $(DLLIB) +static: $(STATIC_LIB) +.PHONY: all install static install-so install-rb +.PHONY: clean clean-so clean-static clean-rb + +clean-static:: +clean-rb-default:: +clean-rb:: +clean-so:: +clean: clean-so clean-static clean-rb-default clean-rb + -$(Q)$(RM) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES) .*.time + +distclean-rb-default:: +distclean-rb:: +distclean-so:: +distclean-static:: +distclean: clean distclean-so distclean-static distclean-rb-default distclean-rb + -$(Q)$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log + -$(Q)$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES) + -$(Q)$(RMDIRS) $(DISTCLEANDIRS) 2> /dev/null || true + +realclean: distclean +install: install-so install-rb + +install-so: $(DLLIB) $(TIMESTAMP_DIR)/.sitearchdir.time + $(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR) +clean-static:: + -$(Q)$(RM) $(STATIC_LIB) +install-rb: pre-install-rb do-install-rb install-rb-default +install-rb-default: pre-install-rb-default do-install-rb-default +pre-install-rb: Makefile +pre-install-rb-default: Makefile +do-install-rb: +do-install-rb-default: +pre-install-rb-default: + @$(NULLCMD) +$(TIMESTAMP_DIR)/.sitearchdir.time: + $(Q) $(MAKEDIRS) $(@D) $(RUBYARCHDIR) + $(Q) $(TOUCH) $@ + +site-install: site-install-so site-install-rb +site-install-so: install-so +site-install-rb: install-rb + +.SUFFIXES: .c .m .cc .mm .cxx .cpp .o .S + +.cc.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cc.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.mm.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.mm.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.cxx.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cxx.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.cpp.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cpp.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.c.o: + $(ECHO) compiling $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.c.S: + $(ECHO) translating $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.m.o: + $(ECHO) compiling $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.m.S: + $(ECHO) translating $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +$(TARGET_SO): $(OBJS) Makefile + $(ECHO) linking shared-object $(DLLIB) + -$(Q)$(RM) $(@) + $(Q) $(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS) + $(Q) $(POSTLINK) + + + +$(OBJS): $(HDRS) $(ruby_headers) diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/bcrypt_ext.c b/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/bcrypt_ext.c new file mode 100644 index 00000000..b2edd1d5 --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/bcrypt_ext.c @@ -0,0 +1,64 @@ +#include +#include + +static VALUE mBCrypt; +static VALUE cBCryptEngine; + +/* Given a logarithmic cost parameter, generates a salt for use with +bc_crypt+. +*/ +static VALUE bc_salt(VALUE self, VALUE prefix, VALUE count, VALUE input) { + char * salt; + VALUE str_salt; + + salt = crypt_gensalt_ra( + StringValuePtr(prefix), + NUM2ULONG(count), + NIL_P(input) ? NULL : StringValuePtr(input), + NIL_P(input) ? 0 : RSTRING_LEN(input)); + + if(!salt) return Qnil; + + str_salt = rb_str_new2(salt); + xfree(salt); + + return str_salt; +} + +/* Given a secret and a salt, generates a salted hash (which you can then store safely). +*/ +static VALUE bc_crypt(VALUE self, VALUE key, VALUE setting) { + char * value; + void * data; + int size; + VALUE out; + + data = NULL; + size = 0xDEADBEEF; + + if(NIL_P(key) || NIL_P(setting)) return Qnil; + + value = crypt_ra( + NIL_P(key) ? NULL : StringValuePtr(key), + NIL_P(setting) ? NULL : StringValuePtr(setting), + &data, + &size); + + if(!value) return Qnil; + + out = rb_str_new2(value); + + xfree(data); + + return out; +} + +/* Create the BCrypt and BCrypt::Engine modules, and populate them with methods. */ +void Init_bcrypt_ext(){ + mBCrypt = rb_define_module("BCrypt"); + cBCryptEngine = rb_define_class_under(mBCrypt, "Engine", rb_cObject); + + rb_define_singleton_method(cBCryptEngine, "__bc_salt", bc_salt, 3); + rb_define_singleton_method(cBCryptEngine, "__bc_crypt", bc_crypt, 2); +} + +/* vim: set noet sws=4 sw=4: */ diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/crypt.c b/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/crypt.c new file mode 100644 index 00000000..522b4958 --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/crypt.c @@ -0,0 +1,57 @@ +#include +#include + +VALUE mCrypt; + +static VALUE crypt_salt(VALUE self, VALUE prefix, VALUE count, VALUE input) +{ + char * salt; + VALUE str_salt; + + salt = crypt_gensalt_ra( + StringValuePtr(prefix), + NUM2ULONG(count), + NIL_P(input) ? NULL : StringValuePtr(input), + NIL_P(input) ? 0 : RSTRING_LEN(input)); + + if(!salt) return Qnil; + + str_salt = rb_str_new2(salt); + free(salt); + + return str_salt; +} + +static VALUE ra(VALUE self, VALUE key, VALUE setting) +{ + char * value; + void * data; + int size; + VALUE out; + + data = NULL; + size = 0xDEADBEEF; + + if(NIL_P(key) || NIL_P(setting)) return Qnil; + + value = crypt_ra( + NIL_P(key) ? NULL : StringValuePtr(key), + NIL_P(setting) ? NULL : StringValuePtr(setting), + &data, + &size); + + if(!value) return Qnil; + + out = rb_str_new(data, size - 1); + + free(data); + + return out; +} + +void Init_crypt() +{ + mCrypt = rb_define_module("Crypt"); + rb_define_singleton_method(mCrypt, "salt", crypt_salt, 3); + rb_define_singleton_method(mCrypt, "crypt", ra, 2); +} diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/crypt.h b/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/crypt.h new file mode 100644 index 00000000..12e67055 --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/crypt.h @@ -0,0 +1,24 @@ +/* + * Written by Solar Designer in 2000-2002. + * No copyright is claimed, and the software is hereby placed in the public + * domain. In case this attempt to disclaim copyright and place the software + * in the public domain is deemed null and void, then the software is + * Copyright (c) 2000-2002 Solar Designer and it is hereby released to the + * general public under the following terms: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * There's ABSOLUTELY NO WARRANTY, express or implied. + * + * See crypt_blowfish.c for more information. + */ + +#include + +#if defined(_OW_SOURCE) || defined(__USE_OW) +#define __SKIP_GNU +#undef __SKIP_OW +#include +#undef __SKIP_GNU +#endif diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/crypt_blowfish.c b/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/crypt_blowfish.c new file mode 100644 index 00000000..9d3f3be8 --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/crypt_blowfish.c @@ -0,0 +1,907 @@ +/* + * The crypt_blowfish homepage is: + * + * http://www.openwall.com/crypt/ + * + * This code comes from John the Ripper password cracker, with reentrant + * and crypt(3) interfaces added, but optimizations specific to password + * cracking removed. + * + * Written by Solar Designer in 1998-2014. + * No copyright is claimed, and the software is hereby placed in the public + * domain. In case this attempt to disclaim copyright and place the software + * in the public domain is deemed null and void, then the software is + * Copyright (c) 1998-2014 Solar Designer and it is hereby released to the + * general public under the following terms: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * There's ABSOLUTELY NO WARRANTY, express or implied. + * + * It is my intent that you should be able to use this on your system, + * as part of a software package, or anywhere else to improve security, + * ensure compatibility, or for any other purpose. I would appreciate + * it if you give credit where it is due and keep your modifications in + * the public domain as well, but I don't require that in order to let + * you place this code and any modifications you make under a license + * of your choice. + * + * This implementation is fully compatible with OpenBSD's bcrypt.c for prefix + * "$2b$", originally by Niels Provos , and it uses + * some of his ideas. The password hashing algorithm was designed by David + * Mazieres . For information on the level of + * compatibility for bcrypt hash prefixes other than "$2b$", please refer to + * the comments in BF_set_key() below and to the included crypt(3) man page. + * + * There's a paper on the algorithm that explains its design decisions: + * + * http://www.usenix.org/events/usenix99/provos.html + * + * Some of the tricks in BF_ROUND might be inspired by Eric Young's + * Blowfish library (I can't be sure if I would think of something if I + * hadn't seen his code). + */ + +#include + +#include +#ifndef __set_errno +#define __set_errno(val) errno = (val) +#endif + +/* Just to make sure the prototypes match the actual definitions */ +#include "crypt_blowfish.h" + +#ifdef __i386__ +#define BF_ASM 1 +#define BF_SCALE 1 +#elif defined(__x86_64__) || defined(__alpha__) || defined(__hppa__) +#define BF_ASM 0 +#define BF_SCALE 1 +#else +#define BF_ASM 0 +#define BF_SCALE 0 +#endif + +typedef unsigned int BF_word; +typedef signed int BF_word_signed; + +/* Number of Blowfish rounds, this is also hardcoded into a few places */ +#define BF_N 16 + +typedef BF_word BF_key[BF_N + 2]; + +typedef struct { + BF_word S[4][0x100]; + BF_key P; +} BF_ctx; + +/* + * Magic IV for 64 Blowfish encryptions that we do at the end. + * The string is "OrpheanBeholderScryDoubt" on big-endian. + */ +static BF_word BF_magic_w[6] = { + 0x4F727068, 0x65616E42, 0x65686F6C, + 0x64657253, 0x63727944, 0x6F756274 +}; + +/* + * P-box and S-box tables initialized with digits of Pi. + */ +static BF_ctx BF_init_state = { + { + { + 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, + 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, + 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, + 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, + 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, + 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, + 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, + 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, + 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, + 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, + 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, + 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, + 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, + 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, + 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, + 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, + 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, + 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, + 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, + 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, + 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, + 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, + 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, + 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, + 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, + 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, + 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, + 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, + 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, + 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, + 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, + 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, + 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, + 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, + 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, + 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, + 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, + 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, + 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, + 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, + 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, + 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, + 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, + 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, + 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, + 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, + 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, + 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, + 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, + 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, + 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, + 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, + 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, + 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, + 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, + 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, + 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, + 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, + 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, + 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, + 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, + 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, + 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, + 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a + }, { + 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, + 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, + 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, + 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, + 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, + 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, + 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, + 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, + 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, + 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, + 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, + 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, + 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, + 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, + 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, + 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, + 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, + 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, + 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, + 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, + 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, + 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, + 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, + 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, + 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, + 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, + 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, + 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, + 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, + 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, + 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, + 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, + 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, + 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, + 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, + 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, + 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, + 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, + 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, + 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, + 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, + 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, + 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, + 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, + 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, + 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, + 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, + 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, + 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, + 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, + 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, + 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, + 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, + 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, + 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, + 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, + 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, + 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, + 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, + 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, + 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, + 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, + 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, + 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7 + }, { + 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, + 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, + 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, + 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, + 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, + 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, + 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, + 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, + 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, + 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, + 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, + 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, + 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, + 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, + 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, + 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, + 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, + 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, + 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, + 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, + 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, + 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, + 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, + 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, + 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, + 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, + 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, + 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, + 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, + 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, + 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, + 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, + 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, + 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, + 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, + 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, + 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, + 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, + 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, + 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, + 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, + 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, + 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, + 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, + 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, + 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, + 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, + 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, + 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, + 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, + 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, + 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, + 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, + 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, + 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, + 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, + 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, + 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, + 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, + 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, + 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, + 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, + 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, + 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0 + }, { + 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, + 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, + 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, + 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, + 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, + 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, + 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, + 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, + 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, + 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, + 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, + 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, + 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, + 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, + 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, + 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, + 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, + 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, + 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, + 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, + 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, + 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, + 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, + 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, + 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, + 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, + 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, + 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, + 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, + 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, + 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, + 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, + 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, + 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, + 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, + 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, + 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, + 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, + 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, + 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, + 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, + 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, + 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, + 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, + 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, + 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, + 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, + 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, + 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, + 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, + 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, + 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, + 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, + 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, + 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, + 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, + 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, + 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, + 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, + 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, + 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, + 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, + 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, + 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 + } + }, { + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, + 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, + 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, + 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, + 0x9216d5d9, 0x8979fb1b + } +}; + +static unsigned char BF_itoa64[64 + 1] = + "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + +static unsigned char BF_atoi64[0x60] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 1, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 64, 64, 64, 64, 64, + 64, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 64, 64, 64, 64, 64, + 64, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, + 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 64, 64, 64, 64, 64 +}; + +#define BF_safe_atoi64(dst, src) \ +{ \ + tmp = (unsigned char)(src); \ + if ((unsigned int)(tmp -= 0x20) >= 0x60) return -1; \ + tmp = BF_atoi64[tmp]; \ + if (tmp > 63) return -1; \ + (dst) = tmp; \ +} + +static int BF_decode(BF_word *dst, const char *src, int size) +{ + unsigned char *dptr = (unsigned char *)dst; + unsigned char *end = dptr + size; + const unsigned char *sptr = (const unsigned char *)src; + unsigned int tmp, c1, c2, c3, c4; + + do { + BF_safe_atoi64(c1, *sptr++); + BF_safe_atoi64(c2, *sptr++); + *dptr++ = (c1 << 2) | ((c2 & 0x30) >> 4); + if (dptr >= end) break; + + BF_safe_atoi64(c3, *sptr++); + *dptr++ = ((c2 & 0x0F) << 4) | ((c3 & 0x3C) >> 2); + if (dptr >= end) break; + + BF_safe_atoi64(c4, *sptr++); + *dptr++ = ((c3 & 0x03) << 6) | c4; + } while (dptr < end); + + return 0; +} + +static void BF_encode(char *dst, const BF_word *src, int size) +{ + const unsigned char *sptr = (const unsigned char *)src; + const unsigned char *end = sptr + size; + unsigned char *dptr = (unsigned char *)dst; + unsigned int c1, c2; + + do { + c1 = *sptr++; + *dptr++ = BF_itoa64[c1 >> 2]; + c1 = (c1 & 0x03) << 4; + if (sptr >= end) { + *dptr++ = BF_itoa64[c1]; + break; + } + + c2 = *sptr++; + c1 |= c2 >> 4; + *dptr++ = BF_itoa64[c1]; + c1 = (c2 & 0x0f) << 2; + if (sptr >= end) { + *dptr++ = BF_itoa64[c1]; + break; + } + + c2 = *sptr++; + c1 |= c2 >> 6; + *dptr++ = BF_itoa64[c1]; + *dptr++ = BF_itoa64[c2 & 0x3f]; + } while (sptr < end); +} + +static void BF_swap(BF_word *x, int count) +{ + static int endianness_check = 1; + char *is_little_endian = (char *)&endianness_check; + BF_word tmp; + + if (*is_little_endian) + do { + tmp = *x; + tmp = (tmp << 16) | (tmp >> 16); + *x++ = ((tmp & 0x00FF00FF) << 8) | ((tmp >> 8) & 0x00FF00FF); + } while (--count); +} + +#if BF_SCALE +/* Architectures which can shift addresses left by 2 bits with no extra cost */ +#define BF_ROUND(L, R, N) \ + tmp1 = L & 0xFF; \ + tmp2 = L >> 8; \ + tmp2 &= 0xFF; \ + tmp3 = L >> 16; \ + tmp3 &= 0xFF; \ + tmp4 = L >> 24; \ + tmp1 = data.ctx.S[3][tmp1]; \ + tmp2 = data.ctx.S[2][tmp2]; \ + tmp3 = data.ctx.S[1][tmp3]; \ + tmp3 += data.ctx.S[0][tmp4]; \ + tmp3 ^= tmp2; \ + R ^= data.ctx.P[N + 1]; \ + tmp3 += tmp1; \ + R ^= tmp3; +#else +/* Architectures with no complicated addressing modes supported */ +#define BF_INDEX(S, i) \ + (*((BF_word *)(((unsigned char *)S) + (i)))) +#define BF_ROUND(L, R, N) \ + tmp1 = L & 0xFF; \ + tmp1 <<= 2; \ + tmp2 = L >> 6; \ + tmp2 &= 0x3FC; \ + tmp3 = L >> 14; \ + tmp3 &= 0x3FC; \ + tmp4 = L >> 22; \ + tmp4 &= 0x3FC; \ + tmp1 = BF_INDEX(data.ctx.S[3], tmp1); \ + tmp2 = BF_INDEX(data.ctx.S[2], tmp2); \ + tmp3 = BF_INDEX(data.ctx.S[1], tmp3); \ + tmp3 += BF_INDEX(data.ctx.S[0], tmp4); \ + tmp3 ^= tmp2; \ + R ^= data.ctx.P[N + 1]; \ + tmp3 += tmp1; \ + R ^= tmp3; +#endif + +/* + * Encrypt one block, BF_N is hardcoded here. + */ +#define BF_ENCRYPT \ + L ^= data.ctx.P[0]; \ + BF_ROUND(L, R, 0); \ + BF_ROUND(R, L, 1); \ + BF_ROUND(L, R, 2); \ + BF_ROUND(R, L, 3); \ + BF_ROUND(L, R, 4); \ + BF_ROUND(R, L, 5); \ + BF_ROUND(L, R, 6); \ + BF_ROUND(R, L, 7); \ + BF_ROUND(L, R, 8); \ + BF_ROUND(R, L, 9); \ + BF_ROUND(L, R, 10); \ + BF_ROUND(R, L, 11); \ + BF_ROUND(L, R, 12); \ + BF_ROUND(R, L, 13); \ + BF_ROUND(L, R, 14); \ + BF_ROUND(R, L, 15); \ + tmp4 = R; \ + R = L; \ + L = tmp4 ^ data.ctx.P[BF_N + 1]; + +#if BF_ASM +#define BF_body() \ + _BF_body_r(&data.ctx); +#else +#define BF_body() \ + L = R = 0; \ + ptr = data.ctx.P; \ + do { \ + ptr += 2; \ + BF_ENCRYPT; \ + *(ptr - 2) = L; \ + *(ptr - 1) = R; \ + } while (ptr < &data.ctx.P[BF_N + 2]); \ +\ + ptr = data.ctx.S[0]; \ + do { \ + ptr += 2; \ + BF_ENCRYPT; \ + *(ptr - 2) = L; \ + *(ptr - 1) = R; \ + } while (ptr < &data.ctx.S[3][0xFF]); +#endif + +static void BF_set_key(const char *key, BF_key expanded, BF_key initial, + unsigned char flags) +{ + const char *ptr = key; + unsigned int bug, i, j; + BF_word safety, sign, diff, tmp[2]; + +/* + * There was a sign extension bug in older revisions of this function. While + * we would have liked to simply fix the bug and move on, we have to provide + * a backwards compatibility feature (essentially the bug) for some systems and + * a safety measure for some others. The latter is needed because for certain + * multiple inputs to the buggy algorithm there exist easily found inputs to + * the correct algorithm that produce the same hash. Thus, we optionally + * deviate from the correct algorithm just enough to avoid such collisions. + * While the bug itself affected the majority of passwords containing + * characters with the 8th bit set (although only a percentage of those in a + * collision-producing way), the anti-collision safety measure affects + * only a subset of passwords containing the '\xff' character (not even all of + * those passwords, just some of them). This character is not found in valid + * UTF-8 sequences and is rarely used in popular 8-bit character encodings. + * Thus, the safety measure is unlikely to cause much annoyance, and is a + * reasonable tradeoff to use when authenticating against existing hashes that + * are not reliably known to have been computed with the correct algorithm. + * + * We use an approach that tries to minimize side-channel leaks of password + * information - that is, we mostly use fixed-cost bitwise operations instead + * of branches or table lookups. (One conditional branch based on password + * length remains. It is not part of the bug aftermath, though, and is + * difficult and possibly unreasonable to avoid given the use of C strings by + * the caller, which results in similar timing leaks anyway.) + * + * For actual implementation, we set an array index in the variable "bug" + * (0 means no bug, 1 means sign extension bug emulation) and a flag in the + * variable "safety" (bit 16 is set when the safety measure is requested). + * Valid combinations of settings are: + * + * Prefix "$2a$": bug = 0, safety = 0x10000 + * Prefix "$2b$": bug = 0, safety = 0 + * Prefix "$2x$": bug = 1, safety = 0 + * Prefix "$2y$": bug = 0, safety = 0 + */ + bug = (unsigned int)flags & 1; + safety = ((BF_word)flags & 2) << 15; + + sign = diff = 0; + + for (i = 0; i < BF_N + 2; i++) { + tmp[0] = tmp[1] = 0; + for (j = 0; j < 4; j++) { + tmp[0] <<= 8; + tmp[0] |= (unsigned char)*ptr; /* correct */ + tmp[1] <<= 8; + tmp[1] |= (BF_word_signed)(signed char)*ptr; /* bug */ +/* + * Sign extension in the first char has no effect - nothing to overwrite yet, + * and those extra 24 bits will be fully shifted out of the 32-bit word. For + * chars 2, 3, 4 in each four-char block, we set bit 7 of "sign" if sign + * extension in tmp[1] occurs. Once this flag is set, it remains set. + */ + if (j) + sign |= tmp[1] & 0x80; + if (!*ptr) + ptr = key; + else + ptr++; + } + diff |= tmp[0] ^ tmp[1]; /* Non-zero on any differences */ + + expanded[i] = tmp[bug]; + initial[i] = BF_init_state.P[i] ^ tmp[bug]; + } + +/* + * At this point, "diff" is zero iff the correct and buggy algorithms produced + * exactly the same result. If so and if "sign" is non-zero, which indicates + * that there was a non-benign sign extension, this means that we have a + * collision between the correctly computed hash for this password and a set of + * passwords that could be supplied to the buggy algorithm. Our safety measure + * is meant to protect from such many-buggy to one-correct collisions, by + * deviating from the correct algorithm in such cases. Let's check for this. + */ + diff |= diff >> 16; /* still zero iff exact match */ + diff &= 0xffff; /* ditto */ + diff += 0xffff; /* bit 16 set iff "diff" was non-zero (on non-match) */ + sign <<= 9; /* move the non-benign sign extension flag to bit 16 */ + sign &= ~diff & safety; /* action needed? */ + +/* + * If we have determined that we need to deviate from the correct algorithm, + * flip bit 16 in initial expanded key. (The choice of 16 is arbitrary, but + * let's stick to it now. It came out of the approach we used above, and it's + * not any worse than any other choice we could make.) + * + * It is crucial that we don't do the same to the expanded key used in the main + * Eksblowfish loop. By doing it to only one of these two, we deviate from a + * state that could be directly specified by a password to the buggy algorithm + * (and to the fully correct one as well, but that's a side-effect). + */ + initial[0] ^= sign; +} + +static const unsigned char flags_by_subtype[26] = + {2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0}; + +static char *BF_crypt(const char *key, const char *setting, + char *output, int size, + BF_word min) +{ +#if BF_ASM + extern void _BF_body_r(BF_ctx *ctx); +#endif + struct { + BF_ctx ctx; + BF_key expanded_key; + union { + BF_word salt[4]; + BF_word output[6]; + } binary; + } data; + BF_word L, R; + BF_word tmp1, tmp2, tmp3, tmp4; + BF_word *ptr; + BF_word count; + int i; + + if (size < 7 + 22 + 31 + 1) { + __set_errno(ERANGE); + return NULL; + } + + if (setting[0] != '$' || + setting[1] != '2' || + setting[2] < 'a' || setting[2] > 'z' || + !flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a'] || + setting[3] != '$' || + setting[4] < '0' || setting[4] > '3' || + setting[5] < '0' || setting[5] > '9' || + (setting[4] == '3' && setting[5] > '1') || + setting[6] != '$') { + __set_errno(EINVAL); + return NULL; + } + + count = (BF_word)1 << ((setting[4] - '0') * 10 + (setting[5] - '0')); + if (count < min || BF_decode(data.binary.salt, &setting[7], 16)) { + __set_errno(EINVAL); + return NULL; + } + BF_swap(data.binary.salt, 4); + + BF_set_key(key, data.expanded_key, data.ctx.P, + flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a']); + + memcpy(data.ctx.S, BF_init_state.S, sizeof(data.ctx.S)); + + L = R = 0; + for (i = 0; i < BF_N + 2; i += 2) { + L ^= data.binary.salt[i & 2]; + R ^= data.binary.salt[(i & 2) + 1]; + BF_ENCRYPT; + data.ctx.P[i] = L; + data.ctx.P[i + 1] = R; + } + + ptr = data.ctx.S[0]; + do { + ptr += 4; + L ^= data.binary.salt[(BF_N + 2) & 3]; + R ^= data.binary.salt[(BF_N + 3) & 3]; + BF_ENCRYPT; + *(ptr - 4) = L; + *(ptr - 3) = R; + + L ^= data.binary.salt[(BF_N + 4) & 3]; + R ^= data.binary.salt[(BF_N + 5) & 3]; + BF_ENCRYPT; + *(ptr - 2) = L; + *(ptr - 1) = R; + } while (ptr < &data.ctx.S[3][0xFF]); + + do { + int done; + + for (i = 0; i < BF_N + 2; i += 2) { + data.ctx.P[i] ^= data.expanded_key[i]; + data.ctx.P[i + 1] ^= data.expanded_key[i + 1]; + } + + done = 0; + do { + BF_body(); + if (done) + break; + done = 1; + + tmp1 = data.binary.salt[0]; + tmp2 = data.binary.salt[1]; + tmp3 = data.binary.salt[2]; + tmp4 = data.binary.salt[3]; + for (i = 0; i < BF_N; i += 4) { + data.ctx.P[i] ^= tmp1; + data.ctx.P[i + 1] ^= tmp2; + data.ctx.P[i + 2] ^= tmp3; + data.ctx.P[i + 3] ^= tmp4; + } + data.ctx.P[16] ^= tmp1; + data.ctx.P[17] ^= tmp2; + } while (1); + } while (--count); + + for (i = 0; i < 6; i += 2) { + L = BF_magic_w[i]; + R = BF_magic_w[i + 1]; + + count = 64; + do { + BF_ENCRYPT; + } while (--count); + + data.binary.output[i] = L; + data.binary.output[i + 1] = R; + } + + memcpy(output, setting, 7 + 22 - 1); + output[7 + 22 - 1] = BF_itoa64[(int) + BF_atoi64[(int)setting[7 + 22 - 1] - 0x20] & 0x30]; + +/* This has to be bug-compatible with the original implementation, so + * only encode 23 of the 24 bytes. :-) */ + BF_swap(data.binary.output, 6); + BF_encode(&output[7 + 22], data.binary.output, 23); + output[7 + 22 + 31] = '\0'; + + return output; +} + +int _crypt_output_magic(const char *setting, char *output, int size) +{ + if (size < 3) + return -1; + + output[0] = '*'; + output[1] = '0'; + output[2] = '\0'; + + if (setting[0] == '*' && setting[1] == '0') + output[1] = '1'; + + return 0; +} + +/* + * Please preserve the runtime self-test. It serves two purposes at once: + * + * 1. We really can't afford the risk of producing incompatible hashes e.g. + * when there's something like gcc bug 26587 again, whereas an application or + * library integrating this code might not also integrate our external tests or + * it might not run them after every build. Even if it does, the miscompile + * might only occur on the production build, but not on a testing build (such + * as because of different optimization settings). It is painful to recover + * from incorrectly-computed hashes - merely fixing whatever broke is not + * enough. Thus, a proactive measure like this self-test is needed. + * + * 2. We don't want to leave sensitive data from our actual password hash + * computation on the stack or in registers. Previous revisions of the code + * would do explicit cleanups, but simply running the self-test after hash + * computation is more reliable. + * + * The performance cost of this quick self-test is around 0.6% at the "$2a$08" + * setting. + */ +char *_crypt_blowfish_rn(const char *key, const char *setting, + char *output, int size) +{ + const char *test_key = "8b \xd0\xc1\xd2\xcf\xcc\xd8"; + const char *test_setting = "$2a$00$abcdefghijklmnopqrstuu"; + static const char * const test_hashes[2] = + {"i1D709vfamulimlGcq0qq3UvuUasvEa\0\x55", /* 'a', 'b', 'y' */ + "VUrPmXD6q/nVSSp7pNDhCR9071IfIRe\0\x55"}; /* 'x' */ + const char *test_hash = test_hashes[0]; + char *retval; + const char *p; + int save_errno, ok; + struct { + char s[7 + 22 + 1]; + char o[7 + 22 + 31 + 1 + 1 + 1]; + } buf; + +/* Hash the supplied password */ + _crypt_output_magic(setting, output, size); + retval = BF_crypt(key, setting, output, size, 16); + save_errno = errno; + +/* + * Do a quick self-test. It is important that we make both calls to BF_crypt() + * from the same scope such that they likely use the same stack locations, + * which makes the second call overwrite the first call's sensitive data on the + * stack and makes it more likely that any alignment related issues would be + * detected by the self-test. + */ + memcpy(buf.s, test_setting, sizeof(buf.s)); + if (retval) { + unsigned int flags = flags_by_subtype[ + (unsigned int)(unsigned char)setting[2] - 'a']; + test_hash = test_hashes[flags & 1]; + buf.s[2] = setting[2]; + } + memset(buf.o, 0x55, sizeof(buf.o)); + buf.o[sizeof(buf.o) - 1] = 0; + p = BF_crypt(test_key, buf.s, buf.o, sizeof(buf.o) - (1 + 1), 1); + + ok = (p == buf.o && + !memcmp(p, buf.s, 7 + 22) && + !memcmp(p + (7 + 22), test_hash, 31 + 1 + 1 + 1)); + + { + const char *k = "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"; + BF_key ae, ai, ye, yi; + BF_set_key(k, ae, ai, 2); /* $2a$ */ + BF_set_key(k, ye, yi, 4); /* $2y$ */ + ai[0] ^= 0x10000; /* undo the safety (for comparison) */ + ok = ok && ai[0] == 0xdb9c59bc && ye[17] == 0x33343500 && + !memcmp(ae, ye, sizeof(ae)) && + !memcmp(ai, yi, sizeof(ai)); + } + + __set_errno(save_errno); + if (ok) + return retval; + +/* Should not happen */ + _crypt_output_magic(setting, output, size); + __set_errno(EINVAL); /* pretend we don't support this hash type */ + return NULL; +} + +char *_crypt_gensalt_blowfish_rn(const char *prefix, unsigned long count, + const char *input, int size, char *output, int output_size) +{ + if (size < 16 || output_size < 7 + 22 + 1 || + (count && (count < 4 || count > 31)) || + prefix[0] != '$' || prefix[1] != '2' || + (prefix[2] != 'a' && prefix[2] != 'b' && prefix[2] != 'y')) { + if (output_size > 0) output[0] = '\0'; + __set_errno((output_size < 7 + 22 + 1) ? ERANGE : EINVAL); + return NULL; + } + + if (!count) count = 5; + + output[0] = '$'; + output[1] = '2'; + output[2] = prefix[2]; + output[3] = '$'; + output[4] = '0' + count / 10; + output[5] = '0' + count % 10; + output[6] = '$'; + + BF_encode(&output[7], (const BF_word *)input, 16); + output[7 + 22] = '\0'; + + return output; +} diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/crypt_blowfish.h b/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/crypt_blowfish.h new file mode 100644 index 00000000..2ee0d8c1 --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/crypt_blowfish.h @@ -0,0 +1,27 @@ +/* + * Written by Solar Designer in 2000-2011. + * No copyright is claimed, and the software is hereby placed in the public + * domain. In case this attempt to disclaim copyright and place the software + * in the public domain is deemed null and void, then the software is + * Copyright (c) 2000-2011 Solar Designer and it is hereby released to the + * general public under the following terms: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * There's ABSOLUTELY NO WARRANTY, express or implied. + * + * See crypt_blowfish.c for more information. + */ + +#ifndef _CRYPT_BLOWFISH_H +#define _CRYPT_BLOWFISH_H + +extern int _crypt_output_magic(const char *setting, char *output, int size); +extern char *_crypt_blowfish_rn(const char *key, const char *setting, + char *output, int size); +extern char *_crypt_gensalt_blowfish_rn(const char *prefix, + unsigned long count, + const char *input, int size, char *output, int output_size); + +#endif diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/crypt_gensalt.c b/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/crypt_gensalt.c new file mode 100644 index 00000000..73c15a1a --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/crypt_gensalt.c @@ -0,0 +1,124 @@ +/* + * Written by Solar Designer in 2000-2011. + * No copyright is claimed, and the software is hereby placed in the public + * domain. In case this attempt to disclaim copyright and place the software + * in the public domain is deemed null and void, then the software is + * Copyright (c) 2000-2011 Solar Designer and it is hereby released to the + * general public under the following terms: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * There's ABSOLUTELY NO WARRANTY, express or implied. + * + * See crypt_blowfish.c for more information. + * + * This file contains salt generation functions for the traditional and + * other common crypt(3) algorithms, except for bcrypt which is defined + * entirely in crypt_blowfish.c. + */ + +#include + +#include +#ifndef __set_errno +#define __set_errno(val) errno = (val) +#endif + +/* Just to make sure the prototypes match the actual definitions */ +#include "crypt_gensalt.h" + +unsigned char _crypt_itoa64[64 + 1] = + "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +char *_crypt_gensalt_traditional_rn(const char *prefix, unsigned long count, + const char *input, int size, char *output, int output_size) +{ + (void) prefix; + + if (size < 2 || output_size < 2 + 1 || (count && count != 25)) { + if (output_size > 0) output[0] = '\0'; + __set_errno((output_size < 2 + 1) ? ERANGE : EINVAL); + return NULL; + } + + output[0] = _crypt_itoa64[(unsigned int)input[0] & 0x3f]; + output[1] = _crypt_itoa64[(unsigned int)input[1] & 0x3f]; + output[2] = '\0'; + + return output; +} + +char *_crypt_gensalt_extended_rn(const char *prefix, unsigned long count, + const char *input, int size, char *output, int output_size) +{ + unsigned long value; + + (void) prefix; + +/* Even iteration counts make it easier to detect weak DES keys from a look + * at the hash, so they should be avoided */ + if (size < 3 || output_size < 1 + 4 + 4 + 1 || + (count && (count > 0xffffff || !(count & 1)))) { + if (output_size > 0) output[0] = '\0'; + __set_errno((output_size < 1 + 4 + 4 + 1) ? ERANGE : EINVAL); + return NULL; + } + + if (!count) count = 725; + + output[0] = '_'; + output[1] = _crypt_itoa64[count & 0x3f]; + output[2] = _crypt_itoa64[(count >> 6) & 0x3f]; + output[3] = _crypt_itoa64[(count >> 12) & 0x3f]; + output[4] = _crypt_itoa64[(count >> 18) & 0x3f]; + value = (unsigned long)(unsigned char)input[0] | + ((unsigned long)(unsigned char)input[1] << 8) | + ((unsigned long)(unsigned char)input[2] << 16); + output[5] = _crypt_itoa64[value & 0x3f]; + output[6] = _crypt_itoa64[(value >> 6) & 0x3f]; + output[7] = _crypt_itoa64[(value >> 12) & 0x3f]; + output[8] = _crypt_itoa64[(value >> 18) & 0x3f]; + output[9] = '\0'; + + return output; +} + +char *_crypt_gensalt_md5_rn(const char *prefix, unsigned long count, + const char *input, int size, char *output, int output_size) +{ + unsigned long value; + + (void) prefix; + + if (size < 3 || output_size < 3 + 4 + 1 || (count && count != 1000)) { + if (output_size > 0) output[0] = '\0'; + __set_errno((output_size < 3 + 4 + 1) ? ERANGE : EINVAL); + return NULL; + } + + output[0] = '$'; + output[1] = '1'; + output[2] = '$'; + value = (unsigned long)(unsigned char)input[0] | + ((unsigned long)(unsigned char)input[1] << 8) | + ((unsigned long)(unsigned char)input[2] << 16); + output[3] = _crypt_itoa64[value & 0x3f]; + output[4] = _crypt_itoa64[(value >> 6) & 0x3f]; + output[5] = _crypt_itoa64[(value >> 12) & 0x3f]; + output[6] = _crypt_itoa64[(value >> 18) & 0x3f]; + output[7] = '\0'; + + if (size >= 6 && output_size >= 3 + 4 + 4 + 1) { + value = (unsigned long)(unsigned char)input[3] | + ((unsigned long)(unsigned char)input[4] << 8) | + ((unsigned long)(unsigned char)input[5] << 16); + output[7] = _crypt_itoa64[value & 0x3f]; + output[8] = _crypt_itoa64[(value >> 6) & 0x3f]; + output[9] = _crypt_itoa64[(value >> 12) & 0x3f]; + output[10] = _crypt_itoa64[(value >> 18) & 0x3f]; + output[11] = '\0'; + } + + return output; +} diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/crypt_gensalt.h b/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/crypt_gensalt.h new file mode 100644 index 00000000..457bbfe2 --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/crypt_gensalt.h @@ -0,0 +1,30 @@ +/* + * Written by Solar Designer in 2000-2011. + * No copyright is claimed, and the software is hereby placed in the public + * domain. In case this attempt to disclaim copyright and place the software + * in the public domain is deemed null and void, then the software is + * Copyright (c) 2000-2011 Solar Designer and it is hereby released to the + * general public under the following terms: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * There's ABSOLUTELY NO WARRANTY, express or implied. + * + * See crypt_blowfish.c for more information. + */ + +#ifndef _CRYPT_GENSALT_H +#define _CRYPT_GENSALT_H + +extern unsigned char _crypt_itoa64[]; +extern char *_crypt_gensalt_traditional_rn(const char *prefix, + unsigned long count, + const char *input, int size, char *output, int output_size); +extern char *_crypt_gensalt_extended_rn(const char *prefix, + unsigned long count, + const char *input, int size, char *output, int output_size); +extern char *_crypt_gensalt_md5_rn(const char *prefix, unsigned long count, + const char *input, int size, char *output, int output_size); + +#endif diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/extconf.rb b/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/extconf.rb new file mode 100644 index 00000000..c2222de9 --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/extconf.rb @@ -0,0 +1,22 @@ +if RUBY_PLATFORM == "java" + # Don't do anything when run in JRuby; this allows gem installation to pass. + # We need to write a dummy Makefile so that RubyGems doesn't think compilation + # failed. + File.open('Makefile', 'w') do |f| + f.puts "all:" + f.puts "\t@true" + f.puts "install:" + f.puts "\t@true" + end + exit 0 +else + require "mkmf" + + # From Openwall's crypt_blowfish Makefile. + # This is `bcrypt_ext` (our extension) + CRYPT_OBJS from that Makefile. + $objs = %w(bcrypt_ext.o crypt_blowfish.o x86.o crypt_gensalt.o wrapper.o) + + $defs << "-D__SKIP_GNU" + dir_config("bcrypt_ext") + create_makefile("bcrypt_ext") +end diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/ow-crypt.h b/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/ow-crypt.h new file mode 100644 index 00000000..2e487942 --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/ow-crypt.h @@ -0,0 +1,43 @@ +/* + * Written by Solar Designer in 2000-2011. + * No copyright is claimed, and the software is hereby placed in the public + * domain. In case this attempt to disclaim copyright and place the software + * in the public domain is deemed null and void, then the software is + * Copyright (c) 2000-2011 Solar Designer and it is hereby released to the + * general public under the following terms: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * There's ABSOLUTELY NO WARRANTY, express or implied. + * + * See crypt_blowfish.c for more information. + */ + +#ifndef _OW_CRYPT_H +#define _OW_CRYPT_H + +#ifndef __GNUC__ +#undef __const +#define __const const +#endif + +#ifndef __SKIP_GNU +extern char *crypt(__const char *key, __const char *setting); +extern char *crypt_r(__const char *key, __const char *setting, void *data); +#endif + +#ifndef __SKIP_OW +extern char *crypt_rn(__const char *key, __const char *setting, + void *data, int size); +extern char *crypt_ra(__const char *key, __const char *setting, + void **data, int *size); +extern char *crypt_gensalt(__const char *prefix, unsigned long count, + __const char *input, int size); +extern char *crypt_gensalt_rn(__const char *prefix, unsigned long count, + __const char *input, int size, char *output, int output_size); +extern char *crypt_gensalt_ra(__const char *prefix, unsigned long count, + __const char *input, int size); +#endif + +#endif diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/wrapper.c b/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/wrapper.c new file mode 100644 index 00000000..1e49c90d --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/wrapper.c @@ -0,0 +1,551 @@ +/* + * Written by Solar Designer in 2000-2014. + * No copyright is claimed, and the software is hereby placed in the public + * domain. In case this attempt to disclaim copyright and place the software + * in the public domain is deemed null and void, then the software is + * Copyright (c) 2000-2014 Solar Designer and it is hereby released to the + * general public under the following terms: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * There's ABSOLUTELY NO WARRANTY, express or implied. + * + * See crypt_blowfish.c for more information. + */ + +#include +#include + +#include +#ifndef __set_errno +#define __set_errno(val) errno = (val) +#endif + +#ifdef TEST +#include +#include +#include +#include +#include +#include +#ifdef TEST_THREADS +#include +#endif +#endif + +#define CRYPT_OUTPUT_SIZE (7 + 22 + 31 + 1) +#define CRYPT_GENSALT_OUTPUT_SIZE (7 + 22 + 1) + +#if defined(__GLIBC__) && defined(_LIBC) +#define __SKIP_GNU +#endif +#include "ow-crypt.h" + +#include "crypt_blowfish.h" +#include "crypt_gensalt.h" + +#if defined(__GLIBC__) && defined(_LIBC) +/* crypt.h from glibc-crypt-2.1 will define struct crypt_data for us */ +#include "crypt.h" +extern char *__md5_crypt_r(const char *key, const char *salt, + char *buffer, int buflen); +/* crypt-entry.c needs to be patched to define __des_crypt_r rather than + * __crypt_r, and not define crypt_r and crypt at all */ +extern char *__des_crypt_r(const char *key, const char *salt, + struct crypt_data *data); +extern struct crypt_data _ufc_foobar; +#endif + +static int _crypt_data_alloc(void **data, int *size, int need) +{ + void *updated; + + if (*data && *size >= need) return 0; + + updated = realloc(*data, need); + + if (!updated) { +#ifndef __GLIBC__ + /* realloc(3) on glibc sets errno, so we don't need to bother */ + __set_errno(ENOMEM); +#endif + return -1; + } + +#if defined(__GLIBC__) && defined(_LIBC) + if (need >= sizeof(struct crypt_data)) + ((struct crypt_data *)updated)->initialized = 0; +#endif + + *data = updated; + *size = need; + + return 0; +} + +static char *_crypt_retval_magic(char *retval, const char *setting, + char *output, int size) +{ + if (retval) + return retval; + + if (_crypt_output_magic(setting, output, size)) + return NULL; /* shouldn't happen */ + + return output; +} + +#if defined(__GLIBC__) && defined(_LIBC) +/* + * Applications may re-use the same instance of struct crypt_data without + * resetting the initialized field in order to let crypt_r() skip some of + * its initialization code. Thus, it is important that our multiple hashing + * algorithms either don't conflict with each other in their use of the + * data area or reset the initialized field themselves whenever required. + * Currently, the hashing algorithms simply have no conflicts: the first + * field of struct crypt_data is the 128-byte large DES key schedule which + * __des_crypt_r() calculates each time it is called while the two other + * hashing algorithms use less than 128 bytes of the data area. + */ + +char *__crypt_rn(__const char *key, __const char *setting, + void *data, int size) +{ + if (setting[0] == '$' && setting[1] == '2') + return _crypt_blowfish_rn(key, setting, (char *)data, size); + if (setting[0] == '$' && setting[1] == '1') + return __md5_crypt_r(key, setting, (char *)data, size); + if (setting[0] == '$' || setting[0] == '_') { + __set_errno(EINVAL); + return NULL; + } + if (size >= sizeof(struct crypt_data)) + return __des_crypt_r(key, setting, (struct crypt_data *)data); + __set_errno(ERANGE); + return NULL; +} + +char *__crypt_ra(__const char *key, __const char *setting, + void **data, int *size) +{ + if (setting[0] == '$' && setting[1] == '2') { + if (_crypt_data_alloc(data, size, CRYPT_OUTPUT_SIZE)) + return NULL; + return _crypt_blowfish_rn(key, setting, (char *)*data, *size); + } + if (setting[0] == '$' && setting[1] == '1') { + if (_crypt_data_alloc(data, size, CRYPT_OUTPUT_SIZE)) + return NULL; + return __md5_crypt_r(key, setting, (char *)*data, *size); + } + if (setting[0] == '$' || setting[0] == '_') { + __set_errno(EINVAL); + return NULL; + } + if (_crypt_data_alloc(data, size, sizeof(struct crypt_data))) + return NULL; + return __des_crypt_r(key, setting, (struct crypt_data *)*data); +} + +char *__crypt_r(__const char *key, __const char *setting, + struct crypt_data *data) +{ + return _crypt_retval_magic( + __crypt_rn(key, setting, data, sizeof(*data)), + setting, (char *)data, sizeof(*data)); +} + +char *__crypt(__const char *key, __const char *setting) +{ + return _crypt_retval_magic( + __crypt_rn(key, setting, &_ufc_foobar, sizeof(_ufc_foobar)), + setting, (char *)&_ufc_foobar, sizeof(_ufc_foobar)); +} +#else +char *crypt_rn(const char *key, const char *setting, void *data, int size) +{ + return _crypt_blowfish_rn(key, setting, (char *)data, size); +} + +char *crypt_ra(const char *key, const char *setting, + void **data, int *size) +{ + if (_crypt_data_alloc(data, size, CRYPT_OUTPUT_SIZE)) + return NULL; + return _crypt_blowfish_rn(key, setting, (char *)*data, *size); +} + +char *crypt_r(const char *key, const char *setting, void *data) +{ + return _crypt_retval_magic( + crypt_rn(key, setting, data, CRYPT_OUTPUT_SIZE), + setting, (char *)data, CRYPT_OUTPUT_SIZE); +} + +char *crypt(const char *key, const char *setting) +{ + static char output[CRYPT_OUTPUT_SIZE]; + + return _crypt_retval_magic( + crypt_rn(key, setting, output, sizeof(output)), + setting, output, sizeof(output)); +} + +#define __crypt_gensalt_rn crypt_gensalt_rn +#define __crypt_gensalt_ra crypt_gensalt_ra +#define __crypt_gensalt crypt_gensalt +#endif + +char *__crypt_gensalt_rn(const char *prefix, unsigned long count, + const char *input, int size, char *output, int output_size) +{ + char *(*use)(const char *_prefix, unsigned long _count, + const char *_input, int _size, + char *_output, int _output_size); + + /* This may be supported on some platforms in the future */ + if (!input) { + __set_errno(EINVAL); + return NULL; + } + + if (!strncmp(prefix, "$2a$", 4) || !strncmp(prefix, "$2b$", 4) || + !strncmp(prefix, "$2y$", 4)) + use = _crypt_gensalt_blowfish_rn; + else + if (!strncmp(prefix, "$1$", 3)) + use = _crypt_gensalt_md5_rn; + else + if (prefix[0] == '_') + use = _crypt_gensalt_extended_rn; + else + if (!prefix[0] || + (prefix[0] && prefix[1] && + memchr(_crypt_itoa64, prefix[0], 64) && + memchr(_crypt_itoa64, prefix[1], 64))) + use = _crypt_gensalt_traditional_rn; + else { + __set_errno(EINVAL); + return NULL; + } + + return use(prefix, count, input, size, output, output_size); +} + +char *__crypt_gensalt_ra(const char *prefix, unsigned long count, + const char *input, int size) +{ + char output[CRYPT_GENSALT_OUTPUT_SIZE]; + char *retval; + + retval = __crypt_gensalt_rn(prefix, count, + input, size, output, sizeof(output)); + + if (retval) { + retval = strdup(retval); +#ifndef __GLIBC__ + /* strdup(3) on glibc sets errno, so we don't need to bother */ + if (!retval) + __set_errno(ENOMEM); +#endif + } + + return retval; +} + +char *__crypt_gensalt(const char *prefix, unsigned long count, + const char *input, int size) +{ + static char output[CRYPT_GENSALT_OUTPUT_SIZE]; + + return __crypt_gensalt_rn(prefix, count, + input, size, output, sizeof(output)); +} + +#if defined(__GLIBC__) && defined(_LIBC) +weak_alias(__crypt_rn, crypt_rn) +weak_alias(__crypt_ra, crypt_ra) +weak_alias(__crypt_r, crypt_r) +weak_alias(__crypt, crypt) +weak_alias(__crypt_gensalt_rn, crypt_gensalt_rn) +weak_alias(__crypt_gensalt_ra, crypt_gensalt_ra) +weak_alias(__crypt_gensalt, crypt_gensalt) +weak_alias(crypt, fcrypt) +#endif + +#ifdef TEST +static const char *tests[][3] = { + {"$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW", + "U*U"}, + {"$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK", + "U*U*"}, + {"$2a$05$XXXXXXXXXXXXXXXXXXXXXOAcXxm9kjPGEMsLznoKqmqw7tc8WCx4a", + "U*U*U"}, + {"$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui", + "0123456789abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + "chars after 72 are ignored"}, + {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e", + "\xa3"}, + {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e", + "\xff\xff\xa3"}, + {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e", + "\xff\xff\xa3"}, + {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.nqd1wy.pTMdcvrRWxyiGL2eMz.2a85.", + "\xff\xff\xa3"}, + {"$2b$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e", + "\xff\xff\xa3"}, + {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq", + "\xa3"}, + {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq", + "\xa3"}, + {"$2b$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq", + "\xa3"}, + {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi", + "1\xa3" "345"}, + {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi", + "\xff\xa3" "345"}, + {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi", + "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"}, + {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi", + "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"}, + {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.ZC1JEJ8Z4gPfpe1JOr/oyPXTWl9EFd.", + "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"}, + {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.nRht2l/HRhr6zmCp9vYUvvsqynflf9e", + "\xff\xa3" "345"}, + {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.nRht2l/HRhr6zmCp9vYUvvsqynflf9e", + "\xff\xa3" "345"}, + {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS", + "\xa3" "ab"}, + {"$2x$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS", + "\xa3" "ab"}, + {"$2y$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS", + "\xa3" "ab"}, + {"$2x$05$6bNw2HLQYeqHYyBfLMsv/OiwqTymGIGzFsA4hOTWebfehXHNprcAS", + "\xd1\x91"}, + {"$2x$05$6bNw2HLQYeqHYyBfLMsv/O9LIGgn8OMzuDoHfof8AQimSGfcSWxnS", + "\xd0\xc1\xd2\xcf\xcc\xd8"}, + {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.swQOIzjOiJ9GHEPuhEkvqrUyvWhEMx6", + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + "chars after 72 are ignored as usual"}, + {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.R9xrDjiycxMbQE2bp.vgqlYpW5wx2yy", + "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55" + "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55" + "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55" + "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55" + "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55" + "\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55\xaa\x55"}, + {"$2a$05$/OK.fbVrR/bpIqNJ5ianF.9tQZzcJfm3uj2NvJ/n5xkhpqLrMpWCe", + "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff" + "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff" + "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff" + "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff" + "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff" + "\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff\x55\xaa\xff"}, + {"$2a$05$CCCCCCCCCCCCCCCCCCCCC.7uG0VCzI2bS7j6ymqJi9CdcdxiRTWNy", + ""}, + {"*0", "", "$2a$03$CCCCCCCCCCCCCCCCCCCCC."}, + {"*0", "", "$2a$32$CCCCCCCCCCCCCCCCCCCCC."}, + {"*0", "", "$2c$05$CCCCCCCCCCCCCCCCCCCCC."}, + {"*0", "", "$2z$05$CCCCCCCCCCCCCCCCCCCCC."}, + {"*0", "", "$2`$05$CCCCCCCCCCCCCCCCCCCCC."}, + {"*0", "", "$2{$05$CCCCCCCCCCCCCCCCCCCCC."}, + {"*1", "", "*0"}, + {NULL} +}; + +#define which tests[0] + +static volatile sig_atomic_t running; + +static void handle_timer(int signum) +{ + (void) signum; + running = 0; +} + +static void *run(void *arg) +{ + unsigned long count = 0; + int i = 0; + void *data = NULL; + int size = 0x12345678; + + do { + const char *hash = tests[i][0]; + const char *key = tests[i][1]; + const char *setting = tests[i][2]; + + if (!tests[++i][0]) + i = 0; + + if (setting && strlen(hash) < 30) /* not for benchmark */ + continue; + + if (strcmp(crypt_ra(key, hash, &data, &size), hash)) { + printf("%d: FAILED (crypt_ra/%d/%lu)\n", + (int)((char *)arg - (char *)0), i, count); + free(data); + return NULL; + } + count++; + } while (running); + + free(data); + return count + (char *)0; +} + +int main(void) +{ + struct itimerval it; + struct tms buf; + clock_t clk_tck, start_real, start_virtual, end_real, end_virtual; + unsigned long count; + void *data; + int size; + char *setting1, *setting2; + int i; +#ifdef TEST_THREADS + pthread_t t[TEST_THREADS]; + void *t_retval; +#endif + + data = NULL; + size = 0x12345678; + + for (i = 0; tests[i][0]; i++) { + const char *hash = tests[i][0]; + const char *key = tests[i][1]; + const char *setting = tests[i][2]; + const char *p; + int ok = !setting || strlen(hash) >= 30; + int o_size; + char s_buf[30], o_buf[61]; + if (!setting) { + memcpy(s_buf, hash, sizeof(s_buf) - 1); + s_buf[sizeof(s_buf) - 1] = 0; + setting = s_buf; + } + + __set_errno(0); + p = crypt(key, setting); + if ((!ok && !errno) || strcmp(p, hash)) { + printf("FAILED (crypt/%d)\n", i); + return 1; + } + + if (ok && strcmp(crypt(key, hash), hash)) { + printf("FAILED (crypt/%d)\n", i); + return 1; + } + + for (o_size = -1; o_size <= (int)sizeof(o_buf); o_size++) { + int ok_n = ok && o_size == (int)sizeof(o_buf); + const char *x = "abc"; + strcpy(o_buf, x); + if (o_size >= 3) { + x = "*0"; + if (setting[0] == '*' && setting[1] == '0') + x = "*1"; + } + __set_errno(0); + p = crypt_rn(key, setting, o_buf, o_size); + if ((ok_n && (!p || strcmp(p, hash))) || + (!ok_n && (!errno || p || strcmp(o_buf, x)))) { + printf("FAILED (crypt_rn/%d)\n", i); + return 1; + } + } + + __set_errno(0); + p = crypt_ra(key, setting, &data, &size); + if ((ok && (!p || strcmp(p, hash))) || + (!ok && (!errno || p || strcmp((char *)data, hash)))) { + printf("FAILED (crypt_ra/%d)\n", i); + return 1; + } + } + + setting1 = crypt_gensalt(which[0], 12, data, size); + if (!setting1 || strncmp(setting1, "$2a$12$", 7)) { + puts("FAILED (crypt_gensalt)\n"); + return 1; + } + + setting2 = crypt_gensalt_ra(setting1, 12, data, size); + if (strcmp(setting1, setting2)) { + puts("FAILED (crypt_gensalt_ra/1)\n"); + return 1; + } + + (*(char *)data)++; + setting1 = crypt_gensalt_ra(setting2, 12, data, size); + if (!strcmp(setting1, setting2)) { + puts("FAILED (crypt_gensalt_ra/2)\n"); + return 1; + } + + free(setting1); + free(setting2); + free(data); + +#if defined(_SC_CLK_TCK) || !defined(CLK_TCK) + clk_tck = sysconf(_SC_CLK_TCK); +#else + clk_tck = CLK_TCK; +#endif + + running = 1; + signal(SIGALRM, handle_timer); + + memset(&it, 0, sizeof(it)); + it.it_value.tv_sec = 5; + setitimer(ITIMER_REAL, &it, NULL); + + start_real = times(&buf); + start_virtual = buf.tms_utime + buf.tms_stime; + + count = (char *)run((char *)0) - (char *)0; + + end_real = times(&buf); + end_virtual = buf.tms_utime + buf.tms_stime; + if (end_virtual == start_virtual) end_virtual++; + + printf("%.1f c/s real, %.1f c/s virtual\n", + (float)count * clk_tck / (end_real - start_real), + (float)count * clk_tck / (end_virtual - start_virtual)); + +#ifdef TEST_THREADS + running = 1; + it.it_value.tv_sec = 60; + setitimer(ITIMER_REAL, &it, NULL); + start_real = times(&buf); + + for (i = 0; i < TEST_THREADS; i++) + if (pthread_create(&t[i], NULL, run, i + (char *)0)) { + perror("pthread_create"); + return 1; + } + + for (i = 0; i < TEST_THREADS; i++) { + if (pthread_join(t[i], &t_retval)) { + perror("pthread_join"); + continue; + } + if (!t_retval) continue; + count = (char *)t_retval - (char *)0; + end_real = times(&buf); + printf("%d: %.1f c/s real\n", i, + (float)count * clk_tck / (end_real - start_real)); + } +#endif + + return 0; +} +#endif diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/x86.S b/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/x86.S new file mode 100644 index 00000000..b0f1cd2e --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/ext/mri/x86.S @@ -0,0 +1,203 @@ +/* + * Written by Solar Designer in 1998-2010. + * No copyright is claimed, and the software is hereby placed in the public + * domain. In case this attempt to disclaim copyright and place the software + * in the public domain is deemed null and void, then the software is + * Copyright (c) 1998-2010 Solar Designer and it is hereby released to the + * general public under the following terms: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted. + * + * There's ABSOLUTELY NO WARRANTY, express or implied. + * + * See crypt_blowfish.c for more information. + */ + +#ifdef __i386__ + +#if defined(__OpenBSD__) && !defined(__ELF__) +#define UNDERSCORES +#define ALIGN_LOG +#endif + +#if defined(__CYGWIN32__) || defined(__MINGW32__) +#define UNDERSCORES +#endif + +#ifdef __DJGPP__ +#define UNDERSCORES +#define ALIGN_LOG +#endif + +#ifdef UNDERSCORES +#define _BF_body_r __BF_body_r +#endif + +#ifdef ALIGN_LOG +#define DO_ALIGN(log) .align (log) +#elif defined(DUMBAS) +#define DO_ALIGN(log) .align 1 << log +#else +#define DO_ALIGN(log) .align (1 << (log)) +#endif + +#define BF_FRAME 0x200 +#define ctx %esp + +#define BF_ptr (ctx) + +#define S(N, r) N+BF_FRAME(ctx,r,4) +#ifdef DUMBAS +#define P(N) 0x1000+N+N+N+N+BF_FRAME(ctx) +#else +#define P(N) 0x1000+4*N+BF_FRAME(ctx) +#endif + +/* + * This version of the assembly code is optimized primarily for the original + * Intel Pentium but is also careful to avoid partial register stalls on the + * Pentium Pro family of processors (tested up to Pentium III Coppermine). + * + * It is possible to do 15% faster on the Pentium Pro family and probably on + * many non-Intel x86 processors, but, unfortunately, that would make things + * twice slower for the original Pentium. + * + * An additional 2% speedup may be achieved with non-reentrant code. + */ + +#define L %esi +#define R %edi +#define tmp1 %eax +#define tmp1_lo %al +#define tmp2 %ecx +#define tmp2_hi %ch +#define tmp3 %edx +#define tmp3_lo %dl +#define tmp4 %ebx +#define tmp4_hi %bh +#define tmp5 %ebp + +.text + +#define BF_ROUND(L, R, N) \ + xorl L,tmp2; \ + xorl tmp1,tmp1; \ + movl tmp2,L; \ + shrl $16,tmp2; \ + movl L,tmp4; \ + movb tmp2_hi,tmp1_lo; \ + andl $0xFF,tmp2; \ + movb tmp4_hi,tmp3_lo; \ + andl $0xFF,tmp4; \ + movl S(0,tmp1),tmp1; \ + movl S(0x400,tmp2),tmp5; \ + addl tmp5,tmp1; \ + movl S(0x800,tmp3),tmp5; \ + xorl tmp5,tmp1; \ + movl S(0xC00,tmp4),tmp5; \ + addl tmp1,tmp5; \ + movl 4+P(N),tmp2; \ + xorl tmp5,R + +#define BF_ENCRYPT_START \ + BF_ROUND(L, R, 0); \ + BF_ROUND(R, L, 1); \ + BF_ROUND(L, R, 2); \ + BF_ROUND(R, L, 3); \ + BF_ROUND(L, R, 4); \ + BF_ROUND(R, L, 5); \ + BF_ROUND(L, R, 6); \ + BF_ROUND(R, L, 7); \ + BF_ROUND(L, R, 8); \ + BF_ROUND(R, L, 9); \ + BF_ROUND(L, R, 10); \ + BF_ROUND(R, L, 11); \ + BF_ROUND(L, R, 12); \ + BF_ROUND(R, L, 13); \ + BF_ROUND(L, R, 14); \ + BF_ROUND(R, L, 15); \ + movl BF_ptr,tmp5; \ + xorl L,tmp2; \ + movl P(17),L + +#define BF_ENCRYPT_END \ + xorl R,L; \ + movl tmp2,R + +DO_ALIGN(5) +.globl _BF_body_r +_BF_body_r: + movl 4(%esp),%eax + pushl %ebp + pushl %ebx + pushl %esi + pushl %edi + subl $BF_FRAME-8,%eax + xorl L,L + cmpl %esp,%eax + ja BF_die + xchgl %eax,%esp + xorl R,R + pushl %eax + leal 0x1000+BF_FRAME-4(ctx),%eax + movl 0x1000+BF_FRAME-4(ctx),tmp2 + pushl %eax + xorl tmp3,tmp3 +BF_loop_P: + BF_ENCRYPT_START + addl $8,tmp5 + BF_ENCRYPT_END + leal 0x1000+18*4+BF_FRAME(ctx),tmp1 + movl tmp5,BF_ptr + cmpl tmp5,tmp1 + movl L,-8(tmp5) + movl R,-4(tmp5) + movl P(0),tmp2 + ja BF_loop_P + leal BF_FRAME(ctx),tmp5 + xorl tmp3,tmp3 + movl tmp5,BF_ptr +BF_loop_S: + BF_ENCRYPT_START + BF_ENCRYPT_END + movl P(0),tmp2 + movl L,(tmp5) + movl R,4(tmp5) + BF_ENCRYPT_START + BF_ENCRYPT_END + movl P(0),tmp2 + movl L,8(tmp5) + movl R,12(tmp5) + BF_ENCRYPT_START + BF_ENCRYPT_END + movl P(0),tmp2 + movl L,16(tmp5) + movl R,20(tmp5) + BF_ENCRYPT_START + addl $32,tmp5 + BF_ENCRYPT_END + leal 0x1000+BF_FRAME(ctx),tmp1 + movl tmp5,BF_ptr + cmpl tmp5,tmp1 + movl P(0),tmp2 + movl L,-8(tmp5) + movl R,-4(tmp5) + ja BF_loop_S + movl 4(%esp),%esp + popl %edi + popl %esi + popl %ebx + popl %ebp + ret + +BF_die: +/* Oops, need to re-compile with a larger BF_FRAME. */ + hlt + jmp BF_die + +#endif + +#if defined(__ELF__) && defined(__linux__) +.section .note.GNU-stack,"",@progbits +#endif diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/lib/bcrypt.rb b/path/ruby/2.6.0/gems/bcrypt-3.1.13/lib/bcrypt.rb new file mode 100644 index 00000000..c1b9e479 --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/lib/bcrypt.rb @@ -0,0 +1,16 @@ +# A Ruby library implementing OpenBSD's bcrypt()/crypt_blowfish algorithm for +# hashing passwords. +module BCrypt +end + +if RUBY_PLATFORM == "java" + require 'java' +else + require "openssl" +end + +require "bcrypt_ext" + +require 'bcrypt/error' +require 'bcrypt/engine' +require 'bcrypt/password' diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/lib/bcrypt/engine.rb b/path/ruby/2.6.0/gems/bcrypt-3.1.13/lib/bcrypt/engine.rb new file mode 100644 index 00000000..226d142e --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/lib/bcrypt/engine.rb @@ -0,0 +1,116 @@ +module BCrypt + # A Ruby wrapper for the bcrypt() C extension calls and the Java calls. + class Engine + # The default computational expense parameter. + DEFAULT_COST = 12 + # The minimum cost supported by the algorithm. + MIN_COST = 4 + # Maximum possible size of bcrypt() salts. + MAX_SALT_LENGTH = 16 + + if RUBY_PLATFORM != "java" + # C-level routines which, if they don't get the right input, will crash the + # hell out of the Ruby process. + private_class_method :__bc_salt + private_class_method :__bc_crypt + end + + @cost = nil + + # Returns the cost factor that will be used if one is not specified when + # creating a password hash. Defaults to DEFAULT_COST if not set. + def self.cost + @cost || DEFAULT_COST + end + + # Set a default cost factor that will be used if one is not specified when + # creating a password hash. + # + # Example: + # + # BCrypt::Engine::DEFAULT_COST #=> 12 + # BCrypt::Password.create('secret').cost #=> 12 + # + # BCrypt::Engine.cost = 8 + # BCrypt::Password.create('secret').cost #=> 8 + # + # # cost can still be overridden as needed + # BCrypt::Password.create('secret', :cost => 6).cost #=> 6 + def self.cost=(cost) + @cost = cost + end + + # Given a secret and a valid salt (see BCrypt::Engine.generate_salt) calculates + # a bcrypt() password hash. + def self.hash_secret(secret, salt, _ = nil) + if valid_secret?(secret) + if valid_salt?(salt) + if RUBY_PLATFORM == "java" + Java.bcrypt_jruby.BCrypt.hashpw(secret.to_s.to_java_bytes, salt.to_s) + else + __bc_crypt(secret.to_s, salt) + end + else + raise Errors::InvalidSalt.new("invalid salt") + end + else + raise Errors::InvalidSecret.new("invalid secret") + end + end + + # Generates a random salt with a given computational cost. + def self.generate_salt(cost = self.cost) + cost = cost.to_i + if cost > 0 + if cost < MIN_COST + cost = MIN_COST + end + if RUBY_PLATFORM == "java" + Java.bcrypt_jruby.BCrypt.gensalt(cost) + else + prefix = "$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW" + __bc_salt(prefix, cost, OpenSSL::Random.random_bytes(MAX_SALT_LENGTH)) + end + else + raise Errors::InvalidCost.new("cost must be numeric and > 0") + end + end + + # Returns true if +salt+ is a valid bcrypt() salt, false if not. + def self.valid_salt?(salt) + !!(salt =~ /^\$[0-9a-z]{2,}\$[0-9]{2,}\$[A-Za-z0-9\.\/]{22,}$/) + end + + # Returns true if +secret+ is a valid bcrypt() secret, false if not. + def self.valid_secret?(secret) + secret.respond_to?(:to_s) + end + + # Returns the cost factor which will result in computation times less than +upper_time_limit_in_ms+. + # + # Example: + # + # BCrypt::Engine.calibrate(200) #=> 10 + # BCrypt::Engine.calibrate(1000) #=> 12 + # + # # should take less than 200ms + # BCrypt::Password.create("woo", :cost => 10) + # + # # should take less than 1000ms + # BCrypt::Password.create("woo", :cost => 12) + def self.calibrate(upper_time_limit_in_ms) + 40.times do |i| + start_time = Time.now + Password.create("testing testing", :cost => i+1) + end_time = Time.now - start_time + return i if end_time * 1_000 > upper_time_limit_in_ms + end + end + + # Autodetects the cost from the salt string. + def self.autodetect_cost(salt) + salt[4..5].to_i + end + end + +end diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/lib/bcrypt/error.rb b/path/ruby/2.6.0/gems/bcrypt-3.1.13/lib/bcrypt/error.rb new file mode 100644 index 00000000..c7e42a4d --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/lib/bcrypt/error.rb @@ -0,0 +1,22 @@ +module BCrypt + + class Error < StandardError # :nodoc: + end + + module Errors # :nodoc: + + # The salt parameter provided to bcrypt() is invalid. + class InvalidSalt < BCrypt::Error; end + + # The hash parameter provided to bcrypt() is invalid. + class InvalidHash < BCrypt::Error; end + + # The cost parameter provided to bcrypt() is invalid. + class InvalidCost < BCrypt::Error; end + + # The secret parameter provided to bcrypt() is invalid. + class InvalidSecret < BCrypt::Error; end + + end + +end diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/lib/bcrypt/password.rb b/path/ruby/2.6.0/gems/bcrypt-3.1.13/lib/bcrypt/password.rb new file mode 100644 index 00000000..509a8d9c --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/lib/bcrypt/password.rb @@ -0,0 +1,87 @@ +module BCrypt + # A password management class which allows you to safely store users' passwords and compare them. + # + # Example usage: + # + # include BCrypt + # + # # hash a user's password + # @password = Password.create("my grand secret") + # @password #=> "$2a$12$C5.FIvVDS9W4AYZ/Ib37YuWd/7ozp1UaMhU28UKrfSxp2oDchbi3K" + # + # # store it safely + # @user.update_attribute(:password, @password) + # + # # read it back + # @user.reload! + # @db_password = Password.new(@user.password) + # + # # compare it after retrieval + # @db_password == "my grand secret" #=> true + # @db_password == "a paltry guess" #=> false + # + class Password < String + # The hash portion of the stored password hash. + attr_reader :checksum + # The salt of the store password hash (including version and cost). + attr_reader :salt + # The version of the bcrypt() algorithm used to create the hash. + attr_reader :version + # The cost factor used to create the hash. + attr_reader :cost + + class << self + # Hashes a secret, returning a BCrypt::Password instance. Takes an optional :cost option, which is a + # logarithmic variable which determines how computational expensive the hash is to calculate (a :cost of + # 4 is twice as much work as a :cost of 3). The higher the :cost the harder it becomes for + # attackers to try to guess passwords (even if a copy of your database is stolen), but the slower it is to check + # users' passwords. + # + # Example: + # + # @password = BCrypt::Password.create("my secret", :cost => 13) + def create(secret, options = {}) + cost = options[:cost] || BCrypt::Engine.cost + raise ArgumentError if cost > 31 + Password.new(BCrypt::Engine.hash_secret(secret, BCrypt::Engine.generate_salt(cost))) + end + + def valid_hash?(h) + h =~ /^\$[0-9a-z]{2}\$[0-9]{2}\$[A-Za-z0-9\.\/]{53}$/ + end + end + + # Initializes a BCrypt::Password instance with the data from a stored hash. + def initialize(raw_hash) + if valid_hash?(raw_hash) + self.replace(raw_hash) + @version, @cost, @salt, @checksum = split_hash(self) + else + raise Errors::InvalidHash.new("invalid hash") + end + end + + # Compares a potential secret against the hash. Returns true if the secret is the original secret, false otherwise. + def ==(secret) + super(BCrypt::Engine.hash_secret(secret, @salt)) + end + alias_method :is_password?, :== + + private + + # Returns true if +h+ is a valid hash. + def valid_hash?(h) + self.class.valid_hash?(h) + end + + # call-seq: + # split_hash(raw_hash) -> version, cost, salt, hash + # + # Splits +h+ into version, cost, salt, and hash and returns them in that order. + def split_hash(h) + _, v, c, mash = h.split('$') + return v.to_str, c.to_i, h[0, 29].to_str, mash[-31, 31].to_str + end + end + +end diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/spec/TestBCrypt.java b/path/ruby/2.6.0/gems/bcrypt-3.1.13/spec/TestBCrypt.java new file mode 100644 index 00000000..c481ff3c --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/spec/TestBCrypt.java @@ -0,0 +1,194 @@ +// Copyright (c) 2006 Damien Miller +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +import junit.framework.TestCase; + +/** + * JUnit unit tests for BCrypt routines + * @author Damien Miller + * @version 0.2 + */ +public class TestBCrypt extends TestCase { + String test_vectors[][] = { + { "", + "$2a$06$DCq7YPn5Rq63x1Lad4cll.", + "$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s." }, + { "", + "$2a$08$HqWuK6/Ng6sg9gQzbLrgb.", + "$2a$08$HqWuK6/Ng6sg9gQzbLrgb.Tl.ZHfXLhvt/SgVyWhQqgqcZ7ZuUtye" }, + { "", + "$2a$10$k1wbIrmNyFAPwPVPSVa/ze", + "$2a$10$k1wbIrmNyFAPwPVPSVa/zecw2BCEnBwVS2GbrmgzxFUOqW9dk4TCW" }, + { "", + "$2a$12$k42ZFHFWqBp3vWli.nIn8u", + "$2a$12$k42ZFHFWqBp3vWli.nIn8uYyIkbvYRvodzbfbK18SSsY.CsIQPlxO" }, + { "a", + "$2a$06$m0CrhHm10qJ3lXRY.5zDGO", + "$2a$06$m0CrhHm10qJ3lXRY.5zDGO3rS2KdeeWLuGmsfGlMfOxih58VYVfxe" }, + { "a", + "$2a$08$cfcvVd2aQ8CMvoMpP2EBfe", + "$2a$08$cfcvVd2aQ8CMvoMpP2EBfeodLEkkFJ9umNEfPD18.hUF62qqlC/V." }, + { "a", + "$2a$10$k87L/MF28Q673VKh8/cPi.", + "$2a$10$k87L/MF28Q673VKh8/cPi.SUl7MU/rWuSiIDDFayrKk/1tBsSQu4u" }, + { "a", + "$2a$12$8NJH3LsPrANStV6XtBakCe", + "$2a$12$8NJH3LsPrANStV6XtBakCez0cKHXVxmvxIlcz785vxAIZrihHZpeS" }, + { "abc", + "$2a$06$If6bvum7DFjUnE9p2uDeDu", + "$2a$06$If6bvum7DFjUnE9p2uDeDu0YHzrHM6tf.iqN8.yx.jNN1ILEf7h0i" }, + { "abc", + "$2a$08$Ro0CUfOqk6cXEKf3dyaM7O", + "$2a$08$Ro0CUfOqk6cXEKf3dyaM7OhSCvnwM9s4wIX9JeLapehKK5YdLxKcm" }, + { "abc", + "$2a$10$WvvTPHKwdBJ3uk0Z37EMR.", + "$2a$10$WvvTPHKwdBJ3uk0Z37EMR.hLA2W6N9AEBhEgrAOljy2Ae5MtaSIUi" }, + { "abc", + "$2a$12$EXRkfkdmXn2gzds2SSitu.", + "$2a$12$EXRkfkdmXn2gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q" }, + { "abcdefghijklmnopqrstuvwxyz", + "$2a$06$.rCVZVOThsIa97pEDOxvGu", + "$2a$06$.rCVZVOThsIa97pEDOxvGuRRgzG64bvtJ0938xuqzv18d3ZpQhstC" }, + { "abcdefghijklmnopqrstuvwxyz", + "$2a$08$aTsUwsyowQuzRrDqFflhge", + "$2a$08$aTsUwsyowQuzRrDqFflhgekJ8d9/7Z3GV3UcgvzQW3J5zMyrTvlz." }, + { "abcdefghijklmnopqrstuvwxyz", + "$2a$10$fVH8e28OQRj9tqiDXs1e1u", + "$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq" }, + { "abcdefghijklmnopqrstuvwxyz", + "$2a$12$D4G5f18o7aMMfwasBL7Gpu", + "$2a$12$D4G5f18o7aMMfwasBL7GpuQWuP3pkrZrOAnqP.bmezbMng.QwJ/pG" }, + { "~!@#$%^&*() ~!@#$%^&*()PNBFRD", + "$2a$06$fPIsBO8qRqkjj273rfaOI.", + "$2a$06$fPIsBO8qRqkjj273rfaOI.HtSV9jLDpTbZn782DC6/t7qT67P6FfO" }, + { "~!@#$%^&*() ~!@#$%^&*()PNBFRD", + "$2a$08$Eq2r4G/76Wv39MzSX262hu", + "$2a$08$Eq2r4G/76Wv39MzSX262huzPz612MZiYHVUJe/OcOql2jo4.9UxTW" }, + { "~!@#$%^&*() ~!@#$%^&*()PNBFRD", + "$2a$10$LgfYWkbzEvQ4JakH7rOvHe", + "$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS" }, + { "~!@#$%^&*() ~!@#$%^&*()PNBFRD", + "$2a$12$WApznUOJfkEGSmYRfnkrPO", + "$2a$12$WApznUOJfkEGSmYRfnkrPOr466oFDCaj4b6HY3EXGvfxm43seyhgC" }, + }; + + /** + * Entry point for unit tests + * @param args unused + */ + public static void main(String[] args) { + junit.textui.TestRunner.run(TestBCrypt.class); + } + + /** + * Test method for 'BCrypt.hashpw(String, String)' + */ + public void testHashpw() { + System.out.print("BCrypt.hashpw(): "); + for (int i = 0; i < test_vectors.length; i++) { + String plain = test_vectors[i][0]; + String salt = test_vectors[i][1]; + String expected = test_vectors[i][2]; + String hashed = BCrypt.hashpw(plain, salt); + assertEquals(hashed, expected); + System.out.print("."); + } + System.out.println(""); + } + + /** + * Test method for 'BCrypt.gensalt(int)' + */ + public void testGensaltInt() { + System.out.print("BCrypt.gensalt(log_rounds):"); + for (int i = 4; i <= 12; i++) { + System.out.print(" " + Integer.toString(i) + ":"); + for (int j = 0; j < test_vectors.length; j += 4) { + String plain = test_vectors[j][0]; + String salt = BCrypt.gensalt(i); + String hashed1 = BCrypt.hashpw(plain, salt); + String hashed2 = BCrypt.hashpw(plain, hashed1); + assertEquals(hashed1, hashed2); + System.out.print("."); + } + } + System.out.println(""); + } + + /** + * Test method for 'BCrypt.gensalt()' + */ + public void testGensalt() { + System.out.print("BCrypt.gensalt(): "); + for (int i = 0; i < test_vectors.length; i += 4) { + String plain = test_vectors[i][0]; + String salt = BCrypt.gensalt(); + String hashed1 = BCrypt.hashpw(plain, salt); + String hashed2 = BCrypt.hashpw(plain, hashed1); + assertEquals(hashed1, hashed2); + System.out.print("."); + } + System.out.println(""); + } + + /** + * Test method for 'BCrypt.checkpw(String, String)' + * expecting success + */ + public void testCheckpw_success() { + System.out.print("BCrypt.checkpw w/ good passwords: "); + for (int i = 0; i < test_vectors.length; i++) { + String plain = test_vectors[i][0]; + String expected = test_vectors[i][2]; + assertTrue(BCrypt.checkpw(plain, expected)); + System.out.print("."); + } + System.out.println(""); + } + + /** + * Test method for 'BCrypt.checkpw(String, String)' + * expecting failure + */ + public void testCheckpw_failure() { + System.out.print("BCrypt.checkpw w/ bad passwords: "); + for (int i = 0; i < test_vectors.length; i++) { + int broken_index = (i + 4) % test_vectors.length; + String plain = test_vectors[i][0]; + String expected = test_vectors[broken_index][2]; + assertFalse(BCrypt.checkpw(plain, expected)); + System.out.print("."); + } + System.out.println(""); + } + + /** + * Test for correct hashing of non-US-ASCII passwords + */ + public void testInternationalChars() { + System.out.print("BCrypt.hashpw w/ international chars: "); + String pw1 = "ππππππππ"; + String pw2 = "????????"; + + String h1 = BCrypt.hashpw(pw1, BCrypt.gensalt()); + assertFalse(BCrypt.checkpw(pw2, h1)); + System.out.print("."); + + String h2 = BCrypt.hashpw(pw2, BCrypt.gensalt()); + assertFalse(BCrypt.checkpw(pw1, h2)); + System.out.print("."); + System.out.println(""); + } + +} diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/spec/bcrypt/engine_spec.rb b/path/ruby/2.6.0/gems/bcrypt-3.1.13/spec/bcrypt/engine_spec.rb new file mode 100644 index 00000000..cde842c8 --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/spec/bcrypt/engine_spec.rb @@ -0,0 +1,147 @@ +require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper")) + +describe "The BCrypt engine" do + specify "should calculate the optimal cost factor to fit in a specific time" do + first = BCrypt::Engine.calibrate(100) + second = BCrypt::Engine.calibrate(400) + expect(second).to be > first + end +end + +describe "Generating BCrypt salts" do + + specify "should produce strings" do + expect(BCrypt::Engine.generate_salt).to be_an_instance_of(String) + end + + specify "should produce random data" do + expect(BCrypt::Engine.generate_salt).to_not equal(BCrypt::Engine.generate_salt) + end + + specify "should raise a InvalidCostError if the cost parameter isn't numeric" do + expect { BCrypt::Engine.generate_salt('woo') }.to raise_error(BCrypt::Errors::InvalidCost) + end + + specify "should raise a InvalidCostError if the cost parameter isn't greater than 0" do + expect { BCrypt::Engine.generate_salt(-1) }.to raise_error(BCrypt::Errors::InvalidCost) + end +end + +describe "Autodetecting of salt cost" do + + specify "should work" do + expect(BCrypt::Engine.autodetect_cost("$2a$08$hRx2IVeHNsTSYYtUWn61Ou")).to eq 8 + expect(BCrypt::Engine.autodetect_cost("$2a$05$XKd1bMnLgUnc87qvbAaCUu")).to eq 5 + expect(BCrypt::Engine.autodetect_cost("$2a$13$Lni.CZ6z5A7344POTFBBV.")).to eq 13 + end + +end + +describe "Generating BCrypt hashes" do + + class MyInvalidSecret + undef to_s + end + + before :each do + @salt = BCrypt::Engine.generate_salt(4) + @password = "woo" + end + + specify "should produce a string" do + expect(BCrypt::Engine.hash_secret(@password, @salt)).to be_an_instance_of(String) + end + + specify "should raise an InvalidSalt error if the salt is invalid" do + expect { BCrypt::Engine.hash_secret(@password, 'nino') }.to raise_error(BCrypt::Errors::InvalidSalt) + end + + specify "should raise an InvalidSecret error if the secret is invalid" do + expect { BCrypt::Engine.hash_secret(MyInvalidSecret.new, @salt) }.to raise_error(BCrypt::Errors::InvalidSecret) + expect { BCrypt::Engine.hash_secret(nil, @salt) }.not_to raise_error + expect { BCrypt::Engine.hash_secret(false, @salt) }.not_to raise_error + end + + specify "should call #to_s on the secret and use the return value as the actual secret data" do + expect(BCrypt::Engine.hash_secret(false, @salt)).to eq BCrypt::Engine.hash_secret("false", @salt) + end + + specify "should be interoperable with other implementations" do + test_vectors = [ + # test vectors from the OpenWall implementation , found in wrapper.c + ["U*U", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW"], + ["U*U*", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK"], + ["U*U*U", "$2a$05$XXXXXXXXXXXXXXXXXXXXXO", "$2a$05$XXXXXXXXXXXXXXXXXXXXXOAcXxm9kjPGEMsLznoKqmqw7tc8WCx4a"], + ["0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789chars after 72 are ignored", "$2a$05$abcdefghijklmnopqrstuu", "$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui"], + ["\xa3", "$2x$05$/OK.fbVrR/bpIqNJ5ianF.", "$2x$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e"], + ["\xff\xff\xa3", "$2x$05$/OK.fbVrR/bpIqNJ5ianF.", "$2x$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e"], + ["\xff\xff\xa3", "$2y$05$/OK.fbVrR/bpIqNJ5ianF.", "$2y$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e"], + ["\xff\xff\xa3", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.nqd1wy.pTMdcvrRWxyiGL2eMz.2a85."], + ["\xff\xff\xa3", "$2b$05$/OK.fbVrR/bpIqNJ5ianF.", "$2b$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e"], + ["\xa3", "$2y$05$/OK.fbVrR/bpIqNJ5ianF.", "$2y$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq"], + ["\xa3", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq"], + ["\xa3", "$2b$05$/OK.fbVrR/bpIqNJ5ianF.", "$2b$05$/OK.fbVrR/bpIqNJ5ianF.Sa7shbm4.OzKpvFnX1pQLmQW96oUlCq"], + ["1\xa3" "345", "$2x$05$/OK.fbVrR/bpIqNJ5ianF.", "$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi"], + ["\xff\xa3" "345", "$2x$05$/OK.fbVrR/bpIqNJ5ianF.", "$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi"], + ["\xff\xa3" "34" "\xff\xff\xff\xa3" "345", "$2x$05$/OK.fbVrR/bpIqNJ5ianF.", "$2x$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi"], + ["\xff\xa3" "34" "\xff\xff\xff\xa3" "345", "$2y$05$/OK.fbVrR/bpIqNJ5ianF.", "$2y$05$/OK.fbVrR/bpIqNJ5ianF.o./n25XVfn6oAPaUvHe.Csk4zRfsYPi"], + ["\xff\xa3" "34" "\xff\xff\xff\xa3" "345", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.ZC1JEJ8Z4gPfpe1JOr/oyPXTWl9EFd."], + ["\xff\xa3" "345", "$2y$05$/OK.fbVrR/bpIqNJ5ianF.", "$2y$05$/OK.fbVrR/bpIqNJ5ianF.nRht2l/HRhr6zmCp9vYUvvsqynflf9e"], + ["\xff\xa3" "345", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.nRht2l/HRhr6zmCp9vYUvvsqynflf9e"], + ["\xa3" "ab", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS"], + ["\xa3" "ab", "$2x$05$/OK.fbVrR/bpIqNJ5ianF.", "$2x$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS"], + ["\xa3" "ab", "$2y$05$/OK.fbVrR/bpIqNJ5ianF.", "$2y$05$/OK.fbVrR/bpIqNJ5ianF.6IflQkJytoRVc1yuaNtHfiuq.FRlSIS"], + ["\xd1\x91", "$2x$05$6bNw2HLQYeqHYyBfLMsv/O", "$2x$05$6bNw2HLQYeqHYyBfLMsv/OiwqTymGIGzFsA4hOTWebfehXHNprcAS"], + ["\xd0\xc1\xd2\xcf\xcc\xd8", "$2x$05$6bNw2HLQYeqHYyBfLMsv/O", "$2x$05$6bNw2HLQYeqHYyBfLMsv/O9LIGgn8OMzuDoHfof8AQimSGfcSWxnS"], + ["\xaa"*72+"chars after 72 are ignored as usual", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.swQOIzjOiJ9GHEPuhEkvqrUyvWhEMx6"], + ["\xaa\x55"*36, "$2a$05$/OK.fbVrR/bpIqNJ5ianF.", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.R9xrDjiycxMbQE2bp.vgqlYpW5wx2yy"], + ["\x55\xaa\xff"*24, "$2a$05$/OK.fbVrR/bpIqNJ5ianF.", "$2a$05$/OK.fbVrR/bpIqNJ5ianF.9tQZzcJfm3uj2NvJ/n5xkhpqLrMpWCe"], + ["", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.", "$2a$05$CCCCCCCCCCCCCCCCCCCCC.7uG0VCzI2bS7j6ymqJi9CdcdxiRTWNy"], + + # test vectors from the Java implementation, found in https://github.com/spring-projects/spring-security/blob/master/crypto/src/test/java/org/springframework/security/crypto/bcrypt/BCryptTests.java + ["", "$2a$06$DCq7YPn5Rq63x1Lad4cll.", "$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s."], + ["", "$2a$08$HqWuK6/Ng6sg9gQzbLrgb.", "$2a$08$HqWuK6/Ng6sg9gQzbLrgb.Tl.ZHfXLhvt/SgVyWhQqgqcZ7ZuUtye"], + ["", "$2a$10$k1wbIrmNyFAPwPVPSVa/ze", "$2a$10$k1wbIrmNyFAPwPVPSVa/zecw2BCEnBwVS2GbrmgzxFUOqW9dk4TCW"], + ["", "$2a$12$k42ZFHFWqBp3vWli.nIn8u", "$2a$12$k42ZFHFWqBp3vWli.nIn8uYyIkbvYRvodzbfbK18SSsY.CsIQPlxO"], + ["", "$2b$06$8eVN9RiU8Yki430X.wBvN.", "$2b$06$8eVN9RiU8Yki430X.wBvN.LWaqh2962emLVSVXVZIXJvDYLsV0oFu"], + ["", "$2b$06$NlgfNgpIc6GlHciCkMEW8u", "$2b$06$NlgfNgpIc6GlHciCkMEW8uKOBsyvAp7QwlHpysOlKdtyEw50WQua2"], + ["", "$2y$06$mFDtkz6UN7B3GZ2qi2hhaO", "$2y$06$mFDtkz6UN7B3GZ2qi2hhaO3OFWzNEdcY84ELw6iHCPruuQfSAXBLK"], + ["", "$2y$06$88kSqVttBx.e9iXTPCLa5u", "$2y$06$88kSqVttBx.e9iXTPCLa5uFPrVFjfLH4D.KcO6pBiAmvUkvdg0EYy"], + ["a", "$2a$06$m0CrhHm10qJ3lXRY.5zDGO", "$2a$06$m0CrhHm10qJ3lXRY.5zDGO3rS2KdeeWLuGmsfGlMfOxih58VYVfxe"], + ["a", "$2a$08$cfcvVd2aQ8CMvoMpP2EBfe", "$2a$08$cfcvVd2aQ8CMvoMpP2EBfeodLEkkFJ9umNEfPD18.hUF62qqlC/V."], + ["a", "$2a$10$k87L/MF28Q673VKh8/cPi.", "$2a$10$k87L/MF28Q673VKh8/cPi.SUl7MU/rWuSiIDDFayrKk/1tBsSQu4u"], + ["a", "$2a$12$8NJH3LsPrANStV6XtBakCe", "$2a$12$8NJH3LsPrANStV6XtBakCez0cKHXVxmvxIlcz785vxAIZrihHZpeS"], + ["a", "$2b$06$ehKGYiS4wt2HAr7KQXS5z.", "$2b$06$ehKGYiS4wt2HAr7KQXS5z.OaRjB4jHO7rBHJKlGXbqEH3QVJfO7iO"], + ["a", "$2b$06$PWxFFHA3HiCD46TNOZh30e", "$2b$06$PWxFFHA3HiCD46TNOZh30eNto1hg5uM9tHBlI4q/b03SW/gGKUYk6"], + ["a", "$2y$06$LUdD6/aD0e/UbnxVAVbvGu", "$2y$06$LUdD6/aD0e/UbnxVAVbvGuUmIoJ3l/OK94ThhadpMWwKC34LrGEey"], + ["a", "$2y$06$eqgY.T2yloESMZxgp76deO", "$2y$06$eqgY.T2yloESMZxgp76deOROa7nzXDxbO0k.PJvuClTa.Vu1AuemG"], + ["abc", "$2a$06$If6bvum7DFjUnE9p2uDeDu", "$2a$06$If6bvum7DFjUnE9p2uDeDu0YHzrHM6tf.iqN8.yx.jNN1ILEf7h0i"], + ["abc", "$2a$08$Ro0CUfOqk6cXEKf3dyaM7O", "$2a$08$Ro0CUfOqk6cXEKf3dyaM7OhSCvnwM9s4wIX9JeLapehKK5YdLxKcm"], + ["abc", "$2a$10$WvvTPHKwdBJ3uk0Z37EMR.", "$2a$10$WvvTPHKwdBJ3uk0Z37EMR.hLA2W6N9AEBhEgrAOljy2Ae5MtaSIUi"], + ["abc", "$2a$12$EXRkfkdmXn2gzds2SSitu.", "$2a$12$EXRkfkdmXn2gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q"], + ["abc", "$2b$06$5FyQoicpbox1xSHFfhhdXu", "$2b$06$5FyQoicpbox1xSHFfhhdXuR2oxLpO1rYsQh5RTkI/9.RIjtoF0/ta"], + ["abc", "$2b$06$1kJyuho8MCVP3HHsjnRMkO", "$2b$06$1kJyuho8MCVP3HHsjnRMkO1nvCOaKTqLnjG2TX1lyMFbXH/aOkgc."], + ["abc", "$2y$06$ACfku9dT6.H8VjdKb8nhlu", "$2y$06$ACfku9dT6.H8VjdKb8nhluaoBmhJyK7GfoNScEfOfrJffUxoUeCjK"], + ["abc", "$2y$06$9JujYcoWPmifvFA3RUP90e", "$2y$06$9JujYcoWPmifvFA3RUP90e5rSEHAb5Ye6iv3.G9ikiHNv5cxjNEse"], + ["abcdefghijklmnopqrstuvwxyz", "$2a$06$.rCVZVOThsIa97pEDOxvGu", "$2a$06$.rCVZVOThsIa97pEDOxvGuRRgzG64bvtJ0938xuqzv18d3ZpQhstC"], + ["abcdefghijklmnopqrstuvwxyz", "$2a$08$aTsUwsyowQuzRrDqFflhge", "$2a$08$aTsUwsyowQuzRrDqFflhgekJ8d9/7Z3GV3UcgvzQW3J5zMyrTvlz."], + ["abcdefghijklmnopqrstuvwxyz", "$2a$10$fVH8e28OQRj9tqiDXs1e1u", "$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq"], + ["abcdefghijklmnopqrstuvwxyz", "$2a$12$D4G5f18o7aMMfwasBL7Gpu", "$2a$12$D4G5f18o7aMMfwasBL7GpuQWuP3pkrZrOAnqP.bmezbMng.QwJ/pG"], + ["abcdefghijklmnopqrstuvwxyz", "$2b$06$O8E89AQPj1zJQA05YvIAU.", "$2b$06$O8E89AQPj1zJQA05YvIAU.hMpj25BXri1bupl/Q7CJMlpLwZDNBoO"], + ["abcdefghijklmnopqrstuvwxyz", "$2b$06$PDqIWr./o/P3EE/P.Q0A/u", "$2b$06$PDqIWr./o/P3EE/P.Q0A/uFg86WL/PXTbaW267TDALEwDylqk00Z."], + ["abcdefghijklmnopqrstuvwxyz", "$2y$06$34MG90ZLah8/ZNr3ltlHCu", "$2y$06$34MG90ZLah8/ZNr3ltlHCuz6bachF8/3S5jTuzF1h2qg2cUk11sFW"], + ["abcdefghijklmnopqrstuvwxyz", "$2y$06$AK.hSLfMyw706iEW24i68u", "$2y$06$AK.hSLfMyw706iEW24i68uKAc2yorPTrB0cimvjJHEBUrPkOq7VvG"], + ["~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2a$06$fPIsBO8qRqkjj273rfaOI.", "$2a$06$fPIsBO8qRqkjj273rfaOI.HtSV9jLDpTbZn782DC6/t7qT67P6FfO"], + ["~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2a$08$Eq2r4G/76Wv39MzSX262hu", "$2a$08$Eq2r4G/76Wv39MzSX262huzPz612MZiYHVUJe/OcOql2jo4.9UxTW"], + ["~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2a$10$LgfYWkbzEvQ4JakH7rOvHe", "$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS"], + ["~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2a$12$WApznUOJfkEGSmYRfnkrPO", "$2a$12$WApznUOJfkEGSmYRfnkrPOr466oFDCaj4b6HY3EXGvfxm43seyhgC"], + ["~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2b$06$FGWA8OlY6RtQhXBXuCJ8Wu", "$2b$06$FGWA8OlY6RtQhXBXuCJ8WusVipRI15cWOgJK8MYpBHEkktMfbHRIG"], + ["~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2b$06$G6aYU7UhUEUDJBdTgq3CRe", "$2b$06$G6aYU7UhUEUDJBdTgq3CRekiopCN4O4sNitFXrf5NUscsVZj3a2r6"], + ["~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2y$06$sYDFHqOcXTjBgOsqC0WCKe", "$2y$06$sYDFHqOcXTjBgOsqC0WCKeMd3T1UhHuWQSxncLGtXDLMrcE6vFDti"], + ["~!@#$%^&*() ~!@#$%^&*()PNBFRD", "$2y$06$6Xm0gCw4g7ZNDCEp4yTise", "$2y$06$6Xm0gCw4g7ZNDCEp4yTisez0kSdpXEl66MvdxGidnmChIe8dFmMnq"] + ] + for secret, salt, test_vector in test_vectors + expect(BCrypt::Engine.hash_secret(secret, salt)).to eql(test_vector) + end + end +end diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/spec/bcrypt/error_spec.rb b/path/ruby/2.6.0/gems/bcrypt-3.1.13/spec/bcrypt/error_spec.rb new file mode 100644 index 00000000..6f90048e --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/spec/bcrypt/error_spec.rb @@ -0,0 +1,37 @@ +require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper")) + +describe "Errors" do + + shared_examples "descends from StandardError" do + it "can be rescued as a StandardError" do + expect(described_class).to be < StandardError + end + end + + shared_examples "descends from BCrypt::Error" do + it "can be rescued as a BCrypt::Error" do + expect(described_class).to be < BCrypt::Error + end + end + + describe BCrypt::Error do + include_examples "descends from StandardError" + end + + describe BCrypt::Errors::InvalidCost do + include_examples "descends from BCrypt::Error" + end + + describe BCrypt::Errors::InvalidHash do + include_examples "descends from BCrypt::Error" + end + + describe BCrypt::Errors::InvalidSalt do + include_examples "descends from BCrypt::Error" + end + + describe BCrypt::Errors::InvalidSecret do + include_examples "descends from BCrypt::Error" + end + +end diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/spec/bcrypt/password_spec.rb b/path/ruby/2.6.0/gems/bcrypt-3.1.13/spec/bcrypt/password_spec.rb new file mode 100644 index 00000000..648e6140 --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/spec/bcrypt/password_spec.rb @@ -0,0 +1,124 @@ +require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper")) + +describe "Creating a hashed password" do + + before :each do + @secret = "wheedle" + @password = BCrypt::Password.create(@secret, :cost => 4) + end + + specify "should return a BCrypt::Password" do + expect(@password).to be_an_instance_of(BCrypt::Password) + end + + specify "should return a valid bcrypt password" do + expect { BCrypt::Password.new(@password) }.not_to raise_error + end + + specify "should behave normally if the secret is not a string" do + expect { BCrypt::Password.create(nil) }.not_to raise_error + expect { BCrypt::Password.create({:woo => "yeah"}) }.not_to raise_error + expect { BCrypt::Password.create(false) }.not_to raise_error + end + + specify "should tolerate empty string secrets" do + expect { BCrypt::Password.create( "\n".chop ) }.not_to raise_error + expect { BCrypt::Password.create( "" ) }.not_to raise_error + expect { BCrypt::Password.create( String.new ) }.not_to raise_error + end +end + +describe "Reading a hashed password" do + before :each do + @secret = "U*U" + @hash = "$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW" + end + + specify "the cost is too damn high" do + expect { + BCrypt::Password.create("hello", :cost => 32) + }.to raise_error(ArgumentError) + end + + specify "the cost should be set to the default if nil" do + expect(BCrypt::Password.create("hello", :cost => nil).cost).to equal(BCrypt::Engine::DEFAULT_COST) + end + + specify "the cost should be set to the default if empty hash" do + expect(BCrypt::Password.create("hello", {}).cost).to equal(BCrypt::Engine::DEFAULT_COST) + end + + specify "the cost should be set to the passed value if provided" do + expect(BCrypt::Password.create("hello", :cost => 5).cost).to equal(5) + end + + specify "the cost should be set to the global value if set" do + BCrypt::Engine.cost = 5 + expect(BCrypt::Password.create("hello").cost).to equal(5) + # unset the global value to not affect other tests + BCrypt::Engine.cost = nil + end + + specify "the cost should be set to an overridden constant for backwards compatibility" do + # suppress "already initialized constant" warning + old_verbose, $VERBOSE = $VERBOSE, nil + old_default_cost = BCrypt::Engine::DEFAULT_COST + + BCrypt::Engine::DEFAULT_COST = 5 + expect(BCrypt::Password.create("hello").cost).to equal(5) + + # reset default to not affect other tests + BCrypt::Engine::DEFAULT_COST = old_default_cost + $VERBOSE = old_verbose + end + + specify "should read the version, cost, salt, and hash" do + password = BCrypt::Password.new(@hash) + expect(password.version).to eql("2a") + expect(password.version.class).to eq String + expect(password.cost).to equal(5) + expect(password.salt).to eql("$2a$05$CCCCCCCCCCCCCCCCCCCCC.") + expect(password.salt.class).to eq String + expect(password.checksum).to eq("E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW") + expect(password.checksum.class).to eq String + expect(password.to_s).to eql(@hash) + end + + specify "should raise an InvalidHashError when given an invalid hash" do + expect { BCrypt::Password.new('weedle') }.to raise_error(BCrypt::Errors::InvalidHash) + end +end + +describe "Comparing a hashed password with a secret" do + before :each do + @secret = "U*U" + @hash = "$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW" + @password = BCrypt::Password.create(@secret) + end + + specify "should compare successfully to the original secret" do + expect((@password == @secret)).to be(true) + end + + specify "should compare unsuccessfully to anything besides original secret" do + expect((@password == "@secret")).to be(false) + end +end + +describe "Validating a generated salt" do + specify "should not accept an invalid salt" do + expect(BCrypt::Engine.valid_salt?("invalid")).to eq(false) + end + specify "should accept a valid salt" do + expect(BCrypt::Engine.valid_salt?(BCrypt::Engine.generate_salt)).to eq(true) + end +end + +describe "Validating a password hash" do + specify "should not accept an invalid password" do + expect(BCrypt::Password.valid_hash?("i_am_so_not_valid")).to be_falsey + end + specify "should accept a valid password" do + expect(BCrypt::Password.valid_hash?(BCrypt::Password.create "i_am_so_valid")).to be_truthy + end +end diff --git a/path/ruby/2.6.0/gems/bcrypt-3.1.13/spec/spec_helper.rb b/path/ruby/2.6.0/gems/bcrypt-3.1.13/spec/spec_helper.rb new file mode 100644 index 00000000..54882506 --- /dev/null +++ b/path/ruby/2.6.0/gems/bcrypt-3.1.13/spec/spec_helper.rb @@ -0,0 +1,2 @@ +$:.unshift File.expand_path('../../lib', __FILE__) +require 'bcrypt' diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/.gitignore b/path/ruby/2.6.0/gems/bindex-0.8.1/.gitignore new file mode 100644 index 00000000..49c2a576 --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/.gitignore @@ -0,0 +1,15 @@ +/.bundle/ +/.yardoc +/Gemfile.lock +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ +*.bundle +*.so +*.o +*.a +.ycm_extra_conf.py* +mkmf.log diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/.travis.yml b/path/ruby/2.6.0/gems/bindex-0.8.1/.travis.yml new file mode 100644 index 00000000..762e7248 --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/.travis.yml @@ -0,0 +1,18 @@ +language: ruby + +rvm: + - ruby-2.5 + - ruby-2.6 + - ruby-head + +allow_failures: + - rvm: ruby-head + - rvm: jruby-head + +env: + global: + - JRUBY_OPTS=--dev + +before_install: gem install bundler + +cache: bundler diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/CONTRIBUTING.md b/path/ruby/2.6.0/gems/bindex-0.8.1/CONTRIBUTING.md new file mode 100644 index 00000000..cdd82eb1 --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/CONTRIBUTING.md @@ -0,0 +1,15 @@ +# Contributing + +1. Fork it ( https://github.com/gsamokovarov/skiptrace/fork ) +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create a new Pull Request + +## Etiquette + +If you want to contribute code, which is not your own or is heavily inspired by +someone else's code, please give them a warm shoutout in the pull request (or +the commit message) and the code itself. + +Of course, don't try to sneak in non MIT compatible code. diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/Gemfile b/path/ruby/2.6.0/gems/bindex-0.8.1/Gemfile new file mode 100644 index 00000000..cea976f9 --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/Gemfile @@ -0,0 +1,10 @@ +source 'https://rubygems.org' + +gemspec name: 'skiptrace' + +# Rubinius 2.2.2 travis tests complain about this one. +platforms :rbx do + gem "rubysl-mutex_m" + gem "rubysl-open3" + gem "rubysl-singleton" +end diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/LICENSE.txt b/path/ruby/2.6.0/gems/bindex-0.8.1/LICENSE.txt new file mode 100644 index 00000000..6733a5a4 --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2014-2017 Genadi Samokovarov + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/README.md b/path/ruby/2.6.0/gems/bindex-0.8.1/README.md new file mode 100644 index 00000000..a0f272a0 --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/README.md @@ -0,0 +1,56 @@ +# Skiptrace [![Build Status](https://travis-ci.org/gsamokovarov/skiptrace.svg?branch=master)](https://travis-ci.org/gsamokovarov/skiptrace) + +When Ruby raises an exception, it leaves you a backtrace to help you figure out +where did the exception originated in. Skiptrace gives you the bindings as well. +This can help you introspect the state of the Ruby program when at the point +the exception occurred. + +## Usage + +**Do not** use this gem on production environments. The performance penalty isn't +worth it anywhere outside of development. + +### API + +Skiptrace defines the following API: + +#### Exception#bindings + +Returns all the bindings up to the one in which the exception originated in. + +#### Exception#binding_locations + +Returns an array of `Skiptrace::Location` objects that are like [`Thread::Backtrace::Location`](https://ruby-doc.org/core-2.6.3/Thread/Backtrace/Location.html) +but also carry a `Binding` object for that frame through the `#binding` method. + +#### Skiptrace.current_bindings + +Returns all of the current Ruby execution state bindings. The first one is the +current one, the second is the caller one, the third is the caller of the +caller one and so on. + +## Support + +### CRuby + +CRuby 2.5.0 and above is supported. + +### JRuby + +To get the best support, run JRuby in interpreted mode. + +```bash +export JRUBY_OPTS=--dev +``` + +Only JRuby 9k is supported. + +### Rubinius + +Internal errors like `ZeroDevisionError` aren't caught. + +## Credits + +Thanks to John Mair for his work on binding_of_caller, which is a huge +inspiration. Thanks to Charlie Somerville for better_errors where the idea +comes from. Thanks to Koichi Sasada for the debug inspector API in CRuby. diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/Rakefile b/path/ruby/2.6.0/gems/bindex-0.8.1/Rakefile new file mode 100644 index 00000000..aa769767 --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/Rakefile @@ -0,0 +1,37 @@ +require 'rake/testtask' +require "rake/clean" + +CLOBBER.include "pkg" + +Bundler::GemHelper.install_tasks name: ENV.fetch('GEM_NAME', 'skiptrace') + +Rake::TestTask.new do |t| + t.libs << 'test' + t.test_files = FileList['test/**/*_test.rb'] + t.verbose = true +end + +case RUBY_ENGINE +when 'ruby' + require 'rake/extensiontask' + + Rake::ExtensionTask.new('skiptrace') do |ext| + ext.name = 'cruby' + ext.lib_dir = 'lib/skiptrace/internal' + end + + task default: [:clean, :compile, :test] +when 'jruby' + require 'rake/javaextensiontask' + + Rake::JavaExtensionTask.new('skiptrace') do |ext| + ext.name = 'jruby_internals' + ext.lib_dir = 'lib/skiptrace/internal' + ext.source_version = '1.8' + ext.target_version = '1.8' + end + + task default: [:clean, :compile, :test] +else + task default: [:test] +end diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/bindex.gemspec b/path/ruby/2.6.0/gems/bindex-0.8.1/bindex.gemspec new file mode 100644 index 00000000..b55c2589 --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/bindex.gemspec @@ -0,0 +1,27 @@ +$LOAD_PATH << File.expand_path('../lib', __FILE__) + +require 'skiptrace/version' + +Gem::Specification.new do |spec| + spec.name = "bindex" + spec.version = Skiptrace::VERSION + spec.authors = ["Genadi Samokovarov"] + spec.email = ["gsamokovarov@gmail.com"] + spec.extensions = ["ext/skiptrace/extconf.rb"] + spec.summary = "Bindings for your Ruby exceptions" + spec.homepage = "https://github.com/gsamokovarov/bindex" + spec.license = "MIT" + + spec.required_ruby_version = ">= 2.0.0" + + spec.files = `git ls-files -z`.split("\x0") + spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } + spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) + spec.require_paths = ["lib"] + spec.extensions = ["ext/skiptrace/extconf.rb"] + + spec.add_development_dependency "minitest", "~> 5.4" + spec.add_development_dependency "bundler" + spec.add_development_dependency "rake" + spec.add_development_dependency "rake-compiler" +end diff --git a/tutor-virtual-2/tmp/.keep b/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/.sitearchdir.-.skiptrace.-.internal.time similarity index 100% rename from tutor-virtual-2/tmp/.keep rename to path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/.sitearchdir.-.skiptrace.-.internal.time diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/Makefile b/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/Makefile new file mode 100644 index 00000000..00b70049 --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/Makefile @@ -0,0 +1,266 @@ + +SHELL = /bin/sh + +# V=0 quiet, V=1 verbose. other values don't work. +V = 0 +Q1 = $(V:1=) +Q = $(Q1:0=@) +ECHO1 = $(V:1=@ :) +ECHO = $(ECHO1:0=@ echo) +NULLCMD = : + +#### Start of system configuration section. #### + +srcdir = . +topdir = /Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 +hdrdir = $(topdir) +arch_hdrdir = /Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 +PATH_SEPARATOR = : +VPATH = $(srcdir):$(arch_hdrdir)/ruby:$(hdrdir)/ruby +prefix = $(DESTDIR)/Users/sergio/.rvm/rubies/ruby-2.6.3 +rubysitearchprefix = $(rubylibprefix)/$(sitearch) +rubyarchprefix = $(rubylibprefix)/$(arch) +rubylibprefix = $(libdir)/$(RUBY_BASE_NAME) +exec_prefix = $(prefix) +vendorarchhdrdir = $(vendorhdrdir)/$(sitearch) +sitearchhdrdir = $(sitehdrdir)/$(sitearch) +rubyarchhdrdir = $(rubyhdrdir)/$(arch) +vendorhdrdir = $(rubyhdrdir)/vendor_ruby +sitehdrdir = $(rubyhdrdir)/site_ruby +rubyhdrdir = $(includedir)/$(RUBY_VERSION_NAME) +vendorarchdir = $(vendorlibdir)/$(sitearch) +vendorlibdir = $(vendordir)/$(ruby_version) +vendordir = $(rubylibprefix)/vendor_ruby +sitearchdir = $(DESTDIR)./.gem.20191018-47188-1f390n2 +sitelibdir = $(DESTDIR)./.gem.20191018-47188-1f390n2 +sitedir = $(rubylibprefix)/site_ruby +rubyarchdir = $(rubylibdir)/$(arch) +rubylibdir = $(rubylibprefix)/$(ruby_version) +sitearchincludedir = $(includedir)/$(sitearch) +archincludedir = $(includedir)/$(arch) +sitearchlibdir = $(libdir)/$(sitearch) +archlibdir = $(libdir)/$(arch) +ridir = $(datarootdir)/$(RI_BASE_NAME) +mandir = $(datarootdir)/man +localedir = $(datarootdir)/locale +libdir = $(exec_prefix)/lib +psdir = $(docdir) +pdfdir = $(docdir) +dvidir = $(docdir) +htmldir = $(docdir) +infodir = $(datarootdir)/info +docdir = $(datarootdir)/doc/$(PACKAGE) +oldincludedir = $(SDKROOT)/usr/include +includedir = $(prefix)/include +localstatedir = $(prefix)/var +sharedstatedir = $(prefix)/com +sysconfdir = $(prefix)/etc +datadir = $(datarootdir) +datarootdir = $(prefix)/share +libexecdir = $(exec_prefix)/libexec +sbindir = $(exec_prefix)/sbin +bindir = $(exec_prefix)/bin +archdir = $(rubyarchdir) + + +CC_WRAPPER = +CC = gcc +CXX = g++ +LIBRUBY = $(LIBRUBY_SO) +LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a +LIBRUBYARG_SHARED = -l$(RUBY_SO_NAME) +LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static -framework Security -framework Foundation $(MAINLIBS) +empty = +OUTFLAG = -o $(empty) +COUTFLAG = -o $(empty) +CSRCFLAG = $(empty) + +RUBY_EXTCONF_H = +cflags = $(optflags) $(debugflags) $(warnflags) +cxxflags = $(optflags) $(debugflags) $(warnflags) +optflags = -O3 +debugflags = -ggdb3 +warnflags = -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens +cppflags = +CCDLFLAGS = -fno-common +CFLAGS = $(CCDLFLAGS) $(cflags) -fno-common -pipe -Wall $(ARCH_FLAG) +INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir)/ruby/backward -I$(hdrdir) -I$(srcdir) +DEFS = +CPPFLAGS = -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT $(DEFS) $(cppflags) +CXXFLAGS = $(CCDLFLAGS) $(cxxflags) $(ARCH_FLAG) +ldflags = -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib +dldflags = -Wl,-undefined,dynamic_lookup -Wl,-multiply_defined,suppress -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib +ARCH_FLAG = +DLDFLAGS = $(ldflags) $(dldflags) $(ARCH_FLAG) +LDSHARED = $(CC) -dynamic -bundle +LDSHAREDXX = $(CXX) -dynamic -bundle +AR = libtool -static +EXEEXT = + +RUBY_INSTALL_NAME = $(RUBY_BASE_NAME) +RUBY_SO_NAME = ruby.2.6 +RUBYW_INSTALL_NAME = +RUBY_VERSION_NAME = $(RUBY_BASE_NAME)-$(ruby_version) +RUBYW_BASE_NAME = rubyw +RUBY_BASE_NAME = ruby + +arch = x86_64-darwin18 +sitearch = $(arch) +ruby_version = 2.6.0 +ruby = $(bindir)/$(RUBY_BASE_NAME) +RUBY = $(ruby) +ruby_headers = $(hdrdir)/ruby.h $(hdrdir)/ruby/backward.h $(hdrdir)/ruby/ruby.h $(hdrdir)/ruby/defines.h $(hdrdir)/ruby/missing.h $(hdrdir)/ruby/intern.h $(hdrdir)/ruby/st.h $(hdrdir)/ruby/subst.h $(arch_hdrdir)/ruby/config.h + +RM = rm -f +RM_RF = $(RUBY) -run -e rm -- -rf +RMDIRS = rmdir -p +MAKEDIRS = /usr/local/opt/coreutils/bin/gmkdir -p +INSTALL = /usr/local/opt/coreutils/bin/ginstall -c +INSTALL_PROG = $(INSTALL) -m 0755 +INSTALL_DATA = $(INSTALL) -m 644 +COPY = cp +TOUCH = exit > + +#### End of system configuration section. #### + +preload = +libpath = . $(libdir) /usr/local/opt/libyaml/lib /usr/local/opt/libksba/lib /usr/local/opt/readline/lib /usr/local/opt/zlib/lib /usr/local/opt/openssl@1.1/lib +LIBPATH = -L. -L$(libdir) -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib +DEFFILE = + +CLEANFILES = mkmf.log +DISTCLEANFILES = +DISTCLEANDIRS = + +extout = +extout_prefix = +target_prefix = /skiptrace/internal +LOCAL_LIBS = +LIBS = $(LIBRUBYARG_SHARED) +ORIG_SRCS = cruby.c +SRCS = $(ORIG_SRCS) +OBJS = cruby.o +HDRS = +LOCAL_HDRS = +TARGET = cruby +TARGET_NAME = cruby +TARGET_ENTRY = Init_$(TARGET_NAME) +DLLIB = $(TARGET).bundle +EXTSTATIC = +STATIC_LIB = + +TIMESTAMP_DIR = . +BINDIR = $(bindir) +RUBYCOMMONDIR = $(sitedir)$(target_prefix) +RUBYLIBDIR = $(sitelibdir)$(target_prefix) +RUBYARCHDIR = $(sitearchdir)$(target_prefix) +HDRDIR = $(rubyhdrdir)/ruby$(target_prefix) +ARCHHDRDIR = $(rubyhdrdir)/$(arch)/ruby$(target_prefix) +TARGET_SO_DIR = +TARGET_SO = $(TARGET_SO_DIR)$(DLLIB) +CLEANLIBS = $(TARGET_SO) +CLEANOBJS = *.o *.bak + +all: $(DLLIB) +static: $(STATIC_LIB) +.PHONY: all install static install-so install-rb +.PHONY: clean clean-so clean-static clean-rb + +clean-static:: +clean-rb-default:: +clean-rb:: +clean-so:: +clean: clean-so clean-static clean-rb-default clean-rb + -$(Q)$(RM) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES) .*.time + +distclean-rb-default:: +distclean-rb:: +distclean-so:: +distclean-static:: +distclean: clean distclean-so distclean-static distclean-rb-default distclean-rb + -$(Q)$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log + -$(Q)$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES) + -$(Q)$(RMDIRS) $(DISTCLEANDIRS) 2> /dev/null || true + +realclean: distclean +install: install-so install-rb + +install-so: $(DLLIB) $(TIMESTAMP_DIR)/.sitearchdir.-.skiptrace.-.internal.time + $(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR) +clean-static:: + -$(Q)$(RM) $(STATIC_LIB) +install-rb: pre-install-rb do-install-rb install-rb-default +install-rb-default: pre-install-rb-default do-install-rb-default +pre-install-rb: Makefile +pre-install-rb-default: Makefile +do-install-rb: +do-install-rb-default: +pre-install-rb-default: + @$(NULLCMD) +$(TIMESTAMP_DIR)/.sitearchdir.-.skiptrace.-.internal.time: + $(Q) $(MAKEDIRS) $(@D) $(RUBYARCHDIR) + $(Q) $(TOUCH) $@ + +site-install: site-install-so site-install-rb +site-install-so: install-so +site-install-rb: install-rb + +.SUFFIXES: .c .m .cc .mm .cxx .cpp .o .S + +.cc.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cc.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.mm.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.mm.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.cxx.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cxx.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.cpp.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cpp.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.c.o: + $(ECHO) compiling $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.c.S: + $(ECHO) translating $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.m.o: + $(ECHO) compiling $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.m.S: + $(ECHO) translating $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +$(TARGET_SO): $(OBJS) Makefile + $(ECHO) linking shared-object skiptrace/internal/$(DLLIB) + -$(Q)$(RM) $(@) + $(Q) $(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS) + $(Q) $(POSTLINK) + + + +$(OBJS): $(HDRS) $(ruby_headers) diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/com/gsamokovarov/skiptrace/BindingBuilder.java b/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/com/gsamokovarov/skiptrace/BindingBuilder.java new file mode 100644 index 00000000..ad44dfc6 --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/com/gsamokovarov/skiptrace/BindingBuilder.java @@ -0,0 +1,13 @@ +package com.gsamokovarov.skiptrace; + +import org.jruby.runtime.DynamicScope; +import org.jruby.runtime.Binding; +import org.jruby.runtime.Frame; +import org.jruby.runtime.DynamicScope; +import org.jruby.runtime.backtrace.BacktraceElement; + +class BindingBuilder { + public static Binding build(Frame frame, DynamicScope scope, BacktraceElement element) { + return new Binding(frame, scope, element.method, element.filename, element.line); + } +} diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/com/gsamokovarov/skiptrace/CurrentBindingsIterator.java b/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/com/gsamokovarov/skiptrace/CurrentBindingsIterator.java new file mode 100644 index 00000000..c599af84 --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/com/gsamokovarov/skiptrace/CurrentBindingsIterator.java @@ -0,0 +1,54 @@ +package com.gsamokovarov.skiptrace; + +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.DynamicScope; +import org.jruby.runtime.Binding; +import org.jruby.runtime.Frame; +import org.jruby.runtime.DynamicScope; +import org.jruby.runtime.backtrace.BacktraceElement; +import java.util.Iterator; +import java.util.NoSuchElementException; + +class CurrentBindingsIterator implements Iterator { + private Frame[] frameStack; + private int frameIndex; + + private DynamicScope[] scopeStack; + private int scopeIndex; + + private BacktraceElement[] backtrace; + private int backtraceIndex; + + CurrentBindingsIterator(ThreadContext context) { + ThreadContextInternals contextInternals = new ThreadContextInternals(context); + + this.frameStack = contextInternals.getFrameStack(); + this.frameIndex = contextInternals.getFrameIndex(); + + this.scopeStack = contextInternals.getScopeStack(); + this.scopeIndex = contextInternals.getScopeIndex(); + + this.backtrace = contextInternals.getBacktrace(); + this.backtraceIndex = contextInternals.getBacktraceIndex(); + } + + public boolean hasNext() { + return frameIndex >= 0 && scopeIndex >= 0 && backtraceIndex >= 0; + } + + public Binding next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + Frame frame = frameStack[frameIndex--]; + DynamicScope scope = scopeStack[scopeIndex--]; + BacktraceElement element = backtrace[backtraceIndex--]; + + return BindingBuilder.build(frame, scope, element); + } + + public void remove() { + throw new UnsupportedOperationException(); + } +} diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/com/gsamokovarov/skiptrace/JRubyIntegration.java b/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/com/gsamokovarov/skiptrace/JRubyIntegration.java new file mode 100644 index 00000000..6c43e06a --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/com/gsamokovarov/skiptrace/JRubyIntegration.java @@ -0,0 +1,49 @@ +package com.gsamokovarov.skiptrace; + +import org.jruby.Ruby; +import org.jruby.RubyArray; +import org.jruby.RubyModule; +import org.jruby.RubyClass; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; +import org.jruby.runtime.builtin.InstanceVariables; +import org.jruby.anno.JRubyMethod; + +public class JRubyIntegration { + public static void setup(Ruby runtime) { + RubyModule skiptrace = runtime.defineModule("Skiptrace"); + skiptrace.defineAnnotatedMethods(SkiptraceMethods.class); + + RubyClass exception = runtime.getException(); + exception.defineAnnotatedMethods(ExceptionExtensionMethods.class); + + IRubyObject verbose = runtime.getVerbose(); + try { + runtime.setVerbose(runtime.getNil()); + runtime.addEventHook(new SetExceptionBindingsEventHook()); + } finally { + runtime.setVerbose(verbose); + } + } + + public static class SkiptraceMethods { + @JRubyMethod(name = "current_bindings", meta = true) + public static IRubyObject currentBindings(ThreadContext context, IRubyObject self) { + return RubyBindingsCollector.collectCurrentFor(context); + } + } + + public static class ExceptionExtensionMethods { + @JRubyMethod + public static IRubyObject bindings(ThreadContext context, IRubyObject self) { + InstanceVariables instanceVariables = self.getInstanceVariables(); + + IRubyObject bindings = instanceVariables.getInstanceVariable("@bindings"); + if (bindings != null && !bindings.isNil()) { + return bindings; + } + + return RubyArray.newArray(context.getRuntime()); + } + } +} diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/com/gsamokovarov/skiptrace/RubyBindingsCollector.java b/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/com/gsamokovarov/skiptrace/RubyBindingsCollector.java new file mode 100644 index 00000000..c471215c --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/com/gsamokovarov/skiptrace/RubyBindingsCollector.java @@ -0,0 +1,34 @@ +package com.gsamokovarov.skiptrace; + +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.Binding; +import org.jruby.runtime.DynamicScope; +import org.jruby.runtime.builtin.IRubyObject; +import org.jruby.RubyBinding; +import org.jruby.RubyArray; +import org.jruby.Ruby; +import java.util.Iterator; + +public class RubyBindingsCollector { + private final Ruby runtime; + private Iterator iterator; + + public static RubyArray collectCurrentFor(ThreadContext context) { + return new RubyBindingsCollector(context).collectCurrent(); + } + + private RubyBindingsCollector(ThreadContext context) { + this.iterator = new CurrentBindingsIterator(context); + this.runtime = context.getRuntime(); + } + + private RubyArray collectCurrent() { + RubyArray bindings = RubyArray.newArray(runtime); + + while (iterator.hasNext()) { + bindings.append(((IRubyObject) RubyBinding.newBinding(runtime, iterator.next()))); + } + + return bindings; + } +} diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/com/gsamokovarov/skiptrace/SetExceptionBindingsEventHook.java b/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/com/gsamokovarov/skiptrace/SetExceptionBindingsEventHook.java new file mode 100644 index 00000000..108c063d --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/com/gsamokovarov/skiptrace/SetExceptionBindingsEventHook.java @@ -0,0 +1,24 @@ +package com.gsamokovarov.skiptrace; + +import org.jruby.runtime.EventHook; +import org.jruby.runtime.RubyEvent; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; +import org.jruby.RubyArray; +import org.jruby.RubyException; + +public class SetExceptionBindingsEventHook extends EventHook { + public boolean isInterestedInEvent(RubyEvent event) { + return event == RubyEvent.RAISE; + } + + public void eventHandler(ThreadContext context, String eventName, String file, int line, String name, IRubyObject type) { + RubyArray bindings = RubyBindingsCollector.collectCurrentFor(context); + RubyException exception = (RubyException) context.runtime.getGlobalVariables().get("$!"); + + IRubyObject exceptionBindings = exception.getInstanceVariable("@bindings"); + if (exceptionBindings == null || exceptionBindings.isNil()) { + exception.setInstanceVariable("@bindings", bindings); + } + } +} diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/com/gsamokovarov/skiptrace/ThreadContextInterfaceException.java b/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/com/gsamokovarov/skiptrace/ThreadContextInterfaceException.java new file mode 100644 index 00000000..f9cf4223 --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/com/gsamokovarov/skiptrace/ThreadContextInterfaceException.java @@ -0,0 +1,14 @@ +package com.gsamokovarov.skiptrace; + +class ThreadContextInterfaceException extends RuntimeException { + private static final String MESSAGE_TEMPLATE = + "Expected private field %s in ThreadContext is missing"; + + ThreadContextInterfaceException(String fieldName) { + super(String.format(MESSAGE_TEMPLATE, fieldName)); + } + + ThreadContextInterfaceException(String fieldName, Throwable cause) { + super(String.format(MESSAGE_TEMPLATE, fieldName), cause); + } +} diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/com/gsamokovarov/skiptrace/ThreadContextInternals.java b/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/com/gsamokovarov/skiptrace/ThreadContextInternals.java new file mode 100644 index 00000000..c3a945e8 --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/com/gsamokovarov/skiptrace/ThreadContextInternals.java @@ -0,0 +1,54 @@ +package com.gsamokovarov.skiptrace; + +import java.lang.reflect.Field; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.DynamicScope; +import org.jruby.runtime.Frame; +import org.jruby.runtime.backtrace.BacktraceElement; +import org.jruby.runtime.builtin.IRubyObject; + +public class ThreadContextInternals { + private ThreadContext context; + + public ThreadContextInternals(ThreadContext context) { + this.context = context; + } + + public Frame[] getFrameStack() { + return (Frame[]) getPrivateField("frameStack"); + } + + public int getFrameIndex() { + return (Integer) getPrivateField("frameIndex"); + } + + public DynamicScope[] getScopeStack() { + return (DynamicScope[]) getPrivateField("scopeStack"); + } + + public int getScopeIndex() { + return (Integer) getPrivateField("scopeIndex"); + } + + public BacktraceElement[] getBacktrace() { + return (BacktraceElement[]) getPrivateField("backtrace"); + } + + public int getBacktraceIndex() { + return (Integer) getPrivateField("backtraceIndex"); + } + + private Object getPrivateField(String fieldName) { + try { + Field field = ThreadContext.class.getDeclaredField(fieldName); + + field.setAccessible(true); + + return field.get(context); + } catch (NoSuchFieldException exc) { + throw new ThreadContextInterfaceException(fieldName, exc); + } catch (IllegalAccessException exc) { + throw new ThreadContextInterfaceException(fieldName, exc); + } + } +} diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/cruby.c b/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/cruby.c new file mode 100644 index 00000000..30f51c61 --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/cruby.c @@ -0,0 +1,80 @@ +#include +#include + +static VALUE st_mSkiptrace; +static ID id_bindings; + +static VALUE +current_bindings_callback(const rb_debug_inspector_t *context, void *data) +{ + VALUE locations = rb_debug_inspector_backtrace_locations(context); + VALUE binding, bindings = rb_ary_new(); + long i, length = RARRAY_LEN(locations); + + for (i = 0; i < length; i++) { + binding = rb_debug_inspector_frame_binding_get(context, i); + + if (!NIL_P(binding)) { + rb_ary_push(bindings, binding); + } + } + + return bindings; +} + +static VALUE +current_bindings(void) +{ + return rb_debug_inspector_open(current_bindings_callback, NULL); +} + +static void +set_exception_bindings_callback(VALUE tpval, void *data) +{ + rb_trace_arg_t *trace_arg = rb_tracearg_from_tracepoint(tpval); + VALUE exception = rb_tracearg_raised_exception(trace_arg); + VALUE bindings = rb_attr_get(exception, id_bindings); + + /* Set the bindings, only if they haven't been set already. This may reset + * the binding during reraise. */ + if (NIL_P(bindings)) { + rb_ivar_set(exception, id_bindings, current_bindings()); + } +} + +static void +set_exception_bindings_on_raise(void) +{ + VALUE tpval = rb_tracepoint_new(0, RUBY_EVENT_RAISE, set_exception_bindings_callback, 0); + rb_tracepoint_enable(tpval); +} + +static VALUE +st_current_bindings(VALUE self) +{ + return current_bindings(); +} + +static VALUE +st_exc_bindings(VALUE self) +{ + VALUE bindings = rb_attr_get(self, id_bindings); + + if (NIL_P(bindings)) { + bindings = rb_ary_new(); + } + + return bindings; +} + +void +Init_cruby(void) +{ + st_mSkiptrace = rb_define_module("Skiptrace"); + id_bindings = rb_intern("bindings"); + + rb_define_singleton_method(st_mSkiptrace, "current_bindings", st_current_bindings, 0); + rb_define_method(rb_eException, "bindings", st_exc_bindings, 0); + + set_exception_bindings_on_raise(); +} diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/extconf.rb b/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/extconf.rb new file mode 100644 index 00000000..9a96a77f --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/ext/skiptrace/extconf.rb @@ -0,0 +1,15 @@ +case RUBY_ENGINE +when "ruby" + require "mkmf" + + $CFLAGS << " -Wall" + $CFLAGS << " -g3 -O0" if ENV["DEBUG"] + + create_makefile("skiptrace/internal/cruby") +else + IO.write(File.expand_path("../Makefile", __FILE__), <<-END) + all install static install-so install-rb: Makefile + .PHONY: all install static install-so install-rb + .PHONY: clean clean-so clean-static clean-rb + END +end diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/lib/bindex.rb b/path/ruby/2.6.0/gems/bindex-0.8.1/lib/bindex.rb new file mode 100644 index 00000000..c8de02e0 --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/lib/bindex.rb @@ -0,0 +1,4 @@ +require_relative "skiptrace" + +# Keep backwards compatibility with the previous name. +Bindex = Skiptrace diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/lib/skiptrace.rb b/path/ruby/2.6.0/gems/bindex-0.8.1/lib/skiptrace.rb new file mode 100644 index 00000000..ac8337dd --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/lib/skiptrace.rb @@ -0,0 +1,14 @@ +case RUBY_ENGINE +when 'rbx' + require 'skiptrace/internal/rubinius' +when 'jruby' + require 'skiptrace/internal/jruby' +when 'ruby' + require 'skiptrace/internal/cruby' +end + +require 'skiptrace/location' +require 'skiptrace/binding_locations' +require 'skiptrace/binding_ext' +require 'skiptrace/exception_ext' +require 'skiptrace/version' diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/lib/skiptrace/binding_ext.rb b/path/ruby/2.6.0/gems/bindex-0.8.1/lib/skiptrace/binding_ext.rb new file mode 100644 index 00000000..5fdb5c12 --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/lib/skiptrace/binding_ext.rb @@ -0,0 +1,5 @@ +class Binding + def source_location + eval '[__FILE__, __LINE__.to_i]' + end unless method_defined?(:source_location) +end diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/lib/skiptrace/binding_locations.rb b/path/ruby/2.6.0/gems/bindex-0.8.1/lib/skiptrace/binding_locations.rb new file mode 100644 index 00000000..c21f6543 --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/lib/skiptrace/binding_locations.rb @@ -0,0 +1,34 @@ +module Skiptrace + class BindingLocations < BasicObject + def initialize(locations, bindings) + @locations = locations + @bindings = bindings + @cached_locations = {} + end + + private + + def cached_location(location) + @cached_locations[location.to_s] ||= Location.new(location, guess_binding_around(location)) + end + + def guess_binding_around(location) + location && @bindings.find do |binding| + binding.source_location == [location.path, location.lineno] + end + end + + def method_missing(name, *args, &block) + case maybe_location = @locations.public_send(name, *args, &block) + when ::Thread::Backtrace::Location + cached_location(maybe_location) + else + maybe_location + end + end + + def respond_to_missing?(name, include_all = false) + @locations.respond_to?(name, include_all) + end + end +end diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/lib/skiptrace/exception_ext.rb b/path/ruby/2.6.0/gems/bindex-0.8.1/lib/skiptrace/exception_ext.rb new file mode 100644 index 00000000..a998e93f --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/lib/skiptrace/exception_ext.rb @@ -0,0 +1,5 @@ +class Exception + def binding_locations + @binding_locations ||= Skiptrace::BindingLocations.new(backtrace_locations, bindings) + end +end diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/lib/skiptrace/internal/jruby.rb b/path/ruby/2.6.0/gems/bindex-0.8.1/lib/skiptrace/internal/jruby.rb new file mode 100644 index 00000000..a295ccb0 --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/lib/skiptrace/internal/jruby.rb @@ -0,0 +1,7 @@ +require 'skiptrace/internal/jruby_internals' + +module Skiptrace + java_import com.gsamokovarov.skiptrace.JRubyIntegration + + JRubyIntegration.setup(JRuby.runtime) +end diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/lib/skiptrace/internal/jruby_internals.jar b/path/ruby/2.6.0/gems/bindex-0.8.1/lib/skiptrace/internal/jruby_internals.jar new file mode 100644 index 00000000..1f58641b Binary files /dev/null and b/path/ruby/2.6.0/gems/bindex-0.8.1/lib/skiptrace/internal/jruby_internals.jar differ diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/lib/skiptrace/internal/rubinius.rb b/path/ruby/2.6.0/gems/bindex-0.8.1/lib/skiptrace/internal/rubinius.rb new file mode 100644 index 00000000..905208de --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/lib/skiptrace/internal/rubinius.rb @@ -0,0 +1,66 @@ +module Skiptrace + module Rubinius + # Filters internal Rubinius locations. + # + # There are a couple of reasons why we wanna filter out the locations. + # + # * ::Kernel.raise, is implemented in Ruby for Rubinius. We don't wanna + # have the frame for it to align with the CRuby and JRuby implementations. + # + # * For internal methods location variables can be nil. We can't create a + # bindings for them. + # + # * Bindings from the current file are considered internal and ignored. + # + # We do that all that so we can align the bindings with the backtraces + # entries. + class InternalLocationFilter + def initialize(locations) + @locations = locations + end + + def filter + @locations.reject do |location| + location.file.start_with?('kernel/delta/kernel.rb') || + location.file == __FILE__ || + location.variables.nil? + end + end + end + end +end + +# Gets the current bindings for all available Ruby frames. +# +# Filters the internal Rubinius and Skiptrace frames. +def Skiptrace.current_bindings + locations = ::Rubinius::VM.backtrace(1, true) + + Skiptrace::Rubinius::InternalLocationFilter.new(locations).filter.map do |location| + Binding.setup( + location.variables, + location.variables.method, + location.constant_scope, + location.variables.self, + location + ) + end +end + +::Exception.class_eval do + def bindings + @bindings || [] + end +end + +::Rubinius.singleton_class.class_eval do + raise_exception = instance_method(:raise_exception) + + define_method(:raise_exception) do |exc| + if exc.bindings.empty? + exc.instance_variable_set(:@bindings, Skiptrace.current_bindings) + end + + raise_exception.bind(self).call(exc) + end +end diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/lib/skiptrace/location.rb b/path/ruby/2.6.0/gems/bindex-0.8.1/lib/skiptrace/location.rb new file mode 100644 index 00000000..4f099f1f --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/lib/skiptrace/location.rb @@ -0,0 +1,34 @@ +module Skiptrace + class Location + attr_reader :binding + + def initialize(location, binding) + @location = location + @binding = binding + end + + def absolute_path + @location.absolute_path + end + + def base_label + @location.base_label + end + + def inspect + @location.inspect + end + + def label + @location.label + end + + def lineno + @location.lineno + end + + def to_s + @location.to_s + end + end +end diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/lib/skiptrace/version.rb b/path/ruby/2.6.0/gems/bindex-0.8.1/lib/skiptrace/version.rb new file mode 100644 index 00000000..86858766 --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/lib/skiptrace/version.rb @@ -0,0 +1,3 @@ +module Skiptrace + VERSION = "0.8.1" +end diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/skiptrace.gemspec b/path/ruby/2.6.0/gems/bindex-0.8.1/skiptrace.gemspec new file mode 100644 index 00000000..f78688d3 --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/skiptrace.gemspec @@ -0,0 +1,27 @@ +$LOAD_PATH << File.expand_path('../lib', __FILE__) + +require 'skiptrace/version' + +Gem::Specification.new do |spec| + spec.name = "skiptrace" + spec.version = Skiptrace::VERSION + spec.authors = ["Genadi Samokovarov"] + spec.email = ["gsamokovarov@gmail.com"] + spec.extensions = ["ext/skiptrace/extconf.rb"] + spec.summary = "Bindings for your Ruby exceptions" + spec.homepage = "https://github.com/gsamokovarov/skiptrace" + spec.license = "MIT" + + spec.required_ruby_version = ">= 2.5.0" + + spec.files = `git ls-files -z`.split("\x0") + spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } + spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) + spec.require_paths = ["lib"] + spec.extensions = ["ext/skiptrace/extconf.rb"] + + spec.add_development_dependency "minitest", "~> 5.4" + spec.add_development_dependency "bundler" + spec.add_development_dependency "rake" + spec.add_development_dependency "rake-compiler" +end diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/test/fixtures/basic_nested_fixture.rb b/path/ruby/2.6.0/gems/bindex-0.8.1/test/fixtures/basic_nested_fixture.rb new file mode 100644 index 00000000..1aa680a0 --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/test/fixtures/basic_nested_fixture.rb @@ -0,0 +1,17 @@ +module Skiptrace + module BasicNestedFixture + extend self + + def call + raise_an_error + rescue => exc + exc + end + + private + + def raise_an_error + raise + end + end +end diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/test/fixtures/custom_error_fixture.rb b/path/ruby/2.6.0/gems/bindex-0.8.1/test/fixtures/custom_error_fixture.rb new file mode 100644 index 00000000..695c4ae0 --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/test/fixtures/custom_error_fixture.rb @@ -0,0 +1,11 @@ +module Skiptrace + module CustomErrorFixture + Error = Class.new(StandardError) + + def self.call + raise Error + rescue => exc + exc + end + end +end diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/test/fixtures/eval_nested_fixture.rb b/path/ruby/2.6.0/gems/bindex-0.8.1/test/fixtures/eval_nested_fixture.rb new file mode 100644 index 00000000..3f0049eb --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/test/fixtures/eval_nested_fixture.rb @@ -0,0 +1,17 @@ +module Skiptrace + module EvalNestedFixture + extend self + + def call + tap { raise_an_error_in_eval } + rescue => exc + exc + end + + private + + def raise_an_error_in_eval + eval 'raise', binding, __FILE__, __LINE__ + end + end +end diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/test/fixtures/flat_fixture.rb b/path/ruby/2.6.0/gems/bindex-0.8.1/test/fixtures/flat_fixture.rb new file mode 100644 index 00000000..a1aa5ab7 --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/test/fixtures/flat_fixture.rb @@ -0,0 +1,9 @@ +module Skiptrace + module FlatFixture + def self.call + raise + rescue => exc + exc + end + end +end diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/test/fixtures/reraised_fixture.rb b/path/ruby/2.6.0/gems/bindex-0.8.1/test/fixtures/reraised_fixture.rb new file mode 100644 index 00000000..f13962f1 --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/test/fixtures/reraised_fixture.rb @@ -0,0 +1,23 @@ +module Skiptrace + module ReraisedFixture + extend self + + def call + reraise_an_error + rescue => exc + exc + end + + private + + def raise_an_error_in_eval + method_that_raises + rescue => exc + raise exc + end + + def method_that_raises + raise + end + end +end diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/test/skiptrace/current_bindings_test.rb b/path/ruby/2.6.0/gems/bindex-0.8.1/test/skiptrace/current_bindings_test.rb new file mode 100644 index 00000000..4da2cb67 --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/test/skiptrace/current_bindings_test.rb @@ -0,0 +1,11 @@ +require 'test_helper' + +module Skiptrace + class CurrentBindingsTest < Test + test 'first binding returned is the current one' do + _, lineno = Skiptrace.current_bindings.first.source_location + + assert_equal __LINE__ - 2, lineno + end + end +end diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/test/skiptrace/exception_test.rb b/path/ruby/2.6.0/gems/bindex-0.8.1/test/skiptrace/exception_test.rb new file mode 100644 index 00000000..94158a26 --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/test/skiptrace/exception_test.rb @@ -0,0 +1,67 @@ +require 'test_helper' + +module Skiptrace + class ExceptionTest < Test + test 'bindings returns all the bindings of where the error originated' do + exc = FlatFixture.() + + assert_equal 4, exc.bindings.first.source_location.last + end + + test 'bindings returns all the bindings of where a custom error originate' do + exc = CustomErrorFixture.() + + assert_equal 6, exc.bindings.first.source_location.last + end + + test 'bindings goes down the stack' do + exc = BasicNestedFixture.() + + assert_equal 14, exc.bindings.first.source_location.last + end + + test 'bindings inside of an eval' do + exc = EvalNestedFixture.() + + assert_equal 14, exc.bindings.first.source_location.last + end + + test "re-raising doesn't lose bindings information" do + exc = ReraisedFixture.() + + assert_equal 6, exc.bindings.first.source_location.last + end + + test 'bindings is empty when exception is still not raised' do + exc = RuntimeError.new + + assert_equal [], exc.bindings + end + + test 'bindings is empty when set backtrace is badly called' do + exc = RuntimeError.new + + # Exception#set_backtrace expects a string or array of strings. If the + # input isn't like this it will raise a TypeError. + assert_raises(TypeError) do + exc.set_backtrace([nil]) + end + + assert_equal [], exc.bindings + end + + test 'binding_locations maps closely to backtrace_locations' do + exc = FlatFixture.() + + exc.binding_locations.first.tap do |location| + assert_equal 4, location.lineno + assert_equal exc, location.binding.eval('exc') + end + + exc.binding_locations[1].tap do |location| + assert_equal 54, location.lineno + assert_equal exc, location.binding.eval('exc') + end + end + end +end diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/test/skiptrace/location_test.rb b/path/ruby/2.6.0/gems/bindex-0.8.1/test/skiptrace/location_test.rb new file mode 100644 index 00000000..932e54ae --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/test/skiptrace/location_test.rb @@ -0,0 +1,18 @@ +require 'test_helper' + +module Skiptrace + class LocationTest < Test + test 'behaves like Thread::Backtrace::Location' do + native_location = caller_locations.first + location = Skiptrace::Location.new(native_location, binding) + + assert_equal native_location.absolute_path, location.absolute_path + assert_equal native_location.base_label, location.base_label + assert_equal native_location.inspect, location.inspect + assert_equal native_location.label, location.label + assert_equal native_location.to_s, location.to_s + + assert_equal [__FILE__, __LINE__ - 8], location.binding.source_location + end + end +end diff --git a/path/ruby/2.6.0/gems/bindex-0.8.1/test/test_helper.rb b/path/ruby/2.6.0/gems/bindex-0.8.1/test/test_helper.rb new file mode 100644 index 00000000..68e4b924 --- /dev/null +++ b/path/ruby/2.6.0/gems/bindex-0.8.1/test/test_helper.rb @@ -0,0 +1,19 @@ +$LOAD_PATH << File.expand_path('../lib', __FILE__) + +require 'minitest/autorun' +require 'skiptrace' + +current_directory = File.dirname(File.expand_path(__FILE__)) + +# Fixtures are plain classes that respond to #call. +Dir["#{current_directory}/fixtures/**/*.rb"].each do |fixture| + require fixture +end + +module Skiptrace + class Test < MiniTest::Test + def self.test(name, &block) + define_method("test_#{name}", &block) + end + end +end diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/.github/CODEOWNERS b/path/ruby/2.6.0/gems/bootsnap-1.4.5/.github/CODEOWNERS new file mode 100644 index 00000000..90725432 --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# mvm:maintainer +* @burke diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/.github/probots.yml b/path/ruby/2.6.0/gems/bootsnap-1.4.5/.github/probots.yml new file mode 100644 index 00000000..1491d275 --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/.github/probots.yml @@ -0,0 +1,2 @@ +enabled: + - cla diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/.gitignore b/path/ruby/2.6.0/gems/bootsnap-1.4.5/.gitignore new file mode 100644 index 00000000..d3182608 --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/.gitignore @@ -0,0 +1,17 @@ +/.bundle/ +/.yardoc +/Gemfile.lock +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ +*.bundle +*.so +*.o +*.a +*.gem +*.db +mkmf.log +.rubocop-* diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/.rubocop.yml b/path/ruby/2.6.0/gems/bootsnap-1.4.5/.rubocop.yml new file mode 100644 index 00000000..1bdaa512 --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/.rubocop.yml @@ -0,0 +1,20 @@ +inherit_from: + - http://shopify.github.io/ruby-style-guide/rubocop.yml + +AllCops: + Exclude: + - 'vendor/**/*' + - 'tmp/**/*' + TargetRubyVersion: '2.2' + +# This doesn't take into account retrying from an exception +Lint/HandleExceptions: + Enabled: false + +# allow String.new to create mutable strings +Style/EmptyLiteral: + Enabled: false + +# allow the use of globals which makes sense in a CLI app like this +Style/GlobalVars: + Enabled: false diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/.travis.yml b/path/ruby/2.6.0/gems/bootsnap-1.4.5/.travis.yml new file mode 100644 index 00000000..f1e7f826 --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/.travis.yml @@ -0,0 +1,21 @@ +language: ruby +sudo: false + +os: + - linux + - osx + +rvm: + - ruby-2.4 + - ruby-2.5 + - ruby-head + +matrix: + allow_failures: + - rvm: ruby-head + include: + - rvm: jruby + os: linux + env: MINIMAL_SUPPORT=1 + +script: bin/ci diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/CHANGELOG.md b/path/ruby/2.6.0/gems/bootsnap-1.4.5/CHANGELOG.md new file mode 100644 index 00000000..909d434b --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/CHANGELOG.md @@ -0,0 +1,122 @@ +# 1.4.5 + +* MRI 2.7 support +* Fixed concurrency bugs + +# 1.4.4 + +* Disable ISeq cache in `bootsnap/setup` by default in Ruby 2.5 + +# 1.4.3 + +* Fix some cache permissions and umask issues after switch to mkstemp + +# 1.4.2 + +* Fix bug when removing features loaded by relative path from `$LOADED_FEATURES` +* Fix bug with propagation of `NameError` up from nested calls to `require` + +# 1.4.1 + +* Don't register change observers to frozen objects. + +# 1.4.0 + +* When running in development mode, always fall back to a full path scan on LoadError, making + bootsnap more able to detect newly-created files. (#230) +* Respect `$LOADED_FEATURES.delete` in order to support code reloading, for integration with + Zeitwerk. (#230) +* Minor performance improvement: flow-control exceptions no longer generate backtraces. +* Better support for requiring from environments where some features are not supported (especially + JRuby). (#226)k +* More robust handling of OS errors when creating files. (#225) + +# 1.3.2 + +* Fix Spring + Bootsnap incompatibility when there are files with similar names. +* Fix `YAML.load_file` monkey patch to keep accepting File objects as arguments. +* Fix the API for `ActiveSupport::Dependencies#autoloadable_module?`. +* Some performance improvements. + +# 1.3.1 + +* Change load path scanning to more correctly follow symlinks. + +# 1.3.0 + +* Handle cases where load path entries are symlinked (https://github.com/Shopify/bootsnap/pull/136) + +# 1.2.1 + +* Fix method visibility of `Kernel#require`. + +# 1.2.0 + +* Add `LoadedFeaturesIndex` to preserve fix a common bug related to `LOAD_PATH` modifications after + loading bootsnap. + +# 1.1.8 + +* Don't cache YAML documents with `!ruby/object` +* Fix cache write mode on Windows + +# 1.1.7 + +* Create cache entries as 0775/0664 instead of 0755/0644 +* Better handling around cache updates in highly-parallel workloads + +# 1.1.6 + +* Assortment of minor bugfixes + +# 1.1.5 + +* bugfix re-release of 1.1.4 + +# 1.1.4 (yanked) + +* Avoid loading a constant twice by checking if it is already defined + +# 1.1.3 + +* Properly resolve symlinked path entries + +# 1.1.2 + +* Minor fix: deprecation warning + +# 1.1.1 + +* Fix crash in `Native.compile_option_crc32=` on 32-bit platforms. + +# 1.1.0 + +* Add `bootsnap/setup` +* Support jruby (without compile caching features) +* Better deoptimization when Coverage is enabled +* Consider `Bundler.bundle_path` to be stable + +# 1.0.0 + +* (none) + +# 0.3.2 + +* Minor performance savings around checking validity of cache in the presence of relative paths. +* When coverage is enabled, skips optimization instead of exploding. + +# 0.3.1 + +* Don't whitelist paths under `RbConfig::CONFIG['prefix']` as stable; instead use `['libdir']` (#41). +* Catch `EOFError` when reading load-path-cache and regenerate cache. +* Support relative paths in load-path-cache. + +# 0.3.0 + +* Migrate CompileCache from xattr as a cache backend to a cache directory + * Adds support for Linux and FreeBSD + +# 0.2.15 + +* Support more versions of ActiveSupport (`depend_on`'s signature varies; don't reiterate it) +* Fix bug in handling autoloaded modules that raise NoMethodError diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/CODE_OF_CONDUCT.md b/path/ruby/2.6.0/gems/bootsnap-1.4.5/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..83e2f1a8 --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at burke@libbey.me. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/CONTRIBUTING.md b/path/ruby/2.6.0/gems/bootsnap-1.4.5/CONTRIBUTING.md new file mode 100644 index 00000000..8b65c28c --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/CONTRIBUTING.md @@ -0,0 +1,21 @@ +# Contributing to Bootsnap + +We love receiving pull requests! + +## Standards + +* PR should explain what the feature does, and why the change exists. +* PR should include any carrier specific documentation explaining how it works. +* Code _must_ be tested, including both unit and remote tests where applicable. +* Be consistent. Write clean code that follows [Ruby community standards](https://github.com/bbatsov/ruby-style-guide). +* Code should be generic and reusable. + +If you're stuck, ask questions! + +## How to contribute + +1. Fork it ( https://github.com/Shopify/bootsnap/fork ) +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create a new Pull Request diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/Gemfile b/path/ruby/2.6.0/gems/bootsnap-1.4.5/Gemfile new file mode 100644 index 00000000..d56191f4 --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/Gemfile @@ -0,0 +1,8 @@ +source 'https://rubygems.org' + +# Specify your gem's dependencies in bootsnap.gemspec +gemspec + +group :development do + gem 'rubocop' +end diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/LICENSE.txt b/path/ruby/2.6.0/gems/bootsnap-1.4.5/LICENSE.txt new file mode 100644 index 00000000..7ae79d1e --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Shopify, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/README.jp.md b/path/ruby/2.6.0/gems/bootsnap-1.4.5/README.jp.md new file mode 100644 index 00000000..f4380cd5 --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/README.jp.md @@ -0,0 +1,231 @@ +# Bootsnap [![Build Status](https://travis-ci.org/Shopify/bootsnap.svg?branch=master)](https://travis-ci.org/Shopify/bootsnap) + +Bootsnap は RubyVM におけるバイトコード生成やファイルルックアップ等の時間のかかる処理を最適化するためのライブラリです。ActiveSupport や YAML もサポートしています。[内部動作](#内部動作)もご覧ください。 + +注意書き: このライブラリは英語話者によって管理されています。この README は日本語ですが、日本語でのサポートはしておらず、リクエストにお答えすることもできません。バイリンガルの方がサポートをサポートしてくださる場合はお知らせください!:) + +### パフォーマンス + +* [Discourse](https://github.com/discourse/discourse) では、約6秒から3秒まで、約50%の起動時間短縮が確認されています。 +* 小さなアプリケーションでも、50%の改善(3.6秒から1.8秒)が確認されています。 +* 非常に巨大でモノリシックなアプリである Shopify のプラットフォームでは、約25秒から6.5秒へと約75%短縮されました。 + +## 使用方法 + +この gem は macOS と Linux で作動します。まずは、`bootsnap` を `Gemfile` に追加します: + +```ruby +gem 'bootsnap', require: false +``` + +Rails を使用している場合は、以下のコードを、`config/boot.rb` 内にある `require 'bundler/setup'` の直後に追加してください。 + +```ruby +require 'bootsnap/setup' +``` + +単に `gem 'bootsnap', require: 'bootsnap/setup'` と指定することも技術的には可能ですが、最大限のパフォーマンス改善を得るためには Bootsnap をできるだけ早く読み込むことが重要です。 + +この require の仕組みは[こちら](https://github.com/Shopify/bootsnap/blob/master/lib/bootsnap/setup.rb)で確認できます。 + +Rails を使用していない場合、または、より多くの設定を変更したい場合は、以下のコードを `require 'bundler/setup'` の直後に追加してください(早く読み込まれるほど、より多くのものを最適化することができます)。 + +```ruby +require 'bootsnap' +env = ENV['RAILS_ENV'] || "development" +Bootsnap.setup( +  cache_dir:           'tmp/cache',         # キャッシュファイルを保存する path +  development_mode:     env == 'development', # 現在の作業環境、例えば RACK_ENV, RAILS_ENV など。 + load_path_cache: true, # キャッシュで LOAD_PATH を最適化する。 +  autoload_paths_cache: true,                 # キャッシュで ActiveSupport による autoload を行う。 +  disable_trace:       true,                 # (アルファ) `RubyVM::InstructionSequence.compile_option = { trace_instruction: false }`をセットする。 +  compile_cache_iseq:   true,                 # ISeq キャッシュをコンパイルする +  compile_cache_yaml:   true                 # YAML キャッシュをコンパイルする +) +``` + +**ヒント**: `require 'bootsnap'` を `BootLib::Require.from_gem('bootsnap', 'bootsnap')` で、 [こちらのトリック](https://github.com/Shopify/bootsnap/wiki/Bootlib::Require)を使って置き換えることができます。こうすると、巨大な`$LOAD_PATH`がある場合でも、起動時間を最短化するのに役立ちます。 + +注意: Bootsnap と [Spring](https://github.com/rails/spring) は別領域の問題を扱うツールです。Bootsnap は個々のソースファイルの読み込みを高速化します。一方で、Spring は起動されたRailsプロセスのコピーを保持して次回の起動時に起動プロセスの一部を完全にスキップします。2つのツールはうまく連携しており、どちらも新しく生成された Rails アプリケーションにデフォルトで含まれています。 + +### 環境 +Bootsnapのすべての機能はセットアップ時の設定に従って開発、テスト、プロダクション、および他のすべての環境で有効化されます。Shopify では、この gem を問題なくすべての環境で安全に使用しています。 + +特定の環境で機能を無効にする場合は、必要に応じて適切な ENV 変数または設定を考慮して設定を変更することをおすすめします。 + +## 内部動作 + +Bootsnap は、処理に時間のかかるメソッドの結果をキャッシュすることで最適化しています。これは、大きく分けて2つのカテゴリに分けられます。 + +* [Path Pre-Scanning](#path-pre-scanning) + * `Kernel#require` と `Kernel#load` を `$LOAD_PATH` フルスキャンを行わないように変更します。 + * `ActiveSupport::Dependencies.{autoloadable_module?,load_missing_constant,depend_on}` を `ActiveSupport::Dependencies.autoload_paths` のフルスキャンを行わないようにオーバーライドします。 +* [Compilation caching](#compilation-caching) + * Ruby バイトコードのコンパイル結果をキャッシュするためのメソッド `RubyVM::InstructionSequence.load_iseq` が実装されています。 + * `YAML.load_file` を YAML オブジェクトのロード結果を MessagePack でキャッシュするように変更します。 MessagePack でサポートされていないタイプが使われている場合は Marshal が使われます。 + +### Path Pre-Scanning + +_(このライブラリは [bootscale](https://github.com/byroot/bootscale) という別のライブラリを元に開発されました)_ + +Bootsnap の初期化時、あるいはパス(例えば、`$LOAD_PATH`)の変更時に、`Bootsnap::LoadPathCache` がキャッシュから必要なエントリーのリストを読み込みます。または、必要に応じてフルスキャンを実行し結果をキャッシュします。 +その後、たとえば `require 'foo'` を評価する場合, Ruby は `$LOAD_PATH` `['x', 'y', ...]` のすべてのエントリーを繰り返し評価することで `x/foo.rb`, `y/foo.rb` などを探索します。これに対して Bootsnap は、キャッシュされた require 可能なファイルと `$LOAD_PATH` を見ることで、Rubyが最終的に選択するであろうパスで置き換えます。 + +この動作によって生成された syscall を見ると、最終的な結果は以前なら次のようになります。 + +``` +open x/foo.rb # (fail) +# (imagine this with 500 $LOAD_PATH entries instead of two) +open y/foo.rb # (success) +close y/foo.rb +open y/foo.rb +... +``` + +これが、次のようになります: + +``` +open y/foo.rb +... +``` + +`autoload_paths_cache` オプションが `Bootsnap.setup` に与えられている場合、`ActiveSupport::Dependencies.autoload_paths` をトラバースする方法にはまったく同じ最適化が使用されます。 + +`*_path_cache` を機能させるオーバーライドを図にすると、次のようになります。 + +![Bootsnapの説明図](https://cloud.githubusercontent.com/assets/3074765/24532120/eed94e64-158b-11e7-9137-438d759b2ac8.png) + +Bootsnap は、 `$LOAD_PATH` エントリを安定エントリと不安定エントリの2つのカテゴリに分類します。不安定エントリはアプリケーションが起動するたびにスキャンされ、そのキャッシュは30秒間だけ有効になります。安定エントリーに期限切れはありません。コンテンツがスキャンされると、決して変更されないものとみなされます。 + +安定していると考えられる唯一のディレクトリは、Rubyのインストールプレフィックス (`RbConfig::CONFIG['prefix']`, または `/usr/local/ruby` や `~/.rubies/x.y.z`)下にあるものと、`Gem.path` (たとえば `~/.gem/ruby/x.y.z`) や `Bundler.bundle_path` 下にあるものです。他のすべては不安定エントリと分類されます。 + +[`Bootsnap::LoadPathCache::Cache`](https://github.com/Shopify/bootsnap/blob/master/lib/bootsnap/load_path_cache/cache.rb) に加えて次の図では、エントリの解決がどのように機能するかを理解するのに役立つかもしれません。経路探索は以下のようになります。 + +![パス探索の仕組み](https://cloud.githubusercontent.com/assets/3074765/25388270/670b5652-299b-11e7-87fb-975647f68981.png) + +また、`LoadError` のスキャンがどれほど重いかに注意を払うことも大切です。もし Ruby が `require 'something'` を評価し、そのファイルが `$LOAD_PATH` にない場合は、それを知るために `2 * $LOAD_PATH.length` のファイルシステムアスセスが必要になります。Bootsnap は、ファイルシステムにまったく触れずに `LoadError` を投げ、この結果をキャッシュします。 + +## Compilation Caching + +*(このコンセプトのより分かりやすい解説は [yomikomu](https://github.com/ko1/yomikomu) をお読み下さい。)* + +Ruby には複雑な文法が実装されており、構文解析は簡単なオペレーションではありません。1.9以降、Ruby は Ruby ソースを内部のバイトコードに変換した後、Ruby VM によって実行してきました。2.3.0 以降、[RubyはAPIを公開し](https://ruby-doc.org/core-2.3.0/RubyVM/InstructionSequence.html)、そのバイトコードをキャッシュすることができるようになりました。これにより、同じファイルが複数ロードされた時の、比較的時間のかかる部分をバイパスすることができます。 + +また、アプリケーションの起動時に YAML ドキュメントの読み込みに多くの時間を費やしていることを発見しました。そして、 MessagePack と Marshal は deserialization にあたって YAML よりもはるかに高速であるということに気付きました。そこで、YAML ドキュメントを、Ruby バイトコードと同じコンパイルキャッシングの最適化を施すことで、高速化しています。Ruby の "バイトコード" フォーマットに相当するものは MessagePack ドキュメント (あるいは、MessagePack をサポートしていないタイプの YAML ドキュメントの場合は、Marshal stream)になります。 + +これらのコンパイル結果は、入力ファイル(FNV1a-64)のフルパスのハッシュを取って生成されたファイル名で、キャッシュディレクトリに保存されます。 + +Bootsnap 無しでは、ファイルを `require` するために生成された syscall の順序は次のようになっていました: + +``` +open /c/foo.rb -> m +fstat64 m +close m +open /c/foo.rb -> o +fstat64 o +fstat64 o +read o +read o +... +close o +``` + +しかし Bootsnap では、次のようになります: + +``` +open /c/foo.rb -> n +fstat64 n +close n +open /c/foo.rb -> n +fstat64 n +open (cache) -> m +read m +read m +close m +close n +``` + +これは一見劣化していると思われるかもしれませんが、性能に大きな違いがあります。 + +*(両方のリストの最初の3つの syscalls -- `open`, `fstat64`, `close` -- は本質的に有用ではありません。[このRubyパッチ](https://bugs.ruby-lang.org/issues/13378)は、Boosnap と組み合わせることによって、それらを最適化しています)* + +Bootsnap は、64バイトのヘッダーとそれに続くキャッシュの内容を含んだキャッシュファイルを書き込みます。ヘッダーは、次のいくつかのフィールドで構成されるキャッシュキーです。 + +- `version`、Bootsnapにハードコードされる基本的なスキーマのバージョン +- `os_version`、(macOS, BSDの) 現在のカーネルバージョンか 、(Linuxの) glibc のバージョンのハッシュ +- `compile_option`、`RubyVM::InstructionSequence.compile_option` の返り値 +- `ruby_revision`、コンパイルされたRubyのバージョン +- `size`、ソースファイルのサイズ +- `mtime`、コンパイル時のソースファイルの最終変更タイムスタンプ +- `data_size`、バッファに読み込む必要のあるヘッダーに続くバイト数。 + +キーが有効な場合、キャッシュがファイルからロードされます。そうでない場合、キャッシュは再生成され、現在のキャッシュを破棄します。 + +# 最終的なキャッシュ結果 + +次のファイル構造があるとします。 + +``` +/ +├── a +├── b +└── c + └── foo.rb +``` + +そして、このような `$LOAD_PATH` があるとします。 + +``` +["/a", "/b", "/c"] +``` + +Bootsnap なしで `require 'foo'` を呼び出すと、Ruby は次の順序で syscalls を生成します: + +``` +open /a/foo.rb -> -1 +open /b/foo.rb -> -1 +open /c/foo.rb -> n +close n +open /c/foo.rb -> m +fstat64 m +close m +open /c/foo.rb -> o +fstat64 o +fstat64 o +read o +read o +... +close o +``` + +しかし Bootsnap では、次のようになります: + +``` +open /c/foo.rb -> n +fstat64 n +close n +open /c/foo.rb -> n +fstat64 n +open (cache) -> m +read m +read m +close m +close n +``` + +Bootsnap なしで `require 'nope'` を呼び出すと、次のようになります: + +``` +open /a/nope.rb -> -1 +open /b/nope.rb -> -1 +open /c/nope.rb -> -1 +open /a/nope.bundle -> -1 +open /b/nope.bundle -> -1 +open /c/nope.bundle -> -1 +``` + +...そして、Bootsnap で `require 'nope'` を呼び出すと、次のようになります... + +``` +# (nothing!) +``` diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/README.md b/path/ruby/2.6.0/gems/bootsnap-1.4.5/README.md new file mode 100644 index 00000000..68bd33cc --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/README.md @@ -0,0 +1,304 @@ +# Bootsnap [![Build Status](https://travis-ci.org/Shopify/bootsnap.svg?branch=master)](https://travis-ci.org/Shopify/bootsnap) + +Bootsnap is a library that plugs into Ruby, with optional support for `ActiveSupport` and `YAML`, +to optimize and cache expensive computations. See [How Does This Work](#how-does-this-work). + +#### Performance + +- [Discourse](https://github.com/discourse/discourse) reports a boot time reduction of approximately + 50%, from roughly 6 to 3 seconds on one machine; +- One of our smaller internal apps also sees a reduction of 50%, from 3.6 to 1.8 seconds; +- The core Shopify platform -- a rather large monolithic application -- boots about 75% faster, + dropping from around 25s to 6.5s. +* In Shopify core (a large app), about 25% of this gain can be attributed to `compile_cache_*` + features; 75% to path caching, and ~1% to `disable_trace`. This is fairly representative. + +## Usage + +This gem works on macOS and Linux. + +Add `bootsnap` to your `Gemfile`: + +```ruby +gem 'bootsnap', require: false +``` + +If you are using Rails, add this to `config/boot.rb` immediately after `require 'bundler/setup'`: + +```ruby +require 'bootsnap/setup' +``` + +Note that bootsnap writes to `tmp/cache`, and that directory *must* be writable. Rails will fail to +boot if it is not. If this is unacceptable (e.g. you are running in a read-only container and +unwilling to mount in a writable tmpdir), you should remove this line or wrap it in a conditional. + +**Note also that bootsnap will never clean up its own cache: this is left up to you. Depending on your +deployment strategy, you may need to periodically purge `tmp/cache/bootsnap*`. If you notice deploys +getting progressively slower, this is almost certainly the cause.** + +It's technically possible to simply specify `gem 'bootsnap', require: 'bootsnap/setup'`, but it's +important to load Bootsnap as early as possible to get maximum performance improvement. + +You can see how this require works [here](https://github.com/Shopify/bootsnap/blob/master/lib/bootsnap/setup.rb). + +If you are not using Rails, or if you are but want more control over things, add this to your +application setup immediately after `require 'bundler/setup'` (i.e. as early as possible: the sooner +this is loaded, the sooner it can start optimizing things) + +```ruby +require 'bootsnap' +env = ENV['RAILS_ENV'] || "development" +Bootsnap.setup( + cache_dir: 'tmp/cache', # Path to your cache + development_mode: env == 'development', # Current working environment, e.g. RACK_ENV, RAILS_ENV, etc + load_path_cache: true, # Optimize the LOAD_PATH with a cache + autoload_paths_cache: true, # Optimize ActiveSupport autoloads with cache + disable_trace: true, # Set `RubyVM::InstructionSequence.compile_option = { trace_instruction: false }` + compile_cache_iseq: true, # Compile Ruby code into ISeq cache, breaks coverage reporting. + compile_cache_yaml: true # Compile YAML into a cache +) +``` + +**Note that `disable_trace` will break debuggers and tracing.** + +**Protip:** You can replace `require 'bootsnap'` with `BootLib::Require.from_gem('bootsnap', +'bootsnap')` using [this trick](https://github.com/Shopify/bootsnap/wiki/Bootlib::Require). This +will help optimize boot time further if you have an extremely large `$LOAD_PATH`. + +Note: Bootsnap and [Spring](https://github.com/rails/spring) are orthogonal tools. While Bootsnap +speeds up the loading of individual source files, Spring keeps a copy of a pre-booted Rails process +on hand to completely skip parts of the boot process the next time it's needed. The two tools work +well together, and are both included in a newly-generated Rails applications by default. + +### Environments + +All Bootsnap features are enabled in development, test, production, and all other environments according to the configuration in the setup. At Shopify, we use this gem safely in all environments without issue. + +If you would like to disable any feature for a certain environment, we suggest changing the configuration to take into account the appropriate ENV var or configuration according to your needs. + +## How does this work? + +Bootsnap optimizes methods to cache results of expensive computations, and can be grouped +into two broad categories: + +* [Path Pre-Scanning](#path-pre-scanning) + * `Kernel#require` and `Kernel#load` are modified to eliminate `$LOAD_PATH` scans. + * `ActiveSupport::Dependencies.{autoloadable_module?,load_missing_constant,depend_on}` are + overridden to eliminate scans of `ActiveSupport::Dependencies.autoload_paths`. +* [Compilation caching](#compilation-caching) + * `RubyVM::InstructionSequence.load_iseq` is implemented to cache the result of ruby bytecode + compilation. + * `YAML.load_file` is modified to cache the result of loading a YAML object in MessagePack format + (or Marshal, if the message uses types unsupported by MessagePack). + +### Path Pre-Scanning + +*(This work is a minor evolution of [bootscale](https://github.com/byroot/bootscale)).* + +Upon initialization of bootsnap or modification of the path (e.g. `$LOAD_PATH`), +`Bootsnap::LoadPathCache` will fetch a list of requirable entries from a cache, or, if necessary, +perform a full scan and cache the result. + +Later, when we run (e.g.) `require 'foo'`, ruby *would* iterate through every item on our +`$LOAD_PATH` `['x', 'y', ...]`, looking for `x/foo.rb`, `y/foo.rb`, and so on. Bootsnap instead +looks at all the cached requirables for each `$LOAD_PATH` entry and substitutes the full expanded +path of the match ruby would have eventually chosen. + +If you look at the syscalls generated by this behaviour, the net effect is that what would +previously look like this: + +``` +open x/foo.rb # (fail) +# (imagine this with 500 $LOAD_PATH entries instead of two) +open y/foo.rb # (success) +close y/foo.rb +open y/foo.rb +... +``` + +becomes this: + +``` +open y/foo.rb +... +``` + +Exactly the same strategy is employed for methods that traverse +`ActiveSupport::Dependencies.autoload_paths` if the `autoload_paths_cache` option is given to +`Bootsnap.setup`. + +The following diagram flowcharts the overrides that make the `*_path_cache` features work. + +![Flowchart explaining +Bootsnap](https://cloud.githubusercontent.com/assets/3074765/24532120/eed94e64-158b-11e7-9137-438d759b2ac8.png) + +Bootsnap classifies path entries into two categories: stable and volatile. Volatile entries are +scanned each time the application boots, and their caches are only valid for 30 seconds. Stable +entries do not expire -- once their contents has been scanned, it is assumed to never change. + +The only directories considered "stable" are things under the Ruby install prefix +(`RbConfig::CONFIG['prefix']`, e.g. `/usr/local/ruby` or `~/.rubies/x.y.z`), and things under the +`Gem.path` (e.g. `~/.gem/ruby/x.y.z`) or `Bundler.bundle_path`. Everything else is considered +"volatile". + +In addition to the [`Bootsnap::LoadPathCache::Cache` +source](https://github.com/Shopify/bootsnap/blob/master/lib/bootsnap/load_path_cache/cache.rb), +this diagram may help clarify how entry resolution works: + +![How path searching works](https://cloud.githubusercontent.com/assets/3074765/25388270/670b5652-299b-11e7-87fb-975647f68981.png) + + +It's also important to note how expensive `LoadError`s can be. If ruby invokes +`require 'something'`, but that file isn't on `$LOAD_PATH`, it takes `2 * +$LOAD_PATH.length` filesystem accesses to determine that. Bootsnap caches this +result too, raising a `LoadError` without touching the filesystem at all. + +### Compilation Caching + +*(A more readable implementation of this concept can be found in +[yomikomu](https://github.com/ko1/yomikomu)).* + +Ruby has complex grammar and parsing it is not a particularly cheap operation. Since 1.9, Ruby has +translated ruby source to an internal bytecode format, which is then executed by the Ruby VM. Since +2.3.0, Ruby [exposes an API](https://ruby-doc.org/core-2.3.0/RubyVM/InstructionSequence.html) that +allows caching that bytecode. This allows us to bypass the relatively-expensive compilation step on +subsequent loads of the same file. + +We also noticed that we spend a lot of time loading YAML documents during our application boot, and +that MessagePack and Marshal are *much* faster at deserialization than YAML, even with a fast +implementation. We use the same strategy of compilation caching for YAML documents, with the +equivalent of Ruby's "bytecode" format being a MessagePack document (or, in the case of YAML +documents with types unsupported by MessagePack, a Marshal stream). + +These compilation results are stored in a cache directory, with filenames generated by taking a hash +of the full expanded path of the input file (FNV1a-64). + +Whereas before, the sequence of syscalls generated to `require` a file would look like: + +``` +open /c/foo.rb -> m +fstat64 m +close m +open /c/foo.rb -> o +fstat64 o +fstat64 o +read o +read o +... +close o +``` + +With bootsnap, we get: + +``` +open /c/foo.rb -> n +fstat64 n +close n +open /c/foo.rb -> n +fstat64 n +open (cache) -> m +read m +read m +close m +close n +``` + +This may look worse at a glance, but underlies a large performance difference. + +*(The first three syscalls in both listings -- `open`, `fstat64`, `close` -- are not inherently +useful. [This ruby patch](https://bugs.ruby-lang.org/issues/13378) optimizes them out when coupled +with bootsnap.)* + +Bootsnap writes a cache file containing a 64 byte header followed by the cache contents. The header +is a cache key including several fields: + +* `version`, hardcoded in bootsnap. Essentially a schema version; +* `os_version`, A hash of the current kernel version (on macOS, BSD) or glibc version (on Linux); +* `compile_option`, which changes with `RubyVM::InstructionSequence.compile_option` does; +* `ruby_revision`, the version of Ruby this was compiled with; +* `size`, the size of the source file; +* `mtime`, the last-modification timestamp of the source file when it was compiled; and +* `data_size`, the number of bytes following the header, which we need to read it into a buffer. + +If the key is valid, the result is loaded from the value. Otherwise, it is regenerated and clobbers +the current cache. + +### Putting it all together + +Imagine we have this file structure: + +``` +/ +├── a +├── b +└── c + └── foo.rb +``` + +And this `$LOAD_PATH`: + +``` +["/a", "/b", "/c"] +``` + +When we call `require 'foo'` without bootsnap, Ruby would generate this sequence of syscalls: + + +``` +open /a/foo.rb -> -1 +open /b/foo.rb -> -1 +open /c/foo.rb -> n +close n +open /c/foo.rb -> m +fstat64 m +close m +open /c/foo.rb -> o +fstat64 o +fstat64 o +read o +read o +... +close o +``` + +With bootsnap, we get: + +``` +open /c/foo.rb -> n +fstat64 n +close n +open /c/foo.rb -> n +fstat64 n +open (cache) -> m +read m +read m +close m +close n +``` + +If we call `require 'nope'` without bootsnap, we get: + +``` +open /a/nope.rb -> -1 +open /b/nope.rb -> -1 +open /c/nope.rb -> -1 +open /a/nope.bundle -> -1 +open /b/nope.bundle -> -1 +open /c/nope.bundle -> -1 +``` + +...and if we call `require 'nope'` *with* bootsnap, we get... + +``` +# (nothing!) +``` + +## When not to use Bootsnap + +*Alternative engines*: Bootsnap is pretty reliant on MRI features, and parts are disabled entirely on alternative ruby +engines. + +*Non-local filesystems*: Bootsnap depends on `tmp/cache` (or whatever you set its cache directory +to) being on a relatively fast filesystem. If you put it on a network mount, bootsnap is very likely +to slow your application down quite a lot. diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/Rakefile b/path/ruby/2.6.0/gems/bootsnap-1.4.5/Rakefile new file mode 100644 index 00000000..947b48cc --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/Rakefile @@ -0,0 +1,12 @@ +require('rake/extensiontask') +require('bundler/gem_tasks') + +gemspec = Gem::Specification.load('bootsnap.gemspec') +Rake::ExtensionTask.new do |ext| + ext.name = 'bootsnap' + ext.ext_dir = 'ext/bootsnap' + ext.lib_dir = 'lib/bootsnap' + ext.gem_spec = gemspec +end + +task(default: :compile) diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/bin/ci b/path/ruby/2.6.0/gems/bootsnap-1.4.5/bin/ci new file mode 100755 index 00000000..e9224a7e --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/bin/ci @@ -0,0 +1,10 @@ +#!/bin/bash + +set -euxo pipefail + +if [[ "${MINIMAL_SUPPORT-0}" -eq 1 ]]; then + exec bin/test-minimal-support +else + rake + exec bin/testunit +fi diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/bin/console b/path/ruby/2.6.0/gems/bootsnap-1.4.5/bin/console new file mode 100755 index 00000000..ea59d447 --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/bin/console @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby + +require("bundler/setup") +require("bootsnap") + +# You can add fixtures and/or initialization code here to make experimenting +# with your gem easier. You can also use a different console, if you like. + +# (If you use this, don't forget to add pry to your Gemfile!) +# require "pry" +# Pry.start + +require("irb") +IRB.start(__FILE__) diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/bin/setup b/path/ruby/2.6.0/gems/bootsnap-1.4.5/bin/setup new file mode 100755 index 00000000..dce67d86 --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install + +# Do any other automated setup that you need to do here diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/bin/test-minimal-support b/path/ruby/2.6.0/gems/bootsnap-1.4.5/bin/test-minimal-support new file mode 100755 index 00000000..95d2ba70 --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/bin/test-minimal-support @@ -0,0 +1,7 @@ +#!/bin/bash + +set -euxo pipefail + +cd test/minimal_support +bundle +BOOTSNAP_CACHE_DIR=/tmp bundle exec ruby -w -I ../../lib bootsnap_setup.rb diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/bin/testunit b/path/ruby/2.6.0/gems/bootsnap-1.4.5/bin/testunit new file mode 100755 index 00000000..5c631e2b --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/bin/testunit @@ -0,0 +1,8 @@ +#!/bin/bash + +if [[ $# -eq 0 ]]; then + exec ruby -I"test" -w -e 'Dir.glob("./test/**/*_test.rb").each { |f| require f }' -- "$@" +else + path=$1 + exec ruby -I"test" -w -e "require '${path#test/}'" -- "$@" +fi diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/bootsnap.gemspec b/path/ruby/2.6.0/gems/bootsnap-1.4.5/bootsnap.gemspec new file mode 100644 index 00000000..3858eab5 --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/bootsnap.gemspec @@ -0,0 +1,45 @@ +# coding: utf-8 +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require('bootsnap/version') + +Gem::Specification.new do |spec| + spec.name = "bootsnap" + spec.version = Bootsnap::VERSION + spec.authors = ["Burke Libbey"] + spec.email = ["burke.libbey@shopify.com"] + + spec.license = "MIT" + + spec.summary = "Boot large ruby/rails apps faster" + spec.description = spec.summary + spec.homepage = "https://github.com/Shopify/bootsnap" + + spec.metadata = { + 'bug_tracker_uri' => 'https://github.com/Shopify/bootsnap/issues', + 'changelog_uri' => 'https://github.com/Shopify/bootsnap/blob/master/CHANGELOG.md', + 'source_code_uri' => 'https://github.com/Shopify/bootsnap', + } + + spec.files = %x(git ls-files -z).split("\x0").reject do |f| + f.match(%r{^(test|spec|features)/}) + end + spec.require_paths = %w(lib) + + spec.required_ruby_version = '>= 2.0.0' + + if RUBY_PLATFORM =~ /java/ + spec.platform = 'java' + else + spec.platform = Gem::Platform::RUBY + spec.extensions = ['ext/bootsnap/extconf.rb'] + end + + spec.add_development_dependency("bundler") + spec.add_development_dependency('rake', '~> 10.0') + spec.add_development_dependency('rake-compiler', '~> 0') + spec.add_development_dependency("minitest", "~> 5.0") + spec.add_development_dependency("mocha", "~> 1.2") + + spec.add_runtime_dependency("msgpack", "~> 1.0") +end diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/dev.yml b/path/ruby/2.6.0/gems/bootsnap-1.4.5/dev.yml new file mode 100644 index 00000000..22c0abf3 --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/dev.yml @@ -0,0 +1,10 @@ +env: + BOOTSNAP_PEDANTIC: '1' + +up: + - ruby: 2.6.0 + - bundler +commands: + build: rake compile + test: 'rake compile && exec bin/testunit' + style: 'exec rubocop -D' diff --git a/tutor-virtual-2/tmp/restart.txt b/path/ruby/2.6.0/gems/bootsnap-1.4.5/ext/bootsnap/.sitearchdir.-.bootsnap.time similarity index 100% rename from tutor-virtual-2/tmp/restart.txt rename to path/ruby/2.6.0/gems/bootsnap-1.4.5/ext/bootsnap/.sitearchdir.-.bootsnap.time diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/ext/bootsnap/Makefile b/path/ruby/2.6.0/gems/bootsnap-1.4.5/ext/bootsnap/Makefile new file mode 100644 index 00000000..a09bf61f --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/ext/bootsnap/Makefile @@ -0,0 +1,266 @@ + +SHELL = /bin/sh + +# V=0 quiet, V=1 verbose. other values don't work. +V = 0 +Q1 = $(V:1=) +Q = $(Q1:0=@) +ECHO1 = $(V:1=@ :) +ECHO = $(ECHO1:0=@ echo) +NULLCMD = : + +#### Start of system configuration section. #### + +srcdir = . +topdir = /Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 +hdrdir = $(topdir) +arch_hdrdir = /Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 +PATH_SEPARATOR = : +VPATH = $(srcdir):$(arch_hdrdir)/ruby:$(hdrdir)/ruby +prefix = $(DESTDIR)/Users/sergio/.rvm/rubies/ruby-2.6.3 +rubysitearchprefix = $(rubylibprefix)/$(sitearch) +rubyarchprefix = $(rubylibprefix)/$(arch) +rubylibprefix = $(libdir)/$(RUBY_BASE_NAME) +exec_prefix = $(prefix) +vendorarchhdrdir = $(vendorhdrdir)/$(sitearch) +sitearchhdrdir = $(sitehdrdir)/$(sitearch) +rubyarchhdrdir = $(rubyhdrdir)/$(arch) +vendorhdrdir = $(rubyhdrdir)/vendor_ruby +sitehdrdir = $(rubyhdrdir)/site_ruby +rubyhdrdir = $(includedir)/$(RUBY_VERSION_NAME) +vendorarchdir = $(vendorlibdir)/$(sitearch) +vendorlibdir = $(vendordir)/$(ruby_version) +vendordir = $(rubylibprefix)/vendor_ruby +sitearchdir = $(DESTDIR)./.gem.20191018-47188-19iu98h +sitelibdir = $(DESTDIR)./.gem.20191018-47188-19iu98h +sitedir = $(rubylibprefix)/site_ruby +rubyarchdir = $(rubylibdir)/$(arch) +rubylibdir = $(rubylibprefix)/$(ruby_version) +sitearchincludedir = $(includedir)/$(sitearch) +archincludedir = $(includedir)/$(arch) +sitearchlibdir = $(libdir)/$(sitearch) +archlibdir = $(libdir)/$(arch) +ridir = $(datarootdir)/$(RI_BASE_NAME) +mandir = $(datarootdir)/man +localedir = $(datarootdir)/locale +libdir = $(exec_prefix)/lib +psdir = $(docdir) +pdfdir = $(docdir) +dvidir = $(docdir) +htmldir = $(docdir) +infodir = $(datarootdir)/info +docdir = $(datarootdir)/doc/$(PACKAGE) +oldincludedir = $(SDKROOT)/usr/include +includedir = $(prefix)/include +localstatedir = $(prefix)/var +sharedstatedir = $(prefix)/com +sysconfdir = $(prefix)/etc +datadir = $(datarootdir) +datarootdir = $(prefix)/share +libexecdir = $(exec_prefix)/libexec +sbindir = $(exec_prefix)/sbin +bindir = $(exec_prefix)/bin +archdir = $(rubyarchdir) + + +CC_WRAPPER = +CC = gcc +CXX = g++ +LIBRUBY = $(LIBRUBY_SO) +LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a +LIBRUBYARG_SHARED = -l$(RUBY_SO_NAME) +LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static -framework Security -framework Foundation $(MAINLIBS) +empty = +OUTFLAG = -o $(empty) +COUTFLAG = -o $(empty) +CSRCFLAG = $(empty) + +RUBY_EXTCONF_H = +cflags = $(optflags) $(debugflags) $(warnflags) +cxxflags = $(optflags) $(debugflags) $(warnflags) +optflags = -O3 +debugflags = -ggdb3 +warnflags = -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens +cppflags = +CCDLFLAGS = -fno-common +CFLAGS = $(CCDLFLAGS) $(cflags) -fno-common -pipe -O3 -std=c99 $(ARCH_FLAG) +INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir)/ruby/backward -I$(hdrdir) -I$(srcdir) +DEFS = +CPPFLAGS = -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT $(DEFS) $(cppflags) +CXXFLAGS = $(CCDLFLAGS) $(cxxflags) $(ARCH_FLAG) +ldflags = -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib +dldflags = -Wl,-undefined,dynamic_lookup -Wl,-multiply_defined,suppress -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib +ARCH_FLAG = +DLDFLAGS = $(ldflags) $(dldflags) $(ARCH_FLAG) +LDSHARED = $(CC) -dynamic -bundle +LDSHAREDXX = $(CXX) -dynamic -bundle +AR = libtool -static +EXEEXT = + +RUBY_INSTALL_NAME = $(RUBY_BASE_NAME) +RUBY_SO_NAME = ruby.2.6 +RUBYW_INSTALL_NAME = +RUBY_VERSION_NAME = $(RUBY_BASE_NAME)-$(ruby_version) +RUBYW_BASE_NAME = rubyw +RUBY_BASE_NAME = ruby + +arch = x86_64-darwin18 +sitearch = $(arch) +ruby_version = 2.6.0 +ruby = $(bindir)/$(RUBY_BASE_NAME) +RUBY = $(ruby) +ruby_headers = $(hdrdir)/ruby.h $(hdrdir)/ruby/backward.h $(hdrdir)/ruby/ruby.h $(hdrdir)/ruby/defines.h $(hdrdir)/ruby/missing.h $(hdrdir)/ruby/intern.h $(hdrdir)/ruby/st.h $(hdrdir)/ruby/subst.h $(arch_hdrdir)/ruby/config.h + +RM = rm -f +RM_RF = $(RUBY) -run -e rm -- -rf +RMDIRS = rmdir -p +MAKEDIRS = /usr/local/opt/coreutils/bin/gmkdir -p +INSTALL = /usr/local/opt/coreutils/bin/ginstall -c +INSTALL_PROG = $(INSTALL) -m 0755 +INSTALL_DATA = $(INSTALL) -m 644 +COPY = cp +TOUCH = exit > + +#### End of system configuration section. #### + +preload = +libpath = . $(libdir) /usr/local/opt/libyaml/lib /usr/local/opt/libksba/lib /usr/local/opt/readline/lib /usr/local/opt/zlib/lib /usr/local/opt/openssl@1.1/lib +LIBPATH = -L. -L$(libdir) -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib +DEFFILE = + +CLEANFILES = mkmf.log +DISTCLEANFILES = +DISTCLEANDIRS = + +extout = +extout_prefix = +target_prefix = /bootsnap +LOCAL_LIBS = +LIBS = $(LIBRUBYARG_SHARED) +ORIG_SRCS = bootsnap.c +SRCS = $(ORIG_SRCS) +OBJS = bootsnap.o +HDRS = $(srcdir)/bootsnap.h +LOCAL_HDRS = +TARGET = bootsnap +TARGET_NAME = bootsnap +TARGET_ENTRY = Init_$(TARGET_NAME) +DLLIB = $(TARGET).bundle +EXTSTATIC = +STATIC_LIB = + +TIMESTAMP_DIR = . +BINDIR = $(bindir) +RUBYCOMMONDIR = $(sitedir)$(target_prefix) +RUBYLIBDIR = $(sitelibdir)$(target_prefix) +RUBYARCHDIR = $(sitearchdir)$(target_prefix) +HDRDIR = $(rubyhdrdir)/ruby$(target_prefix) +ARCHHDRDIR = $(rubyhdrdir)/$(arch)/ruby$(target_prefix) +TARGET_SO_DIR = +TARGET_SO = $(TARGET_SO_DIR)$(DLLIB) +CLEANLIBS = $(TARGET_SO) +CLEANOBJS = *.o *.bak + +all: $(DLLIB) +static: $(STATIC_LIB) +.PHONY: all install static install-so install-rb +.PHONY: clean clean-so clean-static clean-rb + +clean-static:: +clean-rb-default:: +clean-rb:: +clean-so:: +clean: clean-so clean-static clean-rb-default clean-rb + -$(Q)$(RM) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES) .*.time + +distclean-rb-default:: +distclean-rb:: +distclean-so:: +distclean-static:: +distclean: clean distclean-so distclean-static distclean-rb-default distclean-rb + -$(Q)$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log + -$(Q)$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES) + -$(Q)$(RMDIRS) $(DISTCLEANDIRS) 2> /dev/null || true + +realclean: distclean +install: install-so install-rb + +install-so: $(DLLIB) $(TIMESTAMP_DIR)/.sitearchdir.-.bootsnap.time + $(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR) +clean-static:: + -$(Q)$(RM) $(STATIC_LIB) +install-rb: pre-install-rb do-install-rb install-rb-default +install-rb-default: pre-install-rb-default do-install-rb-default +pre-install-rb: Makefile +pre-install-rb-default: Makefile +do-install-rb: +do-install-rb-default: +pre-install-rb-default: + @$(NULLCMD) +$(TIMESTAMP_DIR)/.sitearchdir.-.bootsnap.time: + $(Q) $(MAKEDIRS) $(@D) $(RUBYARCHDIR) + $(Q) $(TOUCH) $@ + +site-install: site-install-so site-install-rb +site-install-so: install-so +site-install-rb: install-rb + +.SUFFIXES: .c .m .cc .mm .cxx .cpp .o .S + +.cc.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cc.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.mm.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.mm.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.cxx.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cxx.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.cpp.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cpp.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.c.o: + $(ECHO) compiling $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.c.S: + $(ECHO) translating $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.m.o: + $(ECHO) compiling $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.m.S: + $(ECHO) translating $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +$(TARGET_SO): $(OBJS) Makefile + $(ECHO) linking shared-object bootsnap/$(DLLIB) + -$(Q)$(RM) $(@) + $(Q) $(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS) + $(Q) $(POSTLINK) + + + +$(OBJS): $(HDRS) $(ruby_headers) diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/ext/bootsnap/bootsnap.c b/path/ruby/2.6.0/gems/bootsnap-1.4.5/ext/bootsnap/bootsnap.c new file mode 100644 index 00000000..3bf003f0 --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/ext/bootsnap/bootsnap.c @@ -0,0 +1,825 @@ +/* + * Suggested reading order: + * 1. Skim Init_bootsnap + * 2. Skim bs_fetch + * 3. The rest of everything + * + * Init_bootsnap sets up the ruby objects and binds bs_fetch to + * Bootsnap::CompileCache::Native.fetch. + * + * bs_fetch is the ultimate caller for for just about every other function in + * here. + */ + +#include "bootsnap.h" +#include "ruby.h" +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif + +/* 1000 is an arbitrary limit; FNV64 plus some slashes brings the cap down to + * 981 for the cache dir */ +#define MAX_CACHEPATH_SIZE 1000 +#define MAX_CACHEDIR_SIZE 981 + +#define KEY_SIZE 64 + +/* + * An instance of this key is written as the first 64 bytes of each cache file. + * The mtime and size members track whether the file contents have changed, and + * the version, ruby_platform, compile_option, and ruby_revision members track + * changes to the environment that could invalidate compile results without + * file contents having changed. The data_size member is not truly part of the + * "key". Really, this could be called a "header" with the first six members + * being an embedded "key" struct and an additional data_size member. + * + * The data_size indicates the remaining number of bytes in the cache file + * after the header (the size of the cached artifact). + * + * After data_size, the struct is padded to 64 bytes. + */ +struct bs_cache_key { + uint32_t version; + uint32_t ruby_platform; + uint32_t compile_option; + uint32_t ruby_revision; + uint64_t size; + uint64_t mtime; + uint64_t data_size; /* not used for equality */ + uint8_t pad[24]; +} __attribute__((packed)); + +/* + * If the struct padding isn't correct to pad the key to 64 bytes, refuse to + * compile. + */ +#define STATIC_ASSERT(X) STATIC_ASSERT2(X,__LINE__) +#define STATIC_ASSERT2(X,L) STATIC_ASSERT3(X,L) +#define STATIC_ASSERT3(X,L) STATIC_ASSERT_MSG(X,at_line_##L) +#define STATIC_ASSERT_MSG(COND,MSG) typedef char static_assertion_##MSG[(!!(COND))*2-1] +STATIC_ASSERT(sizeof(struct bs_cache_key) == KEY_SIZE); + +/* Effectively a schema version. Bumping invalidates all previous caches */ +static const uint32_t current_version = 2; + +/* hash of e.g. "x86_64-darwin17", invalidating when ruby is recompiled on a + * new OS ABI, etc. */ +static uint32_t current_ruby_platform; +/* Invalidates cache when switching ruby versions */ +static uint32_t current_ruby_revision; +/* Invalidates cache when RubyVM::InstructionSequence.compile_option changes */ +static uint32_t current_compile_option_crc32 = 0; +/* Current umask */ +static mode_t current_umask; + +/* Bootsnap::CompileCache::{Native, Uncompilable} */ +static VALUE rb_mBootsnap; +static VALUE rb_mBootsnap_CompileCache; +static VALUE rb_mBootsnap_CompileCache_Native; +static VALUE rb_eBootsnap_CompileCache_Uncompilable; +static ID uncompilable; + +/* Functions exposed as module functions on Bootsnap::CompileCache::Native */ +static VALUE bs_compile_option_crc32_set(VALUE self, VALUE crc32_v); +static VALUE bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler); + +/* Helpers */ +static uint64_t fnv1a_64(const char *str); +static void bs_cache_path(const char * cachedir, const char * path, char ** cache_path); +static int bs_read_key(int fd, struct bs_cache_key * key); +static int cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2); +static VALUE bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler); +static int open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenance); +static int fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, char ** errno_provenance); +static uint32_t get_ruby_revision(void); +static uint32_t get_ruby_platform(void); + +/* + * Helper functions to call ruby methods on handler object without crashing on + * exception. + */ +static int bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data); +static VALUE prot_storage_to_output(VALUE arg); +static VALUE prot_input_to_output(VALUE arg); +static void bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag); +static VALUE prot_input_to_storage(VALUE arg); +static int bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data); +struct s2o_data; +struct i2o_data; +struct i2s_data; + +/* https://bugs.ruby-lang.org/issues/13667 */ +extern VALUE rb_get_coverages(void); +static VALUE +bs_rb_coverage_running(VALUE self) +{ + VALUE cov = rb_get_coverages(); + return RTEST(cov) ? Qtrue : Qfalse; +} + +/* + * Ruby C extensions are initialized by calling Init_. + * + * This sets up the module hierarchy and attaches functions as methods. + * + * We also populate some semi-static information about the current OS and so on. + */ +void +Init_bootsnap(void) +{ + rb_mBootsnap = rb_define_module("Bootsnap"); + rb_mBootsnap_CompileCache = rb_define_module_under(rb_mBootsnap, "CompileCache"); + rb_mBootsnap_CompileCache_Native = rb_define_module_under(rb_mBootsnap_CompileCache, "Native"); + rb_eBootsnap_CompileCache_Uncompilable = rb_define_class_under(rb_mBootsnap_CompileCache, "Uncompilable", rb_eStandardError); + + current_ruby_revision = get_ruby_revision(); + current_ruby_platform = get_ruby_platform(); + + uncompilable = rb_intern("__bootsnap_uncompilable__"); + + rb_define_module_function(rb_mBootsnap_CompileCache_Native, "coverage_running?", bs_rb_coverage_running, 0); + rb_define_module_function(rb_mBootsnap_CompileCache_Native, "fetch", bs_rb_fetch, 3); + rb_define_module_function(rb_mBootsnap_CompileCache_Native, "compile_option_crc32=", bs_compile_option_crc32_set, 1); + + current_umask = umask(0777); + umask(current_umask); +} + +/* + * Bootsnap's ruby code registers a hook that notifies us via this function + * when compile_option changes. These changes invalidate all existing caches. + * + * Note that on 32-bit platforms, a CRC32 can't be represented in a Fixnum, but + * can be represented by a uint. + */ +static VALUE +bs_compile_option_crc32_set(VALUE self, VALUE crc32_v) +{ + if (!RB_TYPE_P(crc32_v, T_BIGNUM) && !RB_TYPE_P(crc32_v, T_FIXNUM)) { + Check_Type(crc32_v, T_FIXNUM); + } + current_compile_option_crc32 = NUM2UINT(crc32_v); + return Qnil; +} + +/* + * We use FNV1a-64 to derive cache paths. The choice is somewhat arbitrary but + * it has several nice properties: + * + * - Tiny implementation + * - No external dependency + * - Solid performance + * - Solid randomness + * - 32 bits doesn't feel collision-resistant enough; 64 is nice. + */ +static uint64_t +fnv1a_64_iter(uint64_t h, const char *str) +{ + unsigned char *s = (unsigned char *)str; + + while (*s) { + h ^= (uint64_t)*s++; + h += (h << 1) + (h << 4) + (h << 5) + (h << 7) + (h << 8) + (h << 40); + } + + return h; +} + +static uint64_t +fnv1a_64(const char *str) +{ + uint64_t h = (uint64_t)0xcbf29ce484222325ULL; + return fnv1a_64_iter(h, str); +} + +/* + * Ruby's revision may be Integer or String. CRuby 2.7 or later uses + * Git commit ID as revision. It's String. + */ +static uint32_t +get_ruby_revision(void) +{ + VALUE ruby_revision; + + ruby_revision = rb_const_get(rb_cObject, rb_intern("RUBY_REVISION")); + if (RB_TYPE_P(ruby_revision, RUBY_T_FIXNUM)) { + return FIX2INT(ruby_revision); + } else { + uint64_t hash; + + hash = fnv1a_64(StringValueCStr(ruby_revision)); + return (uint32_t)(hash >> 32); + } +} + +/* + * When ruby's version doesn't change, but it's recompiled on a different OS + * (or OS version), we need to invalidate the cache. + * + * We actually factor in some extra information here, to be extra confident + * that we don't try to re-use caches that will not be compatible, by factoring + * in utsname.version. + */ +static uint32_t +get_ruby_platform(void) +{ + uint64_t hash; + VALUE ruby_platform; + + ruby_platform = rb_const_get(rb_cObject, rb_intern("RUBY_PLATFORM")); + hash = fnv1a_64(RSTRING_PTR(ruby_platform)); + +#ifdef _WIN32 + return (uint32_t)(hash >> 32) ^ (uint32_t)GetVersion(); +#else + struct utsname utsname; + + /* Not worth crashing if this fails; lose extra cache invalidation potential */ + if (uname(&utsname) >= 0) { + hash = fnv1a_64_iter(hash, utsname.version); + } + + return (uint32_t)(hash >> 32); +#endif +} + +/* + * Given a cache root directory and the full path to a file being cached, + * generate a path under the cache directory at which the cached artifact will + * be stored. + * + * The path will look something like: /12/34567890abcdef + */ +static void +bs_cache_path(const char * cachedir, const char * path, char ** cache_path) +{ + uint64_t hash = fnv1a_64(path); + + uint8_t first_byte = (hash >> (64 - 8)); + uint64_t remainder = hash & 0x00ffffffffffffff; + + sprintf(*cache_path, "%s/%02x/%014llx", cachedir, first_byte, remainder); +} + +/* + * Test whether a newly-generated cache key based on the file as it exists on + * disk matches the one that was generated when the file was cached (or really + * compare any two keys). + * + * The data_size member is not compared, as it serves more of a "header" + * function. + */ +static int +cache_key_equal(struct bs_cache_key * k1, struct bs_cache_key * k2) +{ + return ( + k1->version == k2->version && + k1->ruby_platform == k2->ruby_platform && + k1->compile_option == k2->compile_option && + k1->ruby_revision == k2->ruby_revision && + k1->size == k2->size && + k1->mtime == k2->mtime + ); +} + +/* + * Entrypoint for Bootsnap::CompileCache::Native.fetch. The real work is done + * in bs_fetch; this function just performs some basic typechecks and + * conversions on the ruby VALUE arguments before passing them along. + */ +static VALUE +bs_rb_fetch(VALUE self, VALUE cachedir_v, VALUE path_v, VALUE handler) +{ + FilePathValue(path_v); + + Check_Type(cachedir_v, T_STRING); + Check_Type(path_v, T_STRING); + + if (RSTRING_LEN(cachedir_v) > MAX_CACHEDIR_SIZE) { + rb_raise(rb_eArgError, "cachedir too long"); + } + + char * cachedir = RSTRING_PTR(cachedir_v); + char * path = RSTRING_PTR(path_v); + char cache_path[MAX_CACHEPATH_SIZE]; + + { /* generate cache path to cache_path */ + char * tmp = (char *)&cache_path; + bs_cache_path(cachedir, path, &tmp); + } + + return bs_fetch(path, path_v, cache_path, handler); +} + +/* + * Open the file we want to load/cache and generate a cache key for it if it + * was loaded. + */ +static int +open_current_file(char * path, struct bs_cache_key * key, char ** errno_provenance) +{ + struct stat statbuf; + int fd; + + fd = open(path, O_RDONLY); + if (fd < 0) { + *errno_provenance = (char *)"bs_fetch:open_current_file:open"; + return fd; + } + #ifdef _WIN32 + setmode(fd, O_BINARY); + #endif + + if (fstat(fd, &statbuf) < 0) { + *errno_provenance = (char *)"bs_fetch:open_current_file:fstat"; + close(fd); + return -1; + } + + key->version = current_version; + key->ruby_platform = current_ruby_platform; + key->compile_option = current_compile_option_crc32; + key->ruby_revision = current_ruby_revision; + key->size = (uint64_t)statbuf.st_size; + key->mtime = (uint64_t)statbuf.st_mtime; + + return fd; +} + +#define ERROR_WITH_ERRNO -1 +#define CACHE_MISSING_OR_INVALID -2 + +/* + * Read the cache key from the given fd, which must have position 0 (e.g. + * freshly opened file). + * + * Possible return values: + * - 0 (OK, key was loaded) + * - CACHE_MISSING_OR_INVALID (-2) + * - ERROR_WITH_ERRNO (-1, errno is set) + */ +static int +bs_read_key(int fd, struct bs_cache_key * key) +{ + ssize_t nread = read(fd, key, KEY_SIZE); + if (nread < 0) return ERROR_WITH_ERRNO; + if (nread < KEY_SIZE) return CACHE_MISSING_OR_INVALID; + return 0; +} + +/* + * Open the cache file at a given path, if it exists, and read its key into the + * struct. + * + * Possible return values: + * - 0 (OK, key was loaded) + * - CACHE_MISSING_OR_INVALID (-2) + * - ERROR_WITH_ERRNO (-1, errno is set) + */ +static int +open_cache_file(const char * path, struct bs_cache_key * key, char ** errno_provenance) +{ + int fd, res; + + fd = open(path, O_RDONLY); + if (fd < 0) { + *errno_provenance = (char *)"bs_fetch:open_cache_file:open"; + if (errno == ENOENT) return CACHE_MISSING_OR_INVALID; + return ERROR_WITH_ERRNO; + } + #ifdef _WIN32 + setmode(fd, O_BINARY); + #endif + + res = bs_read_key(fd, key); + if (res < 0) { + *errno_provenance = (char *)"bs_fetch:open_cache_file:read"; + close(fd); + return res; + } + + return fd; +} + +/* + * The cache file is laid out like: + * 0...64 : bs_cache_key + * 64..-1 : cached artifact + * + * This function takes a file descriptor whose position is pre-set to 64, and + * the data_size (corresponding to the remaining number of bytes) listed in the + * cache header. + * + * We load the text from this file into a buffer, and pass it to the ruby-land + * handler with exception handling via the exception_tag param. + * + * Data is returned via the output_data parameter, which, if there's no error + * or exception, will be the final data returnable to the user. + */ +static int +fetch_cached_data(int fd, ssize_t data_size, VALUE handler, VALUE * output_data, int * exception_tag, char ** errno_provenance) +{ + char * data = NULL; + ssize_t nread; + int ret; + + VALUE storage_data; + + if (data_size > 100000000000) { + *errno_provenance = (char *)"bs_fetch:fetch_cached_data:datasize"; + errno = EINVAL; /* because wtf? */ + ret = -1; + goto done; + } + data = ALLOC_N(char, data_size); + nread = read(fd, data, data_size); + if (nread < 0) { + *errno_provenance = (char *)"bs_fetch:fetch_cached_data:read"; + ret = -1; + goto done; + } + if (nread != data_size) { + ret = CACHE_MISSING_OR_INVALID; + goto done; + } + + storage_data = rb_str_new_static(data, data_size); + + *exception_tag = bs_storage_to_output(handler, storage_data, output_data); + ret = 0; +done: + if (data != NULL) xfree(data); + return ret; +} + +/* + * Like mkdir -p, this recursively creates directory parents of a file. e.g. + * given /a/b/c, creates /a and /a/b. + */ +static int +mkpath(char * file_path, mode_t mode) +{ + /* It would likely be more efficient to count back until we + * find a component that *does* exist, but this will only run + * at most 256 times, so it seems not worthwhile to change. */ + char * p; + for (p = strchr(file_path + 1, '/'); p; p = strchr(p + 1, '/')) { + *p = '\0'; + #ifdef _WIN32 + if (mkdir(file_path) == -1) { + #else + if (mkdir(file_path, mode) == -1) { + #endif + if (errno != EEXIST) { + *p = '/'; + return -1; + } + } + *p = '/'; + } + return 0; +} + +/* + * Write a cache header/key and a compiled artifact to a given cache path by + * writing to a tmpfile and then renaming the tmpfile over top of the final + * path. + */ +static int +atomic_write_cache_file(char * path, struct bs_cache_key * key, VALUE data, char ** errno_provenance) +{ + char template[MAX_CACHEPATH_SIZE + 20]; + char * tmp_path; + int fd, ret; + ssize_t nwrite; + + tmp_path = strncpy(template, path, MAX_CACHEPATH_SIZE); + strcat(tmp_path, ".tmp.XXXXXX"); + + // mkstemp modifies the template to be the actual created path + fd = mkstemp(tmp_path); + if (fd < 0) { + if (mkpath(tmp_path, 0775) < 0) { + *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:mkpath"; + return -1; + } + fd = open(tmp_path, O_WRONLY | O_CREAT, 0664); + if (fd < 0) { + *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:open"; + return -1; + } + } + #ifdef _WIN32 + setmode(fd, O_BINARY); + #endif + + key->data_size = RSTRING_LEN(data); + nwrite = write(fd, key, KEY_SIZE); + if (nwrite < 0) { + *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:write"; + return -1; + } + if (nwrite != KEY_SIZE) { + *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:keysize"; + errno = EIO; /* Lies but whatever */ + return -1; + } + + nwrite = write(fd, RSTRING_PTR(data), RSTRING_LEN(data)); + if (nwrite < 0) return -1; + if (nwrite != RSTRING_LEN(data)) { + *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:writelength"; + errno = EIO; /* Lies but whatever */ + return -1; + } + + close(fd); + ret = rename(tmp_path, path); + if (ret < 0) { + *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:rename"; + return -1; + } + ret = chmod(path, 0664 & ~current_umask); + if (ret < 0) { + *errno_provenance = (char *)"bs_fetch:atomic_write_cache_file:chmod"; + } + return ret; +} + + +/* Read contents from an fd, whose contents are asserted to be +size+ bytes + * long, into a buffer */ +static ssize_t +bs_read_contents(int fd, size_t size, char ** contents, char ** errno_provenance) +{ + ssize_t nread; + *contents = ALLOC_N(char, size); + nread = read(fd, *contents, size); + if (nread < 0) { + *errno_provenance = (char *)"bs_fetch:bs_read_contents:read"; + } + return nread; +} + +/* + * This is the meat of the extension. bs_fetch is + * Bootsnap::CompileCache::Native.fetch. + * + * There are three "formats" in use here: + * 1. "input" format, which is what we load from the source file; + * 2. "storage" format, which we write to the cache; + * 3. "output" format, which is what we return. + * + * E.g., For ISeq compilation: + * input: ruby source, as text + * storage: binary string (RubyVM::InstructionSequence#to_binary) + * output: Instance of RubyVM::InstructionSequence + * + * And for YAML: + * input: yaml as text + * storage: MessagePack or Marshal text + * output: ruby object, loaded from yaml/messagepack/marshal + * + * A handler passed in must support three messages: + * * storage_to_output(S) -> O + * * input_to_output(I) -> O + * * input_to_storage(I) -> S + * (input_to_storage may raise Bootsnap::CompileCache::Uncompilable, which + * will prevent caching and cause output to be generated with + * input_to_output) + * + * The semantics of this function are basically: + * + * return storage_to_output(cache[path]) if cache[path] + * storage = input_to_storage(input) + * cache[path] = storage + * return storage_to_output(storage) + * + * Or expanded a bit: + * + * - Check if the cache file exists and is up to date. + * - If it is, load this data to storage_data. + * - return storage_to_output(storage_data) + * - Read the file to input_data + * - Generate storage_data using input_to_storage(input_data) + * - Write storage_data data, with a cache key, to the cache file. + * - Return storage_to_output(storage_data) + */ +static VALUE +bs_fetch(char * path, VALUE path_v, char * cache_path, VALUE handler) +{ + struct bs_cache_key cached_key, current_key; + char * contents = NULL; + int cache_fd = -1, current_fd = -1; + int res, valid_cache = 0, exception_tag = 0; + char * errno_provenance = NULL; + + VALUE input_data; /* data read from source file, e.g. YAML or ruby source */ + VALUE storage_data; /* compiled data, e.g. msgpack / binary iseq */ + VALUE output_data; /* return data, e.g. ruby hash or loaded iseq */ + + VALUE exception; /* ruby exception object to raise instead of returning */ + + /* Open the source file and generate a cache key for it */ + current_fd = open_current_file(path, ¤t_key, &errno_provenance); + if (current_fd < 0) goto fail_errno; + + /* Open the cache key if it exists, and read its cache key in */ + cache_fd = open_cache_file(cache_path, &cached_key, &errno_provenance); + if (cache_fd == CACHE_MISSING_OR_INVALID) { + /* This is ok: valid_cache remains false, we re-populate it. */ + } else if (cache_fd < 0) { + goto fail_errno; + } else { + /* True if the cache existed and no invalidating changes have occurred since + * it was generated. */ + valid_cache = cache_key_equal(¤t_key, &cached_key); + } + + if (valid_cache) { + /* Fetch the cache data and return it if we're able to load it successfully */ + res = fetch_cached_data( + cache_fd, (ssize_t)cached_key.data_size, handler, + &output_data, &exception_tag, &errno_provenance + ); + if (exception_tag != 0) goto raise; + else if (res == CACHE_MISSING_OR_INVALID) valid_cache = 0; + else if (res == ERROR_WITH_ERRNO) goto fail_errno; + else if (!NIL_P(output_data)) goto succeed; /* fast-path, goal */ + } + close(cache_fd); + cache_fd = -1; + /* Cache is stale, invalid, or missing. Regenerate and write it out. */ + + /* Read the contents of the source file into a buffer */ + if (bs_read_contents(current_fd, current_key.size, &contents, &errno_provenance) < 0) goto fail_errno; + input_data = rb_str_new_static(contents, current_key.size); + + /* Try to compile the input_data using input_to_storage(input_data) */ + exception_tag = bs_input_to_storage(handler, input_data, path_v, &storage_data); + if (exception_tag != 0) goto raise; + /* If input_to_storage raised Bootsnap::CompileCache::Uncompilable, don't try + * to cache anything; just return input_to_output(input_data) */ + if (storage_data == uncompilable) { + bs_input_to_output(handler, input_data, &output_data, &exception_tag); + if (exception_tag != 0) goto raise; + goto succeed; + } + /* If storage_data isn't a string, we can't cache it */ + if (!RB_TYPE_P(storage_data, T_STRING)) goto invalid_type_storage_data; + + /* Write the cache key and storage_data to the cache directory */ + res = atomic_write_cache_file(cache_path, ¤t_key, storage_data, &errno_provenance); + if (res < 0) goto fail_errno; + + /* Having written the cache, now convert storage_data to output_data */ + exception_tag = bs_storage_to_output(handler, storage_data, &output_data); + if (exception_tag != 0) goto raise; + + /* If output_data is nil, delete the cache entry and generate the output + * using input_to_output */ + if (NIL_P(output_data)) { + if (unlink(cache_path) < 0) { + errno_provenance = (char *)"bs_fetch:unlink"; + goto fail_errno; + } + bs_input_to_output(handler, input_data, &output_data, &exception_tag); + if (exception_tag != 0) goto raise; + } + + goto succeed; /* output_data is now the correct return. */ + +#define CLEANUP \ + if (contents != NULL) xfree(contents); \ + if (current_fd >= 0) close(current_fd); \ + if (cache_fd >= 0) close(cache_fd); + +succeed: + CLEANUP; + return output_data; +fail_errno: + CLEANUP; + exception = rb_syserr_new(errno, errno_provenance); + rb_exc_raise(exception); + __builtin_unreachable(); +raise: + CLEANUP; + rb_jump_tag(exception_tag); + __builtin_unreachable(); +invalid_type_storage_data: + CLEANUP; + Check_Type(storage_data, T_STRING); + __builtin_unreachable(); + +#undef CLEANUP +} + +/*****************************************************************************/ +/********************* Handler Wrappers **************************************/ +/***************************************************************************** + * Everything after this point in the file is just wrappers to deal with ruby's + * clunky method of handling exceptions from ruby methods invoked from C: + * + * In order to call a ruby method from C, while protecting against crashing in + * the event of an exception, we must call the method with rb_protect(). + * + * rb_protect takes a C function and precisely one argument; however, we want + * to pass multiple arguments, so we must create structs to wrap them up. + * + * These functions return an exception_tag, which, if non-zero, indicates an + * exception that should be jumped to with rb_jump_tag after cleaning up + * allocated resources. + */ + +struct s2o_data { + VALUE handler; + VALUE storage_data; +}; + +struct i2o_data { + VALUE handler; + VALUE input_data; +}; + +struct i2s_data { + VALUE handler; + VALUE input_data; + VALUE pathval; +}; + +static VALUE +prot_storage_to_output(VALUE arg) +{ + struct s2o_data * data = (struct s2o_data *)arg; + return rb_funcall(data->handler, rb_intern("storage_to_output"), 1, data->storage_data); +} + +static int +bs_storage_to_output(VALUE handler, VALUE storage_data, VALUE * output_data) +{ + int state; + struct s2o_data s2o_data = { + .handler = handler, + .storage_data = storage_data, + }; + *output_data = rb_protect(prot_storage_to_output, (VALUE)&s2o_data, &state); + return state; +} + +static void +bs_input_to_output(VALUE handler, VALUE input_data, VALUE * output_data, int * exception_tag) +{ + struct i2o_data i2o_data = { + .handler = handler, + .input_data = input_data, + }; + *output_data = rb_protect(prot_input_to_output, (VALUE)&i2o_data, exception_tag); +} + +static VALUE +prot_input_to_output(VALUE arg) +{ + struct i2o_data * data = (struct i2o_data *)arg; + return rb_funcall(data->handler, rb_intern("input_to_output"), 1, data->input_data); +} + +static VALUE +try_input_to_storage(VALUE arg) +{ + struct i2s_data * data = (struct i2s_data *)arg; + return rb_funcall(data->handler, rb_intern("input_to_storage"), 2, data->input_data, data->pathval); +} + +static VALUE +rescue_input_to_storage(VALUE arg) +{ + return uncompilable; +} + +static VALUE +prot_input_to_storage(VALUE arg) +{ + struct i2s_data * data = (struct i2s_data *)arg; + return rb_rescue2( + try_input_to_storage, (VALUE)data, + rescue_input_to_storage, Qnil, + rb_eBootsnap_CompileCache_Uncompilable, 0); +} + +static int +bs_input_to_storage(VALUE handler, VALUE input_data, VALUE pathval, VALUE * storage_data) +{ + int state; + struct i2s_data i2s_data = { + .handler = handler, + .input_data = input_data, + .pathval = pathval, + }; + *storage_data = rb_protect(prot_input_to_storage, (VALUE)&i2s_data, &state); + return state; +} diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/ext/bootsnap/bootsnap.h b/path/ruby/2.6.0/gems/bootsnap-1.4.5/ext/bootsnap/bootsnap.h new file mode 100644 index 00000000..1bdbb4ee --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/ext/bootsnap/bootsnap.h @@ -0,0 +1,6 @@ +#ifndef BOOTSNAP_H +#define BOOTSNAP_H 1 + +/* doesn't expose anything */ + +#endif /* BOOTSNAP_H */ diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/ext/bootsnap/extconf.rb b/path/ruby/2.6.0/gems/bootsnap-1.4.5/ext/bootsnap/extconf.rb new file mode 100644 index 00000000..aae9b458 --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/ext/bootsnap/extconf.rb @@ -0,0 +1,18 @@ +require("mkmf") +$CFLAGS << ' -O3 ' +$CFLAGS << ' -std=c99' + +# ruby.h has some -Wpedantic fails in some cases +# (e.g. https://github.com/Shopify/bootsnap/issues/15) +unless ['0', '', nil].include?(ENV['BOOTSNAP_PEDANTIC']) + $CFLAGS << ' -Wall' + $CFLAGS << ' -Werror' + $CFLAGS << ' -Wextra' + $CFLAGS << ' -Wpedantic' + + $CFLAGS << ' -Wno-unused-parameter' # VALUE self has to be there but we don't care what it is. + $CFLAGS << ' -Wno-keyword-macro' # hiding return + $CFLAGS << ' -Wno-gcc-compat' # ruby.h 2.6.0 on macos 10.14, dunno +end + +create_makefile("bootsnap/bootsnap") diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap.rb b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap.rb new file mode 100644 index 00000000..a96ad417 --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap.rb @@ -0,0 +1,47 @@ +require_relative('bootsnap/version') +require_relative('bootsnap/bundler') +require_relative('bootsnap/load_path_cache') +require_relative('bootsnap/compile_cache') + +module Bootsnap + InvalidConfiguration = Class.new(StandardError) + + def self.setup( + cache_dir:, + development_mode: true, + load_path_cache: true, + autoload_paths_cache: true, + disable_trace: false, + compile_cache_iseq: true, + compile_cache_yaml: true + ) + if autoload_paths_cache && !load_path_cache + raise(InvalidConfiguration, "feature 'autoload_paths_cache' depends on feature 'load_path_cache'") + end + + setup_disable_trace if disable_trace + + Bootsnap::LoadPathCache.setup( + cache_path: cache_dir + '/bootsnap-load-path-cache', + development_mode: development_mode, + active_support: autoload_paths_cache + ) if load_path_cache + + Bootsnap::CompileCache.setup( + cache_dir: cache_dir + '/bootsnap-compile-cache', + iseq: compile_cache_iseq, + yaml: compile_cache_yaml + ) + end + + def self.setup_disable_trace + if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.5.0') + warn( + "from #{caller_locations(1, 1)[0]}: The 'disable_trace' method is not allowed with this Ruby version. " \ + "current: #{RUBY_VERSION}, allowed version: < 2.5.0", + ) + else + RubyVM::InstructionSequence.compile_option = { trace_instruction: false } + end + end +end diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/bundler.rb b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/bundler.rb new file mode 100644 index 00000000..c2f31bc8 --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/bundler.rb @@ -0,0 +1,14 @@ +module Bootsnap + extend(self) + + def bundler? + return false unless defined?(::Bundler) + + # Bundler environment variable + %w(BUNDLE_BIN_PATH BUNDLE_GEMFILE).each do |current| + return true if ENV.key?(current) + end + + false + end +end diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/compile_cache.rb b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/compile_cache.rb new file mode 100644 index 00000000..458b0ecb --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/compile_cache.rb @@ -0,0 +1,42 @@ +module Bootsnap + module CompileCache + Error = Class.new(StandardError) + PermissionError = Class.new(Error) + + def self.setup(cache_dir:, iseq:, yaml:) + if iseq + if supported? + require_relative('compile_cache/iseq') + Bootsnap::CompileCache::ISeq.install!(cache_dir) + elsif $VERBOSE + warn("[bootsnap/setup] bytecode caching is not supported on this implementation of Ruby") + end + end + + if yaml + if supported? + require_relative('compile_cache/yaml') + Bootsnap::CompileCache::YAML.install!(cache_dir) + elsif $VERBOSE + warn("[bootsnap/setup] YAML parsing caching is not supported on this implementation of Ruby") + end + end + end + + def self.permission_error(path) + cpath = Bootsnap::CompileCache::ISeq.cache_dir + raise( + PermissionError, + "bootsnap doesn't have permission to write cache entries in '#{cpath}' " \ + "(or, less likely, doesn't have permission to read '#{path}')", + ) + end + + def self.supported? + # only enable on 'ruby' (MRI), POSIX (darwin, linux, *bsd), and >= 2.3.0 + RUBY_ENGINE == 'ruby' && + RUBY_PLATFORM =~ /darwin|linux|bsd/ && + Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.3.0") + end + end +end diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/compile_cache/iseq.rb b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/compile_cache/iseq.rb new file mode 100644 index 00000000..7da2509a --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/compile_cache/iseq.rb @@ -0,0 +1,72 @@ +require('bootsnap/bootsnap') +require('zlib') + +module Bootsnap + module CompileCache + module ISeq + class << self + attr_accessor(:cache_dir) + end + + def self.input_to_storage(_, path) + RubyVM::InstructionSequence.compile_file(path).to_binary + rescue SyntaxError + raise(Uncompilable, 'syntax error') + end + + def self.storage_to_output(binary) + RubyVM::InstructionSequence.load_from_binary(binary) + rescue RuntimeError => e + if e.message == 'broken binary format' + STDERR.puts("[Bootsnap::CompileCache] warning: rejecting broken binary") + nil + else + raise + end + end + + def self.input_to_output(_) + nil # ruby handles this + end + + module InstructionSequenceMixin + def load_iseq(path) + # Having coverage enabled prevents iseq dumping/loading. + return nil if defined?(Coverage) && Bootsnap::CompileCache::Native.coverage_running? + + Bootsnap::CompileCache::Native.fetch( + Bootsnap::CompileCache::ISeq.cache_dir, + path.to_s, + Bootsnap::CompileCache::ISeq + ) + rescue Errno::EACCES + Bootsnap::CompileCache.permission_error(path) + rescue RuntimeError => e + if e.message =~ /unmatched platform/ + puts("unmatched platform for file #{path}") + end + raise + end + + def compile_option=(hash) + super(hash) + Bootsnap::CompileCache::ISeq.compile_option_updated + end + end + + def self.compile_option_updated + option = RubyVM::InstructionSequence.compile_option + crc = Zlib.crc32(option.inspect) + Bootsnap::CompileCache::Native.compile_option_crc32 = crc + end + + def self.install!(cache_dir) + Bootsnap::CompileCache::ISeq.cache_dir = cache_dir + Bootsnap::CompileCache::ISeq.compile_option_updated + class << RubyVM::InstructionSequence + prepend(InstructionSequenceMixin) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/compile_cache/yaml.rb b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/compile_cache/yaml.rb new file mode 100644 index 00000000..801e05fe --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/compile_cache/yaml.rb @@ -0,0 +1,62 @@ +require('bootsnap/bootsnap') + +module Bootsnap + module CompileCache + module YAML + class << self + attr_accessor(:msgpack_factory) + end + + def self.input_to_storage(contents, _) + raise(Uncompilable) if contents.index("!ruby/object") + obj = ::YAML.load(contents) + msgpack_factory.packer.write(obj).to_s + rescue NoMethodError, RangeError + # if the object included things that we can't serialize, fall back to + # Marshal. It's a bit slower, but can encode anything yaml can. + # NoMethodError is unexpected types; RangeError is Bignums + Marshal.dump(obj) + end + + def self.storage_to_output(data) + # This could have a meaning in messagepack, and we're being a little lazy + # about it. -- but a leading 0x04 would indicate the contents of the YAML + # is a positive integer, which is rare, to say the least. + if data[0] == 0x04.chr && data[1] == 0x08.chr + Marshal.load(data) + else + msgpack_factory.unpacker.feed(data).read + end + end + + def self.input_to_output(data) + ::YAML.load(data) + end + + def self.install!(cache_dir) + require('yaml') + require('msgpack') + + # MessagePack serializes symbols as strings by default. + # We want them to roundtrip cleanly, so we use a custom factory. + # see: https://github.com/msgpack/msgpack-ruby/pull/122 + factory = MessagePack::Factory.new + factory.register_type(0x00, Symbol) + Bootsnap::CompileCache::YAML.msgpack_factory = factory + + klass = class << ::YAML; self; end + klass.send(:define_method, :load_file) do |path| + begin + Bootsnap::CompileCache::Native.fetch( + cache_dir, + path, + Bootsnap::CompileCache::YAML + ) + rescue Errno::EACCES + Bootsnap::CompileCache.permission_error(path) + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/explicit_require.rb b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/explicit_require.rb new file mode 100644 index 00000000..3fb8e725 --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/explicit_require.rb @@ -0,0 +1,49 @@ +module Bootsnap + module ExplicitRequire + ARCHDIR = RbConfig::CONFIG['archdir'] + RUBYLIBDIR = RbConfig::CONFIG['rubylibdir'] + DLEXT = RbConfig::CONFIG['DLEXT'] + + def self.from_self(feature) + require_relative("../#{feature}") + end + + def self.from_rubylibdir(feature) + require(File.join(RUBYLIBDIR, "#{feature}.rb")) + end + + def self.from_archdir(feature) + require(File.join(ARCHDIR, "#{feature}.#{DLEXT}")) + end + + # Given a set of gems, run a block with the LOAD_PATH narrowed to include + # only core ruby source paths and these gems -- that is, roughly, + # temporarily remove all gems not listed in this call from the LOAD_PATH. + # + # This is useful before bootsnap is fully-initialized to load gems that it + # depends on, without forcing full LOAD_PATH traversals. + def self.with_gems(*gems) + orig = $LOAD_PATH.dup + $LOAD_PATH.clear + gems.each do |gem| + pat = %r{ + / + (gems|extensions/[^/]+/[^/]+) # "gems" or "extensions/x64_64-darwin16/2.3.0" + / + #{Regexp.escape(gem)}-(\h{12}|(\d+\.)) # msgpack-1.2.3 or msgpack-1234567890ab + }x + $LOAD_PATH.concat(orig.grep(pat)) + end + $LOAD_PATH << ARCHDIR + $LOAD_PATH << RUBYLIBDIR + begin + yield + rescue LoadError + $LOAD_PATH.replace(orig) + yield + end + ensure + $LOAD_PATH.replace(orig) + end + end +end diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache.rb b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache.rb new file mode 100644 index 00000000..c58f25d8 --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +module Bootsnap + module LoadPathCache + ReturnFalse = Class.new(StandardError) + FallbackScan = Class.new(StandardError) + + DOT_RB = '.rb' + DOT_SO = '.so' + SLASH = '/' + + # If a NameError happens several levels deep, don't re-handle it + # all the way up the chain: mark it once and bubble it up without + # more retries. + ERROR_TAG_IVAR = :@__bootsnap_rescued + + DL_EXTENSIONS = ::RbConfig::CONFIG + .values_at('DLEXT', 'DLEXT2') + .reject { |ext| !ext || ext.empty? } + .map { |ext| ".#{ext}" } + .freeze + DLEXT = DL_EXTENSIONS[0] + # This is nil on linux and darwin, but I think it's '.o' on some other + # platform. I'm not really sure which, but it seems better to replicate + # ruby's semantics as faithfully as possible. + DLEXT2 = DL_EXTENSIONS[1] + + CACHED_EXTENSIONS = DLEXT2 ? [DOT_RB, DLEXT, DLEXT2] : [DOT_RB, DLEXT] + + class << self + attr_reader(:load_path_cache, :autoload_paths_cache, + :loaded_features_index, :realpath_cache) + + def setup(cache_path:, development_mode:, active_support: true) + unless supported? + warn("[bootsnap/setup] Load path caching is not supported on this implementation of Ruby") if $VERBOSE + return + end + + store = Store.new(cache_path) + + @loaded_features_index = LoadedFeaturesIndex.new + @realpath_cache = RealpathCache.new + + @load_path_cache = Cache.new(store, $LOAD_PATH, development_mode: development_mode) + require_relative('load_path_cache/core_ext/kernel_require') + require_relative('load_path_cache/core_ext/loaded_features') + + if active_support + # this should happen after setting up the initial cache because it + # loads a lot of code. It's better to do after +require+ is optimized. + require('active_support/dependencies') + @autoload_paths_cache = Cache.new( + store, + ::ActiveSupport::Dependencies.autoload_paths, + development_mode: development_mode + ) + require_relative('load_path_cache/core_ext/active_support') + end + end + + def supported? + RUBY_ENGINE == 'ruby' && + RUBY_PLATFORM =~ /darwin|linux|bsd/ + end + end + end +end + +if Bootsnap::LoadPathCache.supported? + require_relative('load_path_cache/path_scanner') + require_relative('load_path_cache/path') + require_relative('load_path_cache/cache') + require_relative('load_path_cache/store') + require_relative('load_path_cache/change_observer') + require_relative('load_path_cache/loaded_features_index') + require_relative('load_path_cache/realpath_cache') +end diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/cache.rb b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/cache.rb new file mode 100644 index 00000000..c2f7315e --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/cache.rb @@ -0,0 +1,208 @@ +# frozen_string_literal: true + +require_relative('../explicit_require') + +module Bootsnap + module LoadPathCache + class Cache + AGE_THRESHOLD = 30 # seconds + + def initialize(store, path_obj, development_mode: false) + @development_mode = development_mode + @store = store + @mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new # TODO: Remove once Ruby 2.2 support is dropped. + @path_obj = path_obj.map! { |f| File.exist?(f) ? File.realpath(f) : f } + @has_relative_paths = nil + reinitialize + end + + # What is the path item that contains the dir as child? + # e.g. given "/a/b/c/d" exists, and the path is ["/a/b"], load_dir("c/d") + # is "/a/b". + def load_dir(dir) + reinitialize if stale? + @mutex.synchronize { @dirs[dir] } + end + + # { 'enumerator' => nil, 'enumerator.so' => nil, ... } + BUILTIN_FEATURES = $LOADED_FEATURES.each_with_object({}) do |feat, features| + # Builtin features are of the form 'enumerator.so'. + # All others include paths. + next unless feat.size < 20 && !feat.include?('/') + + base = File.basename(feat, '.*') # enumerator.so -> enumerator + ext = File.extname(feat) # .so + + features[feat] = nil # enumerator.so + features[base] = nil # enumerator + + next unless [DOT_SO, *DL_EXTENSIONS].include?(ext) + DL_EXTENSIONS.each do |dl_ext| + features["#{base}#{dl_ext}"] = nil # enumerator.bundle + end + end.freeze + + # Try to resolve this feature to an absolute path without traversing the + # loadpath. + def find(feature) + reinitialize if (@has_relative_paths && dir_changed?) || stale? + feature = feature.to_s + return feature if absolute_path?(feature) + return expand_path(feature) if feature.start_with?('./') + @mutex.synchronize do + x = search_index(feature) + return x if x + + # Ruby has some built-in features that require lies about. + # For example, 'enumerator' is built in. If you require it, ruby + # returns false as if it were already loaded; however, there is no + # file to find on disk. We've pre-built a list of these, and we + # return false if any of them is loaded. + raise(LoadPathCache::ReturnFalse, '', []) if BUILTIN_FEATURES.key?(feature) + + # The feature wasn't found on our preliminary search through the index. + # We resolve this differently depending on what the extension was. + case File.extname(feature) + # If the extension was one of the ones we explicitly cache (.rb and the + # native dynamic extension, e.g. .bundle or .so), we know it was a + # failure and there's nothing more we can do to find the file. + # no extension, .rb, (.bundle or .so) + when '', *CACHED_EXTENSIONS # rubocop:disable Performance/CaseWhenSplat + nil + # Ruby allows specifying native extensions as '.so' even when DLEXT + # is '.bundle'. This is where we handle that case. + when DOT_SO + x = search_index(feature[0..-4] + DLEXT) + return x if x + if DLEXT2 + x = search_index(feature[0..-4] + DLEXT2) + return x if x + end + else + # other, unknown extension. For example, `.rake`. Since we haven't + # cached these, we legitimately need to run the load path search. + raise(LoadPathCache::FallbackScan, '', []) + end + end + + # In development mode, we don't want to confidently return failures for + # cases where the file doesn't appear to be on the load path. We should + # be able to detect newly-created files without rebooting the + # application. + raise(LoadPathCache::FallbackScan, '', []) if @development_mode + end + + if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/ + def absolute_path?(path) + path[1] == ':' + end + else + def absolute_path?(path) + path.start_with?(SLASH) + end + end + + def unshift_paths(sender, *paths) + return unless sender == @path_obj + @mutex.synchronize { unshift_paths_locked(*paths) } + end + + def push_paths(sender, *paths) + return unless sender == @path_obj + @mutex.synchronize { push_paths_locked(*paths) } + end + + def reinitialize(path_obj = @path_obj) + @mutex.synchronize do + @path_obj = path_obj + ChangeObserver.register(self, @path_obj) + @index = {} + @dirs = {} + @generated_at = now + push_paths_locked(*@path_obj) + end + end + + private + + def dir_changed? + @prev_dir ||= Dir.pwd + if @prev_dir == Dir.pwd + false + else + @prev_dir = Dir.pwd + true + end + end + + def push_paths_locked(*paths) + @store.transaction do + paths.map(&:to_s).each do |path| + p = Path.new(path) + @has_relative_paths = true if p.relative? + next if p.non_directory? + expanded_path = p.expanded_path + entries, dirs = p.entries_and_dirs(@store) + # push -> low precedence -> set only if unset + dirs.each { |dir| @dirs[dir] ||= path } + entries.each { |rel| @index[rel] ||= expanded_path } + end + end + end + + def unshift_paths_locked(*paths) + @store.transaction do + paths.map(&:to_s).reverse_each do |path| + p = Path.new(path) + next if p.non_directory? + expanded_path = p.expanded_path + entries, dirs = p.entries_and_dirs(@store) + # unshift -> high precedence -> unconditional set + dirs.each { |dir| @dirs[dir] = path } + entries.each { |rel| @index[rel] = expanded_path } + end + end + end + + def expand_path(feature) + maybe_append_extension(File.expand_path(feature)) + end + + def stale? + @development_mode && @generated_at + AGE_THRESHOLD < now + end + + def now + Process.clock_gettime(Process::CLOCK_MONOTONIC).to_i + end + + if DLEXT2 + def search_index(f) + try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f + DLEXT2) || try_index(f) + end + + def maybe_append_extension(f) + try_ext(f + DOT_RB) || try_ext(f + DLEXT) || try_ext(f + DLEXT2) || f + end + else + def search_index(f) + try_index(f + DOT_RB) || try_index(f + DLEXT) || try_index(f) + end + + def maybe_append_extension(f) + try_ext(f + DOT_RB) || try_ext(f + DLEXT) || f + end + end + + def try_index(f) + if (p = @index[f]) + p + '/' + f + end + end + + def try_ext(f) + f if File.exist?(f) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/change_observer.rb b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/change_observer.rb new file mode 100644 index 00000000..41793d0a --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/change_observer.rb @@ -0,0 +1,62 @@ +module Bootsnap + module LoadPathCache + module ChangeObserver + module ArrayMixin + # For each method that adds items to one end or another of the array + # (<<, push, unshift, concat), override that method to also notify the + # observer of the change. + def <<(entry) + @lpc_observer.push_paths(self, entry.to_s) + super + end + + def push(*entries) + @lpc_observer.push_paths(self, *entries.map(&:to_s)) + super + end + + def unshift(*entries) + @lpc_observer.unshift_paths(self, *entries.map(&:to_s)) + super + end + + def concat(entries) + @lpc_observer.push_paths(self, *entries.map(&:to_s)) + super + end + + # uniq! keeps the first occurance of each path, otherwise preserving + # order, preserving the effective load path + def uniq!(*args) + ret = super + @lpc_observer.reinitialize if block_given? || !args.empty? + ret + end + + # For each method that modifies the array more aggressively, override + # the method to also have the observer completely reconstruct its state + # after the modification. Many of these could be made to modify the + # internal state of the LoadPathCache::Cache more efficiently, but the + # accounting cost would be greater than the hit from these, since we + # actively discourage calling them. + %i( + []= clear collect! compact! delete delete_at delete_if fill flatten! + insert keep_if map! pop reject! replace reverse! rotate! select! + shift shuffle! slice! sort! sort_by! + ).each do |method_name| + define_method(method_name) do |*args, &block| + ret = super(*args, &block) + @lpc_observer.reinitialize + ret + end + end + end + + def self.register(observer, arr) + return if arr.frozen? # can't register observer, but no need to. + arr.instance_variable_set(:@lpc_observer, observer) + arr.extend(ArrayMixin) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/active_support.rb b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/active_support.rb new file mode 100644 index 00000000..50963c04 --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/active_support.rb @@ -0,0 +1,106 @@ +module Bootsnap + module LoadPathCache + module CoreExt + module ActiveSupport + def self.without_bootsnap_cache + prev = Thread.current[:without_bootsnap_cache] || false + Thread.current[:without_bootsnap_cache] = true + yield + ensure + Thread.current[:without_bootsnap_cache] = prev + end + + def self.allow_bootsnap_retry(allowed) + prev = Thread.current[:without_bootsnap_retry] || false + Thread.current[:without_bootsnap_retry] = !allowed + yield + ensure + Thread.current[:without_bootsnap_retry] = prev + end + + module ClassMethods + def autoload_paths=(o) + super + Bootsnap::LoadPathCache.autoload_paths_cache.reinitialize(o) + end + + def search_for_file(path) + return super if Thread.current[:without_bootsnap_cache] + begin + Bootsnap::LoadPathCache.autoload_paths_cache.find(path) + rescue Bootsnap::LoadPathCache::ReturnFalse + nil # doesn't really apply here + rescue Bootsnap::LoadPathCache::FallbackScan + nil # doesn't really apply here + end + end + + def autoloadable_module?(path_suffix) + Bootsnap::LoadPathCache.autoload_paths_cache.load_dir(path_suffix) + end + + def remove_constant(const) + CoreExt::ActiveSupport.without_bootsnap_cache { super } + end + + def require_or_load(*) + CoreExt::ActiveSupport.allow_bootsnap_retry(true) do + super + end + end + + # If we can't find a constant using the patched implementation of + # search_for_file, try again with the default implementation. + # + # These methods call search_for_file, and we want to modify its + # behaviour. The gymnastics here are a bit awkward, but it prevents + # 200+ lines of monkeypatches. + def load_missing_constant(from_mod, const_name) + CoreExt::ActiveSupport.allow_bootsnap_retry(false) do + super + end + rescue NameError => e + raise(e) if e.instance_variable_defined?(Bootsnap::LoadPathCache::ERROR_TAG_IVAR) + e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true) + + # This function can end up called recursively, we only want to + # retry at the top-level. + raise(e) if Thread.current[:without_bootsnap_retry] + # If we already had cache disabled, there's no use retrying + raise(e) if Thread.current[:without_bootsnap_cache] + # NoMethodError is a NameError, but we only want to handle actual + # NameError instances. + raise(e) unless e.class == NameError + # We can only confidently handle cases when *this* constant fails + # to load, not other constants referred to by it. + raise(e) unless e.name == const_name + # If the constant was actually loaded, something else went wrong? + raise(e) if from_mod.const_defined?(const_name) + CoreExt::ActiveSupport.without_bootsnap_cache { super } + end + + # Signature has changed a few times over the years; easiest to not + # reiterate it with version polymorphism here... + def depend_on(*) + super + rescue LoadError => e + raise(e) if e.instance_variable_defined?(Bootsnap::LoadPathCache::ERROR_TAG_IVAR) + e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true) + + # If we already had cache disabled, there's no use retrying + raise(e) if Thread.current[:without_bootsnap_cache] + CoreExt::ActiveSupport.without_bootsnap_cache { super } + end + end + end + end + end +end + +module ActiveSupport + module Dependencies + class << self + prepend(Bootsnap::LoadPathCache::CoreExt::ActiveSupport::ClassMethods) + end + end +end diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb new file mode 100644 index 00000000..a7aaa542 --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb @@ -0,0 +1,92 @@ +module Bootsnap + module LoadPathCache + module CoreExt + def self.make_load_error(path) + err = LoadError.new("cannot load such file -- #{path}") + err.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true) + err.define_singleton_method(:path) { path } + err + end + end + end +end + +module Kernel + module_function # rubocop:disable Style/ModuleFunction + + alias_method(:require_without_bootsnap, :require) + + # Note that require registers to $LOADED_FEATURES while load does not. + def require_with_bootsnap_lfi(path, resolved = nil) + Bootsnap::LoadPathCache.loaded_features_index.register(path, resolved) do + require_without_bootsnap(resolved || path) + end + end + + def require(path) + return false if Bootsnap::LoadPathCache.loaded_features_index.key?(path) + + if (resolved = Bootsnap::LoadPathCache.load_path_cache.find(path)) + return require_with_bootsnap_lfi(path, resolved) + end + + raise(Bootsnap::LoadPathCache::CoreExt.make_load_error(path)) + rescue LoadError => e + e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true) + raise(e) + rescue Bootsnap::LoadPathCache::ReturnFalse + false + rescue Bootsnap::LoadPathCache::FallbackScan + require_with_bootsnap_lfi(path) + end + + alias_method(:require_relative_without_bootsnap, :require_relative) + def require_relative(path) + realpath = Bootsnap::LoadPathCache.realpath_cache.call( + caller_locations(1..1).first.absolute_path, path + ) + require(realpath) + end + + alias_method(:load_without_bootsnap, :load) + def load(path, wrap = false) + if (resolved = Bootsnap::LoadPathCache.load_path_cache.find(path)) + return load_without_bootsnap(resolved, wrap) + end + + # load also allows relative paths from pwd even when not in $: + if File.exist?(relative = File.expand_path(path)) + return load_without_bootsnap(relative, wrap) + end + + raise(Bootsnap::LoadPathCache::CoreExt.make_load_error(path)) + rescue LoadError => e + e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true) + raise(e) + rescue Bootsnap::LoadPathCache::ReturnFalse + false + rescue Bootsnap::LoadPathCache::FallbackScan + load_without_bootsnap(path, wrap) + end +end + +class Module + alias_method(:autoload_without_bootsnap, :autoload) + def autoload(const, path) + # NOTE: This may defeat LoadedFeaturesIndex, but it's not immediately + # obvious how to make it work. This feels like a pretty niche case, unclear + # if it will ever burn anyone. + # + # The challenge is that we don't control the point at which the entry gets + # added to $LOADED_FEATURES and won't be able to hook that modification + # since it's done in C-land. + autoload_without_bootsnap(const, Bootsnap::LoadPathCache.load_path_cache.find(path) || path) + rescue LoadError => e + e.instance_variable_set(Bootsnap::LoadPathCache::ERROR_TAG_IVAR, true) + raise(e) + rescue Bootsnap::LoadPathCache::ReturnFalse + false + rescue Bootsnap::LoadPathCache::FallbackScan + autoload_without_bootsnap(const, path) + end +end diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb new file mode 100644 index 00000000..acd76765 --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/loaded_features.rb @@ -0,0 +1,17 @@ +class << $LOADED_FEATURES + alias_method(:delete_without_bootsnap, :delete) + def delete(key) + Bootsnap::LoadPathCache.loaded_features_index.purge(key) + delete_without_bootsnap(key) + end + + alias_method(:reject_without_bootsnap!, :reject!) + def reject!(&block) + backup = dup + + # FIXME: if no block is passed we'd need to return a decorated iterator + reject_without_bootsnap!(&block) + + Bootsnap::LoadPathCache.loaded_features_index.purge_multi(backup - self) + end +end diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/loaded_features_index.rb b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/loaded_features_index.rb new file mode 100644 index 00000000..1a2c65e7 --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/loaded_features_index.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +module Bootsnap + module LoadPathCache + # LoadedFeaturesIndex partially mirrors an internal structure in ruby that + # we can't easily obtain an interface to. + # + # This works around an issue where, without bootsnap, *ruby* knows that it + # has already required a file by its short name (e.g. require 'bundler') if + # a new instance of bundler is added to the $LOAD_PATH which resolves to a + # different absolute path. This class makes bootsnap smart enough to + # realize that it has already loaded 'bundler', and not just + # '/path/to/bundler'. + # + # If you disable LoadedFeaturesIndex, you can see the problem this solves by: + # + # 1. `require 'a'` + # 2. Prepend a new $LOAD_PATH element containing an `a.rb` + # 3. `require 'a'` + # + # Ruby returns false from step 3. + # With bootsnap but with no LoadedFeaturesIndex, this loads two different + # `a.rb`s. + # With bootsnap and with LoadedFeaturesIndex, this skips the second load, + # returning false like ruby. + class LoadedFeaturesIndex + def initialize + @lfi = {} + @mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new # TODO: Remove once Ruby 2.2 support is dropped. + + # In theory the user could mutate $LOADED_FEATURES and invalidate our + # cache. If this ever comes up in practice — or if you, the + # enterprising reader, feels inclined to solve this problem — we could + # parallel the work done with ChangeObserver on $LOAD_PATH to mirror + # updates to our @lfi. + $LOADED_FEATURES.each do |feat| + hash = feat.hash + $LOAD_PATH.each do |lpe| + next unless feat.start_with?(lpe) + # /a/b/lib/my/foo.rb + # ^^^^^^^^^ + short = feat[(lpe.length + 1)..-1] + stripped = strip_extension(short) + @lfi[short] = hash + @lfi[stripped] = hash + end + end + end + + # We've optimized for initialize and register to be fast, and purge to be tolerable. + # If access patterns make this not-okay, we can lazy-invert the LFI on + # first purge and work from there. + def purge(feature) + @mutex.synchronize do + feat_hash = feature.hash + @lfi.reject! { |_, hash| hash == feat_hash } + end + end + + def purge_multi(features) + rejected_hashes = features.map(&:hash).to_set + @mutex.synchronize do + @lfi.reject! { |_, hash| rejected_hashes.include?(hash) } + end + end + + def key?(feature) + @mutex.synchronize { @lfi.key?(feature) } + end + + # There is a relatively uncommon case where we could miss adding an + # entry: + # + # If the user asked for e.g. `require 'bundler'`, and we went through the + # `FallbackScan` pathway in `kernel_require.rb` and therefore did not + # pass `long` (the full expanded absolute path), then we did are not able + # to confidently add the `bundler.rb` form to @lfi. + # + # We could either: + # + # 1. Just add `bundler.rb`, `bundler.so`, and so on, which is close but + # not quite right; or + # 2. Inspect $LOADED_FEATURES upon return from yield to find the matching + # entry. + def register(short, long = nil) + if long.nil? + pat = %r{/#{Regexp.escape(short)}(\.[^/]+)?$} + len = $LOADED_FEATURES.size + ret = yield + long = $LOADED_FEATURES[len..-1].detect { |feat| feat =~ pat } + else + ret = yield + end + + hash = long.hash + + # do we have 'bundler' or 'bundler.rb'? + altname = if File.extname(short) != '' + # strip the path from 'bundler.rb' -> 'bundler' + strip_extension(short) + elsif long && (ext = File.extname(long)) + # get the extension from the expanded path if given + # 'bundler' + '.rb' + short + ext + end + + @mutex.synchronize do + @lfi[short] = hash + (@lfi[altname] = hash) if altname + end + + ret + end + + private + + STRIP_EXTENSION = /\.[^.]*?$/ + private_constant(:STRIP_EXTENSION) + + def strip_extension(f) + f.sub(STRIP_EXTENSION, '') + end + end + end +end diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/path.rb b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/path.rb new file mode 100644 index 00000000..8d0eed7c --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/path.rb @@ -0,0 +1,113 @@ +require_relative('path_scanner') + +module Bootsnap + module LoadPathCache + class Path + # A path is considered 'stable' if it is part of a Gem.path or the ruby + # distribution. When adding or removing files in these paths, the cache + # must be cleared before the change will be noticed. + def stable? + stability == STABLE + end + + # A path is considered volatile if it doesn't live under a Gem.path or + # the ruby distribution root. These paths are scanned for new additions + # more frequently. + def volatile? + stability == VOLATILE + end + + attr_reader(:path) + + def initialize(path) + @path = path.to_s + end + + # True if the path exists, but represents a non-directory object + def non_directory? + !File.stat(path).directory? + rescue Errno::ENOENT, Errno::ENOTDIR + false + end + + def relative? + !path.start_with?(SLASH) + end + + # Return a list of all the requirable files and all of the subdirectories + # of this +Path+. + def entries_and_dirs(store) + if stable? + # the cached_mtime field is unused for 'stable' paths, but is + # set to zero anyway, just in case we change the stability heuristics. + _, entries, dirs = store.get(expanded_path) + return [entries, dirs] if entries # cache hit + entries, dirs = scan! + store.set(expanded_path, [0, entries, dirs]) + return [entries, dirs] + end + + cached_mtime, entries, dirs = store.get(expanded_path) + + current_mtime = latest_mtime(expanded_path, dirs || []) + return [[], []] if current_mtime == -1 # path does not exist + return [entries, dirs] if cached_mtime == current_mtime + + entries, dirs = scan! + store.set(expanded_path, [current_mtime, entries, dirs]) + [entries, dirs] + end + + def expanded_path + File.expand_path(path) + end + + private + + def scan! # (expensive) returns [entries, dirs] + PathScanner.call(expanded_path) + end + + # last time a directory was modified in this subtree. +dirs+ should be a + # list of relative paths to directories under +path+. e.g. for /a/b and + # /a/b/c, pass ('/a/b', ['c']) + def latest_mtime(path, dirs) + max = -1 + ["", *dirs].each do |dir| + curr = begin + File.mtime("#{path}/#{dir}").to_i + rescue Errno::ENOENT, Errno::ENOTDIR + -1 + end + max = curr if curr > max + end + max + end + + # a Path can be either stable of volatile, depending on how frequently we + # expect its contents may change. Stable paths aren't rescanned nearly as + # often. + STABLE = :stable + VOLATILE = :volatile + + # Built-in ruby lib stuff doesn't change, but things can occasionally be + # installed into sitedir, which generally lives under libdir. + RUBY_LIBDIR = RbConfig::CONFIG['libdir'] + RUBY_SITEDIR = RbConfig::CONFIG['sitedir'] + + def stability + @stability ||= begin + if Gem.path.detect { |p| expanded_path.start_with?(p.to_s) } + STABLE + elsif Bootsnap.bundler? && expanded_path.start_with?(Bundler.bundle_path.to_s) + STABLE + elsif expanded_path.start_with?(RUBY_LIBDIR) && !expanded_path.start_with?(RUBY_SITEDIR) + STABLE + else + VOLATILE + end + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/path_scanner.rb b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/path_scanner.rb new file mode 100644 index 00000000..57ef11be --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/path_scanner.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require_relative('../explicit_require') + +module Bootsnap + module LoadPathCache + module PathScanner + ALL_FILES = "/{,**/*/**/}*" + REQUIRABLE_EXTENSIONS = [DOT_RB] + DL_EXTENSIONS + NORMALIZE_NATIVE_EXTENSIONS = !DL_EXTENSIONS.include?(LoadPathCache::DOT_SO) + ALTERNATIVE_NATIVE_EXTENSIONS_PATTERN = /\.(o|bundle|dylib)\z/ + + BUNDLE_PATH = if Bootsnap.bundler? + (Bundler.bundle_path.cleanpath.to_s << LoadPathCache::SLASH).freeze + else + '' + end + + def self.call(path) + path = path.to_s + + relative_slice = (path.size + 1)..-1 + # If the bundle path is a descendent of this path, we do additional + # checks to prevent recursing into the bundle path as we recurse + # through this path. We don't want to scan the bundle path because + # anything useful in it will be present on other load path items. + # + # This can happen if, for example, the user adds '.' to the load path, + # and the bundle path is '.bundle'. + contains_bundle_path = BUNDLE_PATH.start_with?(path) + + dirs = [] + requirables = [] + + Dir.glob(path + ALL_FILES).each do |absolute_path| + next if contains_bundle_path && absolute_path.start_with?(BUNDLE_PATH) + relative_path = absolute_path.slice(relative_slice) + + if File.directory?(absolute_path) + dirs << relative_path + elsif REQUIRABLE_EXTENSIONS.include?(File.extname(relative_path)) + requirables << relative_path + end + end + + [requirables, dirs] + end + end + end +end diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/realpath_cache.rb b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/realpath_cache.rb new file mode 100644 index 00000000..f831e705 --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/realpath_cache.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Bootsnap + module LoadPathCache + class RealpathCache + def initialize + @cache = Hash.new { |h, k| h[k] = realpath(*k) } + end + + def call(*key) + @cache[key] + end + + private + + def realpath(caller_location, path) + base = File.dirname(caller_location) + file = find_file(File.expand_path(path, base)) + dir = File.dirname(file) + File.join(dir, File.basename(file)) + end + + def find_file(name) + ['', *CACHED_EXTENSIONS].each do |ext| + filename = "#{name}#{ext}" + return File.realpath(filename) if File.exist?(filename) + end + name + end + end + end +end diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/store.rb b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/store.rb new file mode 100644 index 00000000..9af0e1e5 --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/store.rb @@ -0,0 +1,89 @@ +require_relative('../explicit_require') + +Bootsnap::ExplicitRequire.with_gems('msgpack') { require('msgpack') } +Bootsnap::ExplicitRequire.from_rubylibdir('fileutils') + +module Bootsnap + module LoadPathCache + class Store + NestedTransactionError = Class.new(StandardError) + SetOutsideTransactionNotAllowed = Class.new(StandardError) + + def initialize(store_path) + @store_path = store_path + # TODO: Remove conditional once Ruby 2.2 support is dropped. + @txn_mutex = defined?(::Mutex) ? ::Mutex.new : ::Thread::Mutex.new + @dirty = false + load_data + end + + def get(key) + @data[key] + end + + def fetch(key) + raise(SetOutsideTransactionNotAllowed) unless @txn_mutex.owned? + v = get(key) + unless v + @dirty = true + v = yield + @data[key] = v + end + v + end + + def set(key, value) + raise(SetOutsideTransactionNotAllowed) unless @txn_mutex.owned? + if value != @data[key] + @dirty = true + @data[key] = value + end + end + + def transaction + raise(NestedTransactionError) if @txn_mutex.owned? + @txn_mutex.synchronize do + begin + yield + ensure + commit_transaction + end + end + end + + private + + def commit_transaction + if @dirty + dump_data + @dirty = false + end + end + + def load_data + @data = begin + MessagePack.load(File.binread(@store_path)) + # handle malformed data due to upgrade incompatability + rescue Errno::ENOENT, MessagePack::MalformedFormatError, MessagePack::UnknownExtTypeError, EOFError + {} + rescue ArgumentError => e + e.message =~ /negative array size/ ? {} : raise + end + end + + def dump_data + # Change contents atomically so other processes can't get invalid + # caches if they read at an inopportune time. + tmp = "#{@store_path}.#{Process.pid}.#{(rand * 100000).to_i}.tmp" + FileUtils.mkpath(File.dirname(tmp)) + exclusive_write = File::Constants::CREAT | File::Constants::EXCL | File::Constants::WRONLY + # `encoding:` looks redundant wrt `binwrite`, but necessary on windows + # because binary is part of mode. + File.binwrite(tmp, MessagePack.dump(@data), mode: exclusive_write, encoding: Encoding::BINARY) + FileUtils.mv(tmp, @store_path) + rescue Errno::EEXIST + retry + end + end + end +end diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/setup.rb b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/setup.rb new file mode 100644 index 00000000..a0aa40bc --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/setup.rb @@ -0,0 +1,38 @@ +require_relative('../bootsnap') + +env = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || ENV['ENV'] +development_mode = ['', nil, 'development'].include?(env) + +cache_dir = ENV['BOOTSNAP_CACHE_DIR'] +unless cache_dir + config_dir_frame = caller.detect do |line| + line.include?('/config/') + end + + unless config_dir_frame + $stderr.puts("[bootsnap/setup] couldn't infer cache directory! Either:") + $stderr.puts("[bootsnap/setup] 1. require bootsnap/setup from your application's config directory; or") + $stderr.puts("[bootsnap/setup] 2. Define the environment variable BOOTSNAP_CACHE_DIR") + + raise("couldn't infer bootsnap cache directory") + end + + path = config_dir_frame.split(/:\d+:/).first + path = File.dirname(path) until File.basename(path) == 'config' + app_root = File.dirname(path) + + cache_dir = File.join(app_root, 'tmp', 'cache') +end + +ruby_version = Gem::Version.new(RUBY_VERSION) +iseq_cache_enabled = ruby_version < Gem::Version.new('2.5.0') || ruby_version >= Gem::Version.new('2.6.0') + +Bootsnap.setup( + cache_dir: cache_dir, + development_mode: development_mode, + load_path_cache: true, + autoload_paths_cache: true, # assume rails. open to PRs to impl. detection + disable_trace: false, + compile_cache_iseq: iseq_cache_enabled, + compile_cache_yaml: true, +) diff --git a/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/version.rb b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/version.rb new file mode 100644 index 00000000..3630bea0 --- /dev/null +++ b/path/ruby/2.6.0/gems/bootsnap-1.4.5/lib/bootsnap/version.rb @@ -0,0 +1,3 @@ +module Bootsnap + VERSION = "1.4.5" +end diff --git a/tutor-virtual-2/tmp/storage/.keep b/path/ruby/2.6.0/gems/bootsnap-1.4.5/shipit.rubygems.yml similarity index 100% rename from tutor-virtual-2/tmp/storage/.keep rename to path/ruby/2.6.0/gems/bootsnap-1.4.5/shipit.rubygems.yml diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/CHANGES b/path/ruby/2.6.0/gems/builder-3.2.3/CHANGES new file mode 100644 index 00000000..0f608fe4 --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/CHANGES @@ -0,0 +1,107 @@ += Change Log + +== Version 3.2.0 + +* Ruby 2.0 compatibility changes. + +* Allow single quoted attributes. + +== Version 3.1.0 + +* Included the to_xs arity patch needed for weird Rails compatibility + issue. + +* Escaping newlines in attributes now. + +* Allow method caching + +== Version 3.0.0 + +* Ruby 1.9 compatiblity issues. + +== Version 2.2.0 + +* Applied patch from Thijs van der Vossen to allow UTF-8 encoded + output when the encoding is UTF-8 and $KCODE is UTF8. + +== Version 2.1.2 + +* Fixed bug where private methods in kernel could leak through using + tag!(). Thanks to Hagen Overdick for finding and diagnosing this + bug. + +== Version 2.1.1 + +* Fixed typo in XmlMarkup class docs (ident => indent). (from Martin + Fowler). +* Removed extra directory indirection from legacy CVS to SVN move. +* Removed some extraneous tabs from source. +* Fixed test on private methods in blankslate to differentiate between + targetted and untargetted private methods. +* Removed legacy capture of @self in XmlBase (@self was used back when + we used instance eval). +* Added additional tests for global functions (both direct and included). + +== Version 2.1.0 + +* Fixed bug in BlankSlate where including a module into Object could + cause methods to leak into BlankSlate. +* Made BlankSlate available as its own gem. Currently the builder gem + still directly includes the BlankSlate code. +* Added reveal capability to BlankSlate. + +== Version 2.0.0 + +* Added doc directory +* Added unit tests for XmlEvents. +* Added XChar module and used it in the _escape method. +* Attributes are now quoted by default when strings. Use Symbol + attribute values for unquoted behavior. + +== Version 1.2.4 + +* Added a cdata! command to an XML Builder (from Josh Knowles). + +== Version 1.2.3 + +The attributes in the instruction will be ordered: +version, encoding, standalone. + +== Version 1.2.2 + +Another fix for BlankSlate. The Kernal/Object traps added in 1.2.1 +failed when a method was defined late more than once. Since the +method was already marked as removed, another attempt to undefine it +raised an error. The fix was to check the list of instance methods +before attempting the undef operation. Thanks to Florian Gross and +David Heinemeier Hansson for the patch. + +== Version 1.2.1 + +BlankSlate now traps method definitions in Kernel and Object to avoid +late method definitions inadvertently becoming part of the definition +of BlankSlate as well. + +== Version 1.2.0 + +Improved support for entity declarations by allowing nested +declarations and removal of the attribute processing. + +Added namespace support. + +== Version 1.1.0 + +Added support for comments, entity declarations and processing instructions. + +== Version 1.0.0 + +Removed use of instace_eval making the use of XmlMarkup much +less prone to error. + +== Version 0.1.1 + +Bug fix. + +== Version 0.1.0 + +Initial version release. diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/MIT-LICENSE b/path/ruby/2.6.0/gems/builder-3.2.3/MIT-LICENSE new file mode 100644 index 00000000..7d9be510 --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2003-2012 Jim Weirich (jim.weirich@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/README.md b/path/ruby/2.6.0/gems/builder-3.2.3/README.md new file mode 100644 index 00000000..1bc074b5 --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/README.md @@ -0,0 +1,258 @@ +# Project: Builder + +## Goal + +Provide a simple way to create XML markup and data structures. + +## Classes + +Builder::XmlMarkup:: Generate XML markup notation +Builder::XmlEvents:: Generate XML events (i.e. SAX-like) + +**Notes:** + +* An Builder::XmlTree class to generate XML tree + (i.e. DOM-like) structures is also planned, but not yet implemented. + Also, the events builder is currently lagging the markup builder in + features. + +## Usage + +```ruby + require 'rubygems' + require_gem 'builder', '~> 2.0' + + builder = Builder::XmlMarkup.new + xml = builder.person { |b| b.name("Jim"); b.phone("555-1234") } + xml #=> Jim555-1234 +``` + +or + +```ruby + require 'rubygems' + require_gem 'builder' + + builder = Builder::XmlMarkup.new(:target=>STDOUT, :indent=>2) + builder.person { |b| b.name("Jim"); b.phone("555-1234") } + # + # Prints: + # + # Jim + # 555-1234 + # +``` + +## Compatibility + +### Version 2.0.0 Compatibility Changes + +Version 2.0.0 introduces automatically escaped attribute values for +the first time. Versions prior to 2.0.0 did not insert escape +characters into attribute values in the XML markup. This allowed +attribute values to explicitly reference entities, which was +occasionally used by a small number of developers. Since strings +could always be explicitly escaped by hand, this was not a major +restriction in functionality. + +However, it did surprise most users of builder. Since the body text is +normally escaped, everybody expected the attribute values to be +escaped as well. Escaped attribute values were the number one support +request on the 1.x Builder series. + +Starting with Builder version 2.0.0, all attribute values expressed as +strings will be processed and the appropriate characters will be +escaped (e.g. "&" will be translated to "&amp;"). Attribute values +that are expressed as Symbol values will not be processed for escaped +characters and will be unchanged in output. (Yes, this probably counts +as Symbol abuse, but the convention is convenient and flexible). + +Example: + +```ruby + xml = Builder::XmlMarkup.new + xml.sample(:escaped=>"This&That", :unescaped=>:"Here&There") + xml.target! => + +``` + +### Version 1.0.0 Compatibility Changes + +Version 1.0.0 introduces some changes that are not backwards +compatible with earlier releases of builder. The main areas of +incompatibility are: + +* Keyword based arguments to +new+ (rather than positional based). It + was found that a developer would often like to specify indentation + without providing an explicit target, or specify a target without + indentation. Keyword based arguments handle this situation nicely. + +* Builder must now be an explicit target for markup tags. Instead of + writing + +```ruby + xml_markup = Builder::XmlMarkup.new + xml_markup.div { strong("text") } +``` + + you need to write + +```ruby + xml_markup = Builder::XmlMarkup.new + xml_markup.div { xml_markup.strong("text") } +``` + +* The builder object is passed as a parameter to all nested markup + blocks. This allows you to create a short alias for the builder + object that can be used within the block. For example, the previous + example can be written as: + +```ruby + xml_markup = Builder::XmlMarkup.new + xml_markup.div { |xml| xml.strong("text") } +``` + +* If you have both a pre-1.0 and a post-1.0 gem of builder installed, + you can choose which version to use through the RubyGems + +require_gem+ facility. + +```ruby + require_gem 'builder', "~> 0.0" # Gets the old version + require_gem 'builder', "~> 1.0" # Gets the new version +``` + +## Features + +* XML Comments are supported ... + +```ruby + xml_markup.comment! "This is a comment" + #=> +``` + +* XML processing instructions are supported ... + +```ruby + xml_markup.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8" + #=> +``` + + If the processing instruction is omitted, it defaults to "xml". + When the processing instruction is "xml", the defaults attributes + are: + + version: 1.0 + encoding: "UTF-8" + + (NOTE: if the encoding is set to "UTF-8" and $KCODE is set to + "UTF8", then Builder will emit UTF-8 encoded strings rather than + encoding non-ASCII characters as entities.) + +* XML entity declarations are now supported to a small degree. + +```ruby + xml_markup.declare! :DOCTYPE, :chapter, :SYSTEM, "../dtds/chapter.dtd" + #=> +``` + + The parameters to a declare! method must be either symbols or + strings. Symbols are inserted without quotes, and strings are + inserted with double quotes. Attribute-like arguments in hashes are + not allowed. + + If you need to have an argument to declare! be inserted without + quotes, but the argument does not conform to the typical Ruby + syntax for symbols, then use the :"string" form to specify a symbol. + + For example: + +```ruby + xml_markup.declare! :ELEMENT, :chapter, :"(title,para+)" + #=> +``` + + Nested entity declarations are allowed. For example: + +```ruby + @xml_markup.declare! :DOCTYPE, :chapter do |x| + x.declare! :ELEMENT, :chapter, :"(title,para+)" + x.declare! :ELEMENT, :title, :"(#PCDATA)" + x.declare! :ELEMENT, :para, :"(#PCDATA)" + end + + #=> + + + + + ]> +``` + +* Some support for XML namespaces is now available. If the first + argument to a tag call is a symbol, it will be joined to the tag to + produce a namespace:tag combination. It is easier to show this than + describe it. + +```ruby + xml.SOAP :Envelope do ... end +``` + + Just put a space before the colon in a namespace to produce the + right form for builder (e.g. "SOAP:Envelope" => + "xml.SOAP :Envelope") + +* String attribute values are now escaped by default by + Builder (NOTE: this is _new_ behavior as of version 2.0). + + However, occasionally you need to use entities in attribute values. + Using a symbol (rather than a string) for an attribute value will + cause Builder to not run its quoting/escaping algorithm on that + particular value. + + (Note: The +escape_attrs+ option for builder is now + obsolete). + + Example: + +```ruby + xml = Builder::XmlMarkup.new + xml.sample(:escaped=>"This&That", :unescaped=>:"Here&There") + xml.target! => + +``` + +* UTF-8 Support + + Builder correctly translates UTF-8 characters into valid XML. (New + in version 2.0.0). Thanks to Sam Ruby for the translation code. + + You can get UTF-8 encoded output by making sure that the XML + encoding is set to "UTF-8" and that the $KCODE variable is set to + "UTF8". + +```ruby + $KCODE = 'UTF8' + xml = Builder::Markup.new + xml.instruct!(:xml, :encoding => "UTF-8") + xml.sample("Iñtërnâtiônàl") + xml.target! => + "Iñtërnâtiônàl" +``` + +## Links + +| Description | Link | +| :----: | :----: | +| Documents | http://builder.rubyforge.org/ | +| Github Clone | git://github.com/jimweirich/builder.git | +| Issue / Bug Reports | https://github.com/jimweirich/builder/issues?state=open | + +## Contact + +| Description | Value | +| :----: | :----: | +| Author | Jim Weirich | +| Email | jim.weirich@gmail.com | +| Home Page | http://onestepback.org | +| License | MIT Licence (http://www.opensource.org/licenses/mit-license.html) | diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/Rakefile b/path/ruby/2.6.0/gems/builder-3.2.3/Rakefile new file mode 100644 index 00000000..b082fbe4 --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/Rakefile @@ -0,0 +1,195 @@ +# Rakefile for rake -*- ruby -*- + +# Copyright 2004, 2005, 2006 by Jim Weirich (jim@weirichhouse.org). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. + +require 'rake/clean' +require 'rake/testtask' +begin + require 'rubygems' + require 'rubygems/package_task' + require 'rdoc/task' +rescue Exception + nil +end + +require './lib/builder/version' + +# Determine the current version of the software + +CLOBBER.include('pkg', 'html') +CLEAN.include('pkg/builder-*').include('pkg/blankslate-*').exclude('pkg/*.gem') + +PKG_VERSION = Builder::VERSION + +SRC_RB = FileList['lib/**/*.rb'] + +# The default task is run if rake is given no explicit arguments. + +desc "Default Task" +task :default => :test_all + +# Test Tasks --------------------------------------------------------- + +desc "Run all tests" +task :test_all => [:test_units] +task :ta => [:test_all] + +task :tu => [:test_units] + +Rake::TestTask.new("test_units") do |t| + t.test_files = FileList['test/test*.rb'] + t.libs << "." + t.verbose = false +end + +# Create a task to build the RDOC documentation tree. + +if defined?(RDoc) + rd = RDoc::Task.new("rdoc") { |rdoc| + rdoc.rdoc_dir = 'html' + rdoc.title = "Builder for Markup" + rdoc.options << '--line-numbers' << '--inline-source' << '--main' << 'README.rdoc' + rdoc.rdoc_files.include('lib/**/*.rb', '[A-Z]*', 'doc/**/*.rdoc').exclude("TAGS") + rdoc.template = 'doc/jamis.rb' + } +else + rd = Struct.new(:rdoc_files).new([]) +end + +# ==================================================================== +# Create a task that will package the Rake software into distributable +# gem files. + +PKG_FILES = FileList[ + '[A-Z]*', + 'doc/**/*', + 'lib/**/*.rb', + 'test/**/*.rb', + 'rakelib/**/*' +] +PKG_FILES.exclude('test/test_cssbuilder.rb') +PKG_FILES.exclude('lib/builder/css.rb') +PKG_FILES.exclude('TAGS') + +BLANKSLATE_FILES = FileList[ + 'lib/blankslate.rb', + 'test/test_blankslate.rb' +] + +if ! defined?(Gem) + puts "Package Target requires RubyGEMs" +else + spec = Gem::Specification.new do |s| + + #### Basic information. + + s.name = 'builder' + s.version = PKG_VERSION + s.summary = "Builders for MarkUp." + s.description = %{\ +Builder provides a number of builder objects that make creating structured data +simple to do. Currently the following builder objects are supported: + +* XML Markup +* XML Events +} + + s.files = PKG_FILES.to_a + s.require_path = 'lib' + + s.test_files = PKG_FILES.select { |fn| fn =~ /^test\/test/ } + + s.has_rdoc = true + s.extra_rdoc_files = rd.rdoc_files.reject { |fn| fn =~ /\.rb$/ }.to_a + s.rdoc_options << + '--title' << 'Builder -- Easy XML Building' << + '--main' << 'README.rdoc' << + '--line-numbers' + + s.author = "Jim Weirich" + s.email = "jim.weirich@gmail.com" + s.homepage = "http://onestepback.org" + s.license = 'MIT' + end + + blankslate_spec = Gem::Specification.new do |s| + + #### Basic information. + + s.name = 'blankslate' + s.version = PKG_VERSION + s.summary = "Blank Slate base class." + s.description = %{\ +BlankSlate provides a base class where almost all of the methods from Object and +Kernel have been removed. This is useful when providing proxy object and other +classes that make heavy use of method_missing. +} + + s.files = BLANKSLATE_FILES.to_a + s.require_path = 'lib' + + s.test_files = PKG_FILES.select { |fn| fn =~ /^test\/test/ } + + s.has_rdoc = true + s.extra_rdoc_files = rd.rdoc_files.reject { |fn| fn =~ /\.rb$/ }.to_a + s.rdoc_options << + '--title' << 'BlankSlate -- Base Class for building proxies.' << + '--main' << 'README.rdoc' << + '--line-numbers' + + s.author = "Jim Weirich" + s.email = "jim.weirich@gmail.com" + s.homepage = "http://onestepback.org" + s.license = 'MIT' + end + + namespace 'builder' do + Gem::PackageTask.new(spec) do |t| + t.need_tar = false + end + end + + namespace 'blankslate' do + Gem::PackageTask.new(blankslate_spec) do |t| + t.need_tar = false + end + end + + task :package => [:remove_tags, 'builder:package', 'blankslate:package'] +end + +task :remove_tags do + rm "TAGS" rescue nil +end + +# RCov --------------------------------------------------------------- +begin + require 'rcov/rcovtask' + + Rcov::RcovTask.new do |t| + t.libs << "test" + t.rcov_opts = [ + '-xRakefile', '--text-report' + ] + t.test_files = FileList[ + 'test/test*.rb' + ] + t.output_dir = 'coverage' + t.verbose = true + end +rescue LoadError + # No rcov available +end + +desc "Install the jamis RDoc template" +task :install_jamis_template do + require 'rbconfig' + dest_dir = File.join(Config::CONFIG['rubylibdir'], "rdoc/generators/template/html") + fail "Unabled to write to #{dest_dir}" unless File.writable?(dest_dir) + install "doc/jamis.rb", dest_dir, :verbose => true +end diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/doc/jamis.rb b/path/ruby/2.6.0/gems/builder-3.2.3/doc/jamis.rb new file mode 100644 index 00000000..a00b5834 --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/doc/jamis.rb @@ -0,0 +1,591 @@ +module RDoc +module Page + +FONTS = "\"Bitstream Vera Sans\", Verdana, Arial, Helvetica, sans-serif" + +STYLE = < pre { + padding: 0.5em; + border: 1px dotted black; + background: #FFE; +} + +CSS + +XHTML_PREAMBLE = %{ + +} + +HEADER = XHTML_PREAMBLE + < + + %title% + + + + + + + +ENDHEADER + +FILE_PAGE = < +

    +
    + + + +
    File
    %short_name%
    + + + + + + + + + +
    Path:%full_path% +IF:cvsurl +  (CVS) +ENDIF:cvsurl +
    Modified:%dtm_modified%
    +
    +

    +HTML + +################################################################### + +CLASS_PAGE = < + %classmod%
    %full_name% + + + + + + +IF:parent + + + + +ENDIF:parent +
    In: +START:infiles +HREF:full_path_url:full_path: +IF:cvsurl + (CVS) +ENDIF:cvsurl +END:infiles +
    Parent: +IF:par_url + +ENDIF:par_url +%parent% +IF:par_url + +ENDIF:par_url +
    + + + +HTML + +################################################################### + +METHOD_LIST = < +IF:diagram +
    + %diagram% +
    +ENDIF:diagram + +IF:description +
    %description%
    +ENDIF:description + +IF:requires +
    Required Files
    +
      +START:requires +
    • HREF:aref:name:
    • +END:requires +
    +ENDIF:requires + +IF:toc +
    Contents
    + +ENDIF:toc + +IF:methods +
    Methods
    +
      +START:methods +
    • HREF:aref:name:
    • +END:methods +
    +ENDIF:methods + +IF:includes +
    Included Modules
    +
      +START:includes +
    • HREF:aref:name:
    • +END:includes +
    +ENDIF:includes + +START:sections +IF:sectitle + +IF:seccomment +
    +%seccomment% +
    +ENDIF:seccomment +ENDIF:sectitle + +IF:classlist +
    Classes and Modules
    + %classlist% +ENDIF:classlist + +IF:constants +
    Constants
    + +START:constants + + + + + +IF:desc + + + + +ENDIF:desc +END:constants +
    %name%=%value%
     %desc%
    +ENDIF:constants + +IF:attributes +
    Attributes
    + +START:attributes + + + + + +END:attributes +
    +IF:rw +[%rw%] +ENDIF:rw + %name%%a_desc%
    +ENDIF:attributes + +IF:method_list +START:method_list +IF:methods +
    %type% %category% methods
    +START:methods +
    +
    +IF:callseq + %callseq% +ENDIF:callseq +IFNOT:callseq + %name%%params% +ENDIF:callseq +IF:codeurl +[ source ] +ENDIF:codeurl +
    +IF:m_desc +
    + %m_desc% +
    +ENDIF:m_desc +IF:aka +
    + This method is also aliased as +START:aka + %name% +END:aka +
    +ENDIF:aka +IF:sourcecode +
    + +
    +
    +%sourcecode%
    +
    +
    +
    +ENDIF:sourcecode +
    +END:methods +ENDIF:methods +END:method_list +ENDIF:method_list +END:sections +
    +HTML + +FOOTER = < + +ENDFOOTER + +BODY = HEADER + < + +
    + #{METHOD_LIST} +
    + + #{FOOTER} +ENDBODY + +########################## Source code ########################## + +SRC_PAGE = XHTML_PREAMBLE + < +%title% + + + + +
    %code%
    + + +HTML + +########################## Index ################################ + +FR_INDEX_BODY = < + + + + + + + +
    +START:entries +%name%
    +END:entries +
    + +HTML + +CLASS_INDEX = FILE_INDEX +METHOD_INDEX = FILE_INDEX + +INDEX = XHTML_PREAMBLE + < + + %title% + + + + + + + + + +IF:inline_source + +ENDIF:inline_source +IFNOT:inline_source + + + + +ENDIF:inline_source + + <body bgcolor="white"> + Click <a href="html/index.html">here</a> for a non-frames + version of this page. + </body> + + + + +HTML + +end +end + + diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/doc/releases/builder-1.2.4.rdoc b/path/ruby/2.6.0/gems/builder-3.2.3/doc/releases/builder-1.2.4.rdoc new file mode 100644 index 00000000..a1cf54fd --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/doc/releases/builder-1.2.4.rdoc @@ -0,0 +1,31 @@ += Builder 1.2.4 Released. + +Added a "CDATA" method to the XML Markup builder (from Josh Knowles). + +== What is Builder? + +Builder::XmlMarkup allows easy programmatic creation of XML markup. +For example: + + builder = Builder::XmlMarkup.new(:target=>STDOUT, :indent=>2) + builder.person { |b| b.name("Jim"); b.phone("555-1234") } + puts builder.target! + +will generate: + + + Jim + 555-1234 + + +== Availability + +The easiest way to get and install builder is via RubyGems ... + + gem install builder (you may need root/admin privileges) + +== Thanks + +* Josh Knowles for the cdata! patch. + +-- Jim Weirich diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/doc/releases/builder-2.0.0.rdoc b/path/ruby/2.6.0/gems/builder-3.2.3/doc/releases/builder-2.0.0.rdoc new file mode 100644 index 00000000..ed9e086d --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/doc/releases/builder-2.0.0.rdoc @@ -0,0 +1,46 @@ += Builder 2.0.0 Released. + +== Changes in 2.0.0 + +* UTF-8 characters in data are now correctly translated to their XML + equivalents. (Thanks to Sam Ruby) + +* Attribute values are now escaped by default. See the README + file for details. + +NOTE: The escaping attribute values by default is different +than in previous releases of Builder. This makes version 2.0.0 +somewhat incompatible with the 1.x series of Builder. If you use "&", +"<", or ">" in attributes values, you may have to change your +code. (Essentially you remove the manual escaping. The new way is +easier, believe me). + +== What is Builder? + +Builder::XmlMarkup is a library that allows easy programmatic creation +of XML markup. For example: + + builder = Builder::XmlMarkup.new(:target=>STDOUT, :indent=>2) + builder.person { |b| b.name("Jim"); b.phone("555-1234") } + +will generate: + + + Jim + 555-1234 + + +== Availability + +The easiest way to get and install builder is via RubyGems ... + + gem install builder (you may need root/admin privileges) + +== Thanks + +* Sam Ruby for the XChar module and the related UTF-8 translation + tools. +* Also to Sam Ruby for gently persuading me to start quoting attribute + values. + +-- Jim Weirich diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/doc/releases/builder-2.1.1.rdoc b/path/ruby/2.6.0/gems/builder-3.2.3/doc/releases/builder-2.1.1.rdoc new file mode 100755 index 00000000..dbbf1213 --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/doc/releases/builder-2.1.1.rdoc @@ -0,0 +1,58 @@ += Builder 2.1.1 Released. + +Release 2.1.1 of Builder is mainly a bug fix release. + +== Changes in 2.1.1 + +* Added reveal capability to BlankSlate. + +* Fixed a bug in BlankSlate where including a module into Object could + cause methods to leak into BlankSlate. + +* Fixed typo in XmlMarkup class docs (from Martin Fowler). + +* Fixed test on private methods to differentiate between targetted and + untargetted private methods. + +* Removed legacy capture of @self in XmlBase (@self was used back when + we used instance eval). + +* Added additional tests for global functions (both direct and + included). + +* Several misc internal cleanups, including rearranging the source + code tree. + +NOTE: The escaping attribute values by default is different +than in previous releases of Builder. This makes version 2.0.x +somewhat incompatible with the 1.x series of Builder. If you use "&", +"<", or ">" in attributes values, you may have to change your +code. (Essentially you remove the manual escaping. The new way is +easier, believe me). + +== What is Builder? + +Builder::XmlMarkup is a library that allows easy programmatic creation +of XML markup. For example: + + builder = Builder::XmlMarkup.new(:target=>STDOUT, :indent=>2) + builder.person { |b| b.name("Jim"); b.phone("555-1234") } + +will generate: + + + Jim + 555-1234 + + +== Availability + +The easiest way to get and install builder is via RubyGems ... + + gem install builder (you may need root/admin privileges) + +== Thanks + +* Martin Fowler for spotting some typos in the documentation. + +-- Jim Weirich diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/lib/blankslate.rb b/path/ruby/2.6.0/gems/builder-3.2.3/lib/blankslate.rb new file mode 100644 index 00000000..931c8a76 --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/lib/blankslate.rb @@ -0,0 +1,137 @@ +#!/usr/bin/env ruby +#-- +# Copyright 2004, 2006 by Jim Weirich (jim@weirichhouse.org). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +#++ + +class String + if instance_methods.first.is_a?(Symbol) + def _blankslate_as_name + to_sym + end + else + def _blankslate_as_name + self + end + end +end + +class Symbol + if instance_methods.first.is_a?(Symbol) + def _blankslate_as_name + self + end + else + def _blankslate_as_name + to_s + end + end +end + +###################################################################### +# BlankSlate provides an abstract base class with no predefined +# methods (except for \_\_send__ and \_\_id__). +# BlankSlate is useful as a base class when writing classes that +# depend upon method_missing (e.g. dynamic proxies). +# +class BlankSlate + class << self + + # Hide the method named +name+ in the BlankSlate class. Don't + # hide +instance_eval+ or any method beginning with "__". + def hide(name) + warn_level = $VERBOSE + $VERBOSE = nil + if instance_methods.include?(name._blankslate_as_name) && + name !~ /^(__|instance_eval$)/ + @hidden_methods ||= {} + @hidden_methods[name.to_sym] = instance_method(name) + undef_method name + end + ensure + $VERBOSE = warn_level + end + + def find_hidden_method(name) + @hidden_methods ||= {} + @hidden_methods[name] || superclass.find_hidden_method(name) + end + + # Redefine a previously hidden method so that it may be called on a blank + # slate object. + def reveal(name) + hidden_method = find_hidden_method(name) + fail "Don't know how to reveal method '#{name}'" unless hidden_method + define_method(name, hidden_method) + end + end + + instance_methods.each { |m| hide(m) } +end + +###################################################################### +# Since Ruby is very dynamic, methods added to the ancestors of +# BlankSlate after BlankSlate is defined will show up in the +# list of available BlankSlate methods. We handle this by defining a +# hook in the Object and Kernel classes that will hide any method +# defined after BlankSlate has been loaded. +# +module Kernel + class << self + alias_method :blank_slate_method_added, :method_added + + # Detect method additions to Kernel and remove them in the + # BlankSlate class. + def method_added(name) + result = blank_slate_method_added(name) + return result if self != Kernel + BlankSlate.hide(name) + result + end + end +end + +###################################################################### +# Same as above, except in Object. +# +class Object + class << self + alias_method :blank_slate_method_added, :method_added + + # Detect method additions to Object and remove them in the + # BlankSlate class. + def method_added(name) + result = blank_slate_method_added(name) + return result if self != Object + BlankSlate.hide(name) + result + end + + def find_hidden_method(name) + nil + end + end +end + +###################################################################### +# Also, modules included into Object need to be scanned and have their +# instance methods removed from blank slate. In theory, modules +# included into Kernel would have to be removed as well, but a +# "feature" of Ruby prevents late includes into modules from being +# exposed in the first place. +# +class Module + alias blankslate_original_append_features append_features + def append_features(mod) + result = blankslate_original_append_features(mod) + return result if mod != Object + instance_methods.each do |name| + BlankSlate.hide(name) + end + result + end +end diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/lib/builder.rb b/path/ruby/2.6.0/gems/builder-3.2.3/lib/builder.rb new file mode 100644 index 00000000..97192776 --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/lib/builder.rb @@ -0,0 +1,13 @@ +#!/usr/bin/env ruby + +#-- +# Copyright 2004 by Jim Weirich (jim@weirichhouse.org). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +#++ + +require 'builder/xmlmarkup' +require 'builder/xmlevents' diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/lib/builder/blankslate.rb b/path/ruby/2.6.0/gems/builder-3.2.3/lib/builder/blankslate.rb new file mode 100644 index 00000000..67d2f24d --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/lib/builder/blankslate.rb @@ -0,0 +1,23 @@ +#!/usr/bin/env ruby +#-- +# Copyright 2004, 2006 by Jim Weirich (jim@weirichhouse.org). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +#++ + +###################################################################### +# BlankSlate has been promoted to a top level name and is now +# available as a standalone gem. We make the name available in the +# Builder namespace for compatibility. +# +module Builder + if Object::const_defined?(:BasicObject) + BlankSlate = ::BasicObject + else + require 'blankslate' + BlankSlate = ::BlankSlate + end +end diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/lib/builder/version.rb b/path/ruby/2.6.0/gems/builder-3.2.3/lib/builder/version.rb new file mode 100644 index 00000000..4483a0ce --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/lib/builder/version.rb @@ -0,0 +1,8 @@ +module Builder + VERSION_NUMBERS = [ + VERSION_MAJOR = 3, + VERSION_MINOR = 2, + VERSION_BUILD = 3, + ] + VERSION = VERSION_NUMBERS.join(".") +end diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/lib/builder/xchar.rb b/path/ruby/2.6.0/gems/builder-3.2.3/lib/builder/xchar.rb new file mode 100644 index 00000000..29e353e8 --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/lib/builder/xchar.rb @@ -0,0 +1,197 @@ +#!/usr/bin/env ruby + +# The XChar library is provided courtesy of Sam Ruby (See +# http://intertwingly.net/stories/2005/09/28/xchar.rb) + +# -------------------------------------------------------------------- + +# If the Builder::XChar module is not currently defined, fail on any +# name clashes in standard library classes. + +module Builder + def self.check_for_name_collision(klass, method_name, defined_constant=nil) + if klass.method_defined?(method_name.to_s) + fail RuntimeError, + "Name Collision: Method '#{method_name}' is already defined in #{klass}" + end + end +end + +if ! defined?(Builder::XChar) and ! String.method_defined?(:encode) + Builder.check_for_name_collision(String, "to_xs") + Builder.check_for_name_collision(Integer, "xchr") +end + +###################################################################### +module Builder + + #################################################################### + # XML Character converter, from Sam Ruby: + # (see http://intertwingly.net/stories/2005/09/28/xchar.rb). + # + module XChar # :nodoc: + + # See + # http://intertwingly.net/stories/2004/04/14/i18n.html#CleaningWindows + # for details. + CP1252 = { # :nodoc: + 128 => 8364, # euro sign + 130 => 8218, # single low-9 quotation mark + 131 => 402, # latin small letter f with hook + 132 => 8222, # double low-9 quotation mark + 133 => 8230, # horizontal ellipsis + 134 => 8224, # dagger + 135 => 8225, # double dagger + 136 => 710, # modifier letter circumflex accent + 137 => 8240, # per mille sign + 138 => 352, # latin capital letter s with caron + 139 => 8249, # single left-pointing angle quotation mark + 140 => 338, # latin capital ligature oe + 142 => 381, # latin capital letter z with caron + 145 => 8216, # left single quotation mark + 146 => 8217, # right single quotation mark + 147 => 8220, # left double quotation mark + 148 => 8221, # right double quotation mark + 149 => 8226, # bullet + 150 => 8211, # en dash + 151 => 8212, # em dash + 152 => 732, # small tilde + 153 => 8482, # trade mark sign + 154 => 353, # latin small letter s with caron + 155 => 8250, # single right-pointing angle quotation mark + 156 => 339, # latin small ligature oe + 158 => 382, # latin small letter z with caron + 159 => 376, # latin capital letter y with diaeresis + } + + # See http://www.w3.org/TR/REC-xml/#dt-chardata for details. + PREDEFINED = { + 38 => '&', # ampersand + 60 => '<', # left angle bracket + 62 => '>', # right angle bracket + } + + # See http://www.w3.org/TR/REC-xml/#charsets for details. + VALID = [ + 0x9, 0xA, 0xD, + (0x20..0xD7FF), + (0xE000..0xFFFD), + (0x10000..0x10FFFF) + ] + + # http://www.fileformat.info/info/unicode/char/fffd/index.htm + REPLACEMENT_CHAR = + if String.method_defined?(:encode) + "\uFFFD" + elsif $KCODE == 'UTF8' + "\xEF\xBF\xBD" + else + '*' + end + end + +end + + +if String.method_defined?(:encode) + module Builder + module XChar # :nodoc: + CP1252_DIFFERENCES, UNICODE_EQUIVALENT = Builder::XChar::CP1252.each. + inject([[],[]]) {|(domain,range),(key,value)| + [domain << key,range << value] + }.map {|seq| seq.pack('U*').force_encoding('utf-8')} + + XML_PREDEFINED = Regexp.new('[' + + Builder::XChar::PREDEFINED.keys.pack('U*').force_encoding('utf-8') + + ']') + + INVALID_XML_CHAR = Regexp.new('[^'+ + Builder::XChar::VALID.map { |item| + case item + when Integer + [item].pack('U').force_encoding('utf-8') + when Range + [item.first, '-'.ord, item.last].pack('UUU').force_encoding('utf-8') + end + }.join + + ']') + + ENCODING_BINARY = Encoding.find('BINARY') + ENCODING_UTF8 = Encoding.find('UTF-8') + ENCODING_ISO1 = Encoding.find('ISO-8859-1') + + # convert a string to valid UTF-8, compensating for a number of + # common errors. + def XChar.unicode(string) + if string.encoding == ENCODING_BINARY + if string.ascii_only? + string + else + string = string.clone.force_encoding(ENCODING_UTF8) + if string.valid_encoding? + string + else + string.encode(ENCODING_UTF8, ENCODING_ISO1) + end + end + + elsif string.encoding == ENCODING_UTF8 + if string.valid_encoding? + string + else + string.encode(ENCODING_UTF8, ENCODING_ISO1) + end + + else + string.encode(ENCODING_UTF8) + end + end + + # encode a string per XML rules + def XChar.encode(string) + unicode(string). + tr(CP1252_DIFFERENCES, UNICODE_EQUIVALENT). + gsub(INVALID_XML_CHAR, REPLACEMENT_CHAR). + gsub(XML_PREDEFINED) {|c| PREDEFINED[c.ord]} + end + end + end + +else + + ###################################################################### + # Enhance the Integer class with a XML escaped character conversion. + # + class Integer + XChar = Builder::XChar if ! defined?(XChar) + + # XML escaped version of chr. When escape is set to false + # the CP1252 fix is still applied but utf-8 characters are not + # converted to character entities. + def xchr(escape=true) + n = XChar::CP1252[self] || self + case n when *XChar::VALID + XChar::PREDEFINED[n] or + (n<128 ? n.chr : (escape ? "&##{n};" : [n].pack('U*'))) + else + Builder::XChar::REPLACEMENT_CHAR + end + end + end + + + ###################################################################### + # Enhance the String class with a XML escaped character version of + # to_s. + # + class String + # XML escaped version of to_s. When escape is set to false + # the CP1252 fix is still applied but utf-8 characters are not + # converted to character entities. + def to_xs(escape=true) + unpack('U*').map {|n| n.xchr(escape)}.join # ASCII, UTF-8 + rescue + unpack('C*').map {|n| n.xchr}.join # ISO-8859-1, WIN-1252 + end + end +end diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/lib/builder/xmlbase.rb b/path/ruby/2.6.0/gems/builder-3.2.3/lib/builder/xmlbase.rb new file mode 100644 index 00000000..8f03a82a --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/lib/builder/xmlbase.rb @@ -0,0 +1,199 @@ +#!/usr/bin/env ruby + +require 'builder/blankslate' + +module Builder + + # Generic error for builder + class IllegalBlockError < RuntimeError; end + + # XmlBase is a base class for building XML builders. See + # Builder::XmlMarkup and Builder::XmlEvents for examples. + class XmlBase < BlankSlate + + class << self + attr_accessor :cache_method_calls + end + + # Create an XML markup builder. + # + # out :: Object receiving the markup. +out+ must respond to + # <<. + # indent :: Number of spaces used for indentation (0 implies no + # indentation and no line breaks). + # initial :: Level of initial indentation. + # encoding :: When encoding and $KCODE are set to 'utf-8' + # characters aren't converted to character entities in + # the output stream. + def initialize(indent=0, initial=0, encoding='utf-8') + @indent = indent + @level = initial + @encoding = encoding.downcase + end + + def explicit_nil_handling? + @explicit_nil_handling + end + + # Create a tag named +sym+. Other than the first argument which + # is the tag name, the arguments are the same as the tags + # implemented via method_missing. + def tag!(sym, *args, &block) + text = nil + attrs = nil + sym = "#{sym}:#{args.shift}" if args.first.kind_of?(::Symbol) + sym = sym.to_sym unless sym.class == ::Symbol + args.each do |arg| + case arg + when ::Hash + attrs ||= {} + attrs.merge!(arg) + when nil + attrs ||= {} + attrs.merge!({:nil => true}) if explicit_nil_handling? + else + text ||= '' + text << arg.to_s + end + end + if block + unless text.nil? + ::Kernel::raise ::ArgumentError, + "XmlMarkup cannot mix a text argument with a block" + end + _indent + _start_tag(sym, attrs) + _newline + begin + _nested_structures(block) + ensure + _indent + _end_tag(sym) + _newline + end + elsif text.nil? + _indent + _start_tag(sym, attrs, true) + _newline + else + _indent + _start_tag(sym, attrs) + text! text + _end_tag(sym) + _newline + end + @target + end + + # Create XML markup based on the name of the method. This method + # is never invoked directly, but is called for each markup method + # in the markup block that isn't cached. + def method_missing(sym, *args, &block) + cache_method_call(sym) if ::Builder::XmlBase.cache_method_calls + tag!(sym, *args, &block) + end + + # Append text to the output target. Escape any markup. May be + # used within the markup brackets as: + # + # builder.p { |b| b.br; b.text! "HI" } #=>


    HI

    + def text!(text) + _text(_escape(text)) + end + + # Append text to the output target without escaping any markup. + # May be used within the markup brackets as: + # + # builder.p { |x| x << "
    HI" } #=>


    HI

    + # + # This is useful when using non-builder enabled software that + # generates strings. Just insert the string directly into the + # builder without changing the inserted markup. + # + # It is also useful for stacking builder objects. Builders only + # use << to append to the target, so by supporting this + # method/operation builders can use other builders as their + # targets. + def <<(text) + _text(text) + end + + # For some reason, nil? is sent to the XmlMarkup object. If nil? + # is not defined and method_missing is invoked, some strange kind + # of recursion happens. Since nil? won't ever be an XML tag, it + # is pretty safe to define it here. (Note: this is an example of + # cargo cult programming, + # cf. http://fishbowl.pastiche.org/2004/10/13/cargo_cult_programming). + def nil? + false + end + + private + + require 'builder/xchar' + if ::String.method_defined?(:encode) + def _escape(text) + result = XChar.encode(text) + begin + encoding = ::Encoding::find(@encoding) + raise Exception if encoding.dummy? + result.encode(encoding) + rescue + # if the encoding can't be supported, use numeric character references + result. + gsub(/[^\u0000-\u007F]/) {|c| "&##{c.ord};"}. + force_encoding('ascii') + end + end + else + def _escape(text) + if (text.method(:to_xs).arity == 0) + text.to_xs + else + text.to_xs((@encoding != 'utf-8' or $KCODE != 'UTF8')) + end + end + end + + def _escape_attribute(text) + _escape(text).gsub("\n", " ").gsub("\r", " "). + gsub(%r{"}, '"') # " WART + end + + def _newline + return if @indent == 0 + text! "\n" + end + + def _indent + return if @indent == 0 || @level == 0 + text!(" " * (@level * @indent)) + end + + def _nested_structures(block) + @level += 1 + block.call(self) + ensure + @level -= 1 + end + + # If XmlBase.cache_method_calls = true, we dynamicly create the method + # missed as an instance method on the XMLBase object. Because XML + # documents are usually very repetative in nature, the next node will + # be handled by the new method instead of method_missing. As + # method_missing is very slow, this speeds up document generation + # significantly. + def cache_method_call(sym) + class << self; self; end.class_eval do + unless method_defined?(sym) + define_method(sym) do |*args, &block| + tag!(sym, *args, &block) + end + end + end + end + end + + XmlBase.cache_method_calls = true + +end diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/lib/builder/xmlevents.rb b/path/ruby/2.6.0/gems/builder-3.2.3/lib/builder/xmlevents.rb new file mode 100644 index 00000000..91fcd21e --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/lib/builder/xmlevents.rb @@ -0,0 +1,63 @@ +#!/usr/bin/env ruby + +#-- +# Copyright 2004 by Jim Weirich (jim@weirichhouse.org). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +#++ + +require 'builder/xmlmarkup' + +module Builder + + # Create a series of SAX-like XML events (e.g. start_tag, end_tag) + # from the markup code. XmlEvent objects are used in a way similar + # to XmlMarkup objects, except that a series of events are generated + # and passed to a handler rather than generating character-based + # markup. + # + # Usage: + # xe = Builder::XmlEvents.new(hander) + # xe.title("HI") # Sends start_tag/end_tag/text messages to the handler. + # + # Indentation may also be selected by providing value for the + # indentation size and initial indentation level. + # + # xe = Builder::XmlEvents.new(handler, indent_size, initial_indent_level) + # + # == XML Event Handler + # + # The handler object must expect the following events. + # + # [start_tag(tag, attrs)] + # Announces that a new tag has been found. +tag+ is the name of + # the tag and +attrs+ is a hash of attributes for the tag. + # + # [end_tag(tag)] + # Announces that an end tag for +tag+ has been found. + # + # [text(text)] + # Announces that a string of characters (+text+) has been found. + # A series of characters may be broken up into more than one + # +text+ call, so the client cannot assume that a single + # callback contains all the text data. + # + class XmlEvents < XmlMarkup + def text!(text) + @target.text(text) + end + + def _start_tag(sym, attrs, end_too=false) + @target.start_tag(sym, attrs) + _end_tag(sym) if end_too + end + + def _end_tag(sym) + @target.end_tag(sym) + end + end + +end diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/lib/builder/xmlmarkup.rb b/path/ruby/2.6.0/gems/builder-3.2.3/lib/builder/xmlmarkup.rb new file mode 100644 index 00000000..4730d094 --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/lib/builder/xmlmarkup.rb @@ -0,0 +1,339 @@ +#!/usr/bin/env ruby +#-- +# Copyright 2004, 2005 by Jim Weirich (jim@weirichhouse.org). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +#++ + +# Provide a flexible and easy to use Builder for creating XML markup. +# See XmlBuilder for usage details. + +require 'builder/xmlbase' + +module Builder + + # Create XML markup easily. All (well, almost all) methods sent to + # an XmlMarkup object will be translated to the equivalent XML + # markup. Any method with a block will be treated as an XML markup + # tag with nested markup in the block. + # + # Examples will demonstrate this easier than words. In the + # following, +xm+ is an +XmlMarkup+ object. + # + # xm.em("emphasized") # => emphasized + # xm.em { xm.b("emp & bold") } # => emph & bold + # xm.a("A Link", "href"=>"http://onestepback.org") + # # => A Link + # xm.div { xm.br } # =>

    + # xm.target("name"=>"compile", "option"=>"fast") + # # => + # # NOTE: order of attributes is not specified. + # + # xm.instruct! # + # xm.html { # + # xm.head { # + # xm.title("History") # History + # } # + # xm.body { # + # xm.comment! "HI" # + # xm.h1("Header") #

    Header

    + # xm.p("paragraph") #

    paragraph

    + # } # + # } # + # + # == Notes: + # + # * The order that attributes are inserted in markup tags is + # undefined. + # + # * Sometimes you wish to insert text without enclosing tags. Use + # the text! method to accomplish this. + # + # Example: + # + # xm.div { #
    + # xm.text! "line"; xm.br # line
    + # xm.text! "another line"; xmbr # another line
    + # } #
    + # + # * The special XML characters <, >, and & are converted to <, + # > and & automatically. Use the << operation to + # insert text without modification. + # + # * Sometimes tags use special characters not allowed in ruby + # identifiers. Use the tag! method to handle these + # cases. + # + # Example: + # + # xml.tag!("SOAP:Envelope") { ... } + # + # will produce ... + # + # ... " + # + # tag! will also take text and attribute arguments (after + # the tag name) like normal markup methods. (But see the next + # bullet item for a better way to handle XML namespaces). + # + # * Direct support for XML namespaces is now available. If the + # first argument to a tag call is a symbol, it will be joined to + # the tag to produce a namespace:tag combination. It is easier to + # show this than describe it. + # + # xml.SOAP :Envelope do ... end + # + # Just put a space before the colon in a namespace to produce the + # right form for builder (e.g. "SOAP:Envelope" => + # "xml.SOAP :Envelope") + # + # * XmlMarkup builds the markup in any object (called a _target_) + # that accepts the << method. If no target is given, + # then XmlMarkup defaults to a string target. + # + # Examples: + # + # xm = Builder::XmlMarkup.new + # result = xm.title("yada") + # # result is a string containing the markup. + # + # buffer = "" + # xm = Builder::XmlMarkup.new(buffer) + # # The markup is appended to buffer (using <<) + # + # xm = Builder::XmlMarkup.new(STDOUT) + # # The markup is written to STDOUT (using <<) + # + # xm = Builder::XmlMarkup.new + # x2 = Builder::XmlMarkup.new(:target=>xm) + # # Markup written to +x2+ will be send to +xm+. + # + # * Indentation is enabled by providing the number of spaces to + # indent for each level as a second argument to XmlBuilder.new. + # Initial indentation may be specified using a third parameter. + # + # Example: + # + # xm = Builder.new(:indent=>2) + # # xm will produce nicely formatted and indented XML. + # + # xm = Builder.new(:indent=>2, :margin=>4) + # # xm will produce nicely formatted and indented XML with 2 + # # spaces per indent and an over all indentation level of 4. + # + # builder = Builder::XmlMarkup.new(:target=>$stdout, :indent=>2) + # builder.name { |b| b.first("Jim"); b.last("Weirich) } + # # prints: + # # + # # Jim + # # Weirich + # # + # + # * The instance_eval implementation which forces self to refer to + # the message receiver as self is now obsolete. We now use normal + # block calls to execute the markup block. This means that all + # markup methods must now be explicitly send to the xml builder. + # For instance, instead of + # + # xml.div { strong("text") } + # + # you need to write: + # + # xml.div { xml.strong("text") } + # + # Although more verbose, the subtle change in semantics within the + # block was found to be prone to error. To make this change a + # little less cumbersome, the markup block now gets the markup + # object sent as an argument, allowing you to use a shorter alias + # within the block. + # + # For example: + # + # xml_builder = Builder::XmlMarkup.new + # xml_builder.div { |xml| + # xml.stong("text") + # } + # + class XmlMarkup < XmlBase + + # Create an XML markup builder. Parameters are specified by an + # option hash. + # + # :target => target_object:: + # Object receiving the markup. +target_object+ must respond to + # the <<(a_string) operator and return + # itself. The default target is a plain string target. + # + # :indent => indentation:: + # Number of spaces used for indentation. The default is no + # indentation and no line breaks. + # + # :margin => initial_indentation_level:: + # Amount of initial indentation (specified in levels, not + # spaces). + # + # :quote => :single:: + # Use single quotes for attributes rather than double quotes. + # + # :escape_attrs => OBSOLETE:: + # The :escape_attrs option is no longer supported by builder + # (and will be quietly ignored). String attribute values are + # now automatically escaped. If you need unescaped attribute + # values (perhaps you are using entities in the attribute + # values), then give the value as a Symbol. This allows much + # finer control over escaping attribute values. + # + def initialize(options={}) + indent = options[:indent] || 0 + margin = options[:margin] || 0 + @quote = (options[:quote] == :single) ? "'" : '"' + @explicit_nil_handling = options[:explicit_nil_handling] + super(indent, margin) + @target = options[:target] || "" + end + + # Return the target of the builder. + def target! + @target + end + + def comment!(comment_text) + _ensure_no_block ::Kernel::block_given? + _special("", comment_text, nil) + end + + # Insert an XML declaration into the XML markup. + # + # For example: + # + # xml.declare! :ELEMENT, :blah, "yada" + # # => + def declare!(inst, *args, &block) + _indent + @target << "" + _newline + end + + # Insert a processing instruction into the XML markup. E.g. + # + # For example: + # + # xml.instruct! + # #=> + # xml.instruct! :aaa, :bbb=>"ccc" + # #=> + # + # Note: If the encoding is setup to "UTF-8" and the value of + # $KCODE is "UTF8", then builder will emit UTF-8 encoded strings + # rather than the entity encoding normally used. + def instruct!(directive_tag=:xml, attrs={}) + _ensure_no_block ::Kernel::block_given? + if directive_tag == :xml + a = { :version=>"1.0", :encoding=>"UTF-8" } + attrs = a.merge attrs + @encoding = attrs[:encoding].downcase + end + _special( + "", + nil, + attrs, + [:version, :encoding, :standalone]) + end + + # Insert a CDATA section into the XML markup. + # + # For example: + # + # xml.cdata!("text to be included in cdata") + # #=> + # + def cdata!(text) + _ensure_no_block ::Kernel::block_given? + _special("", text.gsub(']]>', ']]]]>'), nil) + end + + private + + # NOTE: All private methods of a builder object are prefixed when + # a "_" character to avoid possible conflict with XML tag names. + + # Insert text directly in to the builder's target. + def _text(text) + @target << text + end + + # Insert special instruction. + def _special(open, close, data=nil, attrs=nil, order=[]) + _indent + @target << open + @target << data if data + _insert_attributes(attrs, order) if attrs + @target << close + _newline + end + + # Start an XML tag. If end_too is true, then the start + # tag is also the end tag (e.g.
    + def _start_tag(sym, attrs, end_too=false) + @target << "<#{sym}" + _insert_attributes(attrs) + @target << "/" if end_too + @target << ">" + end + + # Insert an ending tag. + def _end_tag(sym) + @target << "" + end + + # Insert the attributes (given in the hash). + def _insert_attributes(attrs, order=[]) + return if attrs.nil? + order.each do |k| + v = attrs[k] + @target << %{ #{k}=#{@quote}#{_attr_value(v)}#{@quote}} if v + end + attrs.each do |k, v| + @target << %{ #{k}=#{@quote}#{_attr_value(v)}#{@quote}} unless order.member?(k) # " WART + end + end + + def _attr_value(value) + case value + when ::Symbol + value.to_s + else + _escape_attribute(value.to_s) + end + end + + def _ensure_no_block(got_block) + if got_block + ::Kernel::raise IllegalBlockError.new( + "Blocks are not allowed on XML instructions" + ) + end + end + + end + +end diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/rakelib/publish.rake b/path/ruby/2.6.0/gems/builder-3.2.3/rakelib/publish.rake new file mode 100644 index 00000000..511536d7 --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/rakelib/publish.rake @@ -0,0 +1,20 @@ +# Optional publish task for Rake + +begin +require 'rake/contrib/sshpublisher' +require 'rake/contrib/rubyforgepublisher' + +publisher = Rake::CompositePublisher.new +publisher.add Rake::RubyForgePublisher.new('builder', 'jimweirich') +publisher.add Rake::SshFilePublisher.new( + 'linode', + 'htdocs/software/builder', + '.', + 'builder.blurb') + +desc "Publish the Documentation to RubyForge." +task :publish => [:rdoc] do + publisher.upload +end +rescue LoadError +end diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/rakelib/tags.rake b/path/ruby/2.6.0/gems/builder-3.2.3/rakelib/tags.rake new file mode 100644 index 00000000..1fefb72f --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/rakelib/tags.rake @@ -0,0 +1,62 @@ +#!/usr/bin/env ruby + +module Tags + extend Rake::DSL if defined?(Rake::DSL) + + PROG = ENV['TAGS'] || 'ctags' + + RAKEFILES = FileList['Rakefile', '**/*.rake'] + + FILES = FileList['**/*.rb', '**/*.js'] + RAKEFILES + FILES.exclude('pkg', 'dist') + + PROJECT_DIR = ['.'] + + RVM_GEMDIR = File.join(`rvm gemdir`.strip, "gems") rescue nil + SYSTEM_DIRS = RVM_GEMDIR && File.exists?(RVM_GEMDIR) ? RVM_GEMDIR : [] + + module_function + + # Convert key_word to --key-word. + def keyword(key) + k = key.to_s.gsub(/_/, '-') + (k.length == 1) ? "-#{k}" : "--#{k}" + end + + # Run ctags command + def run(*args) + opts = { + :e => true, + :totals => true, + :recurse => true, + } + opts = opts.merge(args.pop) if args.last.is_a?(Hash) + command_args = opts.map { |k, v| + (v == true) ? keyword(k) : "#{keyword(k)}=#{v}" + }.join(" ") + sh %{#{Tags::PROG} #{command_args} #{args.join(' ')}} + end +end + +namespace "tags" do + desc "Generate an Emacs TAGS file" + task :emacs, [:all] => Tags::FILES do |t, args| + puts "Making Emacs TAGS file" + verbose(true) do + Tags.run(Tags::PROJECT_DIR) + Tags.run(Tags::RAKEFILES, + :language_force => "ruby", + :append => true) + if args.all + Tags::SYSTEM_DIRS.each do |dir| + Tags.run(dir, + :language_force => "ruby", + :append => true) + end + end + end + end +end + +desc "Generate the TAGS file" +task :tags, [:all] => ["tags:emacs"] diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/rakelib/testing.rake b/path/ruby/2.6.0/gems/builder-3.2.3/rakelib/testing.rake new file mode 100644 index 00000000..0a3a6ffa --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/rakelib/testing.rake @@ -0,0 +1,7 @@ +require 'rake/testtask' + +Rake::TestTask.new do |t| + t.libs << "test" + t.test_files = FileList['test/test*.rb'] + t.verbose = true +end diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/test/helper.rb b/path/ruby/2.6.0/gems/builder-3.2.3/test/helper.rb new file mode 100644 index 00000000..f87bb19d --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/test/helper.rb @@ -0,0 +1,12 @@ +require 'minitest/autorun' + +module Builder + class Test < Minitest::Test + alias :assert_raise :assert_raises + alias :assert_not_nil :refute_nil + + def assert_nothing_raised + yield + end + end +end diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/test/performance.rb b/path/ruby/2.6.0/gems/builder-3.2.3/test/performance.rb new file mode 100644 index 00000000..e764205d --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/test/performance.rb @@ -0,0 +1,41 @@ +#!/usr/bin/env ruby +# encoding: iso-8859-1 + +#-- +# Portions copyright 2004 by Jim Weirich (jim@weirichhouse.org). +# Portions copyright 2005 by Sam Ruby (rubys@intertwingly.net). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +#++ + +require 'builder/xmlmarkup' +require 'benchmark' + +text = "This is a test of the new xml markup. I�t�rn�ti�n�liz�ti�n\n" * 10000 + +include Benchmark # we need the CAPTION and FMTSTR constants +include Builder +n = 50 +Benchmark.benchmark do |bm| + tf = bm.report("base") { + n.times do + x = XmlMarkup.new + x.text(text) + x.target! + end + } + def XmlMarkup._escape(text) + text.to_xs + end + tf = bm.report("to_xs") { + n.times do + x = XmlMarkup.new + x.text(text) + x.target! + end + } +end + diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/test/preload.rb b/path/ruby/2.6.0/gems/builder-3.2.3/test/preload.rb new file mode 100644 index 00000000..395e043a --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/test/preload.rb @@ -0,0 +1,39 @@ +#!/usr/bin/env ruby + +#-- +# Portions copyright 2004 by Jim Weirich (jim@weirichhouse.org). +# Portions copyright 2005 by Sam Ruby (rubys@intertwingly.net). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +#++ + +# We are defining method_added in Kernel and Object so that when +# BlankSlate overrides them later, we can verify that it correctly +# calls the older hooks. + +module Kernel + class << self + attr_reader :k_added_names + alias_method :preload_method_added, :method_added + def method_added(name) + preload_method_added(name) + @k_added_names ||= [] + @k_added_names << name + end + end +end + +class Object + class << self + attr_reader :o_added_names + alias_method :preload_method_added, :method_added + def method_added(name) + preload_method_added(name) + @o_added_names ||= [] + @o_added_names << name + end + end +end diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/test/test_blankslate.rb b/path/ruby/2.6.0/gems/builder-3.2.3/test/test_blankslate.rb new file mode 100644 index 00000000..fe56f094 --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/test/test_blankslate.rb @@ -0,0 +1,213 @@ +#!/usr/bin/env ruby + +#-- +# Portions copyright 2004 by Jim Weirich (jim@weirichhouse.org). +# Portions copyright 2005 by Sam Ruby (rubys@intertwingly.net). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +#++ + +require 'helper' +require 'preload' +require 'blankslate' +require 'stringio' + +# Methods to be introduced into the Object class late. +module LateObject + def late_object + 33 + end + def LateObject.included(mod) + # Modules defining an included method should not prevent blank + # slate erasure! + end +end + +# Methods to be introduced into the Kernel module late. +module LateKernel + def late_kernel + 44 + end + def LateKernel.included(mod) + # Modules defining an included method should not prevent blank + # slate erasure! + end +end + +# Introduce some late methods (both module and direct) into the Kernel +# module. +module Kernel + include LateKernel + + def late_addition + 1234 + end + + def double_late_addition + 22 + end +end + + +# Introduce some late methods (both module and direct) into the Object +# class. +class Object + include LateObject + def another_late_addition + 4321 + end +end + +# Introduce some late methods by inclusion. +module GlobalModule + def global_inclusion + 42 + end +end +include GlobalModule + +def direct_global + 43 +end + +###################################################################### +# Test case for blank slate. +# +class TestBlankSlate < Builder::Test + def setup + @bs = BlankSlate.new + end + + def test_undefined_methods_remain_undefined + assert_raise(NoMethodError) { @bs.no_such_method } + assert_raise(NoMethodError) { @bs.nil? } + end + + + # NOTE: NameError is acceptable because the lack of a '.' means that + # Ruby can't tell if it is a method or a local variable. + def test_undefined_methods_remain_undefined_during_instance_eval + assert_raise(NoMethodError, NameError) do + @bs.instance_eval do nil? end + end + assert_raise(NoMethodError, NameError) do + @bs.instance_eval do no_such_method end + end + end + + def test_private_methods_are_undefined + assert_raise(NoMethodError) do + @bs.puts "HI" + end + end + + def test_targetted_private_methods_are_undefined_during_instance_eval + assert_raise(NoMethodError, NameError) do + @bs.instance_eval do self.puts "HI" end + end + end + + def test_untargetted_private_methods_are_defined_during_instance_eval + oldstdout = $stdout + $stdout = StringIO.new + @bs.instance_eval do + puts "HI" + end + ensure + $stdout = oldstdout + end + + def test_methods_added_late_to_kernel_remain_undefined + assert_equal 1234, nil.late_addition + assert_raise(NoMethodError) { @bs.late_addition } + end + + def test_methods_added_late_to_object_remain_undefined + assert_equal 4321, nil.another_late_addition + assert_raise(NoMethodError) { @bs.another_late_addition } + end + + def test_methods_added_late_to_global_remain_undefined + assert_equal 42, global_inclusion + assert_raise(NoMethodError) { @bs.global_inclusion } + end + + def test_preload_method_added + assert Kernel.k_added_names.include?(:late_addition) + assert Object.o_added_names.include?(:another_late_addition) + end + + def test_method_defined_late_multiple_times_remain_undefined + assert_equal 22, nil.double_late_addition + assert_raise(NoMethodError) { @bs.double_late_addition } + end + + def test_late_included_module_in_object_is_ok + assert_equal 33, 1.late_object + assert_raise(NoMethodError) { @bs.late_object } + end + + def test_late_included_module_in_kernel_is_ok + assert_raise(NoMethodError) { @bs.late_kernel } + end + + def test_revealing_previously_hidden_methods_are_callable + with_to_s = Class.new(BlankSlate) do + reveal :to_s + end + assert_match(/^#<.*>$/, with_to_s.new.to_s) + end + + def test_revealing_previously_hidden_methods_are_callable_with_block + Object.class_eval <<-EOS + def given_block(&block) + block + end + EOS + + with_given_block = Class.new(BlankSlate) do + reveal :given_block + end + assert_not_nil with_given_block.new.given_block {} + end + + def test_revealing_a_hidden_method_twice_is_ok + with_to_s = Class.new(BlankSlate) do + reveal :to_s + reveal :to_s + end + assert_match(/^#<.*>$/, with_to_s.new.to_s) + end + + def test_revealing_unknown_hidden_method_is_an_error + assert_raises(RuntimeError) do + Class.new(BlankSlate) do + reveal :xyz + end + end + end + + def test_global_includes_still_work + assert_nothing_raised do + assert_equal 42, global_inclusion + assert_equal 42, Object.new.global_inclusion + assert_equal 42, "magic number".global_inclusion + assert_equal 43, direct_global + end + end + + def test_reveal_should_not_bind_to_an_instance + with_object_id = Class.new(BlankSlate) do + reveal(:object_id) + end + + obj1 = with_object_id.new + obj2 = with_object_id.new + + assert obj1.object_id != obj2.object_id, + "Revealed methods should not be bound to a particular instance" + end +end diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/test/test_eventbuilder.rb b/path/ruby/2.6.0/gems/builder-3.2.3/test/test_eventbuilder.rb new file mode 100644 index 00000000..80709ef1 --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/test/test_eventbuilder.rb @@ -0,0 +1,150 @@ +#!/usr/bin/env ruby + +#-- +# Portions copyright 2004 by Jim Weirich (jim@weirichhouse.org). +# Portions copyright 2005 by Sam Ruby (rubys@intertwingly.net). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +#++ + +require 'helper' +require 'preload' +require 'builder' +require 'builder/xmlevents' + +class TestEvents < Builder::Test + + class Target + attr_reader :events + + def initialize + @events = [] + end + + def start_tag(tag, attrs) + @events << [:start_tag, tag, attrs] + end + + def end_tag(tag) + @events << [:end_tag, tag] + end + + def text(string) + @events << [:text, string] + end + + end + + + def setup + @target = Target.new + @xml = Builder::XmlEvents.new(:target=>@target) + end + + def test_simple + @xml.one + expect [:start_tag, :one, nil] + expect [:end_tag, :one] + expect_done + end + + def test_nested + @xml.one { @xml.two } + expect [:start_tag, :one, nil] + expect [:start_tag, :two, nil] + expect [:end_tag, :two] + expect [:end_tag, :one] + expect_done + end + + def test_text + @xml.one("a") + expect [:start_tag, :one, nil] + expect [:text, "a"] + expect [:end_tag, :one] + expect_done + end + + def test_special_text + @xml.one("H&R") + expect [:start_tag, :one, nil] + expect [:text, "H&R"] + expect [:end_tag, :one] + expect_done + end + + def test_text_with_entity + @xml.one("H&R") + expect [:start_tag, :one, nil] + expect [:text, "H&R"] + expect [:end_tag, :one] + expect_done + end + + def test_attributes + @xml.a(:b=>"c", :x=>"y") + expect [:start_tag, :a, {:x => "y", :b => "c"}] + expect [:end_tag, :a] + expect_done + end + + def test_moderately_complex + @xml.tag! "address-book" do |x| + x.entry :id=>"1" do + x.name { + x.first "Bill" + x.last "Smith" + } + x.address "Cincinnati" + end + x.entry :id=>"2" do + x.name { + x.first "John" + x.last "Doe" + } + x.address "Columbus" + end + end + expect [:start_tag, "address-book".intern, nil] + expect [:start_tag, :entry, {:id => "1"}] + expect [:start_tag, :name, nil] + expect [:start_tag, :first, nil] + expect [:text, "Bill"] + expect [:end_tag, :first] + expect [:start_tag, :last, nil] + expect [:text, "Smith"] + expect [:end_tag, :last] + expect [:end_tag, :name] + expect [:start_tag, :address, nil] + expect [:text, "Cincinnati"] + expect [:end_tag, :address] + expect [:end_tag, :entry] + expect [:start_tag, :entry, {:id => "2"}] + expect [:start_tag, :name, nil] + expect [:start_tag, :first, nil] + expect [:text, "John"] + expect [:end_tag, :first] + expect [:start_tag, :last, nil] + expect [:text, "Doe"] + expect [:end_tag, :last] + expect [:end_tag, :name] + expect [:start_tag, :address, nil] + expect [:text, "Columbus"] + expect [:end_tag, :address] + expect [:end_tag, :entry] + expect [:end_tag, "address-book".intern] + expect_done + end + + def expect(value) + assert_equal value, @target.events.shift + end + + def expect_done + assert_nil @target.events.shift + end + +end diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/test/test_markupbuilder.rb b/path/ruby/2.6.0/gems/builder-3.2.3/test/test_markupbuilder.rb new file mode 100644 index 00000000..39f5750a --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/test/test_markupbuilder.rb @@ -0,0 +1,611 @@ +#!/usr/bin/env ruby + +#-- +# Portions copyright 2004 by Jim Weirich (jim@weirichhouse.org). +# Portions copyright 2005 by Sam Ruby (rubys@intertwingly.net). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +#++ + +require 'helper' +require 'preload' +require 'builder' +require 'builder/xmlmarkup' + +class TestMarkup < Builder::Test + def setup + @xml = Builder::XmlMarkup.new + end + + def test_create + assert_not_nil @xml + end + + def test_simple + @xml.simple + assert_equal "", @xml.target! + end + + def test_value + @xml.value("hi") + assert_equal "hi", @xml.target! + end + + def test_empty_value + @xml.value("") + assert_equal "", @xml.target! + end + + def test_nil_value + @xml.value(nil) + assert_equal "", @xml.target! + end + + def test_no_value + @xml.value() + assert_equal "", @xml.target! + end + + def test_nested + @xml.outer { |x| x.inner("x") } + assert_equal "x", @xml.target! + end + + def test_attributes + @xml.ref(:id => 12) + assert_equal %{}, @xml.target! + end + + def test_single_quotes_for_attrs + @xml = Builder::XmlMarkup.new(:quote => :single) + @xml.ref(:id => 12) + assert_equal %{}, @xml.target! + end + + def test_mixed_quotes_for_attrs + @xml = Builder::XmlMarkup.new(:quote => :single) + x = Builder::XmlMarkup.new(:target=>@xml, :quote => :double) + @xml.ref(:id => 12) do + x.link(:id => 13) + end + assert_equal %{}, @xml.target! + end + + def test_string_attributes_are_escaped_by_default + @xml.ref(:id => "H&R") + assert_equal %{}, @xml.target! + end + + def test_symbol_attributes_are_unescaped_by_default + @xml.ref(:id => :"H&R") + assert_equal %{}, @xml.target! + end + + def test_attributes_escaping_can_be_turned_on + @xml = Builder::XmlMarkup.new + @xml.ref(:id => "") + assert_equal %{}, @xml.target! + end + + def test_mixed_attribute_escaping_with_nested_builders + x = Builder::XmlMarkup.new(:target=>@xml) + @xml.ref(:id=>:"H&R") { + x.element(:tag=>"Long&Short") + } + assert_equal "", + @xml.target! + end + + def test_multiple_attributes + @xml.ref(:id => 12, :name => "bill") + assert_match %r{^$}, @xml.target! + end + + def test_attributes_with_text + @xml.a("link", :href=>"http://onestepback.org") + assert_equal %{link}, @xml.target! + end + + def test_attributes_with_newlines + @xml.abbr("W3C", :title=>"World\nWide\rWeb\r\nConsortium") + assert_equal %{W3C}, + @xml.target! + end + + def test_complex + @xml.body(:bg=>"#ffffff") { |x| + x.title("T", :style=>"red") + } + assert_equal %{T}, @xml.target! + end + + def test_funky_symbol + @xml.tag!("non-ruby-token", :id=>1) { |x| x.ok } + assert_equal %{}, @xml.target! + end + + def test_tag_can_handle_private_method + @xml.tag!("loop", :id=>1) { |x| x.ok } + assert_equal %{}, @xml.target! + end + + def test_no_explicit_marker + @xml.p { |x| x.b("HI") } + assert_equal "

    HI

    ", @xml.target! + end + + def test_reference_local_vars + n = 3 + @xml.ol { |x| n.times { x.li(n) } } + assert_equal "
    1. 3
    2. 3
    3. 3
    ", @xml.target! + end + + def test_reference_methods + @xml.title { |x| x.a { x.b(_name) } } + assert_equal "<a><b>bob</b></a>", @xml.target! + end + + def test_append_text + @xml.p { |x| x.br; x.text! "HI" } + assert_equal "


    HI

    ", @xml.target! + end + + def test_ambiguous_markup + ex = assert_raise(ArgumentError) { + @xml.h1("data1") { b } + } + assert_match(/\btext\b/, ex.message) + assert_match(/\bblock\b/, ex.message) + end + + def test_capitalized_method + @xml.P { |x| x.B("hi"); x.BR(); x.EM { x.text! "world" } } + assert_equal "

    hi
    world

    ", @xml.target! + end + + def test_escaping + @xml.div { |x| x.text! ""; x.em("H&R Block") } + assert_equal %{
    <hi>H&R Block
    }, @xml.target! + end + + def test_nil + b = Builder::XmlMarkup.new + b.tag! "foo", nil + assert_equal %{}, b.target! + end + + def test_nil_without_explicit_nil_handling + b = Builder::XmlMarkup.new(:explicit_nil_handling => false) + b.tag! "foo", nil + assert_equal %{}, b.target! + end + + def test_nil_with_explicit_nil_handling + b = Builder::XmlMarkup.new(:explicit_nil_handling => true) + b.tag! "foo", nil + assert_equal %{}, b.target! + end + + def test_non_escaping + @xml.div("ns:xml"=>:"&xml;") { |x| x << ""; x.em("H&R Block") } + assert_equal %{
    H&R Block
    }, @xml.target! + end + + def test_return_value + str = @xml.x("men") + assert_equal @xml.target!, str + end + + def test_stacked_builders + b = Builder::XmlMarkup.new( :target => @xml ) + b.div { @xml.span { @xml.a("text", :href=>"ref") } } + assert_equal "", @xml.target! + end + + def _name + "bob" + end +end + +class TestAttributeEscaping < Builder::Test + + def setup + @xml = Builder::XmlMarkup.new + end + + def test_element_gt + @xml.title('1<2') + assert_equal '1<2', @xml.target! + end + + def test_element_amp + @xml.title('AT&T') + assert_equal 'AT&T', @xml.target! + end + + def test_element_amp2 + @xml.title('&') + assert_equal '&amp;', @xml.target! + end + + def test_attr_less + @xml.a(:title => '2>1') + assert_equal '', @xml.target! + end + + def test_attr_amp + @xml.a(:title => 'AT&T') + assert_equal '', @xml.target! + end + + def test_attr_quot + @xml.a(:title => '"x"') + assert_equal '', @xml.target! + end + +end + +class TestNameSpaces < Builder::Test + def setup + @xml = Builder::XmlMarkup.new(:indent=>2) + end + + def test_simple_name_spaces + @xml.rdf :RDF + assert_equal "\n", @xml.target! + end + + def test_long + xml = Builder::XmlMarkup.new(:indent=>2) + xml.instruct! + xml.rdf :RDF, + "xmlns:rdf" => :"&rdf;", + "xmlns:rdfs" => :"&rdfs;", + "xmlns:xsd" => :"&xsd;", + "xmlns:owl" => :"&owl;" do + xml.owl :Class, :'rdf:ID'=>'Bird' do + xml.rdfs :label, 'bird' + xml.rdfs :subClassOf do + xml.owl :Restriction do + xml.owl :onProperty, 'rdf:resource'=>'#wingspan' + xml.owl :maxCardinality,1,'rdf:datatype'=>'&xsd;nonNegativeInteger' + end + end + end + end + assert_match(/^<\?xml/, xml.target!) + assert_match(/\n/m, xml.target!) + end + + def test_ensure + xml = Builder::XmlMarkup.new + xml.html do + xml.body do + begin + xml.p do + raise Exception.new('boom') + end + rescue Exception => e + xml.pre e + end + end + end + assert_match %r{

    }, xml.target! + assert_match %r{

    }, xml.target! + end +end + +class TestDeclarations < Builder::Test + def setup + @xml = Builder::XmlMarkup.new(:indent=>2) + end + + def test_declare + @xml.declare! :element + assert_equal "\n", @xml.target! + end + + def test_bare_arg + @xml.declare! :element, :arg + assert_equal"\n", @xml.target! + end + + def test_string_arg + @xml.declare! :element, "string" + assert_equal"\n", @xml.target! + end + + def test_mixed_args + @xml.declare! :element, :x, "y", :z, "-//OASIS//DTD DocBook XML//EN" + assert_equal "\n", @xml.target! + end + + def test_nested_declarations + @xml = Builder::XmlMarkup.new + @xml.declare! :DOCTYPE, :chapter do |x| + x.declare! :ELEMENT, :chapter, "(title,para+)".intern + end + assert_equal "]>", @xml.target! + end + + def test_nested_indented_declarations + @xml.declare! :DOCTYPE, :chapter do |x| + x.declare! :ELEMENT, :chapter, "(title,para+)".intern + end + assert_equal "\n]>\n", @xml.target! + end + + def test_complex_declaration + @xml.declare! :DOCTYPE, :chapter do |x| + x.declare! :ELEMENT, :chapter, "(title,para+)".intern + x.declare! :ELEMENT, :title, "(#PCDATA)".intern + x.declare! :ELEMENT, :para, "(#PCDATA)".intern + end + expected = %{ + + +]> +} + assert_equal expected, @xml.target! + end +end + + +class TestSpecialMarkup < Builder::Test + def setup + @xml = Builder::XmlMarkup.new(:indent=>2) + end + + def test_comment + @xml.comment!("COMMENT") + assert_equal "\n", @xml.target! + end + + def test_indented_comment + @xml.p { @xml.comment! "OK" } + assert_equal "

    \n \n

    \n", @xml.target! + end + + def test_instruct + @xml.instruct! :abc, :version=>"0.9" + assert_equal "\n", @xml.target! + end + + def test_indented_instruct + @xml.p { @xml.instruct! :xml } + assert_match %r{

    \n <\?xml version="1.0" encoding="UTF-8"\?>\n

    \n}, + @xml.target! + end + + def test_instruct_without_attributes + @xml.instruct! :zz + assert_equal "\n", @xml.target! + end + + def test_xml_instruct + @xml.instruct! + assert_match(/^<\?xml version="1.0" encoding="UTF-8"\?>$/, @xml.target!) + end + + def test_xml_instruct_with_overrides + @xml.instruct! :xml, :encoding=>"UCS-2" + assert_match(/^<\?xml version="1.0" encoding="UCS-2"\?>$/, @xml.target!) + end + + def test_xml_instruct_with_standalong + @xml.instruct! :xml, :encoding=>"UCS-2", :standalone=>"yes" + assert_match(/^<\?xml version="1.0" encoding="UCS-2" standalone="yes"\?>$/, @xml.target!) + end + + def test_no_blocks + assert_raise(Builder::IllegalBlockError) do + @xml.instruct! { |x| x.hi } + end + assert_raise(Builder::IllegalBlockError) do + @xml.comment!(:element) { |x| x.hi } + end + end + + def test_cdata + @xml.cdata!("TEST") + assert_equal "\n", @xml.target! + end + + def test_cdata_with_ampersand + @xml.cdata!("TEST&CHECK") + assert_equal "\n", @xml.target! + end + + def test_cdata_with_included_close + @xml.cdata!("TEST]]>CHECK") + assert_equal "CHECK]]>\n", @xml.target! + end +end + +class TestIndentedXmlMarkup < Builder::Test + def setup + @xml = Builder::XmlMarkup.new(:indent=>2) + end + + def test_one_level + @xml.ol { |x| x.li "text" } + assert_equal "
      \n
    1. text
    2. \n
    \n", @xml.target! + end + + def test_two_levels + @xml.p { |x| + x.ol { x.li "text" } + x.br + } + assert_equal "

    \n

      \n
    1. text
    2. \n
    \n
    \n

    \n", @xml.target! + end + + def test_initial_level + @xml = Builder::XmlMarkup.new(:indent=>2, :margin=>4) + @xml.name { |x| x.first("Jim") } + assert_equal " \n Jim\n \n", @xml.target! + end + + class TestUtfMarkup < Builder::Test + if ! String.method_defined?(:encode) + def setup + @old_kcode = $KCODE + end + + def teardown + $KCODE = @old_kcode + end + + def test_use_entities_if_no_encoding_is_given_and_kcode_is_none + $KCODE = 'NONE' + xml = Builder::XmlMarkup.new + xml.p("\xE2\x80\x99") + assert_match(%r(

    ), xml.target!) # + end + + def test_use_entities_if_encoding_is_utf_but_kcode_is_not + $KCODE = 'NONE' + xml = Builder::XmlMarkup.new + xml.instruct!(:xml, :encoding => 'UTF-8') + xml.p("\xE2\x80\x99") + assert_match(%r(

    ), xml.target!) # + end + else + # change in behavior. As there is no $KCODE anymore, the default + # moves from "does not understand utf-8" to "supports utf-8". + + def test_use_entities_if_no_encoding_is_given_and_kcode_is_none + xml = Builder::XmlMarkup.new + xml.p("\xE2\x80\x99") + assert_match("

    \u2019

    ", xml.target!) # + end + + def test_use_entities_if_encoding_is_utf_but_kcode_is_not + xml = Builder::XmlMarkup.new + xml.instruct!(:xml, :encoding => 'UTF-8') + xml.p("\xE2\x80\x99") + assert_match("

    \u2019

    ", xml.target!) # + end + end + + def encode string, encoding + if !String.method_defined?(:encode) + $KCODE = encoding + string + elsif encoding == 'UTF8' + string.force_encoding('UTF-8') + else + string + end + end + + def test_use_entities_if_kcode_is_utf_but_encoding_is_dummy_encoding + xml = Builder::XmlMarkup.new + xml.instruct!(:xml, :encoding => 'UTF-16') + xml.p(encode("\xE2\x80\x99", 'UTF8')) + assert_match(%r(

    ), xml.target!) # + end + + def test_use_entities_if_kcode_is_utf_but_encoding_is_unsupported_encoding + xml = Builder::XmlMarkup.new + xml.instruct!(:xml, :encoding => 'UCS-2') + xml.p(encode("\xE2\x80\x99", 'UTF8')) + assert_match(%r(

    ), xml.target!) # + end + + def test_use_utf8_if_encoding_defaults_and_kcode_is_utf8 + xml = Builder::XmlMarkup.new + xml.p(encode("\xE2\x80\x99",'UTF8')) + assert_equal encode("

    \xE2\x80\x99

    ",'UTF8'), xml.target! + end + + def test_use_utf8_if_both_encoding_and_kcode_are_utf8 + xml = Builder::XmlMarkup.new + xml.instruct!(:xml, :encoding => 'UTF-8') + xml.p(encode("\xE2\x80\x99",'UTF8')) + assert_match encode("

    \xE2\x80\x99

    ",'UTF8'), xml.target! + end + + def test_use_utf8_if_both_encoding_and_kcode_are_utf8_with_lowercase + xml = Builder::XmlMarkup.new + xml.instruct!(:xml, :encoding => 'utf-8') + xml.p(encode("\xE2\x80\x99",'UTF8')) + assert_match encode("

    \xE2\x80\x99

    ",'UTF8'), xml.target! + end + end + + class TestXmlEvents < Builder::Test + def setup + @handler = EventHandler.new + @xe = Builder::XmlEvents.new(:target=>@handler) + end + + def test_simple + @xe.p + assert_equal [:start, :p, nil], @handler.events.shift + assert_equal [:end, :p], @handler.events.shift + end + + def test_text + @xe.p("HI") + assert_equal [:start, :p, nil], @handler.events.shift + assert_equal [:text, "HI"], @handler.events.shift + assert_equal [:end, :p], @handler.events.shift + end + + def test_attributes + @xe.p("id"=>"2") + ev = @handler.events.shift + assert_equal [:start, :p], ev[0,2] + assert_equal "2", ev[2]['id'] + assert_equal [:end, :p], @handler.events.shift + end + + def test_indented + @xml = Builder::XmlEvents.new(:indent=>2, :target=>@handler) + @xml.p { |x| x.b("HI") } + assert_equal [:start, :p, nil], @handler.events.shift + assert_equal "\n ", pop_text + assert_equal [:start, :b, nil], @handler.events.shift + assert_equal "HI", pop_text + assert_equal [:end, :b], @handler.events.shift + assert_equal "\n", pop_text + assert_equal [:end, :p], @handler.events.shift + end + + def pop_text + result = '' + while ! @handler.events.empty? && @handler.events[0][0] == :text + result << @handler.events[0][1] + @handler.events.shift + end + result + end + + class EventHandler + attr_reader :events + def initialize + @events = [] + end + + def start_tag(sym, attrs) + @events << [:start, sym, attrs] + end + + def end_tag(sym) + @events << [:end, sym] + end + + def text(txt) + @events << [:text, txt] + end + end + end + +end diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/test/test_method_caching.rb b/path/ruby/2.6.0/gems/builder-3.2.3/test/test_method_caching.rb new file mode 100644 index 00000000..bf5111e2 --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/test/test_method_caching.rb @@ -0,0 +1,62 @@ +#!/usr/bin/env ruby + +#-- +# Portions copyright 2011 by Bart ten Brinke (info@retrosync.com). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +#++ + +require 'helper' +require 'preload' +require 'builder' + +class TestMethodCaching < Builder::Test + + # We can directly ask if xml object responds to the cache_me or + # do_not_cache_me methods because xml is derived from BasicObject + # (and repond_to? is not defined in BasicObject). + # + # Instead we are going to stub out method_missing so that it throws + # an error, and then make sure that error is either thrown or not + # thrown as appropriate. + + def teardown + super + Builder::XmlBase.cache_method_calls = true + end + + def test_caching_does_not_break_weird_symbols + xml = Builder::XmlMarkup.new + xml.__send__("work-order", 1) + assert_equal "1", xml.target! + end + + def test_method_call_caching + xml = Builder::XmlMarkup.new + xml.cache_me + + def xml.method_missing(*args) + ::Kernel.fail StandardError, "SHOULD NOT BE CALLED" + end + assert_nothing_raised do + xml.cache_me + end + end + + def test_method_call_caching_disabled + Builder::XmlBase.cache_method_calls = false + xml = Builder::XmlMarkup.new + xml.do_not_cache_me + + def xml.method_missing(*args) + ::Kernel.fail StandardError, "SHOULD BE CALLED" + end + assert_raise(StandardError, "SHOULD BE CALLED") do + xml.do_not_cache_me + end + end + +end diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/test/test_namecollision.rb b/path/ruby/2.6.0/gems/builder-3.2.3/test/test_namecollision.rb new file mode 100644 index 00000000..a093f1b6 --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/test/test_namecollision.rb @@ -0,0 +1,39 @@ +#!/usr/bin/env ruby + +#-- +# Portions copyright 2004 by Jim Weirich (jim@weirichhouse.org). +# Portions copyright 2005 by Sam Ruby (rubys@intertwingly.net). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +#++ + +require 'helper' +require 'builder/xchar' + +class TestNameCollisions < Builder::Test + module Collide + def xchr + end + end + + def test_no_collision + assert_nothing_raised do + Builder.check_for_name_collision(Collide, :not_defined) + end + end + + def test_collision + assert_raise RuntimeError do + Builder.check_for_name_collision(Collide, "xchr") + end + end + + def test_collision_with_symbol + assert_raise RuntimeError do + Builder.check_for_name_collision(Collide, :xchr) + end + end +end diff --git a/path/ruby/2.6.0/gems/builder-3.2.3/test/test_xchar.rb b/path/ruby/2.6.0/gems/builder-3.2.3/test/test_xchar.rb new file mode 100644 index 00000000..ab895568 --- /dev/null +++ b/path/ruby/2.6.0/gems/builder-3.2.3/test/test_xchar.rb @@ -0,0 +1,78 @@ +#!/usr/bin/env ruby +# encoding: us-ascii + +#-- +# Portions copyright 2004 by Jim Weirich (jim@weirichhouse.org). +# Portions copyright 2005 by Sam Ruby (rubys@intertwingly.net). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +#++ + +#!/usr/bin/env ruby + +require 'helper' +require 'builder/xchar' + +if String.method_defined?(:encode) + class String + ENCODING_BINARY = Encoding.find('BINARY') + + # shim method for testing purposes + def to_xs(escape=true) + raise NameError.new('to_xs') unless caller[0].index(__FILE__) + + result = Builder::XChar.encode(self) + if escape + result.gsub(/[^\u0000-\u007F]/) {|c| "&##{c.ord};"} + else + # really only useful for testing purposes + result.force_encoding(ENCODING_BINARY) + end + end + end +end + +class TestXmlEscaping < Builder::Test + REPLACEMENT_CHAR = Builder::XChar::REPLACEMENT_CHAR.to_xs + + def test_ascii + assert_equal 'abc', 'abc'.to_xs + end + + def test_predefined + assert_equal '&', '&'.to_xs # ampersand + assert_equal '<', '<'.to_xs # left angle bracket + assert_equal '>', '>'.to_xs # right angle bracket + end + + def test_invalid + assert_equal REPLACEMENT_CHAR, "\x00".to_xs # null + assert_equal REPLACEMENT_CHAR, "\x0C".to_xs # form feed + assert_equal REPLACEMENT_CHAR, "\xEF\xBF\xBF".to_xs # U+FFFF + end + + def test_iso_8859_1 + assert_equal 'ç', "\xE7".to_xs # small c cedilla + assert_equal '©', "\xA9".to_xs # copyright symbol + end + + def test_win_1252 + assert_equal '’', "\x92".to_xs # smart quote + assert_equal '€', "\x80".to_xs # euro + end + + def test_utf8 + assert_equal '’', "\xE2\x80\x99".to_xs # right single quote + assert_equal '©', "\xC2\xA9".to_xs # copy + end + + def test_utf8_verbatim + assert_equal "\xE2\x80\x99", "\xE2\x80\x99".to_xs(false) # right single quote + assert_equal "\xC2\xA9", "\xC2\xA9".to_xs(false) # copy + assert_equal "\xC2\xA9&\xC2\xA9", + "\xC2\xA9&\xC2\xA9".to_xs(false) # copy with ampersand + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/CHANGELOG.md b/path/ruby/2.6.0/gems/byebug-11.0.1/CHANGELOG.md new file mode 100644 index 00000000..2387e372 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/CHANGELOG.md @@ -0,0 +1,897 @@ +# Changelog + +## [Unreleased] + +## [11.0.1] - 2019-03-18 + +### Fixed + +* [#546](https://github.com/deivid-rodriguez/byebug/pull/546): `continue!` to ignore further `byebug` calls. +* [#545](https://github.com/deivid-rodriguez/byebug/pull/545): `skip` autolisting code for intermediate skipped breakpoints. + +## [11.0.0] - 2019-02-15 + +### Added + +* [#377](https://github.com/deivid-rodriguez/byebug/pull/377): `skip` to continue until the next breakpoint as long as it is different from the current one. You can use this command to get out of loops, for example ([@tacnoman]). +* [#524](https://github.com/deivid-rodriguez/byebug/pull/524): `continue!` (or `continue unconditionally`) to continue until the end of the program regardless of the currently enabled breakpoints ([@tacnoman]). + +### Fixed + +* [#527](https://github.com/deivid-rodriguez/byebug/pull/527): `break` help text to clarify placeholders from literals. +* [#528](https://github.com/deivid-rodriguez/byebug/pull/528): `quit!` help to not show a space between "quit" and "!". + +### Removed + +* Support for MRI 2.2. Byebug no longer installs on this platform. + +## [10.0.2] - 2018-03-30 + +### Fixed + +* [#447](https://github.com/deivid-rodriguez/byebug/pull/447): Error when using byebug with `debase` gem ([@tzmfreedom]). + +## [10.0.1] - 2018-03-21 + +### Fixed + +* [#443](https://github.com/deivid-rodriguez/byebug/pull/443): Error when using byebug with `debase` gem ([@tzmfreedom]). + +## [10.0.0] - 2018-01-26 + +### Changed + +* Breaking on methods now stops on the first effective line of a method, not on the line containing the `def` keyword. + +### Added + +* [#393](https://github.com/deivid-rodriguez/byebug/pull/393): Show valid breakpoint locations when invalid location given ([@ko1]). +* [#397](https://github.com/deivid-rodriguez/byebug/pull/397): Ruby 2.5.0 support ([@yui-knk]). +* Log host & port when launching byebug's client in remote mode. +* [#82](https://github.com/deivid-rodriguez/byebug/issues/82): Some love & tests to remote debugging. +* [#141](https://github.com/deivid-rodriguez/byebug/issues/141): `remote_byebug` shortcut to start the most common case for remote debugging. + +### Fixed + +* [#419](https://github.com/deivid-rodriguez/byebug/pull/419): Properly ignore ruby fullpath executable when passed to byebug script. +* [#141](https://github.com/deivid-rodriguez/byebug/issues/141): Remote server crash when interrupting client. +* [#274](https://github.com/deivid-rodriguez/byebug/issues/274): Remote server crash when interrupting client. +* [#239](https://github.com/deivid-rodriguez/byebug/issues/239): Control server thread being able to `interrupt` main thread only a single time. + +## [9.1.0] - 2017-08-22 + +### Added + +* Better UI messages for breakpoint management. + +### Fixed + +* `where` command failing on instance_exec block stack frames. +* [#321](https://github.com/deivid-rodriguez/byebug/pull/321): `restart` command crashing in certain cases because of a missing `require "English"` ([@akaneko3]). +* [#321](https://github.com/deivid-rodriguez/byebug/pull/321): `restart` command crashing when debugged script is not executable or has no shebang ([@akaneko3]). + +### Removed + +* Ruby 2.0 and Ruby 2.1 official & unofficial support. Byebug no longer installs on these platforms. + +## [9.0.6] - 2016-09-29 + +### Fixed + +* [#241](https://github.com/deivid-rodriguez/byebug/issues/241): Error when using `byebug` with a ruby compiled against libedit. +* [#277](https://github.com/deivid-rodriguez/byebug/pull/277): Allow `Byebug.start_server` to yield the block passed to it when the actual port is already known ([@cben]). +* [#275](https://github.com/deivid-rodriguez/byebug/pull/275): Use a standard license name so it can be more reliably used by tools. + +## [9.0.5] - 2016-05-28 + +### Fixed + +* Error loading rc file when `ENV["HOME"]` is unset. + +## [9.0.4] - 2016-05-19 + +### Fixed + +* Errors in rc file not being displayed to the user. + +## [9.0.3] - 2016-05-16 + +### Fixed + +* [#256](https://github.com/deivid-rodriguez/byebug/issues/256): Unfriendly output in byebug's executable when no script specified. +* Unfriendly output in byebug's executable when script doesn't exist. +* Unfriendly output in byebug's executable when script has invalid code. + +## [9.0.2] - 2016-05-15 + +### Fixed + +* [#263](https://github.com/deivid-rodriguez/byebug/pull/263): Skip to get a line in eval context ([@k0kubun]). +* [#264](https://github.com/deivid-rodriguez/byebug/pull/264): Debugger getting disabled after `continue` even when linetrace is enabled ([@k0kubun]). + +## [9.0.1] - 2016-05-14 + +### Fixed + +* [#201](https://github.com/deivid-rodriguez/byebug/issues/201): `quit` never exiting when remote debugging. + +## [9.0.0] - 2016-05-11 + +### Fixed + +* `irb` command unintentionally changing $PROGRAM_NAME. +* `pry` command failing. +* Unrelated error message when using `pry` command and Pry not installed. +* [#239](https://github.com/deivid-rodriguez/byebug/issues/239): Interrupting program execution from remote control interface ([@izaera]). + +### Removed + +* Official Ruby 2.0.0 support. `var local` no longer works in Ruby 2.0. The rest of the commands should still work as before, but `byebug` is no longer tested against this version so they might start breaking in the future. + +## [8.2.5] - 2016-04-27 + +### Fixed + +* [#244](https://github.com/deivid-rodriguez/byebug/pull/244): Allows paths with spaces ([@HookyQR]). +* [#244](https://github.com/deivid-rodriguez/byebug/pull/244): Allows paths with colons ([@HookyQR]). + +## [8.2.4] - 2016-04-08 + +### Fixed + +* Reverts [#211](https://github.com/deivid-rodriguez/byebug/pull/211) which leads to an unusable debugger. + +## [8.2.3] - 2016-04-07 + +### Fixed + +* Better interaction with utilities like RSpec when hitting Ctrl-C. +* [#197](https://github.com/deivid-rodriguez/byebug/issues/197): `irb` command when original program modified ARGV ([@josephks]). +* [#211](https://github.com/deivid-rodriguez/byebug/pull/211): Unusable debugger when stdin redirected ([@sethk]). +* [#223](https://github.com/deivid-rodriguez/byebug/issues/223): RC file loading when no explicit flag included. +* [#175](https://github.com/deivid-rodriguez/byebug/issues/175): Installation on some Windows systems. +* [#226](https://github.com/deivid-rodriguez/byebug/issues/226): Installation on some Windows systems. + +## [8.2.2] - 2016-02-01 + +### Fixed + +* Bug in rc file loading where most initialization commands would not be run. + +## [8.2.1] - 2015-11-26 + +### Fixed + +* Bug in evaluations using "eval". + +## [8.2.0] - 2015-11-12 + +### Fixed + +* [#184](https://github.com/deivid-rodriguez/byebug/issues/184): Due to the way of running evaluations in a separate thread. +* [#188](https://github.com/deivid-rodriguez/byebug/issues/188): Due to the way of running evaluations in a separate thread. + +### Added + +* `debug` command to evaluate things in a separate thread, since this behavior was removed from default `eval` to fix the above issues. + +## [8.1.0] - 2015-11-09 + +### Fixed + +* Command history should be specific per project. +* Better error message in certain edge cases when printing the backtrace. +* Bug in evaluator which would show information about having stopped at a breakpoint in some cases. + +### Added + +* Ability to autolist source code after `frame` command. +* Ability to stop at lines where methods return. + +## [8.0.1] - 2015-11-07 + +### Fixed + +* Error stream wouldn't be properly reset when using standalone `byebug`. +* Confusing error message for invalid breakpoint locations. + +## [8.0.0] - 2015-11-05 + +### Fixed + +* [#183](https://github.com/deivid-rodriguez/byebug/issues/183). Compilation in Ruby 2.0. Regression introduced in [7.0.0]. +* "Return value is: nil" would be displayed when stopping right before the end of a class definition. We want to avoid showing anything instead. + +### Changed + +* Plugins now need to implement an `at_end` method (separate from `at_return`) in their custom processors. + +## [7.0.0] - 2015-11-04 + +### Fixed + +* [#177](https://github.com/deivid-rodriguez/byebug/issues/177). Some issues with formatting results of evaluations. +* [#144](https://github.com/deivid-rodriguez/byebug/issues/144). Ruby process after using byebug does no longer get slow. +* [#121](https://github.com/deivid-rodriguez/byebug/issues/121). `byebug` commands inside code evaluated from debugger's prompt are now properly working. +* Another evaluation bug in autocommands. +* `finish 0` command would sometimes fail to stop right before exiting the current frame. +* Runner's `--[no-]stop` option now works ([@windwiny]). +* Change variable name `bool`, avoid conflict clang's predefined macro. + +### Removed + +* `ps` command. + +### Changed + +* [#166](https://github.com/deivid-rodriguez/byebug/issues/166). Don't load the entire library on require, but only when a `byebug` call is issued ([@bquorning]). +* The above fix to the `finish 0` command cause `byebug`'s entrypoint to require 3 steps out instead of 2. In general, plugins using `Byebug::Context.step_out` will need to be changed to consider "c return events" as well. + +### Added + +* `autopry` setting that calls `pry` on every stop. +* Return value information to debugger's output when `finish 0` is used. + +## [6.0.2] - 2015-08-20 + +### Fixed + +* The user should always be given back a prompt unless (s)he explicitly states the opposite. This provides a more general fix to the bug resolved in [6.0.1]. + +## [6.0.1] - 2015-08-19 + +### Fixed + +* Bug in evaluation where the user would lose the command prompt when entering an expression with a syntax error. + +## [6.0.0] - 2015-08-17 + +### Removed + +* `autoeval` setting. I haven't heard of anyone setting it to false. +* `pp`, `putl`, `eval`. People just want to evaluate Ruby code, so the less magic the better. Most of the people probably were not aware that `byebug` was overriding stuff like `pp` or `eval`. Only keeping `ps` as the single "enhanced evaluation" command. +* `verbose` setting. +* `info catch` command. Use `catch` without arguments instead. +* `R` command alias for `restart`. + +### Changed + +* `info args` is now `var args`. +* `interrupt` is now aliased to `int`, not to `i`. +* API to define custom commands and subcommands (see the Command class). + +### Fixed + +* [#140](https://github.com/deivid-rodriguez/byebug/issues/140). `help` command not showing the list of available commands and their descriptions. +* [#147](https://github.com/deivid-rodriguez/byebug/issues/147). Setting breakpoints at symlinked files. + +### Added + +* API to define custom command processors (see the CommandProcessor class). + +## [5.0.0] - 2015-05-18 + +### Fixed + +* [#136](https://github.com/deivid-rodriguez/byebug/issues/136). `frame` command not working with negative numbers ([@ark6]). + +### Added + +* IDE support and a new command/subcommand API for plugins. +* Add a "savefile" setting holding the file where "save" command saves current debugger's state. + +### Changed + +* `disable` no longer disable all breakpoints, it just shows command's help instead. To disable all breakpoints now you need to do `disable breakpoints` (or `dis b`). Similarly, you can't no longer use `dis 1 2 3` but need to do `dis b 1 2 3` to disable specific breakpoints. The same applies to the `enable` command. + +### Removed + +* `help set ` no longer works. `help set` includes that same output and it's not verbose enough so that this is a problem. Same with `help show `. + +## [4.0.5] - 2015-04-02 + +### Fixed + +* [#131](https://github.com/deivid-rodriguez/byebug/issues/131). +* Thread commands help format should be consistent with the rest of the help system now. + +## [4.0.4] - 2015-03-27 + +### Fixed + +* [#127](https://github.com/deivid-rodriguez/byebug/issues/127). + +## [4.0.3] - 2015-03-19 + +### Fixed + +* Unused variable warning in `context.c`. + +## [4.0.2] - 2015-03-16 + +### Fixed + +* [#118](https://github.com/deivid-rodriguez/byebug/issues/118). Remove `rb-readline` as a dependency and show a help message whenever requiring `readline` fails instead. + +## [4.0.1] - 2015-03-13 + +### Fixed + +* .yml files needed for printers support were missing from the release... :S +* [#118](https://github.com/deivid-rodriguez/byebug/issues/118). Add `readline` as a dependency. + +## [4.0.0] - 2015-03-13 + +### Added + +* `untracevar` command that stops tracing a global variable. +* Window CI build through AppVeyor. +* OSX CI build through Travis. +* Style enforcement through RuboCop. +* C style enforment using the `indent` command line utility. +* Some remote debugging tests ([@eric-hu]). +* Printer's support ([@astashov]). + +### Changed + +* A lot of internal refactoring. +* `tracevar` now requires the full global variable name (with "$"). +* [#92](https://github.com/deivid-rodriguez/byebug/issues/92). The `catch` command is not allowed in post_mortem mode anymore. It was not working anyways. +* [#85](https://github.com/deivid-rodriguez/byebug/issues/85). `step` is now more user friendly when used in combination with `up`. +* `var const` can now be called without an argument and will show constants in the current scope. +* `break` with a class name now creates breakpoints regardless of class not being yet defined. If that's the case, it gives a warning but the class is created anyways. + +### Fixed + +* Code reloading issues. +* `set fullpath` was not showing fullpaths. Now it is. +* [#93](https://github.com/deivid-rodriguez/byebug/issues/93): `up`, `down` and `frame` commands now work in post_mortem mode. +* rc file (`.byebugrc`) loading: invalid commands are just ignored instead of aborting, global (home) rc file is now properly loaded before project's file. +* [#93](https://github.com/deivid-rodriguez/byebug/issues/93). Backtraces not working in `post_mortem` mode. +* 'cmd1 ; cmd2 ; ...; cmdN' syntax which allows running several commands sequentially. +* [#101](https://github.com/deivid-rodriguez/byebug/issues/101). `finish` command not stopping at the correct line. +* [#106](https://github.com/deivid-rodriguez/byebug/issues/106). `break` with namespaced class, like `break A::B#c` should now work. +* Command history is now persisted before exiting byebug. +* Setting breakpoint in a method would stop not only at the beginning of the method but also at the beginning of every block inside the method. +* [#122](https://github.com/deivid-rodriguez/byebug/issues/122). Setting breakpoints on module methods ([@x-yuri]). + +### Removed + +* `autoreload` setting as it's not necessary anymore. Code should always be up to date. +* `reload` command for the same reason. +* Gem dependency on `debugger-linecache`. +* `step+`, `step-`, `next+`, `next-`, `set/show linetrace_plus` and `set/show forcestep` commands. These were all mechanisms to deal with TracePoint API event dupplication, but this duplicated events have been completely removed from the API since [r48609]( bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/48609), so they are no longer necessary. +* `info file` subcommands: `info file breakpoints`, `info file mtime`, `info file sha1`, `info file all`. Now all information is listed under `info file`. +* `testing` setting. It was just a hack to be able to test `byebug`. Nobody was supposed to actually use it! +* `var class` command, just use Ruby (`self.class.class_variables`). +* `p` command, just use `eval`, or just type your expression and `byebug` will autoevaluate it. +* `exit` alias for `quit`. + +## [3.5.1] - 2014-09-29 + +### Fixed + +* [#79](https://github.com/deivid-rodriguez/byebug/issues/79). Windows installation. +* `condition` command not properly detecting invalid breakpoint ids. + +## [3.5.0] - 2014-09-28 + +### Fixed + +* [#81](https://github.com/deivid-rodriguez/byebug/issues/81). Byebug's history messing up other programs using Readline. +* Readline's history not being properly saved and inmediately available. +* User not being notified when trying to debug a non existent script. + +### Changed + +* Complete rewrite of byebug's history. +* Complete rewrite of list command. +* Docs about stacktrace related commands (`up`, `down`, `frame`, `backtrace`). + +## [3.4.2] - 2014-09-26 + +### Fixed + +* [#67](https://github.com/deivid-rodriguez/byebug/issues/67). Debugging commands invoked by ruby executable, as in `byebug -- ruby -Itest a_test.rb -n test_something`. + +## [3.4.1] - 2014-09-25 + +### Fixed + +* [#54](https://github.com/deivid-rodriguez/byebug/issues/54). Use of threads inside `eval` command. +* `list` command not listing backwards after reaching the end of the file. + +## [3.4.0] - 2014-09-01 + +### Fixed + +* deivid-rodriguez/pry-byebug#32 in a better way. + +## [3.3.0] - 2014-08-28 + +### Fixed + +* `set verbose` command. +* `set post_mortem false` command. +* Debugger stopping in `byebug`'s internal frames in some cases. +* `backtrace` crashing when `fullpath` setting disabled and calculated stack size being smaller than the real one. + +### Changed + +* The `-t` option for `bin/byebug` now turns tracing on whereas the `-x` option tells byebug to run the initialization file (.byebugrc) on startup. This is the default behaviour though. +* `bin/byebug` libified and tests added. + +### Removed + +* `info locals` command. Use `var local` instead. +* `info instance_variables` command. Use `var instance` instead. +* `info global_variables` command. Use `var global` instead. +* `info variables` command. Use `var all` instead. +* `irb` command stepping capabilities, see [8e226d0](https://github.com/deivid-rodriguez/byebug/commit/8e226d0). +* `script` and `restart-script` options for `bin/byebug`. + +## [3.2.0] - 2014-08-02 + +### Fixed + +* [#71](https://github.com/deivid-rodriguez/byebug/issues/71). Remote debugging ([@shuky19]). +* [#69](https://github.com/deivid-rodriguez/byebug/issues/69). `source` command ([@Olgagr]). + +### Removed + +* `post_mortem` activation through `Byebug.post_mortem`. Use `set post_mortem` instead. +* `info stack` command. Use `where` instead. +* `method iv` command. Use `var instance` instead. +* [#77](https://github.com/deivid-rodriguez/byebug/issues/77). Warning. + +## [3.1.2] - 2014-04-23 + +### Fixed + +* `post_mortem` mode in `bin/byebug` (really). +* Line tracing in `bin/byebug`. + +## [3.1.1] - 2014-04-23 + +### Fixed + +* `post_mortem` mode in bin/byebug. + +## [3.1.0] - 2014-04-23 + +### Removed + +* `show commands` command. Use `history` instead. +* Byebug.start accepting options. Any program settings you want applied from the start should be set in `.byebugrc`. +* `trace` command. Use `set linetrace` for line tracing and `tracevar` for global variable tracing. +* `show version` command. Use `byebug --version` to check byebug's version. +* `set arg` setting. Use the `restart` command instead. + +### Changed + +* `linetrace_plus` setting renamed to `tracing_plus`. + +### Added + +* `history` command to check byebug's history of previous commands. + +## [3.0.0] - 2014-04-17 + +### Fixed + +* Plain `byebug` not working when `pry-byebug` installed. +* `post_mortem` mode. +* Command history not being saved after regular program termination. +* [#54](https://github.com/deivid-rodriguez/byebug/issues/54). (Again) calling `Byebug.start` with `Timeout.timeout` ([@zmoazeni]). + +### Added + +* Allow disabling `post_mortem` mode. + +### Changed + +* `show commands` command for listing history of previous commands now behaves like shell's `history` command. +* `show/set history filename` is now `show/set histfile`. +* `show/set history size` is now `show/set histsize`. +* `show/set history save` is now `show/set autosave`. +* `finish` semantics, see [61f9b4d](https://github.com/deivid-rodriguez/byebug/commit/61f9b4d). +* Use per project history file by default. + +### Removed + +* The `init` option for `Byebug.start`. Information to make the `restart` command work is always saved now. + +## [2.7.0] - 2014-02-24 + +### Fixed + +* [#52](https://github.com/deivid-rodriguez/byebug/issues/52). `IGNORED_FILES` slowing down startup. +* [#53](https://github.com/deivid-rodriguez/byebug/issues/53). Calling `Byebug.start` with `Timeout.timeout`. +* [#54](https://github.com/deivid-rodriguez/byebug/issues/54). Calling `Byebug.start` with `Timeout.timeout`. + +## [2.6.0] - 2014-02-08 + +### Fixed + +* Circular dependency affecting `pry-byebug` ([@andreychernih]). + +## [2.5.0] - 2013-12-14 + +### Added + +* Support for `sublime-debugger`. + +## [2.4.1] - 2013-12-05 + +### Fixed + +* [#40](https://github.com/deivid-rodriguez/byebug/issues/40). Installation error in Mac OSX ([@luislavena]). + +## [2.4.0] - 2013-12-02 + +### Fixed + +* `thread list` showing too many threads. +* Fix setting post mortem mode with `set post_mortem`. Now this is the only post mortem functionality available as specifying `Byebug.post_mortem` with a block has been removed in this version. + +### Added + +* (Again) `debugger` as an alias to `byebug` ([@wallace]). +* `-R` option for `bin/byebug` to specify server's hostname:port for remote debugging ([@mrkn]). + +### Changed + +* Use `require` instead of `require_relative` for loading byebug's extension library ([@nobu]). +* `trace variable $foo` should be now `trace variable $foo`. + +## [2.3.1] - 2013-10-17 + +### Fixed + +* Breakpoint removal. +* Broken test suite. + +## [2.3.0] - 2013-10-09 + +### Added + +* Compatibility with Phusion Passenger Enterprise ([@FooBarWidget]). + +### Changed + +* More minimalist help system. + +## [2.2.2] - 2013-09-25 + +### Fixed + +* Compilation issue in 64 bit systems. + +## [2.2.1] - 2013-09-24 + +### Fixed + +* [#26](https://github.com/deivid-rodriguez/byebug/issues/26). Compilation issue introduced in [2.2.0]. + +### Changed + +* `show/set stack_trace_on_error` is now `show/set stack_on_error`. + +## [2.2.0] - 2013-09-22 + +### Fixed + +* Stack size calculations. +* Setting `post_mortem` mode. + +### Added + +* `verbose` setting for TracePoint API event inspection. + +### Changed + +* Warning free byebug. +* Allow `edit ` without a line number. + +## [2.1.1] - 2013-09-10 + +### Fixed + +* Debugging code inside `-e` Ruby flag. + +## [2.1.0] - 2013-09-08 + +### Fixed + +* Remote debugging display. +* `eval` crashing when inspecting raised an exception (reported by [@iblue]). + +### Changed + +* `enable breakpoints` now enables every breakpoint. +* `disable breakpoints` now disables every breakpoint. + +## [2.0.0] - 2013-08-30 + +### Added + +* "Official" definition of a command API. +* Thread support. + +### Removed + +* `jump` command. It had never worked. + +### Changed + +* Several internal refactorings. + +## [1.8.2] - 2013-08-16 + +### Fixed + +* `save` command now saves the list of `displays`. +* Stack size calculation. + +### Changed + +* More user friendly regexps for commands. +* Better help for some commands. + +## [1.8.1] - 2013-08-12 + +### Fixed + +* Major regression introduced in [1.8.0]. + +## [1.8.0] - 2013-08-12 + +### Added + +* Remote debugging support. + +## [1.7.0] - 2013-08-03 + +### Added + +* List command automatically called after callstack navigation commands. +* C-frames specifically marked in the callstack. +* C-frames skipped when navigating the callstack. + +## [1.6.1] - 2013-07-10 + +### Fixed + +* Windows compatibiliy: compilation and terminal width issues. + +## [1.6.0] - 2013-07-10 + +### Fixed + +* `byebug` placed at the end of a block or method call not working as expected. +* `autolist` being applied when Ruby `-e` option used. + +### Changed + +* Backtrace callstyles. Use `long` for detailed frames in callstack and `short` for more concise frames. + +## [1.5.0] - 2013-06-21 + +### Fixed + +* Incomplete backtraces when the debugger was not started at program startup. + +## [1.4.2] - 2013-06-20 + +### Fixed + +* `help command subcommand` command. +* Interaction with Rails Console debugging flag. +* `post_mortem` mode when running byebug from the outset. +* `no-quit` flag when running byebug from the outset. + +## [1.4.1] - 2013-06-15 + +### Fixed + +* Crash when printing some filenames in backtraces. +* Allow byebug developers to easily use compilers different from gcc ([@GarthSnyder]). + +## [1.4.0] - 2013-06-05 + +### Fixed + +* Memory leaks causing `byebug` to randomly crash. + +### Changed + +* Use the Debug Inspector API for backtrace information. + +## [1.3.1] - 2013-06-02 + +### Fixed + +* Interaction with Rails debugging flag. +* Crash when trying to print lines of code containing the character '%'. +* `basename` and `linetrace` options not working together. + +## [1.3.0] - 2013-05-25 + +### Added + +* Support colon-delimited include paths in command-line front-end ([@ender672]). + +## [1.2.0] - 2013-05-20 + +### Fixed + +* Ctrl+C during command line editing (works like pry/irb). + +### Added + +* `pry` command. + +## [1.1.1] - 2013-05-07 + +### Added + +* `pry-byebug` compatibility. + +### Changed + +* Better help system. +* Code cleanup. + +## [1.1.0] - 2013-04-30 + +### Added + +* Post Mortem support. + +## [1.0.3] - 2013-04-23 + +### Fixed + +* Negative line numbers shown by list command at the beginning of file. +* `backtrace` command segfaulting when trying to show info on some frame args. Don't know the reason yet, but the exception is handled now and command does not segfault anymore. + +### Changed + +* `autoreload` is set by default now. +* Try some thread support (not even close to usable). + +## [1.0.2] - 2013-04-09 + +### Fixed + +* backtraces messed up when using both `next`/`step` and backtrace navigation commands. + +### Changed + +* `autolist` and `autoeval` are default settings now. + +## [1.0.1] - 2013-04-06 + +### Fixed + +* Byebug not loading properly. + +## [1.0.0] - 2013-03-29 + +### Fixed + +* Green test suite. + +## 0.0.1 - 2013-03-18 + +### Added + +* Initial release. + +[Unreleased]: https://github.com/deivid-rodriguez/byebug/compare/v10.0.2...HEAD +[11.0.1]: https://github.com/deivid-rodriguez/byebug/compare/v11.0.0...v11.0.1 +[11.0.0]: https://github.com/deivid-rodriguez/byebug/compare/v10.0.2...v11.0.0 +[10.0.2]: https://github.com/deivid-rodriguez/byebug/compare/v10.0.1...v10.0.2 +[10.0.1]: https://github.com/deivid-rodriguez/byebug/compare/v10.0.0...v10.0.1 +[10.0.0]: https://github.com/deivid-rodriguez/byebug/compare/v9.1.0...v10.0.0 +[9.1.0]: https://github.com/deivid-rodriguez/byebug/compare/v9.0.6...v9.1.0 +[9.0.6]: https://github.com/deivid-rodriguez/byebug/compare/v9.0.5...v9.0.6 +[9.0.5]: https://github.com/deivid-rodriguez/byebug/compare/v9.0.4...v9.0.5 +[9.0.4]: https://github.com/deivid-rodriguez/byebug/compare/v9.0.3...v9.0.4 +[9.0.3]: https://github.com/deivid-rodriguez/byebug/compare/v9.0.2...v9.0.3 +[9.0.2]: https://github.com/deivid-rodriguez/byebug/compare/v9.0.1...v9.0.2 +[9.0.1]: https://github.com/deivid-rodriguez/byebug/compare/v9.0.0...v9.0.1 +[9.0.0]: https://github.com/deivid-rodriguez/byebug/compare/v8.2.5...v9.0.0 +[8.2.5]: https://github.com/deivid-rodriguez/byebug/compare/v8.2.4...v8.2.5 +[8.2.4]: https://github.com/deivid-rodriguez/byebug/compare/v8.2.3...v8.2.4 +[8.2.3]: https://github.com/deivid-rodriguez/byebug/compare/v8.2.2...v8.2.3 +[8.2.2]: https://github.com/deivid-rodriguez/byebug/compare/v8.2.1...v8.2.2 +[8.2.1]: https://github.com/deivid-rodriguez/byebug/compare/v8.2.0...v8.2.1 +[8.2.0]: https://github.com/deivid-rodriguez/byebug/compare/v8.1.0...v8.2.0 +[8.1.0]: https://github.com/deivid-rodriguez/byebug/compare/v8.0.1...v8.1.0 +[8.0.1]: https://github.com/deivid-rodriguez/byebug/compare/v8.0.0...v8.0.1 +[8.0.0]: https://github.com/deivid-rodriguez/byebug/compare/v7.0.0...v8.0.0 +[7.0.0]: https://github.com/deivid-rodriguez/byebug/compare/v6.0.2...v7.0.0 +[6.0.2]: https://github.com/deivid-rodriguez/byebug/compare/v6.0.1...v6.0.2 +[6.0.1]: https://github.com/deivid-rodriguez/byebug/compare/v6.0.0...v6.0.1 +[6.0.0]: https://github.com/deivid-rodriguez/byebug/compare/v5.0.0...v6.0.0 +[5.0.0]: https://github.com/deivid-rodriguez/byebug/compare/v4.0.5...v5.0.0 +[4.0.5]: https://github.com/deivid-rodriguez/byebug/compare/v4.0.4...v4.0.5 +[4.0.4]: https://github.com/deivid-rodriguez/byebug/compare/v4.0.3...v4.0.4 +[4.0.3]: https://github.com/deivid-rodriguez/byebug/compare/v4.0.2...v4.0.3 +[4.0.2]: https://github.com/deivid-rodriguez/byebug/compare/v4.0.1...v4.0.2 +[4.0.1]: https://github.com/deivid-rodriguez/byebug/compare/v4.0.0...v4.0.1 +[4.0.0]: https://github.com/deivid-rodriguez/byebug/compare/v3.5.1...v4.0.0 +[3.5.1]: https://github.com/deivid-rodriguez/byebug/compare/v3.5.0...v3.5.1 +[3.5.0]: https://github.com/deivid-rodriguez/byebug/compare/v3.4.2...v3.5.0 +[3.4.2]: https://github.com/deivid-rodriguez/byebug/compare/v3.4.1...v3.4.2 +[3.4.1]: https://github.com/deivid-rodriguez/byebug/compare/v3.4.0...v3.4.1 +[3.4.0]: https://github.com/deivid-rodriguez/byebug/compare/v3.3.0...v3.4.0 +[3.3.0]: https://github.com/deivid-rodriguez/byebug/compare/v3.2.0...v3.3.0 +[3.2.0]: https://github.com/deivid-rodriguez/byebug/compare/v3.1.2...v3.2.0 +[3.1.2]: https://github.com/deivid-rodriguez/byebug/compare/v3.1.1...v3.1.2 +[3.1.1]: https://github.com/deivid-rodriguez/byebug/compare/v3.1.0...v3.1.1 +[3.1.0]: https://github.com/deivid-rodriguez/byebug/compare/v3.0.0...v3.1.0 +[3.0.0]: https://github.com/deivid-rodriguez/byebug/compare/v2.7.0...v3.0.0 +[2.7.0]: https://github.com/deivid-rodriguez/byebug/compare/v2.6.0...v2.7.0 +[2.6.0]: https://github.com/deivid-rodriguez/byebug/compare/v2.5.0...v2.6.0 +[2.5.0]: https://github.com/deivid-rodriguez/byebug/compare/v2.4.1...v2.5.0 +[2.4.1]: https://github.com/deivid-rodriguez/byebug/compare/v2.4.0...v2.4.1 +[2.4.0]: https://github.com/deivid-rodriguez/byebug/compare/v2.3.1...v2.4.0 +[2.3.1]: https://github.com/deivid-rodriguez/byebug/compare/v2.3.0...v2.3.1 +[2.3.0]: https://github.com/deivid-rodriguez/byebug/compare/v2.2.2...v2.3.0 +[2.2.2]: https://github.com/deivid-rodriguez/byebug/compare/v2.2.1...v2.2.2 +[2.2.1]: https://github.com/deivid-rodriguez/byebug/compare/v2.2.0...v2.2.1 +[2.2.0]: https://github.com/deivid-rodriguez/byebug/compare/v2.1.1...v2.2.0 +[2.1.1]: https://github.com/deivid-rodriguez/byebug/compare/v2.1.0...v2.1.1 +[2.1.0]: https://github.com/deivid-rodriguez/byebug/compare/v2.0.0...v2.1.0 +[2.0.0]: https://github.com/deivid-rodriguez/byebug/compare/v1.8.2...v2.0.0 +[1.8.2]: https://github.com/deivid-rodriguez/byebug/compare/v1.8.1...v1.8.2 +[1.8.1]: https://github.com/deivid-rodriguez/byebug/compare/v1.8.0...v1.8.1 +[1.8.0]: https://github.com/deivid-rodriguez/byebug/compare/v1.7.0...v1.8.0 +[1.7.0]: https://github.com/deivid-rodriguez/byebug/compare/v1.6.1...v1.7.0 +[1.6.1]: https://github.com/deivid-rodriguez/byebug/compare/v1.6.0...v1.6.1 +[1.6.0]: https://github.com/deivid-rodriguez/byebug/compare/v1.5.0...v1.6.0 +[1.5.0]: https://github.com/deivid-rodriguez/byebug/compare/v1.4.2...v1.5.0 +[1.4.2]: https://github.com/deivid-rodriguez/byebug/compare/v1.4.1...v1.4.2 +[1.4.1]: https://github.com/deivid-rodriguez/byebug/compare/v1.4.0...v1.4.1 +[1.4.0]: https://github.com/deivid-rodriguez/byebug/compare/v1.3.1...v1.4.0 +[1.3.1]: https://github.com/deivid-rodriguez/byebug/compare/v1.3.0...v1.3.1 +[1.3.0]: https://github.com/deivid-rodriguez/byebug/compare/v1.2.0...v1.3.0 +[1.2.0]: https://github.com/deivid-rodriguez/byebug/compare/v1.1.1...v1.2.0 +[1.1.1]: https://github.com/deivid-rodriguez/byebug/compare/v1.1.0...v1.1.1 +[1.1.0]: https://github.com/deivid-rodriguez/byebug/compare/v1.0.3...v1.1.0 +[1.0.3]: https://github.com/deivid-rodriguez/byebug/compare/v1.0.2...v1.0.3 +[1.0.2]: https://github.com/deivid-rodriguez/byebug/compare/v1.0.1...v1.0.2 +[1.0.1]: https://github.com/deivid-rodriguez/byebug/compare/v1.0.0...v1.0.1 +[1.0.0]: https://github.com/deivid-rodriguez/byebug/compare/v0.0.1...v1.0.0 + +[@akaneko3]: https://github.com/akaneko3 +[@andreychernih]: https://github.com/andreychernih +[@ark6]: https://github.com/ark6 +[@astashov]: https://github.com/astashov +[@bquorning]: https://github.com/bquorning +[@cben]: https://github.com/cben +[@ender672]: https://github.com/ender672 +[@eric-hu]: https://github.com/eric-hu +[@FooBarWidget]: https://github.com/FooBarWidget +[@GarthSnyder]: https://github.com/GarthSnyder +[@HookyQR]: https://github.com/HookyQR +[@iblue]: https://github.com/iblue +[@izaera]: https://github.com/izaera +[@josephks]: https://github.com/josephks +[@k0kubun]: https://github.com/k0kubun +[@ko1]: https://github.com/ko1 +[@luislavena]: https://github.com/luislavena +[@mrkn]: https://github.com/mrkn +[@nobu]: https://github.com/nobu +[@Olgagr]: https://github.com/Olgagr +[@sethk]: https://github.com/sethk +[@shuky19]: https://github.com/shuky19 +[@tacnoman]: https://github.com/tacnoman +[@tzmfreedom]: https://github.com/tzmfreedom +[@wallace]: https://github.com/wallace +[@windwiny]: https://github.com/windwiny +[@x-yuri]: https://github.com/x-yuri +[@yui-knk]: https://github.com/yui-knk +[@zmoazeni]: https://github.com/zmoazeni diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/CONTRIBUTING.md b/path/ruby/2.6.0/gems/byebug-11.0.1/CONTRIBUTING.md new file mode 100644 index 00000000..ba8cce00 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/CONTRIBUTING.md @@ -0,0 +1,58 @@ +# CONTRIBUTING + +Please note that this project is released with a [Contributor Code of +Conduct](code_of_conduct.md). By participating in this project you agree to +abide by its terms. + +## Bug Reports + +* Try to reproduce the issue against the latest revision. There might be + unrealeased work that fixes your problem! +* Ensure that your issue has not already been reported. +* Include the steps you carried out to produce the problem. If we can't + reproduce it, we can't fix it. +* Include the behavior you observed along with the behavior you expected, + and why you expected it. + +## Development dependencies + +* `Byebug` depends on Ruby's TracePoint API provided by `ruby-core`. This is a + young API and a lot of bugs have been recently corrected, so make sure you + always have the lastest patch level release installed. +* The recommended tool to manage development dependencies is `bundler`. Run + `gem install bundler` to install it. +* Running `bin/bundle install` inside a local clone of `byebug` will get + development dependencies installed. + +## Running the test suite + +* Make sure you compile the C-extension using `bin/rake compile`. + Otherwise you won't be able to use `byebug`. +* Run the test suite using the default rake task (`bin/rake`). This task is + composed of 3 subtasks: `bin/rake compile`, `bin/rake test` & `bin/rake lint`. +* If you want to run specific tests, use the provided test runner, like so: + * Specific test files. For example, `bin/minitest test/commands/break_test.rb` + * Specific test classes. For example, `bin/minitest BreakAtLinesTest` + * Specific tests. For example, + `bin/minitest test_catch_removes_specific_catchpoint` + * Specific fully qualified tests. For example, + `bin/minitest BreakAtLinesTest#test_setting_breakpoint_sets_correct_fields` + * You can combine any of them and you will get the union of all filters. For + example: `bin/minitest BreakAtLinesTest + test_catch_removes_specific_catchpoint` + +## Code style + +* Byebug uses several style checks to check code style consistent. You can run + those using `bin/rake lint`. + +## Byebug as a C-extension + +Byebug is a gem developed as a C-extension. The debugger internal's +functionality is implemented in C (the interaction with the TracePoint API). +The rest of the gem is implemented in Ruby. Normally you won't need to touch +the C-extension, but it will obviously depended on the bug you're trying to fix +or the feature you are willing to add. You can learn more about C-extensions +[here](https://tenderlovemaking.com/2009/12/18/writing-ruby-c-extensions-part-1.html) +or +[here](https://tenderlovemaking.com/2010/12/11/writing-ruby-c-extensions-part-2.html). diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/GUIDE.md b/path/ruby/2.6.0/gems/byebug-11.0.1/GUIDE.md new file mode 100644 index 00000000..dfcd71ea --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/GUIDE.md @@ -0,0 +1,1806 @@ +# GUIDE + +## Introduction + +### First Steps + +A handful of commands are enough to get started using `byebug`. The following +session illustrates these commands. Take the following sample file: + +```ruby +# +# The n'th triangle number: triangle(n) = n*(n+1)/2 = 1 + 2 + ... + n +# +def triangle(n) + tri = 0 + + 0.upto(n) { |i| tri += i } + + tri +end + +t = triangle(3) +puts t + +``` + +Let's debug it. + +```bash +$ byebug /path/to/triangle.rb + +[1, 10] in /path/to/triangle.rb + 1: # + 2: # The n'th triangle number: triangle(n) = n*(n+1)/2 = 1 + 2 + ... + n + 3: # +=> 4: def triangle(n) + 5: tri = 0 + 6: + 7: 0.upto(n) { |i| tri += i } + 8: + 9: tri + 10: end +(byebug) +``` + +We are currently stopped before the first executable line of the program: line 4 +of `triangle.rb`. If you are used to less dynamic languages and have used +debuggers for more statically compiled languages like C, C++, or Java, it may +seem odd to be stopped before a function definition but in Ruby line 4 is +executed. + +Byebug's prompt is `(byebug)`. If the program has died and you are in +post-mortem debugging, `(byebug:post-mortem)` is used instead. If the program +has terminated normally and the `--no-quit` option has been specified in the +command line, the prompt will be `(byebug:ctrl)` instead. The commands available +change depending on the program's state. + +Byebug automatically lists 10 lines of code centered around the current line +every time it is stopped. The current line is marked with `=>`. If the range +would overflow the beggining or the end of the file, byebug will move it +accordingly so that only actual real lines of code are displayed. + +Now let us step through the program. + +```bash +(byebug) step + +[5, 14] in /path/to/triangle.rb + 5: tri = 0 + 6: + 7: 0.upto(n) { |i| tri += i } + 8: + 9: tri + 10: end + 11: +=> 12: t = triangle(3) + 13: puts t +(byebug) # hit enter + +[1, 10] in /path/to/triangle.rb + 1: # + 2: # The n'th triangle number: triangle(n) = n*(n+1)/2 = 1 + 2 + ... + n + 3: # + 4: def triangle(n) +=> 5: tri = 0 + 6: + 7: 0.upto(n) { |i| tri += i } + 8: + 9: tri + 10: end +(byebug) eval tri +nil +(byebug) step + +[2, 11] in /path/to/triangle.rb + 2: # The n'th triangle number: triangle(n) = n*(n+1)/2 = 1 + 2 + ... + n + 3: # + 4: def triangle(n) + 5: tri = 0 + 6: +=> 7: 0.upto(n) { |i| tri += i } + 8: + 9: tri + 10: end + 11: +(byebug) eval tri +0 +``` + +The first `step` command runs the script one executable unit. The second command +we entered was just hitting the return key: `byebug` remembers the last command +you entered was `step` and runs it again. + +One way to print the values of variables is `eval` (there are other ways). When we +look at the value of `tri` the first time, we see it is `nil`. Again we are +stopped _before_ the assignment on line 5, and this variable hadn't been set +previously. However after issuing another `step` command we see that the value +is 0 as expected. If every time we stop we want to see the value of `tri` to see +how things are going, there is a better way by setting a display expression: + +```bash +(byebug) display tri +1: tri = 0 +``` + +Now let us run the program until right before we return from the function. We'll +want to see which lines get run, so we turn on _line tracing_. If we don't want +whole paths to be displayed when tracing, we can turn on _basename_. + +```bash +(byebug) set linetrace +linetrace is on +(byebug) set basename +basename is on +(byebug) finish 0 +Tracing: triangle.rb:7 0.upto(n) { |i| tri += i } +1: tri = 0 +Tracing: triangle.rb:7 0.upto(n) { |i| tri += i } +1: tri = 0 +Tracing: triangle.rb:7 0.upto(n) { |i| tri += i } +1: tri = 1 +Tracing: triangle.rb:7 0.upto(n) { |i| tri += i } +1: tri = 3 +Tracing: triangle.rb:9 tri +1: tri = 6 +1: tri = 6 + +[4, 13] in /home/davidr/Proyectos/byebug/triangle.rb + 4: def triangle(n) + 5: tri = 0 + 6: + 7: 0.upto(n) { |i| tri += i } + 8: + 9: tri +=> 10: end + 11: + 12: t = triangle(3) + 13: puts t +(byebug) quit +Really quit? (y/n) +y +``` + +So far, so good. As you can see from the above, to get out of `byebug`, one +can issue a `quit` command (or the abbreviation `q`). If you want to quit +without being prompted, suffix the command with an exclamation mark, e.g., `q!`. + +### Second Sample Session: Delving Deeper + +In this section we'll introduce breakpoints, the call stack and restarting. +Below we will debug a simple Ruby program to solve the classic Towers of Hanoi +puzzle. It is augmented by the bane of programming: some command-parameter +processing with error checking. + +```ruby +# +# Solves the classic Towers of Hanoi puzzle. +# +def hanoi(n, a, b, c) + hanoi(n - 1, a, c, b) if n - 1 > 0 + + puts "Move disk #{a} to #{b}" + + hanoi(n - 1, c, b, a) if n - 1 > 0 +end + +n_args = $ARGV.length + +raise("*** Need number of disks or no parameter") if n_args > 1 + +n = 3 + +if n_args > 0 + begin + n = $ARGV[0].to_i + rescue ValueError + raise("*** Expecting an integer, got: #{$ARGV[0]}") + end +end + +raise("*** Number of disks should be between 1 and 100") if n < 1 || n > 100 + +hanoi(n, :a, :b, :c) +``` + +Recall in the first section it was stated that before the `def` is run, the +method it names is undefined. Let's check that out. First let's see what +private methods we can call before running `def hanoi`. + +```bash +$ byebug path/to/hanoi.rb + + 1: # + 2: # Solves the classic Towers of Hanoi puzzle. + 3: # + 4: def hanoi(n, a, b, c) + 5: hanoi(n - 1, a, c, b) if n - 1 > 0 + 6: + 7: puts "Move disk #{a} to #{b}" + 8: + 9: hanoi(n - 1, c, b, a) if n - 1 > 0 + 10: end +(byebug) private_methods +public +private +include +using +define_method +default_src_encoding +DelegateClass +Digest +timeout +initialize_copy +initialize_dup +initialize_clone +sprintf +format +Integer +Float +String +Array +Hash +warn +raise +fail +global_variables +__method__ +__callee__ +__dir__ +eval +local_variables +iterator? +block_given? +catch +throw +loop +respond_to_missing? +trace_var +untrace_var +at_exit +syscall +open +printf +print +putc +puts +gets +readline +select +readlines +` +p +test +srand +rand +trap +load +require +require_relative +autoload +autoload? +proc +lambda +binding +caller +caller_locations +exec +fork +exit! +system +spawn +sleep +exit +abort +Rational +Complex +set_trace_func +gem_original_require +Pathname +pp +y +URI +rubygems_require +initialize +singleton_method_added +singleton_method_removed +singleton_method_undefined +method_missing +(byebug) private_methods.member?(:hanoi) +false +``` + +`private_methods` is not a byebug command but a Ruby feature. By default, when +`byebug` doesn't understand a command, it will evaluate it as if it was a Ruby +command. You can use any Ruby to inspect your program's state at the place it +is stopped. + +Now let's see what happens after stepping: + +```bash +(byebug) step + +[5, 14] in /path/to/hanoi.rb + 5: hanoi(n - 1, a, c, b) if n - 1 > 0 + 6: + 7: puts "Move disk #{a} to #{b}" + 8: + 9: hanoi(n - 1, c, b, a) if n - 1 > 0 + 10: end + 11: +=> 12: n_args = $ARGV.length + 13: + 14: raise("*** Need number of disks or no parameter") if n_args > 1 +(byebug) private_methods.member?(:hanoi) +true +(byebug) +``` + +Okay, lets go on and talk about program arguments. + +```bash +(byebug) $ARGV +[] +``` + +Oops. We forgot to specify any parameters to this program. Let's try again. We +can use the `restart` command here. + +```bash +(byebug) restart 3 +Re exec'ing: + /path/to/exe/byebug /path/to/hanoi.rb 3 + +[1, 10] in /path/to/hanoi.rb + 1: # + 2: # Solves the classic Towers of Hanoi puzzle. + 3: # +=> 4: def hanoi(n, a, b, c) + 5: hanoi(n - 1, a, c, b) if n - 1 > 0 + 6: + 7: puts "Move disk #{a} to #{b}" + 8: + 9: hanoi(n - 1, c, b, a) if n - 1 > 0 + 10: end +(byebug) break 5 +Created breakpoint 1 at /path/to/hanoi.rb:5 +(byebug) continue +Stopped by breakpoint 1 at /path/to/hanoi.rb:5 + +[1, 10] in /path/to/hanoi.rb + 1: # + 2: # Solves the classic Towers of Hanoi puzzle. + 3: # + 4: def hanoi(n, a, b, c) +=> 5: hanoi(n - 1, a, c, b) if n - 1 > 0 + 6: + 7: puts "Move disk #{a} to #{b}" + 8: + 9: hanoi(n - 1, c, b, a) if n - 1 > 0 + 10: end +(byebug) display n +1: n = 3 +(byebug) display a +2: a = :a +(byebug) display b +3: b = :b +(byebug) undisplay 3 +(byebug) continue +Stopped by breakpoint 1 at /path/to/hanoi.rb:5 +1: n = 2 +2: a = :a +[1, 10] in /path/to/hanoi.rb + 1: # + 2: # Solves the classic Towers of Hanoi puzzle. + 3: # + 4: def hanoi(n, a, b, c) +=> 5: hanoi(n - 1, a, c, b) if n - 1 > 0 + 6: + 7: puts "Move disk #{a} to #{b}" + 8: + 9: hanoi(n - 1, c, b, a) if n - 1 > 0 + 10: end + +(byebug) c +Stopped by breakpoint 1 at /path/to/hanoi.rb:5 +1: n = 1 +2: a = :a + +[1, 10] in /path/to/hanoi.rb + 1: # + 2: # Solves the classic Towers of Hanoi puzzle. + 3: # + 4: def hanoi(n, a, b, c) +=> 5: hanoi(n - 1, a, c, b) if n - 1 > 0 + 6: + 7: puts "Move disk #{a} to #{b}" + 8: + 9: hanoi(n - 1, c, b, a) if n - 1 > 0 + 10: end +(byebug) set nofullpath +fullpath is off +(byebug) where +--> #0 Object.hanoi(n#Fixnum, a#Symbol, b#Symbol, c#Symbol) at .../shortpath/to/hanoi.rb:5 + #1 Object.hanoi(n#Fixnum, a#Symbol, b#Symbol, c#Symbol) at .../shortpath/to/hanoi.rb:5 + #2 at .../Proyectos/byebug/hanoi.rb:28 +(byebug) +``` + +In the above we added new commands: `break` (see [breakpoints]()), which +indicates to stop just before that line of code is run, and `continue`, which +resumes execution. To remove a display expression `undisplay` is used. If we +give a display number, just that display expression is removed. + +We also used a new command `where`(see [backtrace]()) to show the callstack. In +the above situation, starting from the bottom line we see we called the `hanoi` +method from line 28 of the file `hanoi.rb` and the `hanoi` method called itself +two more times at line 5. + +In the callstack we show a _current frame_ mark, the frame number, the method +being called, the names of the parameters, the types those parameters +_currently_ have and the file-line position. Remember it's possible that when +the program was called the parameters had different types, since the types of +variables can change dynamically. You can alter the style of what to show in the +trace (see [callstyle]()). + +Now let's move around the callstack. + +```bash +(byebug) undisplay +Clear all expressions? (y/n) y +(byebug) n_args +NameError Exception: undefined local variable or method `n_args' for main:Object +(byebug) frame 2 + +[19, 28] in /path/to/hanoi.rb + 19: begin + 20: n = $ARGV[0].to_i + 21: rescue ValueError + 22: raise("*** Expecting an integer, got: #{$ARGV[0]}") + 23: end + 24: end + 25: + 26: raise("*** Number of disks should be between 1 and 100") if n < 1 || n > 100 + 27: +=> 28: hanoi(n, :a, :b, :c) +(byebug) n_args +1 +(byebug) eval n +3 +(byebug) down 2 + +[1, 10] in /path/to/hanoi.rb + 1: # + 2: # Solves the classic Towers of Hanoi puzzle. + 3: # + 4: def hanoi(n, a, b, c) +=> 5: hanoi(n - 1, a, c, b) if n - 1 > 0 + 6: + 7: puts "Move disk #{a} to #{b}" + 8: + 9: hanoi(n - 1, c, b, a) if n - 1 > 0 + 10: end +(byebug) eval n +2 +``` + +Notice in the above to get the value of variable `n` we had to use a print +command like `eval n`. If we entered just `n`, that would be taken to mean byebug +command `next`. In the current scope, variable `n_args` is not defined. However +I can change to the top-most frame by using the `frame 2` command. Notice that +inside frame #2, the value of `n_args` can be shown. Also note that the value of +variable `n` is different. + +### Attaching to a running program with `byebug` + +In the previous sessions we've been calling byebug right at the outset, but +there is another mode of operation you might use. If there's a lot of code that +needs to be run before the part you want to inspect, it might not be efficient +or convenient to run byebug from the outset. + +In this section we'll show how to enter the code in the middle of your program, +while delving more into byebug's operation. We will also use unit testing. Using +unit tests will greatly reduce the amount of debugging needed, while at the same +time, will increase the quality of your program. + +What we'll do is take the `triangle` code from the first session and write a +unit test for that. In a sense we did write a tiny test for the program which +was basically the last line where we printed the value of `triangle(3)`. This +test however wasn't automated: the expectation is that someone would look at the +output and verify that what was printed is what was expected. + +Before we can turn that into something that can be `required`, we probably want +to remove that output. However I like to keep in that line so that when I +look at the file, I have an example of how to run it. Therefore we will +conditionally run this line if that file is invoked directly, but skip it if it +is not. _NOTE: `byebug` resets `$0` to try to make things like this work._ + +```ruby +if __FILE__ == $PROGRAM_NAME + t = triangle(3) + puts t +end +``` + +Okay, we're now ready to write our unit test and we'll use the `minitest` +framework for that. Here's the test code, it should be placed in the same +directory as `triangle.rb`. + +```ruby +require "minitest/autorun" +require_relative "triangle.rb" + +class TestTriangle < Minitest::Test + def test_basic + solutions = [] + + 0.upto(5) { |i| solutions << triangle(i) } + + assert_equal([0, 1, 3, 6, 10, 15], solutions, "First 5 triangle numbers") + end +end +``` + +Let's say we want to stop before the first statement in our test method, we'll +add the following: + +```ruby +... +def test_basic + byebug + solutions = [] +... +``` + +Now we run the program, requiring `byebug` + +```bash +$ ruby -rbyebug test_triangle.rb +Run options: --seed 31679 + +# Running: + +[2, 11] in test_triangle.rb + 2: require_relative "triangle.rb" + 3: + 4: class TestTriangle < Minitest::Test + 5: def test_basic + 6: byebug +=> 7: solutions = [] + 8: + 9: 0.upto(5) { |i| solutions << triangle(i) } + 10: + 11: assert_equal([0, 1, 3, 6, 10, 15], solutions, "First 5 triangle numbers") +(byebug) +``` + +and we see that we are stopped at line 7 just before the initialization of the +list `solutions`. + +Now let's see where we are... + +```bash +(byebug) set nofullpath +Displaying frame's full file names is off. +(byebug) bt +--> #0 TestTriangle.test_basic at .../Proyectos/byebug/test_triangle.rb:7 + #1 block (3 levels) in Minitest::Test.run at .../lib/minitest/test.rb:108 + #2 Minitest::Test.capture_exceptions at .../lib/minitest/test.rb:206 + #3 block (2 levels) in Minitest::Test.run at .../lib/minitest/test.rb:105 + #4 Minitest::Test.time_it at .../lib/minitest/test.rb:258 + #5 block in Minitest::Test.run at .../lib/minitest/test.rb:104 + #6 #.on_signal(name#String, action#Proc) at .../minitest-5.5.0/lib/minitest.rb:321 + #7 Minitest::Test.with_info_handler(&block#Proc) at .../lib/minitest/test.rb:278 + #8 Minitest::Test.run at .../lib/minitest/test.rb:103 + #9 #.run_one_method(klass#Class, method_name#String) at .../minitest-5.5.0/lib/minitest.rb:768 + #10 #.run_one_method(klass#Class, method_name#String, reporter#Minitest::CompositeReporter) at .../minitest-5.5.0/lib/minitest.rb:295 + #11 block (2 levels) in #.run(reporter#Minitest::CompositeReporter, options#Hash) at .../minitest-5.5.0/lib/minitest.rb:289 + ͱ-- #12 Array.each at .../minitest-5.5.0/lib/minitest.rb:288 + #13 block in #.run(reporter#Minitest::CompositeReporter, options#Hash) at .../minitest-5.5.0/lib/minitest.rb:288 + #14 #.on_signal(name#String, action#Proc) at .../minitest-5.5.0/lib/minitest.rb:321 + #15 #.with_info_handler(reporter#Minitest::CompositeReporter, &block#Proc) at .../minitest-5.5.0/lib/minitest.rb:308 + #16 #.run(reporter#Minitest::CompositeReporter, options#Hash) at .../minitest-5.5.0/lib/minitest.rb:287 + #17 block in #.__run(reporter#Minitest::CompositeReporter, options#Hash) at .../minitest-5.5.0/lib/minitest.rb:150 + ͱ-- #18 Array.map at .../minitest-5.5.0/lib/minitest.rb:150 + #19 #.__run(reporter#Minitest::CompositeReporter, options#Hash) at .../minitest-5.5.0/lib/minitest.rb:150 + #20 #.run(args#Array) at .../minitest-5.5.0/lib/minitest.rb:127 + #21 block in #.autorun at .../minitest-5.5.0/lib/minitest.rb:56 +(byebug) +``` + +We get the same result as if we had run byebug from the outset. + +### Debugging Oddities: How debugging Ruby may be different from other languages + +If you are used to debugging in other languages like C, C++, Perl, Java or even +Bash (see [bashdb](http://bashdb.sourceforge.net)), there may be a number of things that +seem or feel a little bit different and may confuse you. A number of these +things aren't oddities of the debugger per se but differences in how Ruby works +compared to those other languages. Because Ruby works a little differently from +those other languages, writing a debugger has to also be a little different as +well if it is to be useful. In this respect, using Byebug may help you +understand Ruby better. + +We've already seen one such difference: the fact that we stop on method +definitions or `def`'s and that is because these are in fact executable +statements. In other compiled languages this would not happen because that's +already been done when you compile the program (or in Perl when it scans in the +program). In this section we'll consider some other things that might throw off +new users to Ruby who are familiar with other languages and debugging in them. + +#### Bouncing Around in Blocks (iterators) + +When debugging languages with coroutines like Python and Ruby, a method call may +not necessarily go to the first statement after the method header. It's possible +that the call will continue after a `yield` statement from a prior call. + +```ruby +# +# Enumerator for primes +# +class SievePrime + def initialize + @odd_primes = [] + end + + def next_prime + candidate = 2 + yield candidate + not_prime = false + candidate += 1 + + loop do + @odd_primes.each do |p| + not_prime = (0 == (candidate % p)) + break if not_prime + end + + unless not_prime + @odd_primes << candidate + yield candidate + end + + candidate += 2 + end + end +end + +SievePrime.new.next_prime do |prime| + puts prime + break if prime > 10 +end +``` + +```bash +$ byebug primes.rb +[1, 10] in /path/to/primes.rb + 1: # + 2: # Enumerator for primes + 3: # +=> 4: class SievePrime + 5: def initialize + 6: @odd_primes = [] + 7: end + 8: + 9: def self.next_prime(&block) + 10: candidate = 2 +(byebug) set linetrace +line tracing is on. +(byebug) set basename +basename in on. +(byebug) step 9 +Tracing: primes.rb:5 def initialize +Tracing: primes.rb:9 def next_prime +Tracing: primes.rb:31 SievePrime.new.next_prime do |prime| +Tracing: primes.rb:6 @odd_primes = [] +Tracing: primes.rb:10 candidate = 2 +Tracing: primes.rb:11 yield candidate +Tracing: primes.rb:32 puts prime +2 +Tracing: primes.rb:33 break if prime > 10 +Tracing: primes.rb:12 not_prime = false + +[7, 16] in /path/to/primes.rb + 7: end + 8: + 9: def next_prime + 10: candidate = 2 + 11: yield candidate +=> 12: not_prime = false + 13: candidate += 1 + 14: + 15: loop do + 16: @odd_primes.each do |p| + 17: not_prime = (0 == (candidate % p)) +(byebug) +``` + +The loop between lines 31-34 gets interleaved between those of +`SievePrime#next_prime`, lines 9-28 above. + +#### No Parameter Values in a Call Stack + +In traditional debuggers, in a call stack you can generally see the names of the +parameters and the values that were passed in. + +Ruby is a very dynamic language and it tries to be efficient within the confines +of the language definition. Values generally aren't taken out of a variable or +expression and pushed onto a stack. Instead a new scope is created and the +parameters are given initial values. Parameter passing is by _reference_ not by +_value_ as it is say Algol, C, or Perl. During the execution of a method, +parameter values can change (and often do). In fact even the _class_ of the +object can change. + +So at present, the name of the parameter is shown. The call-style setting +([callstyle]()) can be used to set whether the name is shown or the name and the +_current_ class of the object. + +#### Lines You Can Stop At + +Consider the following little Ruby program. + +```ruby +"Yes it does" =~ / +(Yes) \s+ +it \s+ +does +/ix +puts $1 +``` + +The stopping points that Ruby records are the last two lines, lines 5 and 6. + +Inside `byebug` you can get a list of stoppable lines for a file using the `info +file` command. + +### Threading support + +Byebug supports debugging Ruby programs making use of multiple threads. + +Let's consider the following sample program: + +```ruby +class Company + def initialize(task) + @tasks, @results = Queue.new, Queue.new + + @tasks.push(task) + end + + def run + manager = Thread.new { manager_routine } + employee = Thread.new { employee_routine } + + sleep 6 + + go_home(manager) + go_home(employee) + end + + # + # An employee doing his thing + # + def employee_routine + loop do + if @tasks.empty? + have_a_break(0.1) + else + work_hard(@tasks.pop) + end + end + end + + # + # A manager doing his thing + # + def manager_routine + loop do + if @results.empty? + have_a_break(1) + else + show_off(@results.pop) + end + end + end + + private + + def show_off(result) + puts result + end + + def work_hard(task) + task ** task + end + + def have_a_break(amount) + sleep amount + end + + def go_home(person) + person.kill + end +end + +Company.new(10).run +``` + +The `Company` class simulates a real company. The company has a manager and an +employee represented by 2 threads: they work concurrently to achieve the +company's targets. + +* The employee looks for tasks to complete. If there are tasks, it works hard to + complete them. Otherwise he has a quick break. + +```ruby +# +# An employee doing his thing +# +def employee_routine + loop do + if @tasks.empty? + have_a_break(0.1) + else + work_hard(@tasks.pop) + end + end +end +``` + +* The manager, on the other hand, sits there all day and sporadically checks + whether there are any results to show off. + +```ruby +# +# A manager doing his thing +# +def manager_routine + loop do + if @results.empty? + have_a_break(1) + else + show_off(@results.pop) + end + end +end +``` + +We do some abstractions easily readable in the code. Our tasks are just a +`Queue` of numbers, so are our results. What our employer does when he works is +some calculation with those numbers and what the manager does with the results +is printing them to the screen. + +We instantiate a new company with an initial task and after running that +company we expect the result to be printed in the screen, but it is not. Lets +debug our sample program: + +```bash +[1, 10] in /path/to/company.rb +=> 1: class Company + 2: def initialize(task) + 3: @tasks, @results = Queue.new, Queue.new + 4: + 5: @tasks.push(task) + 6: end + 7: + 8: def run + 9: manager = Thread.new { manager_routine } + 10: employee = Thread.new { employee_routine } +(byebug) l + +[11, 20] in /path/to/company.rb + 11: + 12: sleep 6 + 13: + 14: go_home(manager) + 15: go_home(employee) + 16: end + 17: + 18: # + 19: # An employee doing his thing + 20: # + +(byebug) c 12 +Stopped by breakpoint 1 at /path/to/company.rb:12 + +[7, 16] in /path/to/company.rb + 7: + 8: def run + 9: manager = Thread.new { manager_routine } + 10: employee = Thread.new { employee_routine } + 11: +=> 12: sleep 6 + 13: + 14: go_home(manager) + 15: go_home(employee) + 16: end +(byebug) th l ++ 1 # /path/to/company.rb:12 + 2 # + 3 # +``` + +What we have done here is just start our program and advance to the point +inmediately after our `employee` and `manager` threads have been created. We +can then check that the threads are there using the `thread list` command. Now +we want to debug both of this threads to check what's happening and look for the +bug. + +```bash +(byebug) th switch 3 + +[5, 14] in /path/to/company.rb + 5: @tasks.push(task) + 6: end + 7: + 8: def run + 9: manager = Thread.new { manager_routine } +=> 10: employee = Thread.new { employee_routine } + 11: + 12: sleep 6 + 13: + 14: go_home(manager) +(byebug) th stop 1; th stop 2 +$ 1 # /path/to/company.rb:12 +$ 2 # /path/to/company.rb:9 +(byebug) th l +$ 1 # /path/to/company.rb:12 +$ 2 # /path/to/company.rb:55 ++ 3 # /path/to/company.rb:10 +``` + +We have started by debugging the `employee` thread. To do that, we switch to +that thread using the `thread switch 3` command. The thread number is the one +specified by `thread list`, we know this is our worker thread because `thread +list` specifies where the thread is defined in the file (and its current +position if the thread is currently running). + +After that we stopped the main thread and the worker thread, using the command +`thread stop`. We do this because we want to focus on the employee thread first +and don't want the program to finish while we are debugging. Notice that stopped +threads are marked with the "$" symbol whereas the current thread is marked with +the "+" symbol. + +```bash +(byebug) s + +[17, 26] in /path/to/company.rb + 17: + 18: # + 19: # An employee doing his thing + 20: # + 21: def employee_routine +=> 22: loop do + 23: if @tasks.empty? + 24: have_a_break(0.1) + 25: else + 26: work_hard(@tasks.pop) +(byebug) s + +[18, 27] in /path/to/company.rb + 18: # + 19: # An employee doing his thing + 20: # + 21: def employee_routine + 22: loop do +=> 23: if @tasks.empty? + 24: have_a_break(0.1) + 25: else + 26: work_hard(@tasks.pop) + 27: end +(byebug) n + +[21, 30] in /path/to/company.rb + 21: def employee_routine + 22: loop do + 23: if @tasks.empty? + 24: have_a_break(0.1) + 25: else +=> 26: work_hard(@tasks.pop) + 27: end + 28: end + 29: end + 30: +(byebug) s + +[49, 58] in /path/to/company.rb + 49: def show_off(result) + 50: puts result + 51: end + 52: + 53: def work_hard(task) +=> 54: task ** task + 55: end + 56: + 57: def have_a_break(amount) + 58: sleep amount +(byebug) s + +[21, 30] in /path/to/company.rb + 21: # + 22: # An employee doing his thing + 23: # + 24: def employee_routine + 25: loop do +=> 26: if @tasks.empty? + 27: have_a_break(0.1) + 28: else + 29: work_hard(@tasks.pop) + 30: end +(byebug) n + +[22, 31] in /path/to/company.rb + 22: # An employee doing his thing + 23: # + 24: def employee_routine + 25: loop do + 26: if @tasks.empty? +=> 27: have_a_break(0.1) + 28: else + 29: work_hard(@tasks.pop) + 30: end + 31: end +(byebug) n + +[21, 30] in /path/to/company.rb + 21: # + 22: # An employee doing his thing + 23: # + 24: def employee_routine + 25: loop do +=> 26: if @tasks.empty? + 27: have_a_break(0.1) + 28: else + 29: work_hard(@tasks.pop) + 30: end + 31: end +(byebug) +``` + +Everything seems fine in this thread. The first iteration the employee will do +his job, and after that it will just check for new tasks and sleep. Let's debug +the manager task now: + +```bash +(byebug) th resume 2 + 2 # /path/to/company.rb:12 +(byebug) th switch 2 + 2 # /path/to/company.rb:12 + +[7, 16] in /path/to/company.rb + 7: + 8: # + 9: # A CEO running his company + 10: # + 11: def run +=> 12: manager = Thread.new { manager_routine } + 13: employee = Thread.new { employee_routine } + 14: + 15: sleep 6 + 16: +(byebug) +``` + +We used the command `thread resume` to restart the manager's thread and then +switch to it using `thread switch`. It's important to resume the thread's +execution before switching to it, otherwise we'll get a hang because we cannot +run a sleeping thread. + +Now we can investigate the problem in the employer's side: + +```bash +(byebug) s +[30, 39] in /path/to/company.rb + 30: + 31: # + 32: # A manager doing his thing + 33: # + 34: def manager_routine +=> 35: loop do + 36: if @results.empty? + 37: have_a_break(1) + 38: else + 39: show_off(@results.pop) +(byebug) s + +[31, 40] in /path/to/company.rb + 31: # + 32: # A manager doing his thing + 33: # + 34: def manager_routine + 35: loop do +=> 36: if @results.empty? + 37: have_a_break(1) + 38: else + 39: show_off(@results.pop) + 40: end +(byebug) n + +[32, 41] in /path/to/company.rb + 32: # A manager doing his thing + 33: # + 34: def manager_routine + 35: loop do + 36: if @results.empty? +=> 37: have_a_break(1) + 38: else + 39: show_off(@results.pop) + 40: end + 41: end +(byebug) n + +[31, 40] in /path/to/company.rb + 31: # + 32: # A manager doing his thing + 33: # + 34: def manager_routine + 35: loop do +=> 36: if @results.empty? + 37: have_a_break(1) + 38: else + 39: show_off(@results.pop) + 40: end +(byebug) +``` + +Now we can see the problem, the `@results` variable is always empty! The +employee forgot to leave the results in his manager's deck. We fix it by +changing the line + +```ruby +work_hard(@tasks.pop) +``` + +in the `employee_routine` method with the line + +```ruby +@results << work_hard(@tasks.pop) +``` + +To be continued... + +* More complex examples with objects, pretty printing and irb. +* Line tracing and non-interactive tracing. +* Post-mortem debugging. + +## Getting in & out + +### Starting byebug + +There is a wrapper script called `byebug` which basically `require`'s the gem +then loads `byebug` before its argument (the program to be debugged) is started. +If you don't need to pass dash options to your program, which might be confused +with byebug options, then you don't need to add the `--`. To get a brief list of +options and descriptions, use the `--help` option. + +```bash +$ byebug --help + + byebug 3.5.1 + + Usage: byebug [options] -- + + -d, --debug Set $DEBUG=true + -I, --include list Add to paths to $LOAD_PATH + -m, --[no-]post-mortem Use post-mortem mode + -q, --[no-]quit Quit when script finishes + -x, --[no-]rc Run byebug initialization file + -s, --[no-]stop Stop when script is loaded + -r, --require file Require library before script + -R, --remote [host:]port Remote debug [host:]port + -t, --[no-]trace Turn on line tracing + -v, --version Print program version + -h, --help Display this message + +``` + +Many options appear as a long option name, such as `--help` and a short one +letter option name, such as `-h`. The list of options is detailed below: + +#### -h | --help + +It causes `byebug` to print some basic help and exit. + +#### -v | --version + +It causes `byebug` to print its version number and exit. + +#### -d | --debug + +Sets `$DEBUG` to `true`. Compatible with Ruby's flag. + +#### -I | --include path + +Adds `path` to load path. `path` can be a single path or a colon separated path +list. + +#### -m | --post-mortem + +If your program raises an exception that isn't caught you can enter byebug for +inspection of what went wrong. You may also want to use this option in +conjunction with `--no-stop`. See also [Post-Mortem Debugging](). + +#### --no-quit + +Keep inside `byebug` after your program terminates normally. + +#### --no-stop + +Normally `byebug` stops before executing the first statement. If instead you +want it to start running initially and perhaps break it later in the execution, +use this option. + +#### -r | --require lib + +Requires the library before executing the script. This option is compatible +with Ruby's. + +#### -t | --trace + +Turns on line tracing. Running `byebug --trace .rb` is pretty much +like running `ruby -rtracer .rb`. If all you want to do however is +get a line trace, `tracer` is most likely faster than `byebug`. + +```bash +$ time byebug --trace --no-stop hanoi.rb > /dev/null + +real 0m0.743s +user 0m0.668s +sys 0m0.068s +$ time ruby -rtracer hanoi.rb > /dev/null + +real 0m0.077s +user 0m0.072s +sys 0m0.004s +``` + +### Byebug default options + +Byebug has many command-line options,; it seems that some people want to set +them differently from the defaults. For example, some people may want +`--no-quit` to be the default behavior. One could write a wrapper script or set +a shell alias to handle this. + +### Command Files + +A command file is a file of lines that are `byebug` commands. Comments (lines +starting with `#`) may also be included. An empty line in a command file does +nothing; it does not mean to repeat the last command, as it would from the +terminal. + +When you start `byebug`, it automatically executes commands from its +_init file_, called `.byebugrc`. During startup, `byebug` does the following: + +* __Processes command line options and operands.__ Reads the init file in your + current directory, if any, and then checks your home directory. The home + directory is the directory named in the `$HOME` or `$HOMEPATH` environment + variable. Thus, you can have more than one init file, one generic in your home + directory, and another, specific to the program you are debugging, in the + directory where you invoke `byebug`. + +You can also request the execution of a command file with the `source` command +(see [Source]()). + +### Quitting byebug + +To exit `byebug`, use the `quit` command (abbreviated to `q`). Normally, if you +are in an interactive session, this command will prompt to ask if you really +want to quit. If you want to quit without being prompted, enter `quit +unconditionally` (abbreviated to `q!`). + +Another way to terminate byebug is to use the `kill` command. This does the +more forceful `kill -9`. It can be used in cases where `quit` doesn't work (I +haven't seen those yet). + +### Calling byebug from inside your program + +Running a program from byebug adds a bit of overhead and slows it down a little. +Furthermore, by necessity, debuggers change the operation of the program they +are debugging. And this can lead to unexpected and unwanted differences. It has +happened so often that the term +[Heisenbugs](https://en.wikipedia.org/wiki/Heisenbug) was coined to describe the +situation where using a debugger (among other possibilities) changes the +behavior of the program so that the bug doesn't manifest itself anymore. + +There is another way to get into byebug which adds no overhead or slowdown until +you reach the point at which you want to start debugging. However here you must +change the script and make an explicit call to byebug. Because byebug isn't +involved before the first call, there is no overhead and the script will run +at the same speed as if there were no byebug. + +To enter byebug this way, just drop `byebug` in whichever line you want to start +debugging at. You also have to require byebug somehow. If using bundler, it will +take care of that for you, otherwise you can use the ruby `-r` flag or add +`require "byebug"` in the line previous to the `byebug` call. + +If speed is crucial, you may want to start and stop this around certain sections +of code, using `Byebug.start` and `Byebug.stop`. Alternatively, instead of +issuing an explicit `Byebug.stop` you can add a block to the `Byebug.start` and +debugging is turned on for that block. If the block of code raises an uncaught +exception that would cause the block to terminate, the `stop` will occur. See +[Byebug.start with a block](). + +When `byebug`is run, `.byebugrc` is read. + +You may want to enter byebug at several points in the program where there is a +problem you want to investigate. And since `byebug` is just a method call it's +possible to enclose it in a conditional expression, for example + +```ruby +byebug if "bar" == foo and 20 == iter_count +``` + +### Restarting Byebug + +You can restart the program using `restart [program args]`. This is a re-exec - +all byebug state is lost. If command arguments are passed, those are used. +Otherwise program arguments from the last invocation are used. + +You won't be able to restart your program in all cases. First, the program +should have been invoked at the outset rather than having been called from +inside your program or invoked as a result of post-mortem handling. + +Also, since this relies on the OS `exec` call, this command is available only if +your OS supports `exec`. + +## Debugging remote programs + +It is possible to set up debugging so that you can issue byebug commands from +outside the process running the Ruby code. In fact, you might even be on a +different computer than the one running the Ruby program. + +To setup remote debugging, drop the following somewhere before the point in the +program that you want to debug (In Rails, the +`config/environments/development.rb` could be a good candidate). + +```ruby + require "byebug/core" + Byebug.wait_connection = true + Byebug.start_server("localhost", ) +``` + +Once this piece gets executed, you can connect to the remote debugger from your +local machine, by running: `byebug -R localhost:`. + +Next, at a place of program execution which gets run just before the code you +want to debug, add a call to `byebug` as was done without remote execution: + +```ruby + # work, work, work... + byebug + some ruby code # byebug will stop before this line is run +``` + +## Byebug Command Reference + +### Command Syntax + +Usually a command is put on a single line. There is no limit on how long it can +be. It starts with a command name, which is followed by arguments whose meaning +depends on the command name. For example, the command `step` accepts an +argument which is the number of times to step, as in `step 5`. You can also use +the `step` command with no arguments. Some commands do not allow any arguments. + +Multiple commands can be put on a line by separating each with a semicolon `;`. +You can disable the meaning of a semicolon to separate commands by escaping it +with a backslash. + +For example, you might want to enter the following code to compute the 5th +Fibonacci number. + +```bash +(byebug) fib1=0; fib2=1; 5.times {|temp| temp=fib1; fib1=fib2; fib2 += temp } +0 +1 +SyntaxError Exception: /home/davidr/Proyectos/sample_app/trace.rb:1: syntax +error, unexpected end-of-input, expecting '}' + 5.times { |temp| temp=fib1 + ^ +nil +1 +SyntaxError Exception: /home/davidr/Proyectos/sample_app/trace.rb:1: syntax +error, unexpected tSTRING_DEND, expecting end-of-input + fib2 += temp } + ^ +nil +(byebug) fib1=0\; fib2=1\; 5.times {|temp| temp=fib1\; fib1=fib2\; fib2 += temp } +5 +(byebug) fib2 +8 +``` + +You might also consider using the [irb]() or [pry]() commands and then you +won't have to escape semicolons. + +A blank line as input (typing just ``) means to repeat the previous +command. + +Byebug uses readline, which handles line editing and retrieval of previous +commands. Up arrow, for example, moves to the previous byebug command; down +arrow moves to the next more recent command (provided you are not already at +the last command). Command history is saved in file `.byebug_history`. A limit +is put on the history size. You can see this with the `show history size` +command. See [history]() for history parameters. + +### Command Output + +In the command-line interface, when `byebug` is waiting for input it presents a +prompt of the form `(byebug)`. If the program has terminated normally the prompt +will be `(byebug:ctrl)` and in post-mortem debugging it will be +`(byebug:post-mortem)`. + +Whenever `byebug` gives an error message such as for an invalid command or an +invalid location position, it will generally preface the message with `***`. + +### Command Help + +Once inside `byebug` you can always ask it for information on its commands using +the `help` command. You can use `help` (abbreviated `h`) with no arguments to +display a short list of named classes of commands + +```bash +(byebug) help + + break -- Sets breakpoints in the source code + catch -- Handles exception catchpoints + condition -- Sets conditions on breakpoints + continue -- Runs until program ends, hits a breakpoint or reaches a line + delete -- Deletes breakpoints + disable -- Disables breakpoints or displays + display -- Evaluates expressions every time the debugger stops + down -- Moves to a lower frame in the stack trace + edit -- Edits source files + enable -- Enables breakpoints or displays + finish -- Runs the program until frame returns + frame -- Moves to a frame in the call stack + help -- Helps you using byebug + history -- Shows byebug's history of commands + info -- Shows several informations about the program being debugged + interrupt -- Interrupts the program + irb -- Starts an IRB session + kill -- Sends a signal to the current process + list -- Lists lines of source code + method -- Shows methods of an object, class or module + next -- Runs one or more lines of code + pry -- Starts a Pry session + quit -- Exits byebug + restart -- Restarts the debugged program + save -- Saves current byebug session to a file + set -- Modifies byebug settings + show -- Shows byebug settings + skip -- Runs until the next breakpoint as long as it is different from the current one + source -- Restores a previously saved byebug session + step -- Steps into blocks or methods one or more times + thread -- Commands to manipulate threads + tracevar -- Enables tracing of a global variable + undisplay -- Stops displaying all or some expressions when program stops + untracevar -- Stops tracing a global variable + up -- Moves to a higher frame in the stack trace + var -- Shows variables and its values + where -- Displays the backtrace + +``` + +With a command name, `help` displays information on how to use the command. + +```bash +(byebug) help list + + l[ist][[-=]][ nn-mm] + + Lists lines of source code + + Lists lines forward from current line or from the place where code was + last listed. If "list-" is specified, lists backwards instead. If + "list=" is specified, lists from current line regardless of where code + was last listed. A line range can also be specified to list specific + sections of code. +(byebug) +``` + +A number of commands, namely `info`, `set`, `show`, `enable` and `disable`, have +many sub-parameters or _subcommands_. When you ask for help for one of these +commands, you will get help for all of the subcommands that command offers. +Sometimes you may want help only on a subcommand and to do this just follow the +command with its subcommand name. For example, `help info breakpoints`will just +give help about the `info breakpoints` command. Furthermore it will give longer +help than the summary information that appears when you ask for help. You don't +need to list the full subcommand name, just enough of the letters to make that +subcommand distinct from others will do. For example, `help info b` is the same +as `help info breakpoints`. + +Some examples follow. + +```bash +(byebug) help info +info[ subcommand] + +Generic command for showing things about the program being debugged. + +-- +List of "info" subcommands: +-- +info args -- Argument variables of current stack frame +info breakpoints -- Status of user-settable breakpoints +info catch -- Exceptions that can be caught in the current stack frame +info display -- Expressions to display when program stops +info file -- Info about a particular file read in +info files -- File names and timestamps of files read in +info line -- Line number and filename of current position in source file +info program -- Execution status of the program +``` + +```bash +(byebug) help info breakpoints +Status of user-settable breakpoints. +Without argument, list info about all breakpoints. +With an integer argument, list info on that breakpoint. +``` + +```bash +(byebug) help info b +Status of user-settable breakpoints. +Without argument, list info about all breakpoints. +With an integer argument, list info on that breakpoint. +``` + +### Control Commands: quit, restart, source + +#### Quit + +To exit `byebug`, type `quit` (abbreviated to `q`). Normally, if you are in an +interactive session, this command will prompt you to confirm you really want to +quit. If you want to quit without being prompted, enter `quit unconditionally` +(abbreviated to `q!`). + +#### Restart + +To restart the program, use the `restart|r` command. This is a re-exec - all +`byebug` state is lost. If command arguments are passed, those are used. +Otherwise program arguments from the last invocation are used. + +You won't be able to restart your program in all cases. First, the program +should have been invoked at the outset rather than having been called from +inside your program or invoked as a result of post-mortem handling. + +#### Source + +You can run `byebug` commands inside a file, using the command `source `. +The lines in a command file are executed sequentially. They are not printed as +they are executed. If there is an error, execution proceeds to the next command +in the file. For information about command files that get run automatically on +startup see [Command Files](). + +### Display Commands: display, undisplay + +#### Display + +If you find that you want to print the value of an expression frequently (to see +how it changes), you might want to add it to the *automatic display list** so +that `byebug` evaluates it each time your program stops or after a line is +printed if line tracing is enabled. Each expression added to the list is given a +number to identify it; to remove an expression from the list, you specify that +number. The automatic display looks like this: + +```bash +(byebug) display n +1: n = 3 +``` + +This display shows item numbers, expressions and their current values. If the +expression is undefined or illegal the expression will be printed but no value +will appear. + +```bash +(byebug) display undefined_variable +2: undefined_variable = +(byebug) display 1/0 +3: 1/0 = +``` + +If you use `display` with no argument, `byebug` will display the current values +of the expressions in the list, just as it is done when your program stops. +Using `info display` has the same effect. + +#### Undisplay + +To remove an item from the list, use `undisplay` followed by the number +identifying the expression you want to remove. `undisplay` does not repeat if +you press ``after using it (otherwise you would just get the error _No +display number n_) + +You can also temporarily disable or enable display expressions, so that the will +not be printed but they won't be forgotten either, so you can toggle them again +later. To do that, use `disable display` or `enable display` followed by the +expression number. + +### Evaluation of expressions: irb, pry + +To examine and change data in your script you can just evaluate any Ruby code +from `byebug`'s prompt. Any input that is not recognized as a command will be +evaluated, so `byebug` essentially works as a REPL. If you want to evaluate +something that conflicts with a `byebug` command, just use Ruby's `eval`. For +example, if you want to print a variable called `n`, type `eval n` because +typing just `n` will execute `byebug`'s command `next`. + +Finally, if you need more advanced functionality from REPL's, you can enter +`irb` or `pry` using `irb` or `pry` commands. The binding's environment will be +set to the current state in the program. When you leave the repl and go back to +`byebug`'s command prompt we show the file, line and text position of the +program. If you issue a `list` without location information, the default +location used is the current line rather than the current position that may have +got updated via a prior `list` command. + +``` +$ byebug triangle.rb +[1, 10] in /path/to/triangle.rb + 1: # Compute the n'th triangle number, the hard way: triangle(n) == (n*(n+1))/2 +=> 2: def triangle(n) + 3: tri = 0 + 4: 0.upto(n) do |i| + 5: tri += i + 6: end + 7: tri + 8: end + 9: + 10: if __FILE__ == $0 +(byebug) irb +irb(main):001:0> (0..6).inject { |sum, i| sum += i } + => 21 +irb(main):002:0> exit +(byebug) +``` + +### Printing variables: var + +Byebug can print many different information about variables. Such as + +* `var const `. Show the constants of ``. This is basically + listing variables and their values in `.constant`. +* `var instance `. Show the instance variables of ``. This is + basically listing `.instance_variables`. +* `var instance`. Show instance_variables of `self`. +* `var local`. Show local variables. +* `var global`. Show global variables. +* `var all`. Show local, global and instance and class variables of `self`. +* `method instance `. Show methods of ``. Basically this is the + same as running `.instance_methods(false)`. +* `method `. Show methods of the class or module + ``. Basically this is the same as running + `.methods`. + +### Examining Program Source Files: list + +`byebug` can print parts of your script's source. When your script stops, +`byebug` spontaneously lists the source code around the line where it stopped +that line. It does that when you change the current stack frame as well. +Implicitly there is a default line location. Each time a list command is run +that implicit location is updated, so that running several list commands in +succession shows a contiguous block of program text. + +If you don't need code context displayed every time, you can issue the `set +noautolist` command. Now whenever you want code listed, you can explicitly issue +the `list` command or its abbreviation `l`. Notice that when a second listing is +displayed, we continue listing from the place we last left off. When the +beginning or end of the file is reached, the line range to be shown is adjusted +so "it doesn't overflow". You can set the `noautolist` option by default by +dropping `set noautolist` in byebug's startup file `.byebugrc`. + +If you want to set how many lines to be printed by default rather than use the +initial number of lines, 10, use the `set listsize` command ([listsize()). To +see the entire program in one shot, give an explicit starting and ending line +number. You can print other portions of source files by giving explicit position +as a parameter to the list command. + +There are several ways to specify what part of the file you want to print. `list +nnn` prints lines centered around line number `nnn` in the current source file. +`l` prints more lines, following the last lines printed. `list -` prints lines +just before the lines last printed. `list nnn-mmm` prints lines between `nnn` +and `mmm` inclusive. `list =` prints lines centered around where the script is +stopped. Repeating a `list` command with `RET` discards the argument, so it is +equivalent to typing just `list`. This is more useful than listing the same +lines again. An exception is made for an argument of `-`: that argument is +preserved in repetition so that each repetition moves up in the source file. + +### Editing Source files: edit + +To edit a source file, use the `edit` command. The editor of your choice is invoked +with the current line set to the active line in the program. Alternatively, you can +give a line specification to specify what part of the file you want to edit. + +You can customize `byebug` to use any editor you want by using the `EDITOR` +environment variable. The only restriction is that your editor (say `ex`) recognizes +the following command-line syntax: + +``` +ex +nnn file +``` + +The optional numeric value `+nnn` specifies the line number in the file where +you want to start editing. For example, to configure `byebug` to use the `vi` editor, +you could use these commands with the `sh` shell: + +```bash +EDITOR=/usr/bin/vi +export EDITOR +byebug ... +``` + +or in the `csh` shell, + +```bash +setenv EDITOR /usr/bin/vi +byebug ... +``` + +### The stack trace + +When your script has stopped, one thing you'll probably want to know is where +it stopped and some idea of how it got there. + +Each time your script calls a method or enters a block, information about this +action is saved. This information is what we call a _stack frame_ or just a +_frame_. The set of all frames at a certain point in the program's execution is +called the _stack trace_ or just the _stack_. Each frame contains a line number +and the source-file name that the line refers to. If the frame is the beginning +of a method it also contains the method name. + +When your script is started, the stack has only one frame, that of the `main` +method. This is called the _initial frame_ or the _outermost frame_. Each time +a method is called, a new frame is added to the stack trace. Each time a method +returns, the frame for that method invocation is removed. If a method is +recursive, there can be many frames for the same method. The frame for the +method in which execution is actually occurring is called the _innermost +frame_. This is the most recently created of all the stack frames that still +exist. + +Every time the debugger stops, one entry in the stack is selected as the +current frame. Many byebug commands refer implicitly to the selected block. In +particular, whenever you ask Byebug to list lines without giving a line number +or location the value is found in the selected frame. There are special +commands to select whichever frame you're interested in, such as `up`, `down` +and `frame`. + +After switching frames, when you issue a `list` command without any position +information, the position used is the location in the frame that you just +switched between, rather than a location that got updated via a prior `list` +command. + +Byebug assigns numbers to all existing stack frames, starting with zero for the +_innermost frame_, one for the frame that called it, and so on upward. These +numbers do not really exist in your script, they are assigned by Byebug to give +you a way of designating stack frames in commands. + +### Printing the Stack: `where` command + +The command `where`, aliased to `bt` or `backtrace` prints the call stack., It +shows one line per frame, for many frames, starting with the place that you are +stopped at (frame zero), followed by its caller (frame one), and on up the +stack. Each frame is numbered and can be referred to in the `frame` command. +The position of the current frame is marked with `-->`. + +The are some special frames generated for methods that are implemented in C. +One such method is `each`. They are marked differently in the call stack to +indicate that we cannot switch to those frames. This is because they have no +source code in Ruby, so we can not debug them using Byebug. + +```bash +(byebug) where +--> #0 Object.gcd(a#Fixnum, b#Fixnum) at line gcd.rb:6 + #1 at line gcd.rb:19 +``` + +### Selecting a frame: `up`, `down` and `frame` commands + +* `up `: Move `n` frames up the stack, towards the outermost frame (higher + frame numbers, frames that have existed longer). `n` defaults to one. + +* `down `: Move `n` frames down the stack, towards the _innermost frame_ + (lower frame numbers, frames that were created more recently). `n` defaults to + one. + +* `frame `: Allows you to move to an arbitrary frame. `n` is the stack frame + number or 0 if no frame number is given. `frame 0` will show the current and + most recent stack frame. If a negative number is given, counting is from the + other end of the stack frame, so `frame -1` shows the least-recent, outermost + stack frame. Without an argument, `frame` prints the current stack frame. diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/LICENSE b/path/ruby/2.6.0/gems/byebug-11.0.1/LICENSE new file mode 100644 index 00000000..94e72ac7 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2018 David Rodríguez +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/README.md b/path/ruby/2.6.0/gems/byebug-11.0.1/README.md new file mode 100644 index 00000000..4d5a977c --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/README.md @@ -0,0 +1,199 @@ +# Byebug + +[![Version][gem]][gem_url] +[![Tidelift][tid]][tid_url] +[![Coverage][cov]][cov_url] +[![Gitter][irc]][irc_url] + +[gem]: https://img.shields.io/gem/v/byebug.svg +[tid]: https://tidelift.com/badges/github/deivid-rodriguez/byebug +[cov]: https://api.codeclimate.com/v1/badges/f1a1bec582752c22da80/test_coverage +[irc]: https://img.shields.io/badge/IRC%20(gitter)-devs%20%26%20users-brightgreen.svg + +[gem_url]: https://rubygems.org/gems/byebug +[tid_url]: https://tidelift.com/subscription/pkg/rubygems-byebug?utm_source=rubygems-byebug&utm_medium=readme_badge +[cov_url]: https://codeclimate.com/github/deivid-rodriguez/byebug/test_coverage +[irc_url]: https://gitter.im/deivid-rodriguez/byebug + +Byebug is a simple to use and feature rich debugger for Ruby. It uses the +TracePoint API for execution control and the Debug Inspector API for call stack +navigation. Therefore, Byebug doesn't depend on internal core sources. Byebug is also +fast because it is developed as a C extension and reliable because it is supported +by a full test suite. + +The debugger permits the ability to understand what is going on _inside_ a Ruby program +while it executes and offers many of the traditional debugging features such as: + +* Stepping: Running your program one line at a time. +* Breaking: Pausing the program at some event or specified instruction, to + examine the current state. +* Evaluating: Basic REPL functionality, although [pry] does a better job at + that. +* Tracking: Keeping track of the different values of your variables or the + different lines executed by your program. + +## Build Status + +Linux [![Cir][cir]][cir_url] +Windows [![Vey][vey]][vey_url] + +[cir]: https://circleci.com/gh/deivid-rodriguez/byebug/tree/master.svg?style=svg +[tra]: https://api.travis-ci.org/deivid-rodriguez/byebug.svg?branch=master +[vey]: https://ci.appveyor.com/api/projects/status/github/deivid-rodriguez/byebug?svg=true + +[cir_url]: https://circleci.com/gh/deivid-rodriguez/byebug/tree/master +[tra_url]: https://travis-ci.org/deivid-rodriguez/byebug +[vey_url]: https://ci.appveyor.com/project/deivid-rodriguez/byebug + +## Requirements + +MRI 2.3.0 or higher. + +## Install + +```shell +gem install byebug +``` + +Alternatively, if you use `bundler`: + +```shell +bundle add byebug --group "development, test" +``` + +## Usage + +### From within the Ruby code + +Simply include `byebug` wherever you want to start debugging and the execution will +stop there. For example, if you were debugging Rails, you would add `byebug` to +your code: + +```ruby +def index + byebug + @articles = Article.find_recent +end +``` + +And then start a Rails server: + +```shell +bin/rails s +``` + +Once the execution gets to your `byebug` command, you will receive a debugging prompt. + +### From the command line + +If you want to debug a Ruby script without editing it, you can invoke byebug from the command line. + +```shell +byebug myscript.rb +``` + +## Byebug's commands + +Command | Aliases | Subcommands +------- | ------- | ----------- +`backtrace` | `bt` `w` `where`| +`break` | `b` | +`catch` | `cat` | +`condition` | `cond` | +`continue` | `c` `cont` | +`debug` | | +`delete` | `del` | +`disable` | `dis` | `breakpoints` `display` +`display` | `disp` | +`down` | | +`edit` | `ed` | +`enable` | `en` | `breakpoints` `display` +`finish` | `fin` | +`frame` | `f` | +`help` | `h` | +`history` | `hist` | +`info` | `i` | `args` `breakpoints` `catch` `display` `file` `line` `program` +`interrupt` | `int` | +`irb` | | +`kill` | | +`list` | `l` | +`method` | `m` | `instance` +`next` | `n` | +`pry` | | +`quit` | `q` | +`restart` | | +`save` | `sa` | +`set` | | `autoirb` `autolist` `autopry` `autosave` `basename` `callstyle` `fullpath` `histfile` `histsize` `linetrace` `listsize` `post_mortem` `savefile` `stack_on_error` `width` +`show` | | `autoirb` `autolist` `autopry` `autosave` `basename` `callstyle` `fullpath` `histfile` `histsize` `linetrace` `listsize` `post_mortem` `savefile` `stack_on_error` `width` +`skip` | `sk` | +`source` | `so` | +`step` | `s` | +`thread` | `th` | `current` `list` `resume` `stop` `switch` +`tracevar` | `tr` | +`undisplay` | `undisp` | +`untracevar`| `untr` | +`up` | | +`var` | `v` | `all` `constant` `global` `instance` `local` + +## Semantic Versioning + +Byebug attempts to follow [semantic versioning](https://semver.org) and +bump major version only when backwards incompatible changes are released. +Backwards compatibility is targeted to [pry-byebug] and any other plugins +relying on `byebug`. + +## Getting Started + +Read [byebug's markdown +guide](https://github.com/deivid-rodriguez/byebug/blob/master/GUIDE.md) to get +started. Proper documentation will be eventually written. + +## Related projects + +* [pry-byebug] adds `next`, `step`, `finish`, `continue` and `break` commands + to `pry` using `byebug`. +* [ruby-debug-passenger] adds a rake task that restarts Passenger with Byebug + connected. +* [minitest-byebug] starts a byebug session on minitest failures. +* [sublime_debugger] provides a plugin for ruby debugging on Sublime Text. +* [atom-byebug] provides integration with the Atom editor [EXPERIMENTAL]. + +## Contribute + +See [Getting Started with Development](CONTRIBUTING.md). + +## Funding + +Subscribe to [Tidelift] to ensure byebug stays actively maintained, and at the +same time get licensing assurances and timely security notifications for your +open source dependencies. + +You can also help `byebug` by leaving a small (or big) tip through [Liberapay]. + +## Security contact information + +Please use the Tidelift security contact to [report a security vulnerability]. +Tidelift will coordinate the fix and disclosure. + +## Credits + +Everybody who has ever contributed to this forked and reforked piece of +software, especially: + +* @ko1, author of the awesome TracePoint API for Ruby. +* @cldwalker, [debugger]'s maintainer. +* @denofevil, author of [debase], the starting point of this. +* @kevjames3 for testing, bug reports and the interest in the project. +* @FooBarWidget for working and helping with remote debugging. + +[debugger]: https://github.com/cldwalker/debugger +[pry]: https://github.com/pry/pry +[debase]: https://github.com/denofevil/debase +[pry-byebug]: https://github.com/deivid-rodriguez/pry-byebug +[ruby-debug-passenger]: https://github.com/davejamesmiller/ruby-debug-passenger +[minitest-byebug]: https://github.com/kaspth/minitest-byebug +[sublime_debugger]: https://github.com/shuky19/sublime_debugger +[atom-byebug]: https://github.com/izaera/atom-byebug +[Liberapay]: https://liberapay.com/byebug/donate +[Tidelift]: https://tidelift.com/subscription/pkg/rubygems-byebug?utm_source=rubygems-byebug&utm_medium=readme_text +[report a security vulnerability]: https://tidelift.com/security diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/exe/byebug b/path/ruby/2.6.0/gems/byebug-11.0.1/exe/byebug new file mode 100755 index 00000000..d71be555 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/exe/byebug @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "byebug/runner" + +Byebug::Runner.new.run diff --git a/tutor-virtual-2/vendor/.keep b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/.sitearchdir.-.byebug.time similarity index 100% rename from tutor-virtual-2/vendor/.keep rename to path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/.sitearchdir.-.byebug.time diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/Makefile b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/Makefile new file mode 100644 index 00000000..26ef16df --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/Makefile @@ -0,0 +1,266 @@ + +SHELL = /bin/sh + +# V=0 quiet, V=1 verbose. other values don't work. +V = 0 +Q1 = $(V:1=) +Q = $(Q1:0=@) +ECHO1 = $(V:1=@ :) +ECHO = $(ECHO1:0=@ echo) +NULLCMD = : + +#### Start of system configuration section. #### + +srcdir = . +topdir = /Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0 +hdrdir = $(topdir) +arch_hdrdir = /Users/sergio/.rvm/rubies/ruby-2.6.3/include/ruby-2.6.0/x86_64-darwin18 +PATH_SEPARATOR = : +VPATH = $(srcdir):$(arch_hdrdir)/ruby:$(hdrdir)/ruby +prefix = $(DESTDIR)/Users/sergio/.rvm/rubies/ruby-2.6.3 +rubysitearchprefix = $(rubylibprefix)/$(sitearch) +rubyarchprefix = $(rubylibprefix)/$(arch) +rubylibprefix = $(libdir)/$(RUBY_BASE_NAME) +exec_prefix = $(prefix) +vendorarchhdrdir = $(vendorhdrdir)/$(sitearch) +sitearchhdrdir = $(sitehdrdir)/$(sitearch) +rubyarchhdrdir = $(rubyhdrdir)/$(arch) +vendorhdrdir = $(rubyhdrdir)/vendor_ruby +sitehdrdir = $(rubyhdrdir)/site_ruby +rubyhdrdir = $(includedir)/$(RUBY_VERSION_NAME) +vendorarchdir = $(vendorlibdir)/$(sitearch) +vendorlibdir = $(vendordir)/$(ruby_version) +vendordir = $(rubylibprefix)/vendor_ruby +sitearchdir = $(DESTDIR)./.gem.20191018-47188-1jvhki +sitelibdir = $(DESTDIR)./.gem.20191018-47188-1jvhki +sitedir = $(rubylibprefix)/site_ruby +rubyarchdir = $(rubylibdir)/$(arch) +rubylibdir = $(rubylibprefix)/$(ruby_version) +sitearchincludedir = $(includedir)/$(sitearch) +archincludedir = $(includedir)/$(arch) +sitearchlibdir = $(libdir)/$(sitearch) +archlibdir = $(libdir)/$(arch) +ridir = $(datarootdir)/$(RI_BASE_NAME) +mandir = $(datarootdir)/man +localedir = $(datarootdir)/locale +libdir = $(exec_prefix)/lib +psdir = $(docdir) +pdfdir = $(docdir) +dvidir = $(docdir) +htmldir = $(docdir) +infodir = $(datarootdir)/info +docdir = $(datarootdir)/doc/$(PACKAGE) +oldincludedir = $(SDKROOT)/usr/include +includedir = $(prefix)/include +localstatedir = $(prefix)/var +sharedstatedir = $(prefix)/com +sysconfdir = $(prefix)/etc +datadir = $(datarootdir) +datarootdir = $(prefix)/share +libexecdir = $(exec_prefix)/libexec +sbindir = $(exec_prefix)/sbin +bindir = $(exec_prefix)/bin +archdir = $(rubyarchdir) + + +CC_WRAPPER = +CC = gcc +CXX = g++ +LIBRUBY = $(LIBRUBY_SO) +LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a +LIBRUBYARG_SHARED = -l$(RUBY_SO_NAME) +LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static -framework Security -framework Foundation $(MAINLIBS) +empty = +OUTFLAG = -o $(empty) +COUTFLAG = -o $(empty) +CSRCFLAG = $(empty) + +RUBY_EXTCONF_H = +cflags = $(optflags) $(debugflags) $(warnflags) +cxxflags = $(optflags) $(debugflags) $(warnflags) +optflags = -O3 +debugflags = -ggdb3 +warnflags = -Wall -Wextra -Wdeclaration-after-statement -Wdeprecated-declarations -Wdivision-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wextra-tokens +cppflags = +CCDLFLAGS = -fno-common +CFLAGS = $(CCDLFLAGS) $(cflags) -fno-common -pipe $(ARCH_FLAG) +INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir)/ruby/backward -I$(hdrdir) -I$(srcdir) +DEFS = +CPPFLAGS = -I/usr/local/opt/libyaml/include -I/usr/local/opt/libksba/include -I/usr/local/opt/readline/include -I/usr/local/opt/zlib/include -I/usr/local/opt/openssl@1.1/include -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT $(DEFS) $(cppflags) +CXXFLAGS = $(CCDLFLAGS) $(cxxflags) $(ARCH_FLAG) +ldflags = -L. -fstack-protector-strong -L/usr/local/lib -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib +dldflags = -Wl,-undefined,dynamic_lookup -Wl,-multiply_defined,suppress -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib +ARCH_FLAG = +DLDFLAGS = $(ldflags) $(dldflags) $(ARCH_FLAG) +LDSHARED = $(CC) -dynamic -bundle +LDSHAREDXX = $(CXX) -dynamic -bundle +AR = libtool -static +EXEEXT = + +RUBY_INSTALL_NAME = $(RUBY_BASE_NAME) +RUBY_SO_NAME = ruby.2.6 +RUBYW_INSTALL_NAME = +RUBY_VERSION_NAME = $(RUBY_BASE_NAME)-$(ruby_version) +RUBYW_BASE_NAME = rubyw +RUBY_BASE_NAME = ruby + +arch = x86_64-darwin18 +sitearch = $(arch) +ruby_version = 2.6.0 +ruby = $(bindir)/$(RUBY_BASE_NAME) +RUBY = $(ruby) +ruby_headers = $(hdrdir)/ruby.h $(hdrdir)/ruby/backward.h $(hdrdir)/ruby/ruby.h $(hdrdir)/ruby/defines.h $(hdrdir)/ruby/missing.h $(hdrdir)/ruby/intern.h $(hdrdir)/ruby/st.h $(hdrdir)/ruby/subst.h $(arch_hdrdir)/ruby/config.h + +RM = rm -f +RM_RF = $(RUBY) -run -e rm -- -rf +RMDIRS = rmdir -p +MAKEDIRS = /usr/local/opt/coreutils/bin/gmkdir -p +INSTALL = /usr/local/opt/coreutils/bin/ginstall -c +INSTALL_PROG = $(INSTALL) -m 0755 +INSTALL_DATA = $(INSTALL) -m 644 +COPY = cp +TOUCH = exit > + +#### End of system configuration section. #### + +preload = +libpath = . $(libdir) /usr/local/opt/libyaml/lib /usr/local/opt/libksba/lib /usr/local/opt/readline/lib /usr/local/opt/zlib/lib /usr/local/opt/openssl@1.1/lib +LIBPATH = -L. -L$(libdir) -L/usr/local/opt/libyaml/lib -L/usr/local/opt/libksba/lib -L/usr/local/opt/readline/lib -L/usr/local/opt/zlib/lib -L/usr/local/opt/openssl@1.1/lib +DEFFILE = + +CLEANFILES = mkmf.log +DISTCLEANFILES = +DISTCLEANDIRS = + +extout = +extout_prefix = +target_prefix = /byebug +LOCAL_LIBS = +LIBS = $(LIBRUBYARG_SHARED) +ORIG_SRCS = breakpoint.c byebug.c context.c locker.c threads.c +SRCS = $(ORIG_SRCS) +OBJS = breakpoint.o byebug.o context.o locker.o threads.o +HDRS = $(srcdir)/byebug.h +LOCAL_HDRS = +TARGET = byebug +TARGET_NAME = byebug +TARGET_ENTRY = Init_$(TARGET_NAME) +DLLIB = $(TARGET).bundle +EXTSTATIC = +STATIC_LIB = + +TIMESTAMP_DIR = . +BINDIR = $(bindir) +RUBYCOMMONDIR = $(sitedir)$(target_prefix) +RUBYLIBDIR = $(sitelibdir)$(target_prefix) +RUBYARCHDIR = $(sitearchdir)$(target_prefix) +HDRDIR = $(rubyhdrdir)/ruby$(target_prefix) +ARCHHDRDIR = $(rubyhdrdir)/$(arch)/ruby$(target_prefix) +TARGET_SO_DIR = +TARGET_SO = $(TARGET_SO_DIR)$(DLLIB) +CLEANLIBS = $(TARGET_SO) +CLEANOBJS = *.o *.bak + +all: $(DLLIB) +static: $(STATIC_LIB) +.PHONY: all install static install-so install-rb +.PHONY: clean clean-so clean-static clean-rb + +clean-static:: +clean-rb-default:: +clean-rb:: +clean-so:: +clean: clean-so clean-static clean-rb-default clean-rb + -$(Q)$(RM) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES) .*.time + +distclean-rb-default:: +distclean-rb:: +distclean-so:: +distclean-static:: +distclean: clean distclean-so distclean-static distclean-rb-default distclean-rb + -$(Q)$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log + -$(Q)$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES) + -$(Q)$(RMDIRS) $(DISTCLEANDIRS) 2> /dev/null || true + +realclean: distclean +install: install-so install-rb + +install-so: $(DLLIB) $(TIMESTAMP_DIR)/.sitearchdir.-.byebug.time + $(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR) +clean-static:: + -$(Q)$(RM) $(STATIC_LIB) +install-rb: pre-install-rb do-install-rb install-rb-default +install-rb-default: pre-install-rb-default do-install-rb-default +pre-install-rb: Makefile +pre-install-rb-default: Makefile +do-install-rb: +do-install-rb-default: +pre-install-rb-default: + @$(NULLCMD) +$(TIMESTAMP_DIR)/.sitearchdir.-.byebug.time: + $(Q) $(MAKEDIRS) $(@D) $(RUBYARCHDIR) + $(Q) $(TOUCH) $@ + +site-install: site-install-so site-install-rb +site-install-so: install-so +site-install-rb: install-rb + +.SUFFIXES: .c .m .cc .mm .cxx .cpp .o .S + +.cc.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cc.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.mm.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.mm.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.cxx.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cxx.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.cpp.o: + $(ECHO) compiling $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.cpp.S: + $(ECHO) translating $(<) + $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.c.o: + $(ECHO) compiling $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.c.S: + $(ECHO) translating $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +.m.o: + $(ECHO) compiling $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$< + +.m.S: + $(ECHO) translating $(<) + $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$< + +$(TARGET_SO): $(OBJS) Makefile + $(ECHO) linking shared-object byebug/$(DLLIB) + -$(Q)$(RM) $(@) + $(Q) $(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS) + $(Q) $(POSTLINK) + + + +$(OBJS): $(HDRS) $(ruby_headers) diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/breakpoint.c b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/breakpoint.c new file mode 100644 index 00000000..00472061 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/breakpoint.c @@ -0,0 +1,517 @@ +#include "byebug.h" + +#ifdef _WIN32 +#include +#endif + +#if defined DOSISH +#define isdirsep(x) ((x) == '/' || (x) == '\\') +#else +#define isdirsep(x) ((x) == '/') +#endif + +static VALUE cBreakpoint; +static int breakpoint_max; + +static ID idEval; + +static VALUE +eval_expression(VALUE args) +{ + return rb_funcall2(rb_mKernel, idEval, 2, RARRAY_PTR(args)); +} + +/* + * call-seq: + * breakpoint.enabled? -> bool + * + * Returns +true+ if breakpoint is enabled, false otherwise. + */ +static VALUE +brkpt_enabled(VALUE self) +{ + breakpoint_t *breakpoint; + + Data_Get_Struct(self, breakpoint_t, breakpoint); + return breakpoint->enabled; +} + +/* + * call-seq: + * breakpoint.enabled = true | false + * + * Enables or disables breakpoint. + */ +static VALUE +brkpt_set_enabled(VALUE self, VALUE enabled) +{ + breakpoint_t *breakpoint; + + Data_Get_Struct(self, breakpoint_t, breakpoint); + return breakpoint->enabled = enabled; +} + +/* + * call-seq: + * breakpoint.expr -> string + * + * Returns a conditional expression which indicates when this breakpoint should + * be activated. + */ +static VALUE +brkpt_expr(VALUE self) +{ + breakpoint_t *breakpoint; + + Data_Get_Struct(self, breakpoint_t, breakpoint); + return breakpoint->expr; +} + +/* + * call-seq: + * breakpoint.expr = string | nil + * + * Sets or unsets the conditional expression which indicates when this + * breakpoint should be activated. + */ +static VALUE +brkpt_set_expr(VALUE self, VALUE expr) +{ + breakpoint_t *breakpoint; + + Data_Get_Struct(self, breakpoint_t, breakpoint); + breakpoint->expr = NIL_P(expr) ? expr : StringValue(expr); + return expr; +} + +/* + * call-seq: + * breakpoint.hit_condition -> symbol + * + * Returns the hit condition of the breakpoint: +nil+ if it is an + * unconditional breakpoint, or :greater_or_equal, :equal or :modulo otherwise + */ +static VALUE +brkpt_hit_condition(VALUE self) +{ + breakpoint_t *breakpoint; + + Data_Get_Struct(self, breakpoint_t, breakpoint); + switch (breakpoint->hit_condition) + { + case HIT_COND_GE: + return ID2SYM(rb_intern("greater_or_equal")); + case HIT_COND_EQ: + return ID2SYM(rb_intern("equal")); + case HIT_COND_MOD: + return ID2SYM(rb_intern("modulo")); + case HIT_COND_NONE: + default: + return Qnil; + } +} + +/* + * call-seq: + * breakpoint.hit_condition = symbol + * + * Sets the hit condition of the breakpoint which must be one of the following + * values: + * + * +nil+ if it is an unconditional breakpoint, or + * :greater_or_equal(:ge), :equal(:eq), :modulo(:mod) + */ +static VALUE +brkpt_set_hit_condition(VALUE self, VALUE value) +{ + breakpoint_t *breakpoint; + ID id_value; + + Data_Get_Struct(self, breakpoint_t, breakpoint); + id_value = rb_to_id(value); + + if (rb_intern("greater_or_equal") == id_value || rb_intern("ge") == id_value) + breakpoint->hit_condition = HIT_COND_GE; + else if (rb_intern("equal") == id_value || rb_intern("eq") == id_value) + breakpoint->hit_condition = HIT_COND_EQ; + else if (rb_intern("modulo") == id_value || rb_intern("mod") == id_value) + breakpoint->hit_condition = HIT_COND_MOD; + else + rb_raise(rb_eArgError, "Invalid condition parameter"); + return value; +} + +/* + * call-seq: + * breakpoint.hit_count -> int + * + * Returns the number of times this breakpoint has been hit. + */ +static VALUE +brkpt_hit_count(VALUE self) +{ + breakpoint_t *breakpoint; + + Data_Get_Struct(self, breakpoint_t, breakpoint); + return INT2FIX(breakpoint->hit_count); +} + +/* + * call-seq: + * breakpoint.hit_value -> int + * + * Returns the hit value of the breakpoint, namely, a value to build a + * condition on the number of hits of the breakpoint. + */ +static VALUE +brkpt_hit_value(VALUE self) +{ + breakpoint_t *breakpoint; + + Data_Get_Struct(self, breakpoint_t, breakpoint); + return INT2FIX(breakpoint->hit_value); +} + +/* + * call-seq: + * breakpoint.hit_value = int + * + * Sets the hit value of the breakpoint. This allows the user to set conditions + * on the number of hits to enable/disable the breakpoint. + */ +static VALUE +brkpt_set_hit_value(VALUE self, VALUE value) +{ + breakpoint_t *breakpoint; + + Data_Get_Struct(self, breakpoint_t, breakpoint); + breakpoint->hit_value = FIX2INT(value); + return value; +} + +/* + * call-seq: + * breakpoint.id -> int + * + * Returns the id of the breakpoint. + */ +static VALUE +brkpt_id(VALUE self) +{ + breakpoint_t *breakpoint; + + Data_Get_Struct(self, breakpoint_t, breakpoint); + return INT2FIX(breakpoint->id); +} + +/* + * call-seq: + * breakpoint.pos -> string or int + * + * Returns the position of this breakpoint, either a method name or a line + * number. + */ +static VALUE +brkpt_pos(VALUE self) +{ + breakpoint_t *breakpoint; + + Data_Get_Struct(self, breakpoint_t, breakpoint); + if (breakpoint->type == BP_METHOD_TYPE) + return rb_str_new2(rb_id2name(breakpoint->pos.mid)); + else + return INT2FIX(breakpoint->pos.line); +} + +/* + * call-seq: + * breakpoint.source -> string + * + * Returns the source file of the breakpoint. + */ +static VALUE +brkpt_source(VALUE self) +{ + breakpoint_t *breakpoint; + + Data_Get_Struct(self, breakpoint_t, breakpoint); + return breakpoint->source; +} + +static void +mark_breakpoint(breakpoint_t *breakpoint) +{ + rb_gc_mark(breakpoint->source); + rb_gc_mark(breakpoint->expr); +} + +static VALUE +brkpt_create(VALUE klass) +{ + breakpoint_t *breakpoint = ALLOC(breakpoint_t); + + return Data_Wrap_Struct(klass, mark_breakpoint, xfree, breakpoint); +} + +static VALUE +brkpt_initialize(VALUE self, VALUE source, VALUE pos, VALUE expr) +{ + breakpoint_t *breakpoint; + + Data_Get_Struct(self, breakpoint_t, breakpoint); + + breakpoint->type = FIXNUM_P(pos) ? BP_POS_TYPE : BP_METHOD_TYPE; + if (breakpoint->type == BP_POS_TYPE) + breakpoint->pos.line = FIX2INT(pos); + else + breakpoint->pos.mid = SYM2ID(pos); + + breakpoint->id = ++breakpoint_max; + breakpoint->source = StringValue(source); + breakpoint->enabled = Qtrue; + breakpoint->expr = NIL_P(expr) ? expr : StringValue(expr); + breakpoint->hit_count = 0; + breakpoint->hit_value = 0; + breakpoint->hit_condition = HIT_COND_NONE; + + return Qnil; +} + +static int +filename_cmp_impl(VALUE source, char *file) +{ + char *source_ptr, *file_ptr; + long s_len, f_len, min_len; + long s, f; + int dirsep_flag = 0; + + s_len = RSTRING_LEN(source); + f_len = strlen(file); + min_len = s_len < f_len ? s_len : f_len; + + source_ptr = RSTRING_PTR(source); + file_ptr = file; + + for (s = s_len - 1, f = f_len - 1; + s >= s_len - min_len && f >= f_len - min_len; s--, f--) + { + if ((source_ptr[s] == '.' || file_ptr[f] == '.') && dirsep_flag) + return 1; + if (isdirsep(source_ptr[s]) && isdirsep(file_ptr[f])) + dirsep_flag = 1; +#ifdef DOSISH_DRIVE_LETTER + else if (s == 0) + return (toupper(source_ptr[s]) == toupper(file_ptr[f])); +#endif + else if (source_ptr[s] != file_ptr[f]) + return 0; + } + return 1; +} + +static int +filename_cmp(VALUE source, char *file) +{ +#ifdef _WIN32 + return filename_cmp_impl(source, file); +#else +#ifdef PATH_MAX + char path[PATH_MAX + 1]; + + path[PATH_MAX] = 0; + return filename_cmp_impl(source, realpath(file, path) != NULL ? path : file); +#else + char *path; + int result; + + path = realpath(file, NULL); + result = filename_cmp_impl(source, path == NULL ? file : path); + free(path); + return result; +#endif +#endif +} + +static int +classname_cmp(VALUE name, VALUE klass) +{ + VALUE mod_name; + VALUE class_name = NIL_P(name) ? rb_str_new2("main") : name; + + if (NIL_P(klass)) + return 0; + + mod_name = rb_mod_name(klass); + return (!NIL_P(mod_name) && rb_str_cmp(class_name, mod_name) == 0); +} + +static int +check_breakpoint_by_hit_condition(VALUE rb_breakpoint) +{ + breakpoint_t *breakpoint; + + if (NIL_P(rb_breakpoint)) + return 0; + + Data_Get_Struct(rb_breakpoint, breakpoint_t, breakpoint); + breakpoint->hit_count++; + + if (Qtrue != breakpoint->enabled) + return 0; + + switch (breakpoint->hit_condition) + { + case HIT_COND_NONE: + return 1; + case HIT_COND_GE: + { + if (breakpoint->hit_count >= breakpoint->hit_value) + return 1; + break; + } + case HIT_COND_EQ: + { + if (breakpoint->hit_count == breakpoint->hit_value) + return 1; + break; + } + case HIT_COND_MOD: + { + if (breakpoint->hit_count % breakpoint->hit_value == 0) + return 1; + break; + } + } + return 0; +} + +static int +check_breakpoint_by_pos(VALUE rb_breakpoint, char *file, int line) +{ + breakpoint_t *breakpoint; + + if (NIL_P(rb_breakpoint)) + return 0; + + Data_Get_Struct(rb_breakpoint, breakpoint_t, breakpoint); + + if (Qfalse == breakpoint->enabled || breakpoint->type != BP_POS_TYPE + || breakpoint->pos.line != line) + return 0; + + return filename_cmp(breakpoint->source, file); +} + +static int +check_breakpoint_by_method(VALUE rb_breakpoint, VALUE klass, ID mid, VALUE self) +{ + breakpoint_t *breakpoint; + + if (NIL_P(rb_breakpoint)) + return 0; + + Data_Get_Struct(rb_breakpoint, breakpoint_t, breakpoint); + + if (Qfalse == breakpoint->enabled || breakpoint->type != BP_METHOD_TYPE + || breakpoint->pos.mid != mid) + return 0; + + if (classname_cmp(breakpoint->source, klass) + || ((rb_type(self) == T_CLASS || rb_type(self) == T_MODULE) + && classname_cmp(breakpoint->source, self))) + return 1; + + return 0; +} + +static int +check_breakpoint_by_expr(VALUE rb_breakpoint, VALUE bind) +{ + breakpoint_t *breakpoint; + VALUE args, expr_result; + + if (NIL_P(rb_breakpoint)) + return 0; + + Data_Get_Struct(rb_breakpoint, breakpoint_t, breakpoint); + + if (Qfalse == breakpoint->enabled) + return 0; + + if (NIL_P(breakpoint->expr)) + return 1; + + args = rb_ary_new3(2, breakpoint->expr, bind); + expr_result = rb_protect(eval_expression, args, 0); + + return RTEST(expr_result); +} + +extern VALUE +find_breakpoint_by_pos(VALUE breakpoints, VALUE source, VALUE pos, VALUE bind) +{ + VALUE breakpoint; + char *file; + int line; + int i; + + file = RSTRING_PTR(source); + line = FIX2INT(pos); + for (i = 0; i < RARRAY_LENINT(breakpoints); i++) + { + breakpoint = rb_ary_entry(breakpoints, i); + if (check_breakpoint_by_pos(breakpoint, file, line) + && check_breakpoint_by_expr(breakpoint, bind) + && check_breakpoint_by_hit_condition(breakpoint)) + { + return breakpoint; + } + } + return Qnil; +} + +extern VALUE +find_breakpoint_by_method(VALUE breakpoints, VALUE klass, ID mid, VALUE bind, + VALUE self) +{ + VALUE breakpoint; + int i; + + for (i = 0; i < RARRAY_LENINT(breakpoints); i++) + { + breakpoint = rb_ary_entry(breakpoints, i); + if (check_breakpoint_by_method(breakpoint, klass, mid, self) + && check_breakpoint_by_expr(breakpoint, bind) + && check_breakpoint_by_hit_condition(breakpoint)) + { + return breakpoint; + } + } + return Qnil; +} + +void +Init_byebug_breakpoint(VALUE mByebug) +{ + breakpoint_max = 0; + + cBreakpoint = rb_define_class_under(mByebug, "Breakpoint", rb_cObject); + + rb_define_alloc_func(cBreakpoint, brkpt_create); + rb_define_method(cBreakpoint, "initialize", brkpt_initialize, 3); + + rb_define_method(cBreakpoint, "enabled?", brkpt_enabled, 0); + rb_define_method(cBreakpoint, "enabled=", brkpt_set_enabled, 1); + rb_define_method(cBreakpoint, "expr", brkpt_expr, 0); + rb_define_method(cBreakpoint, "expr=", brkpt_set_expr, 1); + rb_define_method(cBreakpoint, "hit_count", brkpt_hit_count, 0); + rb_define_method(cBreakpoint, "hit_condition", brkpt_hit_condition, 0); + rb_define_method(cBreakpoint, "hit_condition=", brkpt_set_hit_condition, 1); + rb_define_method(cBreakpoint, "hit_value", brkpt_hit_value, 0); + rb_define_method(cBreakpoint, "hit_value=", brkpt_set_hit_value, 1); + rb_define_method(cBreakpoint, "id", brkpt_id, 0); + rb_define_method(cBreakpoint, "pos", brkpt_pos, 0); + rb_define_method(cBreakpoint, "source", brkpt_source, 0); + + idEval = rb_intern("eval"); +} diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/breakpoint.o b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/breakpoint.o new file mode 100644 index 00000000..c4532cd2 Binary files /dev/null and b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/breakpoint.o differ diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/byebug.bundle b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/byebug.bundle new file mode 100755 index 00000000..ae31c0c5 Binary files /dev/null and b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/byebug.bundle differ diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/byebug.c b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/byebug.c new file mode 100644 index 00000000..efb7735f --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/byebug.c @@ -0,0 +1,905 @@ +#include "byebug.h" + +static VALUE mByebug; /* Ruby Byebug Module object */ + +static VALUE tracing = Qfalse; +static VALUE post_mortem = Qfalse; +static VALUE verbose = Qfalse; + +static VALUE catchpoints = Qnil; +static VALUE breakpoints = Qnil; +static VALUE tracepoints = Qnil; + +static VALUE raised_exception = Qnil; + +static ID idPuts; +static ID idEmpty; + +/* Hash table with active threads and their associated contexts */ +VALUE threads = Qnil; + +/* + * call-seq: + * Byebug.breakpoints -> array + * + * Returns an array of breakpoints. + */ +static VALUE +Breakpoints(VALUE self) +{ + UNUSED(self); + + if (NIL_P(breakpoints)) + breakpoints = rb_ary_new(); + + return breakpoints; +} + +/* + * call-seq: + * Byebug.catchpoints -> hash + * + * Returns the catchpoints hash. + */ +static VALUE +Catchpoints(VALUE self) +{ + UNUSED(self); + + return catchpoints; +} + +/* + * call-seq: + * Byebug.raised_exception -> exception + * + * Returns raised exception when in post_mortem mode. + */ +static VALUE +Raised_exception(VALUE self) +{ + UNUSED(self); + + return raised_exception; +} + +#define IS_STARTED (!NIL_P(catchpoints)) + +static void +check_started() +{ + if (!IS_STARTED) + { + rb_raise(rb_eRuntimeError, "Byebug is not started yet."); + } +} + +static void +trace_print(rb_trace_arg_t *trace_arg, debug_context_t *dc, + const char *file_filter, const char *debug_msg) +{ + char *fullpath = NULL; + const char *basename; + int filtered = 0; + const char *event = rb_id2name(SYM2ID(rb_tracearg_event(trace_arg))); + + VALUE rb_path = rb_tracearg_path(trace_arg); + const char *path = NIL_P(rb_path) ? "" : RSTRING_PTR(rb_path); + + int line = NUM2INT(rb_tracearg_lineno(trace_arg)); + + VALUE rb_mid = rb_tracearg_method_id(trace_arg); + const char *mid = NIL_P(rb_mid) ? "(top level)" : rb_id2name(SYM2ID(rb_mid)); + + VALUE rb_cl = rb_tracearg_defined_class(trace_arg); + VALUE rb_cl_name = NIL_P(rb_cl) ? rb_cl : rb_mod_name(rb_cl); + const char *defined_class = NIL_P(rb_cl_name) ? "" : RSTRING_PTR(rb_cl_name); + + if (!trace_arg) + return; + + if (file_filter) + { +#ifndef _WIN32 + fullpath = realpath(path, NULL); +#endif + basename = fullpath ? strrchr(fullpath, '/') : path; + + if (!basename || strncmp(basename + 1, file_filter, strlen(file_filter))) + filtered = 1; + +#ifndef _WIN32 + free(fullpath); +#endif + } + + if (!filtered) + { + if (debug_msg) + rb_funcall(mByebug, idPuts, 1, + rb_sprintf("[#%d] %s\n", dc->thnum, debug_msg)); + else + rb_funcall(mByebug, idPuts, 1, + rb_sprintf("%*s [#%d] %s@%s:%d %s#%s\n", dc->calced_stack_size, + "", dc->thnum, event, path, line, defined_class, + mid)); + } +} + +static void +cleanup(debug_context_t *dc) +{ + dc->stop_reason = CTX_STOP_NONE; + + release_lock(); +} + +#define EVENT_TEARDOWN cleanup(dc); + +#define EVENT_SETUP \ + debug_context_t *dc; \ + VALUE context; \ + rb_trace_arg_t *trace_arg; \ + \ + UNUSED(data); \ + \ + if (!is_living_thread(rb_thread_current())) \ + return; \ + \ + thread_context_lookup(rb_thread_current(), &context); \ + Data_Get_Struct(context, debug_context_t, dc); \ + \ + trace_arg = rb_tracearg_from_tracepoint(trace_point); \ + if (verbose == Qtrue) \ + trace_print(trace_arg, dc, 0, 0); \ + \ + if (CTX_FL_TEST(dc, CTX_FL_IGNORE)) \ + return; \ + \ + acquire_lock(dc); + + +#define CALL_EVENT_SETUP \ + dc->calced_stack_size++; \ + dc->steps_out = dc->steps_out < 0 ? -1 : dc->steps_out + 1; + +#define RETURN_EVENT_SETUP \ + dc->calced_stack_size--; \ + \ + if (dc->steps_out == 1) \ + dc->steps = 1; + +#define RETURN_EVENT_TEARDOWN \ + dc->steps_out = dc->steps_out <= 0 ? -1 : dc->steps_out - 1; + + +/* Functions that return control to byebug after the different events */ + +static VALUE +call_at(VALUE ctx, debug_context_t *dc, ID mid, int argc, VALUE arg) +{ + struct call_with_inspection_data cwi; + VALUE argv[1]; + + argv[0] = arg; + + cwi.dc = dc; + cwi.ctx = ctx; + cwi.id = mid; + cwi.argc = argc; + cwi.argv = &argv[0]; + + return call_with_debug_inspector(&cwi); +} + +static VALUE +call_at_line(VALUE ctx, debug_context_t *dc) +{ + return call_at(ctx, dc, rb_intern("at_line"), 0, Qnil); +} + +static VALUE +call_at_tracing(VALUE ctx, debug_context_t *dc) +{ + return call_at(ctx, dc, rb_intern("at_tracing"), 0, Qnil); +} + +static VALUE +call_at_breakpoint(VALUE ctx, debug_context_t *dc, VALUE breakpoint) +{ + dc->stop_reason = CTX_STOP_BREAKPOINT; + + return call_at(ctx, dc, rb_intern("at_breakpoint"), 1, breakpoint); +} + +static VALUE +call_at_catchpoint(VALUE ctx, debug_context_t *dc, VALUE exp) +{ + dc->stop_reason = CTX_STOP_CATCHPOINT; + + return call_at(ctx, dc, rb_intern("at_catchpoint"), 1, exp); +} + +static VALUE +call_at_return(VALUE ctx, debug_context_t *dc, VALUE return_value) +{ + dc->stop_reason = CTX_STOP_BREAKPOINT; + + return call_at(ctx, dc, rb_intern("at_return"), 1, return_value); +} + +static VALUE +call_at_end(VALUE ctx, debug_context_t *dc) +{ + dc->stop_reason = CTX_STOP_BREAKPOINT; + + return call_at(ctx, dc, rb_intern("at_end"), 0, Qnil); +} + +static void +call_at_line_check(VALUE ctx, debug_context_t *dc, VALUE breakpoint) +{ + dc->stop_reason = CTX_STOP_STEP; + + if (!NIL_P(breakpoint)) + call_at_breakpoint(ctx, dc, breakpoint); + + byebug_reset_stepping_stop_points(dc); + + call_at_line(ctx, dc); +} + + +/* TracePoint API event handlers */ + +static void +line_event(VALUE trace_point, void *data) +{ + VALUE brkpnt, file, line, binding; + + EVENT_SETUP; + + file = rb_tracearg_path(trace_arg); + line = rb_tracearg_lineno(trace_arg); + binding = rb_tracearg_binding(trace_arg); + + if (RTEST(tracing)) + call_at_tracing(context, dc); + + if (!CTX_FL_TEST(dc, CTX_FL_IGNORE_STEPS)) + dc->steps = dc->steps <= 0 ? -1 : dc->steps - 1; + + if (dc->calced_stack_size <= dc->dest_frame) + { + dc->dest_frame = dc->calced_stack_size; + CTX_FL_UNSET(dc, CTX_FL_IGNORE_STEPS); + + dc->lines = dc->lines <= 0 ? -1 : dc->lines - 1; + } + + if (dc->steps == 0 || dc->lines == 0) + call_at_line_check(context, dc, Qnil); + else + { + brkpnt = Qnil; + + if (!NIL_P(breakpoints)) + brkpnt = find_breakpoint_by_pos(breakpoints, file, line, binding); + + if (!NIL_P(brkpnt)) + call_at_line_check(context, dc, brkpnt); + } + + EVENT_TEARDOWN; +} + +static void +call_event(VALUE trace_point, void *data) +{ + VALUE brkpnt, klass, msym, mid, binding, self; + + EVENT_SETUP; + + if (dc->calced_stack_size <= dc->dest_frame) + CTX_FL_UNSET(dc, CTX_FL_IGNORE_STEPS); + + CALL_EVENT_SETUP; + + /* nil method_id means we are at top level so there can't be a method + * breakpoint here. Just leave then. */ + msym = rb_tracearg_method_id(trace_arg); + if (NIL_P(msym)) + { + EVENT_TEARDOWN; + return; + } + + mid = SYM2ID(msym); + klass = rb_tracearg_defined_class(trace_arg); + binding = rb_tracearg_binding(trace_arg); + self = rb_tracearg_self(trace_arg); + + brkpnt = Qnil; + + if (!NIL_P(breakpoints)) + brkpnt = find_breakpoint_by_method(breakpoints, klass, mid, binding, self); + + if (!NIL_P(brkpnt)) + { + call_at_breakpoint(context, dc, brkpnt); + call_at_line(context, dc); + } + + EVENT_TEARDOWN; +} + +static void +return_event(VALUE trace_point, void *data) +{ + VALUE brkpnt, file, line, binding; + + EVENT_SETUP; + + RETURN_EVENT_SETUP; + + if ((dc->steps_out == 0) && (CTX_FL_TEST(dc, CTX_FL_STOP_ON_RET))) + { + byebug_reset_stepping_stop_points(dc); + + call_at_return(context, dc, rb_tracearg_return_value(trace_arg)); + } + else if (!NIL_P(breakpoints)) + { + file = rb_tracearg_path(trace_arg); + /* + * @todo Sometimes the TracePoint API gives some return events without + * file:line information, so we need to guard for nil until we know what's + * going on. This happens, for example, with active_support core extensions: + * + * [#7] call@.../core_ext/numeric/conversions.rb:124 Fixnum#to_s + * [#7] b_call@.../core_ext/numeric/conversions.rb:124 BigDecimal#to_s + * [#7] line@.../core_ext/numeric/conversions.rb:125 BigDecimal#to_s + * [#7] c_call@.../core_ext/numeric/conversions.rb:125 Kernel#is_a? + * [#7] c_return@.../core_ext/numeric/conversions.rb:125 Kernel#is_a? + * [#7] line@.../core_ext/numeric/conversions.rb:131 BigDecimal#to_s + * [#7] c_call@.../core_ext/numeric/conversions.rb:131 Fixnum#to_default_s + * [#7] c_return@.../core_ext/numeric/conversions.rb:131 Fixnum#to_default_s + * [#7] b_return@/hort/core_ext/numeric/conversions.rb:133 BigDecimal#to_s + * [#7] return@:0 Fixnum#to_s # => This guy... + */ + if (!NIL_P(file)) + { + line = rb_tracearg_lineno(trace_arg); + binding = rb_tracearg_binding(trace_arg); + + brkpnt = find_breakpoint_by_pos(breakpoints, file, line, binding); + + if (!NIL_P(brkpnt)) + call_at_return(context, dc, rb_tracearg_return_value(trace_arg)); + } + } + + RETURN_EVENT_TEARDOWN; + + EVENT_TEARDOWN; +} + +static void +end_event(VALUE trace_point, void *data) +{ + EVENT_SETUP; + + RETURN_EVENT_SETUP; + + if ((dc->steps_out == 0) && (CTX_FL_TEST(dc, CTX_FL_STOP_ON_RET))) + { + byebug_reset_stepping_stop_points(dc); + + call_at_end(context, dc); + } + + RETURN_EVENT_TEARDOWN; + + EVENT_TEARDOWN; +} + +static void +raw_call_event(VALUE trace_point, void *data) +{ + EVENT_SETUP; + + CALL_EVENT_SETUP; + + EVENT_TEARDOWN; +} + +static void +raw_return_event(VALUE trace_point, void *data) +{ + EVENT_SETUP; + + RETURN_EVENT_SETUP; + + RETURN_EVENT_TEARDOWN; + + EVENT_TEARDOWN; +} + +static void +raise_event(VALUE trace_point, void *data) +{ + VALUE expn_class, ancestors, pm_context; + int i; + debug_context_t *new_dc; + + EVENT_SETUP; + + raised_exception = rb_tracearg_raised_exception(trace_arg); + + if (post_mortem == Qtrue) + { + pm_context = context_dup(dc); + rb_ivar_set(raised_exception, rb_intern("@__bb_context"), pm_context); + + Data_Get_Struct(pm_context, debug_context_t, new_dc); + rb_debug_inspector_open(context_backtrace_set, (void *)new_dc); + } + + if (NIL_P(catchpoints) || dc->calced_stack_size == 0 + || RHASH_TBL(catchpoints)->num_entries == 0) + { + EVENT_TEARDOWN; + return; + } + + expn_class = rb_obj_class(raised_exception); + ancestors = rb_mod_ancestors(expn_class); + for (i = 0; i < RARRAY_LENINT(ancestors); i++) + { + VALUE ancestor_class, module_name, hit_count; + + ancestor_class = rb_ary_entry(ancestors, i); + module_name = rb_mod_name(ancestor_class); + hit_count = rb_hash_aref(catchpoints, module_name); + + /* increment exception */ + if (!NIL_P(hit_count)) + { + rb_hash_aset(catchpoints, module_name, INT2FIX(FIX2INT(hit_count) + 1)); + + call_at_catchpoint(context, dc, raised_exception); + call_at_line(context, dc); + + break; + } + } + + EVENT_TEARDOWN; +} + + +/* Setup TracePoint functionality */ + +static void +register_tracepoints(VALUE self) +{ + int i; + VALUE traces = tracepoints; + + UNUSED(self); + + if (NIL_P(traces)) + { + int line_msk = RUBY_EVENT_LINE; + int call_msk = RUBY_EVENT_CALL; + int ret_msk = RUBY_EVENT_RETURN | RUBY_EVENT_B_RETURN; + int end_msk = RUBY_EVENT_END; + int raw_call_msk = RUBY_EVENT_C_CALL | RUBY_EVENT_B_CALL | RUBY_EVENT_CLASS; + int raw_ret_msk = RUBY_EVENT_C_RETURN; + int raise_msk = RUBY_EVENT_RAISE; + + VALUE tpLine = rb_tracepoint_new(Qnil, line_msk, line_event, 0); + VALUE tpCall = rb_tracepoint_new(Qnil, call_msk, call_event, 0); + VALUE tpReturn = rb_tracepoint_new(Qnil, ret_msk, return_event, 0); + VALUE tpEnd = rb_tracepoint_new(Qnil, end_msk, end_event, 0); + VALUE tpCCall = rb_tracepoint_new(Qnil, raw_call_msk, raw_call_event, 0); + VALUE tpCReturn = rb_tracepoint_new(Qnil, raw_ret_msk, raw_return_event, 0); + VALUE tpRaise = rb_tracepoint_new(Qnil, raise_msk, raise_event, 0); + + traces = rb_ary_new(); + rb_ary_push(traces, tpLine); + rb_ary_push(traces, tpCall); + rb_ary_push(traces, tpReturn); + rb_ary_push(traces, tpEnd); + rb_ary_push(traces, tpCCall); + rb_ary_push(traces, tpCReturn); + rb_ary_push(traces, tpRaise); + + tracepoints = traces; + } + + for (i = 0; i < RARRAY_LENINT(traces); i++) + rb_tracepoint_enable(rb_ary_entry(traces, i)); +} + +static void +clear_tracepoints(VALUE self) +{ + int i; + + UNUSED(self); + + for (i = RARRAY_LENINT(tracepoints) - 1; i >= 0; i--) + rb_tracepoint_disable(rb_ary_entry(tracepoints, i)); +} + + +/* Byebug's Public API */ + +/* + * call-seq: + * Byebug.contexts -> array + * + * Returns an array of all contexts. + */ +static VALUE +Contexts(VALUE self) +{ + volatile VALUE list; + volatile VALUE new_list; + VALUE context; + threads_table_t *t_tbl; + debug_context_t *dc; + int i; + + UNUSED(self); + + check_started(); + + new_list = rb_ary_new(); + list = rb_funcall(rb_cThread, rb_intern("list"), 0); + + for (i = 0; i < RARRAY_LENINT(list); i++) + { + VALUE thread = rb_ary_entry(list, i); + + thread_context_lookup(thread, &context); + rb_ary_push(new_list, context); + } + + Data_Get_Struct(threads, threads_table_t, t_tbl); + st_clear(t_tbl->tbl); + + for (i = 0; i < RARRAY_LENINT(new_list); i++) + { + context = rb_ary_entry(new_list, i); + Data_Get_Struct(context, debug_context_t, dc); + st_insert(t_tbl->tbl, dc->thread, context); + } + + return new_list; +} + +/* + * call-seq: + * Byebug.thread_context(thread) -> context + * + * Returns context of the thread passed as an argument. + */ +static VALUE +Thread_context(VALUE self, VALUE thread) +{ + VALUE context; + + UNUSED(self); + + check_started(); + + thread_context_lookup(thread, &context); + + return context; +} + +/* + * call-seq: + * Byebug.current_context -> context + * + * Returns the current context. + * Note: Byebug.current_context.thread == Thread.current + */ +static VALUE +Current_context(VALUE self) +{ + VALUE context; + + UNUSED(self); + + thread_context_lookup(rb_thread_current(), &context); + + return context; +} + +/* + * call-seq: + * Byebug.started? -> bool + * + * Returns +true+ byebug is started. + */ +static VALUE +Started(VALUE self) +{ + UNUSED(self); + + return IS_STARTED ? Qtrue : Qfalse; +} + +/* + * call-seq: + * Byebug.stop -> bool + * + * This method disables byebug. It returns +true+ if byebug was already + * disabled, otherwise it returns +false+. + */ +static VALUE +Stop(VALUE self) +{ + UNUSED(self); + + if (IS_STARTED) + { + clear_tracepoints(self); + + breakpoints = Qnil; + catchpoints = Qnil; + + return Qfalse; + } + + return Qtrue; +} + +static VALUE +Stoppable(VALUE self) +{ + VALUE context; + debug_context_t *dc; + + if (!IS_STARTED) + return Qfalse; + + if (!NIL_P(breakpoints) && rb_funcall(breakpoints, idEmpty, 0) == Qfalse) + return Qfalse; + + if (!NIL_P(catchpoints) && rb_funcall(catchpoints, idEmpty, 0) == Qfalse) + return Qfalse; + + if (post_mortem == Qtrue) + return Qfalse; + + if (RTEST(tracing)) + return Qfalse; + + context = Current_context(self); + if (!NIL_P(context)) + { + Data_Get_Struct(context, debug_context_t, dc); + + if (dc->steps > 0) + return Qfalse; + } + + return Qtrue; +} + +/* + * call-seq: + * Byebug.start -> bool + * + * The return value is the value of !Byebug.started? before issuing the + * +start+; That is, +true+ is returned, unless byebug was previously started. + */ +static VALUE +Start(VALUE self) +{ + if (IS_STARTED) + return Qfalse; + + catchpoints = rb_hash_new(); + + threads = create_threads_table(); + + register_tracepoints(self); + + return Qtrue; +} + +/* + * call-seq: + * Byebug.debug_load(file, stop = false) -> nil + * + * Same as Kernel#load but resets current context's frames. + * +stop+ parameter forces byebug to stop at the first line of code in +file+ + */ +static VALUE +Debug_load(int argc, VALUE *argv, VALUE self) +{ + VALUE file, stop, context; + debug_context_t *dc; + VALUE status = Qnil; + int state = 0; + + UNUSED(self); + + if (rb_scan_args(argc, argv, "11", &file, &stop) == 1) + stop = Qfalse; + + Start(self); + + context = Current_context(self); + Data_Get_Struct(context, debug_context_t, dc); + + dc->calced_stack_size = 1; + + if (RTEST(stop)) + dc->steps = 1; + + rb_load_protect(file, 0, &state); + if (0 != state) + { + status = rb_errinfo(); + byebug_reset_stepping_stop_points(dc); + } + + return status; +} + +/* + * call-seq: + * Byebug.tracing? -> bool + * + * Returns +true+ if global tracing is enabled. + */ +static VALUE +Tracing(VALUE self) +{ + UNUSED(self); + + return tracing; +} + +/* + * call-seq: + * Byebug.tracing = bool + * + * Sets the global tracing flag. + */ +static VALUE +Set_tracing(VALUE self, VALUE value) +{ + UNUSED(self); + + tracing = RTEST(value) ? Qtrue : Qfalse; + return value; +} + +/* + * call-seq: + * Byebug.verbose? -> bool + * + * Returns +true+ if global verbose flag for TracePoint API events is enabled. + */ +static VALUE +Verbose(VALUE self) +{ + UNUSED(self); + + return verbose; +} + +/* + * call-seq: + * Byebug.verbose = bool + * + * Sets the global verbose flag for TracePoint API events is enabled. + */ +static VALUE +Set_verbose(VALUE self, VALUE value) +{ + UNUSED(self); + + verbose = RTEST(value) ? Qtrue : Qfalse; + return value; +} + +/* + * call-seq: + * Byebug.post_mortem? -> bool + * + * Returns +true+ if post-mortem debugging is enabled. + */ +static VALUE +Post_mortem(VALUE self) +{ + UNUSED(self); + + return post_mortem; +} + +/* + * call-seq: + * Byebug.post_mortem = bool + * + * Sets post-moterm flag. + */ +static VALUE +Set_post_mortem(VALUE self, VALUE value) +{ + UNUSED(self); + + post_mortem = RTEST(value) ? Qtrue : Qfalse; + return value; +} + +/* + * call-seq: + * Byebug.add_catchpoint(exception) -> exception + * + * Adds a new exception to the catchpoints hash. + */ +static VALUE +Add_catchpoint(VALUE self, VALUE value) +{ + UNUSED(self); + + if (TYPE(value) != T_STRING) + rb_raise(rb_eTypeError, "value of a catchpoint must be String"); + + rb_hash_aset(catchpoints, rb_str_dup(value), INT2FIX(0)); + return value; +} + +/* + * Document-class: Byebug + * + * == Summary + * + * This is a singleton class allows controlling byebug. Use it to start/stop + * byebug, set/remove breakpoints, etc. + */ +void +Init_byebug() +{ + mByebug = rb_define_module("Byebug"); + + rb_define_module_function(mByebug, "add_catchpoint", Add_catchpoint, 1); + rb_define_module_function(mByebug, "breakpoints", Breakpoints, 0); + rb_define_module_function(mByebug, "catchpoints", Catchpoints, 0); + rb_define_module_function(mByebug, "contexts", Contexts, 0); + rb_define_module_function(mByebug, "current_context", Current_context, 0); + rb_define_module_function(mByebug, "debug_load", Debug_load, -1); + rb_define_module_function(mByebug, "post_mortem?", Post_mortem, 0); + rb_define_module_function(mByebug, "post_mortem=", Set_post_mortem, 1); + rb_define_module_function(mByebug, "raised_exception", Raised_exception, 0); + rb_define_module_function(mByebug, "start", Start, 0); + rb_define_module_function(mByebug, "started?", Started, 0); + rb_define_module_function(mByebug, "stop", Stop, 0); + rb_define_module_function(mByebug, "stoppable?", Stoppable, 0); + rb_define_module_function(mByebug, "thread_context", Thread_context, 1); + rb_define_module_function(mByebug, "tracing?", Tracing, 0); + rb_define_module_function(mByebug, "tracing=", Set_tracing, 1); + rb_define_module_function(mByebug, "verbose?", Verbose, 0); + rb_define_module_function(mByebug, "verbose=", Set_verbose, 1); + + Init_threads_table(mByebug); + Init_byebug_context(mByebug); + Init_byebug_breakpoint(mByebug); + + rb_global_variable(&breakpoints); + rb_global_variable(&catchpoints); + rb_global_variable(&tracepoints); + rb_global_variable(&raised_exception); + rb_global_variable(&threads); + + idPuts = rb_intern("puts"); + idEmpty = rb_intern("empty?"); +} diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/byebug.h b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/byebug.h new file mode 100644 index 00000000..055a599e --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/byebug.h @@ -0,0 +1,143 @@ +#ifndef BYEBUG +#define BYEBUG + +#include +#include + +/* To prevent unused parameter warnings */ +#define UNUSED(x) (void)(x) + +/* flags */ +#define CTX_FL_DEAD (1 << 1) /* this context belonged to a dead thread */ +#define CTX_FL_IGNORE (1 << 2) /* this context belongs to ignored thread */ +#define CTX_FL_SUSPEND (1 << 3) /* thread currently suspended */ +#define CTX_FL_TRACING (1 << 4) /* call at_tracing method */ +#define CTX_FL_WAS_RUNNING (1 << 5) /* thread was previously running */ +#define CTX_FL_STOP_ON_RET (1 << 6) /* can stop on method 'end' */ +#define CTX_FL_IGNORE_STEPS (1 << 7) /* doesn't countdown steps to break */ + +/* macro functions */ +#define CTX_FL_TEST(c, f) ((c)->flags & (f)) +#define CTX_FL_SET(c, f) \ + do \ + { \ + (c)->flags |= (f); \ + } while (0) +#define CTX_FL_UNSET(c, f) \ + do \ + { \ + (c)->flags &= ~(f); \ + } while (0) + +/* types */ +typedef enum { + CTX_STOP_NONE, + CTX_STOP_STEP, + CTX_STOP_BREAKPOINT, + CTX_STOP_CATCHPOINT +} ctx_stop_reason; + +typedef struct +{ + int calced_stack_size; + int flags; + ctx_stop_reason stop_reason; + + VALUE thread; + int thnum; + + int dest_frame; /* next stop's frame if stopped by next */ + int lines; /* # of lines in dest_frame before stopping */ + int steps; /* # of steps before stopping */ + int steps_out; /* # of returns before stopping */ + + VALUE backtrace; /* [[loc, self, klass, binding], ...] */ +} debug_context_t; + +typedef enum { + LOCATION, + SELF, + CLASS, + BINDING +} frame_part; + +struct call_with_inspection_data +{ + debug_context_t *dc; + VALUE ctx; + ID id; + int argc; + VALUE *argv; +}; + +typedef struct +{ + st_table *tbl; +} threads_table_t; + +enum bp_type +{ + BP_POS_TYPE, + BP_METHOD_TYPE +}; + +enum hit_condition +{ + HIT_COND_NONE, + HIT_COND_GE, + HIT_COND_EQ, + HIT_COND_MOD +}; + +typedef struct +{ + int id; + enum bp_type type; + VALUE source; + union + { + int line; + ID mid; + } pos; + VALUE expr; + VALUE enabled; + int hit_count; + int hit_value; + enum hit_condition hit_condition; +} breakpoint_t; + +/* functions from locker.c */ +extern void byebug_add_to_locked(VALUE thread); +extern VALUE byebug_pop_from_locked(); +extern void byebug_remove_from_locked(VALUE thread); + +/* functions from threads.c */ +extern void Init_threads_table(VALUE mByebug); +extern VALUE create_threads_table(void); +extern void thread_context_lookup(VALUE thread, VALUE *context); +extern int is_living_thread(VALUE thread); +extern void acquire_lock(debug_context_t *dc); +extern void release_lock(void); + +/* global variables */ +extern VALUE threads; +extern VALUE next_thread; + +/* functions from context.c */ +extern void Init_byebug_context(VALUE mByebug); +extern VALUE byebug_context_create(VALUE thread); +extern VALUE context_dup(debug_context_t *context); +extern void byebug_reset_stepping_stop_points(debug_context_t *context); +extern VALUE call_with_debug_inspector(struct call_with_inspection_data *data); +extern VALUE context_backtrace_set(const rb_debug_inspector_t *inspector, + void *data); + +/* functions from breakpoint.c */ +extern void Init_byebug_breakpoint(VALUE mByebug); +extern VALUE find_breakpoint_by_pos(VALUE breakpoints, VALUE source, VALUE pos, + VALUE bind); + +extern VALUE find_breakpoint_by_method(VALUE breakpoints, VALUE klass, + VALUE mid, VALUE bind, VALUE self); + +#endif diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/byebug.o b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/byebug.o new file mode 100644 index 00000000..171bc950 Binary files /dev/null and b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/byebug.o differ diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/context.c b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/context.c new file mode 100644 index 00000000..2a2554b7 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/context.c @@ -0,0 +1,673 @@ +#include "byebug.h" + +static VALUE cContext; +static VALUE cDebugThread; +static int thnum_max = 0; + +/* "Step", "Next" and "Finish" do their work by saving information about where + * to stop next. byebug_reset_stepping_stop_points removes/resets this information. */ +extern void +byebug_reset_stepping_stop_points(debug_context_t *context) +{ + context->dest_frame = -1; + context->lines = -1; + context->steps = -1; + context->steps_out = -1; +} + +/* + * call-seq: + * context.dead? -> bool + * + * Returns +true+ if context doesn't represent a live context and is created + * during post-mortem exception handling. + */ +static inline VALUE +Context_dead(VALUE self) +{ + debug_context_t *context; + + Data_Get_Struct(self, debug_context_t, context); + return CTX_FL_TEST(context, CTX_FL_DEAD) ? Qtrue : Qfalse; +} + +static void +context_mark(void *data) +{ + debug_context_t *context = (debug_context_t *)data; + + rb_gc_mark(context->backtrace); +} + +static VALUE +dc_backtrace(const debug_context_t *context) +{ + return context->backtrace; +} + +static int +dc_stack_size(debug_context_t *context) +{ + + if (NIL_P(dc_backtrace(context))) + return 0; + + return RARRAY_LENINT(dc_backtrace(context)); +} + +extern VALUE +byebug_context_create(VALUE thread) +{ + debug_context_t *context = ALLOC(debug_context_t); + + context->flags = 0; + context->thnum = ++thnum_max; + context->thread = thread; + byebug_reset_stepping_stop_points(context); + context->stop_reason = CTX_STOP_NONE; + + rb_debug_inspector_open(context_backtrace_set, (void *)context); + context->calced_stack_size = dc_stack_size(context) + 1; + + if (rb_obj_class(thread) == cDebugThread) + CTX_FL_SET(context, CTX_FL_IGNORE); + + return Data_Wrap_Struct(cContext, context_mark, 0, context); +} + +extern VALUE +context_dup(debug_context_t *context) +{ + debug_context_t *new_context = ALLOC(debug_context_t); + + memcpy(new_context, context, sizeof(debug_context_t)); + byebug_reset_stepping_stop_points(new_context); + new_context->backtrace = context->backtrace; + CTX_FL_SET(new_context, CTX_FL_DEAD); + + return Data_Wrap_Struct(cContext, context_mark, 0, new_context); +} + + +static VALUE +dc_frame_get(const debug_context_t *context, int frame_index, frame_part type) +{ + VALUE frame; + + if (NIL_P(dc_backtrace(context))) + rb_raise(rb_eRuntimeError, "Backtrace information is not available"); + + if (frame_index >= RARRAY_LENINT(dc_backtrace(context))) + rb_raise(rb_eRuntimeError, "That frame doesn't exist!"); + + frame = rb_ary_entry(dc_backtrace(context), frame_index); + return rb_ary_entry(frame, type); +} + +static VALUE +dc_frame_location(const debug_context_t *context, int frame_index) +{ + return dc_frame_get(context, frame_index, LOCATION); +} + +static VALUE +dc_frame_self(const debug_context_t *context, int frame_index) +{ + return dc_frame_get(context, frame_index, SELF); +} + +static VALUE +dc_frame_class(const debug_context_t *context, int frame_index) +{ + return dc_frame_get(context, frame_index, CLASS); +} + +static VALUE +dc_frame_binding(const debug_context_t *context, int frame_index) +{ + return dc_frame_get(context, frame_index, BINDING); +} + +static VALUE +load_backtrace(const rb_debug_inspector_t *inspector) +{ + VALUE backtrace = rb_ary_new(); + VALUE locs = rb_debug_inspector_backtrace_locations(inspector); + int i; + + for (i = 0; i < RARRAY_LENINT(locs); i++) + { + VALUE frame = rb_ary_new(); + + rb_ary_push(frame, rb_ary_entry(locs, i)); + rb_ary_push(frame, rb_debug_inspector_frame_self_get(inspector, i)); + rb_ary_push(frame, rb_debug_inspector_frame_class_get(inspector, i)); + rb_ary_push(frame, rb_debug_inspector_frame_binding_get(inspector, i)); + + rb_ary_push(backtrace, frame); + } + + return backtrace; +} + +extern VALUE +context_backtrace_set(const rb_debug_inspector_t *inspector, void *data) +{ + debug_context_t *dc = (debug_context_t *)data; + + dc->backtrace = load_backtrace(inspector); + + return Qnil; +} + +static VALUE +open_debug_inspector_i(const rb_debug_inspector_t *inspector, void *data) +{ + struct call_with_inspection_data *cwi = + (struct call_with_inspection_data *)data; + + cwi->dc->backtrace = load_backtrace(inspector); + + return rb_funcall2(cwi->ctx, cwi->id, cwi->argc, cwi->argv); +} + +static VALUE +open_debug_inspector(struct call_with_inspection_data *cwi) +{ + return rb_debug_inspector_open(open_debug_inspector_i, cwi); +} + +static VALUE +close_debug_inspector(struct call_with_inspection_data *cwi) +{ + cwi->dc->backtrace = Qnil; + return Qnil; +} + +extern VALUE +call_with_debug_inspector(struct call_with_inspection_data *data) +{ + return rb_ensure(open_debug_inspector, (VALUE)data, close_debug_inspector, + (VALUE)data); +} + +#define FRAME_SETUP \ + debug_context_t *context; \ + VALUE frame_no; \ + int frame_n; \ + Data_Get_Struct(self, debug_context_t, context); \ + if (!rb_scan_args(argc, argv, "01", &frame_no)) \ + frame_n = 0; \ + else \ + frame_n = FIX2INT(frame_no); + +/* + * call-seq: + * context.frame_binding(frame_position = 0) -> binding + * + * Returns frame's binding. + */ +static VALUE +Context_frame_binding(int argc, VALUE *argv, VALUE self) +{ + FRAME_SETUP; + + return dc_frame_binding(context, frame_n); +} + +/* + * call-seq: + * context.frame_class(frame_position = 0) -> class + * + * Returns frame's defined class. + */ +static VALUE +Context_frame_class(int argc, VALUE *argv, VALUE self) +{ + FRAME_SETUP; + + return dc_frame_class(context, frame_n); +} + +/* + * call-seq: + * context.frame_file(frame_position = 0) -> string + * + * Returns the name of the file in the frame. + */ +static VALUE +Context_frame_file(int argc, VALUE *argv, VALUE self) +{ + VALUE loc, absolute_path; + + FRAME_SETUP; + + loc = dc_frame_location(context, frame_n); + + absolute_path = rb_funcall(loc, rb_intern("absolute_path"), 0); + + if (!NIL_P(absolute_path)) + return absolute_path; + + return rb_funcall(loc, rb_intern("path"), 0); +} + +/* + * call-seq: + * context.frame_line(frame_position = 0) -> int + * + * Returns the line number in the file in the frame. + */ +static VALUE +Context_frame_line(int argc, VALUE *argv, VALUE self) +{ + VALUE loc; + + FRAME_SETUP; + + loc = dc_frame_location(context, frame_n); + + return rb_funcall(loc, rb_intern("lineno"), 0); +} + +/* + * call-seq: + * context.frame_method(frame_position = 0) -> sym + * + * Returns the sym of the method in the frame. + */ +static VALUE +Context_frame_method(int argc, VALUE *argv, VALUE self) +{ + VALUE loc; + + FRAME_SETUP; + + loc = dc_frame_location(context, frame_n); + + return rb_str_intern(rb_funcall(loc, rb_intern("label"), 0)); +} + +/* + * call-seq: + * context.frame_self(frame_postion = 0) -> obj + * + * Returns self object of the frame. + */ +static VALUE +Context_frame_self(int argc, VALUE *argv, VALUE self) +{ + FRAME_SETUP; + + return dc_frame_self(context, frame_n); +} + +/* + * call-seq: + * context.ignored? -> bool + * + * Returns the ignore flag for the context, which marks whether the associated + * thread is ignored while debugging. + */ +static inline VALUE +Context_ignored(VALUE self) +{ + debug_context_t *context; + + Data_Get_Struct(self, debug_context_t, context); + return CTX_FL_TEST(context, CTX_FL_IGNORE) ? Qtrue : Qfalse; +} + +/* + * call-seq: + * context.resume -> nil + * + * Resumes thread from the suspended mode. + */ +static VALUE +Context_resume(VALUE self) +{ + debug_context_t *context; + + Data_Get_Struct(self, debug_context_t, context); + + if (!CTX_FL_TEST(context, CTX_FL_SUSPEND)) + return Qnil; + + CTX_FL_UNSET(context, CTX_FL_SUSPEND); + + if (CTX_FL_TEST(context, CTX_FL_WAS_RUNNING)) + rb_thread_wakeup(context->thread); + + return Qnil; +} + +/* + * call-seq: + * context.backtrace-> Array + * + * Returns the frame stack of a context. + */ +static inline VALUE +Context_backtrace(VALUE self) +{ + debug_context_t *context; + + Data_Get_Struct(self, debug_context_t, context); + + return dc_backtrace(context); +} + +static VALUE +Context_stop_reason(VALUE self) +{ + debug_context_t *context; + const char *symbol; + + Data_Get_Struct(self, debug_context_t, context); + + if (CTX_FL_TEST(context, CTX_FL_DEAD)) + symbol = "post-mortem"; + else + switch (context->stop_reason) + { + case CTX_STOP_STEP: + symbol = "step"; + break; + case CTX_STOP_BREAKPOINT: + symbol = "breakpoint"; + break; + case CTX_STOP_CATCHPOINT: + symbol = "catchpoint"; + break; + case CTX_STOP_NONE: + default: + symbol = "none"; + } + return ID2SYM(rb_intern(symbol)); +} + +/* + * call-seq: + * context.step_into(steps, frame = 0) + * + * Stops the current context after a number of +steps+ are made from frame + * +frame+ (by default the newest one). + */ +static VALUE +Context_step_into(int argc, VALUE *argv, VALUE self) +{ + VALUE steps, v_frame; + int n_args, from_frame; + debug_context_t *context; + + Data_Get_Struct(self, debug_context_t, context); + + if (context->calced_stack_size == 0) + rb_raise(rb_eRuntimeError, "No frames collected."); + + n_args = rb_scan_args(argc, argv, "11", &steps, &v_frame); + + if (FIX2INT(steps) <= 0) + rb_raise(rb_eRuntimeError, "Steps argument must be positive."); + + from_frame = n_args == 1 ? 0 : FIX2INT(v_frame); + + if (from_frame < 0 || from_frame >= context->calced_stack_size) + rb_raise(rb_eRuntimeError, "Destination frame (%d) is out of range (%d)", + from_frame, context->calced_stack_size); + else if (from_frame > 0) + CTX_FL_SET(context, CTX_FL_IGNORE_STEPS); + + context->steps = FIX2INT(steps); + context->dest_frame = context->calced_stack_size - from_frame; + + return steps; +} + +/* + * call-seq: + * context.step_out(n_frames = 1, force = false) + * + * Stops after +n_frames+ frames are finished. +force+ parameter (if true) + * ensures that the execution will stop in the specified frame even when there + * are no more instructions to run. In that case, it will stop when the return + * event for that frame is triggered. + */ +static VALUE +Context_step_out(int argc, VALUE *argv, VALUE self) +{ + int n_args, n_frames; + VALUE v_frames, force; + debug_context_t *context; + + n_args = rb_scan_args(argc, argv, "02", &v_frames, &force); + n_frames = n_args == 0 ? 1 : FIX2INT(v_frames); + + Data_Get_Struct(self, debug_context_t, context); + + if (n_frames < 0 || n_frames > context->calced_stack_size) + rb_raise(rb_eRuntimeError, + "You want to finish %d frames, but stack size is only %d", + n_frames, context->calced_stack_size); + + context->steps_out = n_frames; + if (n_args == 2 && RTEST(force)) + CTX_FL_SET(context, CTX_FL_STOP_ON_RET); + else + CTX_FL_UNSET(context, CTX_FL_STOP_ON_RET); + + return Qnil; +} + +/* + * call-seq: + * context.step_over(lines, frame = 0) + * + * Steps over +lines+ lines in frame +frame+ (by default the newest one) or + * higher (if frame +frame+ finishes). + */ +static VALUE +Context_step_over(int argc, VALUE *argv, VALUE self) +{ + int n_args, frame; + VALUE lines, v_frame; + debug_context_t *context; + + Data_Get_Struct(self, debug_context_t, context); + + if (context->calced_stack_size == 0) + rb_raise(rb_eRuntimeError, "No frames collected."); + + n_args = rb_scan_args(argc, argv, "11", &lines, &v_frame); + frame = n_args == 1 ? 0 : FIX2INT(v_frame); + + if (frame < 0 || frame >= context->calced_stack_size) + rb_raise(rb_eRuntimeError, "Destination frame (%d) is out of range (%d)", + frame, context->calced_stack_size); + + context->lines = FIX2INT(lines); + context->dest_frame = context->calced_stack_size - frame; + + return Qnil; +} + +/* + * call-seq: + * context.suspend -> nil + * + * Suspends the thread when it is running. + */ +static VALUE +Context_suspend(VALUE self) +{ + VALUE status; + debug_context_t *context; + + Data_Get_Struct(self, debug_context_t, context); + + status = rb_funcall(context->thread, rb_intern("status"), 0); + + if (rb_str_cmp(status, rb_str_new2("run")) == 0) + CTX_FL_SET(context, CTX_FL_WAS_RUNNING); + else if (rb_str_cmp(status, rb_str_new2("sleep")) == 0) + CTX_FL_UNSET(context, CTX_FL_WAS_RUNNING); + else + return Qnil; + + CTX_FL_SET(context, CTX_FL_SUSPEND); + + return Qnil; +} + +/* + * call-seq: + * context.switch -> nil + * + * Switches execution to this context. + */ +static VALUE +Context_switch(VALUE self) +{ + debug_context_t *context; + + Data_Get_Struct(self, debug_context_t, context); + + next_thread = context->thread; + + context->steps = 1; + context->steps_out = 0; + CTX_FL_SET(context, CTX_FL_STOP_ON_RET); + + return Qnil; +} + +/* + * call-seq: + * context.suspended? -> bool + * + * Returns +true+ if the thread is suspended by debugger. + */ +static VALUE +Context_is_suspended(VALUE self) +{ + debug_context_t *context; + + Data_Get_Struct(self, debug_context_t, context); + + return CTX_FL_TEST(context, CTX_FL_SUSPEND) ? Qtrue : Qfalse; +} + +/* + * call-seq: + * context.thnum -> int + * + * Returns the context's number. + */ +static inline VALUE +Context_thnum(VALUE self) +{ + debug_context_t *context; + + Data_Get_Struct(self, debug_context_t, context); + return INT2FIX(context->thnum); +} + +/* + * call-seq: + * context.thread -> thread + * + * Returns the thread this context is associated with. + */ +static inline VALUE +Context_thread(VALUE self) +{ + debug_context_t *context; + + Data_Get_Struct(self, debug_context_t, context); + return context->thread; +} + +/* + * call-seq: + * context.tracing -> bool + * + * Returns the tracing flag for the current context. + */ +static VALUE +Context_tracing(VALUE self) +{ + debug_context_t *context; + + Data_Get_Struct(self, debug_context_t, context); + return CTX_FL_TEST(context, CTX_FL_TRACING) ? Qtrue : Qfalse; +} + +/* + * call-seq: + * context.tracing = bool + * + * Controls the tracing for this context. + */ +static VALUE +Context_set_tracing(VALUE self, VALUE value) +{ + debug_context_t *context; + + Data_Get_Struct(self, debug_context_t, context); + + if (RTEST(value)) + CTX_FL_SET(context, CTX_FL_TRACING); + else + CTX_FL_UNSET(context, CTX_FL_TRACING); + return value; +} + +/* :nodoc: */ +static VALUE +dt_inherited(VALUE klass) +{ + UNUSED(klass); + + rb_raise(rb_eRuntimeError, "Can't inherit Byebug::DebugThread class"); + + return Qnil; +} + +/* + * Document-class: Context + * + * == Summary + * + * Byebug keeps a single instance of this class per thread. + */ +void +Init_byebug_context(VALUE mByebug) +{ + cContext = rb_define_class_under(mByebug, "Context", rb_cObject); + + rb_define_method(cContext, "backtrace", Context_backtrace, 0); + rb_define_method(cContext, "dead?", Context_dead, 0); + rb_define_method(cContext, "frame_binding", Context_frame_binding, -1); + rb_define_method(cContext, "frame_class", Context_frame_class, -1); + rb_define_method(cContext, "frame_file", Context_frame_file, -1); + rb_define_method(cContext, "frame_line", Context_frame_line, -1); + rb_define_method(cContext, "frame_method", Context_frame_method, -1); + rb_define_method(cContext, "frame_self", Context_frame_self, -1); + rb_define_method(cContext, "ignored?", Context_ignored, 0); + rb_define_method(cContext, "resume", Context_resume, 0); + rb_define_method(cContext, "step_into", Context_step_into, -1); + rb_define_method(cContext, "step_out", Context_step_out, -1); + rb_define_method(cContext, "step_over", Context_step_over, -1); + rb_define_method(cContext, "stop_reason", Context_stop_reason, 0); + rb_define_method(cContext, "suspend", Context_suspend, 0); + rb_define_method(cContext, "suspended?", Context_is_suspended, 0); + rb_define_method(cContext, "switch", Context_switch, 0); + rb_define_method(cContext, "thnum", Context_thnum, 0); + rb_define_method(cContext, "thread", Context_thread, 0); + rb_define_method(cContext, "tracing", Context_tracing, 0); + rb_define_method(cContext, "tracing=", Context_set_tracing, 1); + + cDebugThread = rb_define_class_under(mByebug, "DebugThread", rb_cThread); + rb_define_singleton_method(cDebugThread, "inherited", dt_inherited, 1); +} diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/context.o b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/context.o new file mode 100644 index 00000000..78a45b9d Binary files /dev/null and b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/context.o differ diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/extconf.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/extconf.rb new file mode 100644 index 00000000..057552e2 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/extconf.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require "mkmf" + +makefile_config = RbConfig::MAKEFILE_CONFIG + +makefile_config["CC"] = ENV["CC"] if ENV["CC"] + +makefile_config["CFLAGS"] << " -gdwarf-2 -g3 -O0" if ENV["debug"] + +dir_config("ruby") +with_cflags(makefile_config["CFLAGS"]) { create_makefile("byebug/byebug") } diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/locker.c b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/locker.c new file mode 100644 index 00000000..f74a111f --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/locker.c @@ -0,0 +1,96 @@ +#include "byebug.h" + +/** + * A simple linked list containing locked threads, FIFO style. + */ + +typedef struct locked_thread_t +{ + VALUE thread; + struct locked_thread_t *next; +} locked_thread_t; + +static locked_thread_t *locked_head = NULL; +static locked_thread_t *locked_tail = NULL; + +static int +is_in_locked(VALUE thread) +{ + locked_thread_t *node; + + if (!locked_head) + return 0; + + for (node = locked_head; node != locked_tail; node = node->next) + if (node->thread == thread) + return 1; + + return 0; +} + +extern void +byebug_add_to_locked(VALUE thread) +{ + locked_thread_t *node; + + if (is_in_locked(thread)) + return; + + node = ALLOC(locked_thread_t); + node->thread = thread; + node->next = NULL; + + if (locked_tail) + locked_tail->next = node; + + locked_tail = node; + + if (!locked_head) + locked_head = node; +} + +extern VALUE +byebug_pop_from_locked() +{ + VALUE thread; + locked_thread_t *node; + + if (!locked_head) + return Qnil; + + node = locked_head; + locked_head = locked_head->next; + + if (locked_tail == node) + locked_tail = NULL; + + thread = node->thread; + xfree(node); + + return thread; +} + +extern void +byebug_remove_from_locked(VALUE thread) +{ + locked_thread_t *node; + locked_thread_t *next_node; + + if (NIL_P(thread) || !locked_head || !is_in_locked(thread)) + return; + + if (locked_head->thread == thread) + { + byebug_pop_from_locked(); + return; + } + + for (node = locked_head; node != locked_tail; node = node->next) + if (node->next && node->next->thread == thread) + { + next_node = node->next; + node->next = next_node->next; + xfree(next_node); + return; + } +} diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/locker.o b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/locker.o new file mode 100644 index 00000000..5028f75d Binary files /dev/null and b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/locker.o differ diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/threads.c b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/threads.c new file mode 100644 index 00000000..832ffb13 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/threads.c @@ -0,0 +1,230 @@ +#include "byebug.h" + +/* Threads table class */ +static VALUE cThreadsTable; + +/* If not Qnil, holds the next thread that must be run */ +VALUE next_thread = Qnil; + +/* To allow thread syncronization, we must stop threads when debugging */ +static VALUE locker = Qnil; + +static int +t_tbl_mark_keyvalue(st_data_t key, st_data_t value, st_data_t tbl) +{ + UNUSED(tbl); + + rb_gc_mark((VALUE)key); + + if (!value) + return ST_CONTINUE; + + rb_gc_mark((VALUE)value); + + return ST_CONTINUE; +} + +static void +t_tbl_mark(void *data) +{ + threads_table_t *t_tbl = (threads_table_t *)data; + st_table *tbl = t_tbl->tbl; + + st_foreach(tbl, t_tbl_mark_keyvalue, (st_data_t)tbl); +} + +static void +t_tbl_free(void *data) +{ + threads_table_t *t_tbl = (threads_table_t *)data; + + st_free_table(t_tbl->tbl); + xfree(t_tbl); +} + +/* + * Creates a numeric hash whose keys are the currently active threads and + * whose values are their associated contexts. + */ +VALUE +create_threads_table(void) +{ + threads_table_t *t_tbl; + + t_tbl = ALLOC(threads_table_t); + t_tbl->tbl = st_init_numtable(); + return Data_Wrap_Struct(cThreadsTable, t_tbl_mark, t_tbl_free, t_tbl); +} + +/* + * Checks a single entry in the threads table. + * + * If it has no associated context or the key doesn't correspond to a living + * thread, the entry is removed from the thread's list. + */ +static int +check_thread_i(st_data_t key, st_data_t value, st_data_t data) +{ + UNUSED(data); + + if (!value) + return ST_DELETE; + + if (!is_living_thread((VALUE)key)) + return ST_DELETE; + + return ST_CONTINUE; +} + +/* + * Checks whether a thread is either in the running or sleeping state. + */ +int +is_living_thread(VALUE thread) +{ + VALUE status = rb_funcall(thread, rb_intern("status"), 0); + + if (NIL_P(status) || status == Qfalse) + return 0; + + if (rb_str_cmp(status, rb_str_new2("run")) == 0 + || rb_str_cmp(status, rb_str_new2("sleep")) == 0) + return 1; + + return 0; +} + +/* + * Checks threads table for dead/finished threads. + */ +static void +cleanup_dead_threads(void) +{ + threads_table_t *t_tbl; + + Data_Get_Struct(threads, threads_table_t, t_tbl); + st_foreach(t_tbl->tbl, check_thread_i, 0); +} + +/* + * Looks up a context in the threads table. If not present, it creates it. + */ +void +thread_context_lookup(VALUE thread, VALUE *context) +{ + threads_table_t *t_tbl; + + Data_Get_Struct(threads, threads_table_t, t_tbl); + + if (!st_lookup(t_tbl->tbl, thread, context) || !*context) + { + *context = byebug_context_create(thread); + st_insert(t_tbl->tbl, thread, *context); + } +} + +/* + * Holds thread execution while another thread is active. + * + * Thanks to this, all threads are "frozen" while the user is typing commands. + */ +void +acquire_lock(debug_context_t *dc) +{ + while ((!NIL_P(locker) && locker != rb_thread_current()) + || CTX_FL_TEST(dc, CTX_FL_SUSPEND)) + { + byebug_add_to_locked(rb_thread_current()); + rb_thread_stop(); + + if (CTX_FL_TEST(dc, CTX_FL_SUSPEND)) + CTX_FL_SET(dc, CTX_FL_WAS_RUNNING); + } + + locker = rb_thread_current(); +} + +/* + * Releases our global lock and passes execution on to another thread, either + * the thread specified by +next_thread+ or any other thread if +next_thread+ + * is nil. + */ +void +release_lock(void) +{ + VALUE thread; + + cleanup_dead_threads(); + + locker = Qnil; + + if (NIL_P(next_thread)) + thread = byebug_pop_from_locked(); + else + { + byebug_remove_from_locked(next_thread); + thread = next_thread; + next_thread = Qnil; + } + + if (!NIL_P(thread) && is_living_thread(thread)) + rb_thread_run(thread); +} + +/* + * call-seq: + * Byebug.unlock -> nil + * + * Unlocks global switch so other threads can run. + */ +static VALUE +Unlock(VALUE self) +{ + UNUSED(self); + + release_lock(); + + return locker; +} + +/* + * call-seq: + * Byebug.lock -> Thread.current + * + * Locks global switch to reserve execution to current thread exclusively. + */ +static VALUE +Lock(VALUE self) +{ + debug_context_t *dc; + VALUE context; + + UNUSED(self); + + if (!is_living_thread(rb_thread_current())) + rb_raise(rb_eRuntimeError, "Current thread is dead!"); + + thread_context_lookup(rb_thread_current(), &context); + Data_Get_Struct(context, debug_context_t, dc); + + acquire_lock(dc); + + return locker; +} + +/* + * + * Document-class: ThreadsTable + * + * == Sumary + * + * Hash table holding currently active threads and their associated contexts + */ +void +Init_threads_table(VALUE mByebug) +{ + cThreadsTable = rb_define_class_under(mByebug, "ThreadsTable", rb_cObject); + + rb_define_module_function(mByebug, "unlock", Unlock, 0); + rb_define_module_function(mByebug, "lock", Lock, 0); +} diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/threads.o b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/threads.o new file mode 100644 index 00000000..3ed0ac37 Binary files /dev/null and b/path/ruby/2.6.0/gems/byebug-11.0.1/ext/byebug/threads.o differ diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug.rb new file mode 100644 index 00000000..f46fdd2b --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require "byebug/attacher" diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/attacher.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/attacher.rb new file mode 100644 index 00000000..08068137 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/attacher.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +# +# Main Container for all of Byebug's code +# +module Byebug + # + # Starts byebug, and stops at the first line of user's code. + # + def self.attach + unless started? + self.mode = :attached + + start + run_init_script + end + + current_context.step_out(3, true) + end + + def self.spawn(host = "localhost", port = nil) + require "byebug/core" + + self.wait_connection = true + start_server(host, port || PORT) + end +end + +# +# Adds a `byebug` method to the Kernel module. +# +# Dropping a `byebug` call anywhere in your code, you get a debug prompt. +# +module Kernel + def byebug + require "byebug/core" + + Byebug.attach unless Byebug.mode == :off + end + + def remote_byebug(host = "localhost", port = nil) + Byebug.spawn(host, port) + + Byebug.attach + end + + alias debugger byebug +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/breakpoint.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/breakpoint.rb new file mode 100644 index 00000000..b7dd30e3 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/breakpoint.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +module Byebug + # + # Implements breakpoints + # + class Breakpoint + # + # First breakpoint, in order of creation + # + def self.first + Byebug.breakpoints.first + end + + # + # Last breakpoint, in order of creation + # + def self.last + Byebug.breakpoints.last + end + + # + # Adds a new breakpoint + # + # @param [String] file + # @param [Fixnum] line + # @param [String] expr + # + def self.add(file, line, expr = nil) + breakpoint = Breakpoint.new(file, line, expr) + Byebug.breakpoints << breakpoint + breakpoint + end + + # + # Removes a breakpoint + # + # @param id [integer] breakpoint number + # + def self.remove(id) + Byebug.breakpoints.reject! { |b| b.id == id } + end + + # + # Returns an array of line numbers in file named +filename+ where + # breakpoints could be set. The list will contain an entry for each + # distinct line event call so it is possible (and possibly useful) for a + # line number appear more than once. + # + # @param filename [String] File name to inspect for possible breakpoints + # + def self.potential_lines(filename) + name = "#{Time.new.to_i}_#{rand(2**31)}" + iseq = RubyVM::InstructionSequence.compile(File.read(filename), name) + + if iseq.respond_to?(:each_child) + potential_lines_with_trace_points(iseq, {}) + else + potential_lines_without_trace_points(iseq, {}) + end + end + + def self.potential_lines_with_trace_points(iseq, lines) + iseq.trace_points.each { |(line, _)| lines[line] = true } + iseq.each_child do |child| + potential_lines_with_trace_points(child, lines) + end + + lines.keys.sort + end + + private_class_method :potential_lines_with_trace_points + + def self.potential_lines_without_trace_points(iseq, lines) + iseq.disasm.each_line do |line| + res = /^\d+ (?\w+)\s+.+\(\s*(?\d+)\)$/.match(line) + next unless res && res[:insn] == "trace" + + lines[res[:lineno].to_i] = true + end + + lines.keys + end + + private_class_method :potential_lines_without_trace_points + + # + # Returns true if a breakpoint could be set in line number +lineno+ in file + # name +filename. + # + def self.potential_line?(filename, lineno) + potential_lines(filename).member?(lineno) + end + + # + # True if there's no breakpoints + # + def self.none? + Byebug.breakpoints.empty? + end + + # + # Prints all information associated to the breakpoint + # + def inspect + meths = %w[id pos source expr hit_condition hit_count hit_value enabled?] + values = meths.map { |field| "#{field}: #{send(field)}" }.join(", ") + "#" + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/byebug.bundle b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/byebug.bundle new file mode 100755 index 00000000..ae31c0c5 Binary files /dev/null and b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/byebug.bundle differ diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/command.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/command.rb new file mode 100644 index 00000000..fccec63f --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/command.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +require "forwardable" +require "byebug/helpers/string" + +module Byebug + # + # Parent class of all byebug commands. + # + # Subclass it and name the subclass ending with the word Command to implement + # your own custom command. + # + # @example Define a custom command + # + # class MyCustomCommand < Command + # def self.regexp + # /custom_regexp/ + # end + # + # def self.description + # "Custom long desc" + # end + # + # def.short_description + # "Custom short desc" + # end + # + # def execute + # # My command's implementation + # end + # end + # + class Command + extend Forwardable + + attr_reader :processor + + def initialize(processor, input = self.class.to_s) + @processor = processor + @match = match(input) + end + + def context + @context ||= processor.context + end + + def frame + @frame ||= context.frame + end + + def arguments + @match[0].split(" ").drop(1).join(" ") + end + + def_delegators "self.class", :help, :match + + def_delegator "processor.printer", :print, :pr + def_delegator "processor.printer", :print_collection, :prc + def_delegator "processor.printer", :print_variables, :prv + + def_delegators "processor.interface", :errmsg, :puts, :print, :confirm + + class << self + include Helpers::StringHelper + + # + # Special methods to allow command filtering in processors + # + attr_accessor :allow_in_control, :allow_in_post_mortem + + attr_writer :always_run + + def always_run + @always_run ||= 0 + end + + # + # Name of the command, as executed by the user. + # + def to_s + name + .split("::") + .map { |n| n.gsub(/Command$/, "").downcase if n =~ /Command$/ } + .compact + .join(" ") + end + + def columnize(width) + format( + " %-#{width}s -- %s\n", + name: to_s, + description: short_description + ) + end + + # + # Default help text for a command. + # + def help + prettify(description) + end + + # + # Command's regexp match against an input + # + def match(input) + regexp.match(input) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/command_list.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/command_list.rb new file mode 100644 index 00000000..f19bda2c --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/command_list.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "byebug/errors" + +module Byebug + # + # Holds an array of subcommands for a command + # + class CommandList + include Enumerable + + def initialize(commands) + @commands = commands.sort_by(&:to_s) + end + + def match(input) + find { |cmd| cmd.match(input) } + end + + def each + @commands.each { |cmd| yield(cmd) } + end + + def to_s + "\n" + map { |cmd| cmd.columnize(width) }.join + "\n" + end + + private + + def width + @width ||= map(&:to_s).max_by(&:size).size + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands.rb new file mode 100644 index 00000000..bba6fab8 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "byebug/commands/break" +require "byebug/commands/catch" +require "byebug/commands/condition" +require "byebug/commands/continue" +require "byebug/commands/debug" +require "byebug/commands/delete" +require "byebug/commands/disable" +require "byebug/commands/display" +require "byebug/commands/down" +require "byebug/commands/edit" +require "byebug/commands/enable" +require "byebug/commands/finish" +require "byebug/commands/frame" +require "byebug/commands/help" +require "byebug/commands/history" +require "byebug/commands/info" +require "byebug/commands/interrupt" +require "byebug/commands/irb" +require "byebug/commands/kill" +require "byebug/commands/list" +require "byebug/commands/method" +require "byebug/commands/next" +require "byebug/commands/pry" +require "byebug/commands/quit" +require "byebug/commands/restart" +require "byebug/commands/save" +require "byebug/commands/set" +require "byebug/commands/show" +require "byebug/commands/skip" +require "byebug/commands/source" +require "byebug/commands/step" +require "byebug/commands/thread" +require "byebug/commands/tracevar" +require "byebug/commands/undisplay" +require "byebug/commands/untracevar" +require "byebug/commands/up" +require "byebug/commands/var" +require "byebug/commands/where" diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/break.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/break.rb new file mode 100644 index 00000000..947dc22c --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/break.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +require "byebug/command" +require "byebug/helpers/eval" +require "byebug/helpers/file" +require "byebug/helpers/parse" +require "byebug/source_file_formatter" + +module Byebug + # + # Implements breakpoint functionality + # + class BreakCommand < Command + include Helpers::EvalHelper + include Helpers::FileHelper + include Helpers::ParseHelper + + self.allow_in_control = true + + def self.regexp + /^\s* b(?:reak)? (?:\s+ (.+?))? (?:\s+ if \s+(.+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + b[reak] [:] [if ] + b[reak] [::...](.|#) [if ] + + They can be specified by line or method and an expression can be added + for conditionally enabled breakpoints. + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Sets breakpoints in the source code" + end + + def execute + return puts(help) unless @match[1] + + b = line_breakpoint(@match[1]) || method_breakpoint(@match[1]) + return errmsg(pr("break.errors.location")) unless b + + return puts(pr("break.created", id: b.id, file: b.source, line: b.pos)) if syntax_valid?(@match[2]) + + errmsg(pr("break.errors.expression", expr: @match[2])) + b.enabled = false + end + + private + + def line_breakpoint(location) + line_match = location.match(/^(\d+)$/) + file_line_match = location.match(/^(.+):(\d+)$/) + return unless line_match || file_line_match + + file = line_match ? frame.file : file_line_match[1] + line = line_match ? line_match[1].to_i : file_line_match[2].to_i + + add_line_breakpoint(file, line) + end + + def method_breakpoint(location) + location.match(/([^.#]+)[.#](.+)/) do |match| + klass = target_object(match[1]) + method = match[2].intern + + Breakpoint.add(klass, method, @match[2]) + end + end + + def target_object(str) + k = error_eval(str) + + k&.is_a?(Module) ? k.name : str + rescue StandardError + errmsg("Warning: breakpoint source is not yet defined") + str + end + + def add_line_breakpoint(file, line) + raise(pr("break.errors.source", file: file)) unless File.exist?(file) + + fullpath = File.realpath(file) + + raise(pr("break.errors.far_line", lines: n_lines(file), file: fullpath)) if line > n_lines(file) + + unless Breakpoint.potential_line?(fullpath, line) + msg = pr( + "break.errors.line", + file: fullpath, + line: line, + valid_breakpoints: valid_breakpoints_for(fullpath, line) + ) + + raise(msg) + end + + Breakpoint.add(fullpath, line, @match[2]) + end + + def valid_breakpoints_for(path, line) + potential_lines = Breakpoint.potential_lines(path) + annotator = ->(n) { potential_lines.include?(n) ? "[B]" : " " } + source_file_formatter = SourceFileFormatter.new(path, annotator) + + source_file_formatter.lines_around(line).join.chomp + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/catch.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/catch.rb new file mode 100644 index 00000000..4fa52cbb --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/catch.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require "byebug/command" +require "byebug/helpers/eval" + +module Byebug + # + # Implements exception catching. + # + # Enables the user to catch unhandled assertion when they happen. + # + class CatchCommand < Command + include Helpers::EvalHelper + + self.allow_in_post_mortem = true + + def self.regexp + /^\s* cat(?:ch)? (?:\s+(\S+))? (?:\s+(off))? \s*$/x + end + + def self.description + <<-DESCRIPTION + cat[ch][ (off|[ off])] + + #{short_description} + + catch -- lists catchpoints + catch off -- deletes all catchpoints + catch -- enables handling + catch off -- disables handling + DESCRIPTION + end + + def self.short_description + "Handles exception catchpoints" + end + + def execute + return info unless @match[1] + + return @match[1] == "off" ? clear : add(@match[1]) unless @match[2] + + return errmsg pr("catch.errors.off", off: cmd) unless @match[2] == "off" + + remove(@match[1]) + end + + private + + def remove(exception) + return errmsg pr("catch.errors.not_found", exception: exception) unless Byebug.catchpoints.member?(exception) + + puts pr("catch.removed", exception: exception) + Byebug.catchpoints.delete(exception) + end + + def add(exception) + errmsg pr("catch.errors.not_class", class: exception) if warning_eval(exception.is_a?(Class).to_s) + + puts pr("catch.added", exception: exception) + Byebug.add_catchpoint(exception) + end + + def clear + Byebug.catchpoints.clear if confirm(pr("catch.confirmations.delete_all")) + end + + def info + if Byebug.catchpoints && !Byebug.catchpoints.empty? + Byebug.catchpoints.each_key do |exception| + puts("#{exception}: #{exception.is_a?(Class)}") + end + else + puts "No exceptions set to be caught." + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/condition.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/condition.rb new file mode 100644 index 00000000..e4e81fbf --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/condition.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require "byebug/command" +require "byebug/helpers/parse" + +module Byebug + # + # Implements conditions on breakpoints. + # + # Adds the ability to stop on breakpoints only under certain conditions. + # + class ConditionCommand < Command + include Helpers::ParseHelper + + self.allow_in_post_mortem = true + + def self.regexp + /^\s* cond(?:ition)? (?:\s+(\d+)(?:\s+(.*))?)? \s*$/x + end + + def self.description + <<-DESCRIPTION + cond[ition] [ expr] + + #{short_description} + + Specify breakpoint number to break only if is true. is + an integer and is an expression to be evaluated whenever + breakpoint is reached. If no expression is specified, the condition + is removed. + DESCRIPTION + end + + def self.short_description + "Sets conditions on breakpoints" + end + + def execute + return puts(help) unless @match[1] + + breakpoints = Byebug.breakpoints.sort_by(&:id) + return errmsg(pr("condition.errors.no_breakpoints")) if breakpoints.empty? + + pos, err = get_int(@match[1], "Condition", 1) + return errmsg(err) if err + + breakpoint = breakpoints.find { |b| b.id == pos } + return errmsg(pr("break.errors.no_breakpoint")) unless breakpoint + + return errmsg(pr("break.errors.not_changed", expr: @match[2])) unless syntax_valid?(@match[2]) + + breakpoint.expr = @match[2] + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/continue.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/continue.rb new file mode 100644 index 00000000..2f6d058c --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/continue.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require "byebug/command" +require "byebug/helpers/parse" + +module Byebug + # + # Implements the continue command. + # + # Allows the user to continue execution until the next stopping point, a + # specific line number or until program termination. + # + class ContinueCommand < Command + include Helpers::ParseHelper + + def self.regexp + /^\s* c(?:ont(?:inue)?)? (?:(!|\s+unconditionally|\s+\S+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + c[ont[inue]][ ] + + #{short_description} + + Normally the program stops at the next breakpoint. However, if the + parameter "unconditionally" is given or the command is suffixed with + "!", the program will run until the end regardless of any enabled + breakpoints. + DESCRIPTION + end + + def self.short_description + "Runs until program ends, hits a breakpoint or reaches a line" + end + + def execute + if until_line? + num, err = get_int(modifier, "Continue", 0, nil) + return errmsg(err) unless num + + filename = File.expand_path(frame.file) + return errmsg(pr("continue.errors.unstopped_line", line: num)) unless Breakpoint.potential_line?(filename, num) + + Breakpoint.add(filename, num) + end + + processor.proceed! + + Byebug.mode = :off if unconditionally? + Byebug.stop if unconditionally? || Byebug.stoppable? + end + + private + + def until_line? + @match[1] && !["!", "unconditionally"].include?(modifier) + end + + def unconditionally? + @match[1] && ["!", "unconditionally"].include?(modifier) + end + + def modifier + @match[1].lstrip + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/debug.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/debug.rb new file mode 100644 index 00000000..0dcaf696 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/debug.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "byebug/command" +require "byebug/helpers/eval" + +module Byebug + # + # Spawns a subdebugger and evaluates the given expression + # + class DebugCommand < Command + include Helpers::EvalHelper + + def self.regexp + /^\s* debug (?:\s+(\S+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + debug + + #{short_description} + + Allows, for example, setting breakpoints on expressions evaluated from + the debugger's prompt. + DESCRIPTION + end + + def self.short_description + "Spawns a subdebugger" + end + + def execute + return puts(help) unless @match[1] + + puts safe_inspect(separate_thread_eval(@match[1])) + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/delete.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/delete.rb new file mode 100644 index 00000000..19c60f09 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/delete.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require "byebug/command" +require "byebug/helpers/parse" + +module Byebug + # + # Implements breakpoint deletion. + # + class DeleteCommand < Command + include Helpers::ParseHelper + + self.allow_in_control = true + self.allow_in_post_mortem = true + + def self.regexp + /^\s* del(?:ete)? (?:\s+(.*))?$/x + end + + def self.description + <<-DESCRIPTION + del[ete][ nnn...] + + #{short_description} + + Without and argument, deletes all breakpoints. With integer arguments, + it deletes specific breakpoints. + DESCRIPTION + end + + def self.short_description + "Deletes breakpoints" + end + + def execute + unless @match[1] + Byebug.breakpoints.clear if confirm(pr("break.confirmations.delete_all")) + + return + end + + @match[1].split(/ +/).each do |number| + pos, err = get_int(number, "Delete", 1) + + return errmsg(err) unless pos + + if Breakpoint.remove(pos) + puts(pr("break.messages.breakpoint_deleted", pos: pos)) + else + errmsg(pr("break.errors.no_breakpoint_delete", pos: pos)) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/disable.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/disable.rb new file mode 100644 index 00000000..1419f889 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/disable.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require "byebug/subcommands" + +require "byebug/commands/disable/breakpoints" +require "byebug/commands/disable/display" + +module Byebug + # + # Disabling custom display expressions or breakpoints. + # + class DisableCommand < Command + include Subcommands + + self.allow_in_post_mortem = true + + def self.regexp + /^\s* dis(?:able)? (?:\s+ (.+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + dis[able][[ breakpoints| display)][ n1[ n2[ ...[ nn]]]]] + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Disables breakpoints or displays" + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/disable/breakpoints.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/disable/breakpoints.rb new file mode 100644 index 00000000..c3b53007 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/disable/breakpoints.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require "byebug/helpers/toggle" + +module Byebug + # + # Reopens the +disable+ command to define the +breakpoints+ subcommand + # + class DisableCommand < Command + # + # Disables all or specific breakpoints + # + class BreakpointsCommand < Command + include Helpers::ToggleHelper + + self.allow_in_post_mortem = true + + def self.regexp + /^\s* b(?:reakpoints)? (?:\s+ (.+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + dis[able] b[reakpoints][ .. ] + + #{short_description} + + Give breakpoint numbers (separated by spaces) as arguments or no + argument at all if you want to disable every breakpoint. + DESCRIPTION + end + + def self.short_description + "Disable all or specific breakpoints." + end + + def execute + enable_disable_breakpoints("disable", @match[1]) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/disable/display.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/disable/display.rb new file mode 100644 index 00000000..3b784e15 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/disable/display.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require "byebug/helpers/toggle" + +module Byebug + # + # Reopens the +disable+ command to define the +display+ subcommand + # + class DisableCommand < Command + # + # Enables all or specific displays + # + class DisplayCommand < Command + include Helpers::ToggleHelper + + self.allow_in_post_mortem = true + + def self.regexp + /^\s* d(?:isplay)? (?:\s+ (.+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + dis[able] d[isplay][ .. ] + + #{short_description} + + Arguments are the code numbers of the expressions to disable. Do "info + display" to see the current list of code numbers. If no arguments are + specified, all displays are disabled. + DESCRIPTION + end + + def self.short_description + "Disables expressions to be displayed when program stops." + end + + def execute + enable_disable_display("disable", @match[1]) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/display.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/display.rb new file mode 100644 index 00000000..7a03c5ec --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/display.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require "byebug/command" +require "byebug/helpers/eval" + +module Byebug + # + # Custom expressions to be displayed every time the debugger stops. + # + class DisplayCommand < Command + include Helpers::EvalHelper + + self.allow_in_post_mortem = false + self.always_run = 2 + + def self.regexp + /^\s* disp(?:lay)? (?:\s+ (.+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + disp[lay][ ] + + #{short_description} + + If specified, adds into display expression + list. Otherwise, it lists all expressions. + DESCRIPTION + end + + def self.short_description + "Evaluates expressions every time the debugger stops" + end + + def execute + return print_display_expressions unless @match && @match[1] + + Byebug.displays.push [true, @match[1]] + display_expression(@match[1]) + end + + private + + def display_expression(exp) + print pr("display.result", n: Byebug.displays.size, + exp: exp, + result: eval_expr(exp)) + end + + def print_display_expressions + result = prc("display.result", Byebug.displays) do |item, index| + active, exp = item + + { n: index + 1, exp: exp, result: eval_expr(exp) } if active + end + + print result + end + + def eval_expr(expression) + error_eval(expression).inspect + rescue StandardError + "(undefined)" + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/down.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/down.rb new file mode 100644 index 00000000..1c2c0432 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/down.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require "pathname" +require "byebug/command" +require "byebug/helpers/frame" +require "byebug/helpers/parse" + +module Byebug + # + # Move the current frame down in the backtrace. + # + class DownCommand < Command + include Helpers::FrameHelper + include Helpers::ParseHelper + + self.allow_in_post_mortem = true + + def self.regexp + /^\s* down (?:\s+(\S+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + down[ count] + + #{short_description} + + Use the "bt" command to find out where you want to go. + DESCRIPTION + end + + def self.short_description + "Moves to a lower frame in the stack trace" + end + + def execute + pos, err = parse_steps(@match[1], "Down") + return errmsg(err) unless pos + + jump_frames(-pos) + + ListCommand.new(processor).execute if Setting[:autolist] + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/edit.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/edit.rb new file mode 100644 index 00000000..76e72327 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/edit.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require "byebug/command" + +module Byebug + # + # Edit a file from byebug's prompt. + # + class EditCommand < Command + self.allow_in_control = true + self.allow_in_post_mortem = true + + def self.regexp + /^\s* ed(?:it)? (?:\s+(\S+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + edit[ file:lineno] + + #{short_description} + + With no argumnt, edits file containing most re line listed. Editing + targets can also be specified to start editing at a specific line in a + specific file + DESCRIPTION + end + + def self.short_description + "Edits source files" + end + + def execute + file, line = location(@match[1]) + return edit_error("not_exist", file) unless File.exist?(file) + return edit_error("not_readable", file) unless File.readable?(file) + + cmd = line ? "#{editor} +#{line} #{file}" : "#{editor} #{file}" + + Kernel.system(cmd) + end + + private + + def location(matched) + if matched.nil? + file = frame.file + return errmsg(pr("edit.errors.state")) unless file + + line = frame.line + elsif (@pos_match = /([^:]+)[:]([0-9]+)/.match(matched)) + file, line = @pos_match.captures + else + file = matched + line = nil + end + + [File.expand_path(file), line] + end + + def editor + ENV["EDITOR"] || "vim" + end + + def edit_error(type, file) + errmsg(pr("edit.errors.#{type}", file: file)) + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/enable.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/enable.rb new file mode 100644 index 00000000..5a3b11ef --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/enable.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require "byebug/subcommands" + +require "byebug/commands/enable/breakpoints" +require "byebug/commands/enable/display" + +module Byebug + # + # Enabling custom display expressions or breakpoints. + # + class EnableCommand < Command + include Subcommands + + self.allow_in_post_mortem = true + + def self.regexp + /^\s* en(?:able)? (?:\s+ (.+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + en[able][[ b[reakpoints]| d[isplay])][ n1[ n2[ ...[ nn]]]]] + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Enables breakpoints or displays" + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/enable/breakpoints.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/enable/breakpoints.rb new file mode 100644 index 00000000..a57465f5 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/enable/breakpoints.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require "byebug/helpers/toggle" + +module Byebug + # + # Reopens the +enable+ command to define the +breakpoints+ subcommand + # + class EnableCommand < Command + # + # Enables all or specific breakpoints + # + class BreakpointsCommand < Command + include Helpers::ToggleHelper + + self.allow_in_post_mortem = true + + def self.regexp + /^\s* b(?:reakpoints)? (?:\s+ (.+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + en[able] b[reakpoints][ ] + + #{short_description} + + Give breakpoint numbers (separated by spaces) as arguments or no + argument at all if you want to enable every breakpoint. + DESCRIPTION + end + + def self.short_description + "Enable all or specific breakpoints" + end + + def execute + enable_disable_breakpoints("enable", @match[1]) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/enable/display.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/enable/display.rb new file mode 100644 index 00000000..1ba2146b --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/enable/display.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require "byebug/helpers/toggle" + +module Byebug + # + # Reopens the +enable+ command to define the +display+ subcommand + # + class EnableCommand < Command + # + # Enables all or specific displays + # + class DisplayCommand < Command + include Helpers::ToggleHelper + + self.allow_in_post_mortem = true + + def self.regexp + /^\s* d(?:isplay)? (?:\s+ (.+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + en[able] d[isplay][ .. ] + + #{short_description} + + Arguments are the code numbers of the expressions to enable. Do "info + display" to see the current list of code numbers. If no arguments are + specified, all displays are enabled. + DESCRIPTION + end + + def self.short_description + "Enables expressions to be displayed when program stops." + end + + def execute + enable_disable_display("enable", @match[1]) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/finish.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/finish.rb new file mode 100644 index 00000000..1b5411bf --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/finish.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require "byebug/command" +require "byebug/helpers/parse" + +module Byebug + # + # Implements the finish functionality. + # + # Allows the user to continue execution until certain frames are finished. + # + class FinishCommand < Command + include Helpers::ParseHelper + + self.allow_in_post_mortem = false + + def self.regexp + /^\s* fin(?:ish)? (?:\s+(\S+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + fin[ish][ n_frames] + + #{short_description} + + If no number is given, we run until the current frame returns. If a + number of frames `n_frames` is given, then we run until `n_frames` + return from the current position. + DESCRIPTION + end + + def self.short_description + "Runs the program until frame returns" + end + + def execute + if @match[1] + n_frames, err = get_int(@match[1], "finish", 0, max_frames - 1) + return errmsg(err) unless n_frames + else + n_frames = 1 + end + + force = n_frames.zero? ? true : false + context.step_out(context.frame.pos + n_frames, force) + context.frame = 0 + processor.proceed! + end + + private + + def max_frames + context.stack_size - context.frame.pos + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/frame.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/frame.rb new file mode 100644 index 00000000..40533957 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/frame.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require "pathname" +require "byebug/command" +require "byebug/helpers/frame" +require "byebug/helpers/parse" + +module Byebug + # + # Move to specific frames in the backtrace. + # + class FrameCommand < Command + include Helpers::FrameHelper + include Helpers::ParseHelper + + self.allow_in_post_mortem = true + + def self.regexp + /^\s* f(?:rame)? (?:\s+(\S+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + f[rame][ frame-number] + + #{short_description} + + If a frame number has been specified, to moves to that frame. Otherwise + it moves to the newest frame. + + A negative number indicates position from the other end, so "frame -1" + moves to the oldest frame, and "frame 0" moves to the newest frame. + + Without an argument, the command prints the current stack frame. Since + the current position is redisplayed, it may trigger a resyncronization + if there is a front end also watching over things. + + Use the "bt" command to find out where you want to go. + DESCRIPTION + end + + def self.short_description + "Moves to a frame in the call stack" + end + + def execute + return print(pr("frame.line", context.frame.to_hash)) unless @match[1] + + pos, err = get_int(@match[1], "Frame") + return errmsg(err) unless pos + + switch_to_frame(pos) + + ListCommand.new(processor).execute if Setting[:autolist] + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/help.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/help.rb new file mode 100644 index 00000000..18bc4493 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/help.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require "byebug/command" +require "byebug/errors" + +module Byebug + # + # Ask for help from byebug's prompt. + # + class HelpCommand < Command + self.allow_in_control = true + self.allow_in_post_mortem = true + + def self.regexp + /^\s* h(?:elp)? (?:\s+(\S+))? (?:\s+(\S+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + h[elp][ [ ]] + + #{short_description} + + help -- prints a summary of all commands + help -- prints help on command + help -- prints help on 's subcommand + DESCRIPTION + end + + def self.short_description + "Helps you using byebug" + end + + def execute + return help_for_all unless @match[1] + + return help_for(@match[1], command) unless @match[2] + + help_for(@match[2], subcommand) + end + + private + + def help_for_all + puts(processor.command_list.to_s) + end + + def help_for(input, cmd) + raise CommandNotFound.new(input, command) unless cmd + + puts(cmd.help) + end + + def command + @command ||= processor.command_list.match(@match[1]) + end + + def subcommand + return unless command + + @subcommand ||= command.subcommand_list.match(@match[2]) + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/history.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/history.rb new file mode 100644 index 00000000..abd4da69 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/history.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require "byebug/command" +require "byebug/helpers/parse" + +module Byebug + # + # Show history of byebug commands. + # + class HistoryCommand < Command + include Helpers::ParseHelper + + self.allow_in_post_mortem = true + + def self.regexp + /^\s* hist(?:ory)? (?:\s+(?.+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + hist[ory][ num_cmds] + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Shows byebug's history of commands" + end + + def execute + history = processor.interface.history + + size, = get_int(@match[:num_cmds], "history", 1) if @match[:num_cmds] + + puts history.to_s(size) + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/info.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/info.rb new file mode 100644 index 00000000..e895b828 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/info.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require "byebug/subcommands" + +require "byebug/commands/info/breakpoints" +require "byebug/commands/info/display" +require "byebug/commands/info/file" +require "byebug/commands/info/line" +require "byebug/commands/info/program" + +module Byebug + # + # Shows info about different aspects of the debugger. + # + class InfoCommand < Command + include Subcommands + + self.allow_in_control = true + self.allow_in_post_mortem = true + + def self.regexp + /^\s* i(?:nfo)? (?:\s+ (.+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + info[ subcommand] + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Shows several informations about the program being debugged" + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/info/breakpoints.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/info/breakpoints.rb new file mode 100644 index 00000000..dad21f48 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/info/breakpoints.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +module Byebug + # + # Reopens the +info+ command to define the +breakpoints+ subcommand + # + class InfoCommand < Command + # + # Information about current breakpoints + # + class BreakpointsCommand < Command + self.allow_in_post_mortem = true + + def self.regexp + /^\s* b(?:reakpoints)? (?:\s+ (.+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + inf[o] b[reakpoints] + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Status of user settable breakpoints" + end + + def execute + return puts("No breakpoints.") if Byebug.breakpoints.empty? + + breakpoints = Byebug.breakpoints.sort_by(&:id) + + if @match[1] + indices = @match[1].split(/ +/).map(&:to_i) + breakpoints = breakpoints.select { |b| indices.member?(b.id) } + return errmsg("No breakpoints found among list given") if breakpoints.empty? + end + + puts "Num Enb What" + breakpoints.each { |b| info_breakpoint(b) } + end + + private + + def info_breakpoint(brkpt) + interp = format( + "%-3d %-3s at %s:%s%s", + id: brkpt.id, + status: brkpt.enabled? ? "y" : "n", + file: brkpt.source, + line: brkpt.pos, + expression: brkpt.expr.nil? ? "" : " if #{brkpt.expr}" + ) + puts interp + hits = brkpt.hit_count + return unless hits.positive? + + s = hits > 1 ? "s" : "" + puts " breakpoint already hit #{hits} time#{s}" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/info/display.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/info/display.rb new file mode 100644 index 00000000..8fbb03be --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/info/display.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Byebug + # + # Reopens the +info+ command to define the +display+ subcommand + # + class InfoCommand < Command + # + # Information about display expressions + # + class DisplayCommand < Command + self.allow_in_post_mortem = true + + def self.regexp + /^\s* d(?:isplay)? \s*$/x + end + + def self.description + <<-DESCRIPTION + inf[o] d[display] + + #{short_description} + DESCRIPTION + end + + def self.short_description + "List of expressions to display when program stops" + end + + def execute + return puts("There are no auto-display expressions now.") unless Byebug.displays.find { |d| d[0] } + + puts "Auto-display expressions now in effect:" + puts "Num Enb Expression" + + Byebug.displays.each_with_index do |d, i| + interp = format( + "%3d: %s %s", + number: i + 1, + status: d[0] ? "y" : "n", + expression: d[1] + ) + + puts(interp) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/info/file.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/info/file.rb new file mode 100644 index 00000000..dfb1b0db --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/info/file.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require "byebug/helpers/file" + +module Byebug + # + # Reopens the +info+ command to define the +file+ subcommand + # + class InfoCommand < Command + # + # Information about a particular source file + # + class FileCommand < Command + include Helpers::FileHelper + include Helpers::StringHelper + + self.allow_in_post_mortem = true + + def self.regexp + /^\s* f(?:ile)? (?:\s+ (.+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + inf[o] f[ile] + + #{short_description} + + It informs about file name, number of lines, possible breakpoints in + the file, last modification time and sha1 digest. + DESCRIPTION + end + + def self.short_description + "Information about a particular source file." + end + + def execute + file = @match[1] || frame.file + return errmsg(pr("info.errors.undefined_file", file: file)) unless File.exist?(file) + + puts prettify <<-RUBY + File #{info_file_basic(file)} + + Breakpoint line numbers: #{info_file_breakpoints(file)} + + Modification time: #{info_file_mtime(file)} + + Sha1 Signature: #{info_file_sha1(file)} + RUBY + end + + private + + def info_file_basic(file) + path = File.expand_path(file) + return unless File.exist?(path) + + s = n_lines(path) == 1 ? "" : "s" + "#{path} (#{n_lines(path)} line#{s})" + end + + def info_file_breakpoints(file) + breakpoints = Breakpoint.potential_lines(file) + return unless breakpoints + + breakpoints.to_a.sort.join(" ") + end + + def info_file_mtime(file) + File.stat(file).mtime + end + + def info_file_sha1(file) + require "digest/sha1" + Digest::SHA1.hexdigest(file) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/info/line.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/info/line.rb new file mode 100644 index 00000000..f32bc150 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/info/line.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Byebug + # + # Reopens the +info+ command to define the +line+ subcommand + # + class InfoCommand < Command + # + # Information about current location + # + class LineCommand < Command + self.allow_in_post_mortem = true + + def self.regexp + /^\s* l(?:ine)? \s*$/x + end + + def self.description + <<-DESCRIPTION + inf[o] l[ine] + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Line number and file name of current position in source file." + end + + def execute + puts "Line #{frame.line} of \"#{frame.file}\"" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/info/program.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/info/program.rb new file mode 100644 index 00000000..1b65486a --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/info/program.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Byebug + # + # Reopens the +info+ command to define the +args+ subcommand + # + class InfoCommand < Command + # + # Information about arguments of the current method/block + # + class ProgramCommand < Command + self.allow_in_post_mortem = true + + def self.regexp + /^\s* p(?:rogram)? \s*$/x + end + + def self.description + <<-DESCRIPTION + inf[o] p[rogram] + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Information about the current status of the debugged program." + end + + def execute + puts "Program stopped. " + format_stop_reason context.stop_reason + end + + private + + def format_stop_reason(stop_reason) + case stop_reason + when :step + puts "It stopped after stepping, next'ing or initial start." + when :breakpoint + puts "It stopped at a breakpoint." + when :catchpoint + puts "It stopped at a catchpoint." + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/interrupt.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/interrupt.rb new file mode 100644 index 00000000..c6381c60 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/interrupt.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "byebug/command" + +module Byebug + # + # Interrupting execution of current thread. + # + class InterruptCommand < Command + self.allow_in_control = true + + def self.regexp + /^\s*int(?:errupt)?\s*$/ + end + + def self.description + <<-DESCRIPTION + int[errupt] + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Interrupts the program" + end + + def execute + Byebug.start + + Byebug.thread_context(Thread.main).interrupt + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/irb.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/irb.rb new file mode 100644 index 00000000..033f185a --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/irb.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "byebug/command" +require "irb" +require "English" + +module Byebug + # + # Enter IRB from byebug's prompt + # + class IrbCommand < Command + self.allow_in_post_mortem = true + + def self.regexp + /^\s* irb \s*$/x + end + + def self.description + <<-DESCRIPTION + irb + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Starts an IRB session" + end + + def execute + return errmsg(pr("base.errors.only_local")) unless processor.interface.instance_of?(LocalInterface) + + # @todo IRB tries to parse $ARGV so we must clear it (see #197). Add a + # test case for it so we can remove this comment. + with_clean_argv { IRB.start } + end + + private + + def with_clean_argv + saved_argv = $ARGV.dup + $ARGV.clear + begin + yield + ensure + $ARGV.concat(saved_argv) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/kill.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/kill.rb new file mode 100644 index 00000000..da830a3b --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/kill.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require "byebug/command" + +module Byebug + # + # Send custom signals to the debugged program. + # + class KillCommand < Command + self.allow_in_control = true + + def self.regexp + /^\s* kill \s* (?:\s+(\S+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + kill[ signal] + + #{short_description} + + Equivalent of Process.kill(Process.pid) + DESCRIPTION + end + + def self.short_description + "Sends a signal to the current process" + end + + def execute + if @match[1] + signame = @match[1] + + return errmsg("signal name #{signame} is not a signal I know about\n") unless Signal.list.member?(signame) + else + return unless confirm("Really kill? (y/n) ") + + signame = "KILL" + end + + processor.interface.close if signame == "KILL" + Process.kill(signame, Process.pid) + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/list.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/list.rb new file mode 100644 index 00000000..febcab03 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/list.rb @@ -0,0 +1,159 @@ +# frozen_string_literal: true + +require "byebug/command" +require "byebug/source_file_formatter" +require "byebug/helpers/file" +require "byebug/helpers/parse" + +module Byebug + # + # List parts of the source code. + # + class ListCommand < Command + include Helpers::FileHelper + include Helpers::ParseHelper + + self.allow_in_post_mortem = true + + def self.regexp + /^\s* l(?:ist)? (?:\s*([-=])|\s+(\S+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + l[ist][[-=]][ nn-mm] + + #{short_description} + + Lists lines forward from current line or from the place where code was + last listed. If "list-" is specified, lists backwards instead. If + "list=" is specified, lists from current line regardless of where code + was last listed. A line range can also be specified to list specific + sections of code. + DESCRIPTION + end + + def self.short_description + "Lists lines of source code" + end + + def execute + msg = "No sourcefile available for #{frame.file}" + raise(msg) unless File.exist?(frame.file) + + b, e = range(@match[2]) + + display_lines(b, e) + + processor.prev_line = b + end + + private + + # + # Line range to be printed by `list`. + # + # If is set, range is parsed from it. + # + # Otherwise it's automatically chosen. + # + def range(input) + return auto_range(@match[1] || "+") unless input + + b, e = parse_range(input) + raise("Invalid line range") unless valid_range?(b, e) + + [b, e] + end + + def valid_range?(first, last) + first <= last && (1..max_line).cover?(first) && (1..max_line).cover?(last) + end + + # + # Set line range to be printed by list + # + # @return first line number to list + # @return last line number to list + # + def auto_range(direction) + prev_line = processor.prev_line + + if direction == "=" || prev_line.nil? + source_file_formatter.range_around(frame.line) + else + source_file_formatter.range_from(move(prev_line, size, direction)) + end + end + + def parse_range(input) + first, err = get_int(lower_bound(input), "List", 1, max_line) + raise(err) unless first + + if upper_bound(input) + last, err = get_int(upper_bound(input), "List", 1, max_line) + raise(err) unless last + + last = amend_final(last) + else + first -= (size / 2) + end + + [first, last || move(first, size - 1)] + end + + def move(line, size, direction = "+") + line.send(direction, size) + end + + # + # Show a range of lines in the current file. + # + # @param min [Integer] Lower bound + # @param max [Integer] Upper bound + # + def display_lines(min, max) + puts "\n[#{min}, #{max}] in #{frame.file}" + + puts source_file_formatter.lines(min, max).join + end + + # + # @param range [String] A string with an integer range format + # + # @return [String] The lower bound of the given range + # + def lower_bound(range) + split_range(range)[0] + end + + # + # @param range [String] A string with an integer range format + # + # @return [String] The upper bound of the given range + # + def upper_bound(range) + split_range(range)[1] + end + + # + # @param str [String] A string with an integer range format + # + # @return [Array] The upper & lower bounds of the given range + # + def split_range(str) + str.split(/[-,]/) + end + + extend Forwardable + + def_delegators :source_file_formatter, :amend_final, :size, :max_line + + def source_file_formatter + @source_file_formatter ||= SourceFileFormatter.new( + frame.file, + ->(n) { n == frame.line ? "=>" : " " } + ) + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/method.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/method.rb new file mode 100644 index 00000000..f2b2d156 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/method.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require "byebug/command" +require "byebug/helpers/eval" + +module Byebug + # + # Show methods of specific classes/modules/objects. + # + class MethodCommand < Command + include Helpers::EvalHelper + + self.allow_in_post_mortem = true + + def self.regexp + /^\s* m(?:ethod)? \s+ (i(:?nstance)?\s+)?/x + end + + def self.description + <<-DESCRIPTION + m[ethod] (i[nstance][ ]|) + + #{short_description} + + When invoked with "instance", shows instance methods of the object + specified as argument or of self no object was specified. + + When invoked only with a class or module, shows class methods of the + class or module specified as argument. + DESCRIPTION + end + + def self.short_description + "Shows methods of an object, class or module" + end + + def execute + obj = warning_eval(@match.post_match) + + result = + if @match[1] + prc("method.methods", obj.methods.sort) { |item, _| { name: item } } + elsif !obj.is_a?(Module) + pr("variable.errors.not_module", object: @match.post_match) + else + prc("method.methods", obj.instance_methods(false).sort) do |item, _| + { name: item } + end + end + puts result + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/next.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/next.rb new file mode 100644 index 00000000..10df0cf9 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/next.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "byebug/command" +require "byebug/helpers/parse" + +module Byebug + # + # Implements the next functionality. + # + # Allows the user the continue execution until the next instruction in the + # current frame. + # + class NextCommand < Command + include Helpers::ParseHelper + + def self.regexp + /^\s* n(?:ext)? (?:\s+(\S+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + n[ext][ nnn] + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Runs one or more lines of code" + end + + def execute + steps, err = parse_steps(@match[1], "Next") + return errmsg(err) unless steps + + context.step_over(steps, context.frame.pos) + processor.proceed! + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/pry.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/pry.rb new file mode 100644 index 00000000..c596bd5b --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/pry.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require "byebug/command" +require "byebug/helpers/eval" + +module Byebug + # + # Enter Pry from byebug's prompt + # + class PryCommand < Command + self.allow_in_post_mortem = true + + def self.regexp + /^\s* pry \s*$/x + end + + def self.description + <<-DESCRIPTION + pry + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Starts a Pry session" + end + + def execute + return errmsg(pr("base.errors.only_local")) unless processor.interface.instance_of?(LocalInterface) + + begin + require "pry" + rescue LoadError + return errmsg(pr("pry.errors.not_installed")) + end + + Pry.start(context.frame._binding) + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/quit.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/quit.rb new file mode 100644 index 00000000..31503deb --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/quit.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require "byebug/command" + +module Byebug + # + # Exit from byebug. + # + class QuitCommand < Command + self.allow_in_control = true + self.allow_in_post_mortem = true + + def self.regexp + /^\s* q(?:uit)? \s* (?:(!|\s+unconditionally))? \s*$/x + end + + def self.description + <<-DESCRIPTION + q[uit][!| unconditionally] + + #{short_description} + + Normally we prompt before exiting. However, if the parameter + "unconditionally" is given or command is suffixed with "!", we exit + without asking further questions. + DESCRIPTION + end + + def self.short_description + "Exits byebug" + end + + def execute + return unless @match[1] || confirm(pr("quit.confirmations.really")) + + processor.interface.autosave + processor.interface.close + + Process.exit! + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/restart.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/restart.rb new file mode 100644 index 00000000..8506e92a --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/restart.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require "byebug/command" +require "byebug/helpers/bin" +require "byebug/helpers/path" +require "shellwords" +require "English" +require "rbconfig" + +module Byebug + # + # Restart debugged program from within byebug. + # + class RestartCommand < Command + include Helpers::BinHelper + include Helpers::PathHelper + + self.allow_in_control = true + self.allow_in_post_mortem = true + + def self.regexp + /^\s* restart (?:\s+(?.+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + restart [args] + + #{short_description} + + This is a re-exec - all byebug state is lost. If command arguments are + passed those are used. + DESCRIPTION + end + + def self.short_description + "Restarts the debugged program" + end + + def execute + cmd = [$PROGRAM_NAME] + + cmd = prepend_byebug_bin(cmd) + cmd = prepend_ruby_bin(cmd) + + cmd += (@match[:args] ? @match[:args].shellsplit : $ARGV) + + puts pr("restart.success", cmd: cmd.shelljoin) + Kernel.exec(*cmd) + end + + private + + def prepend_byebug_bin(cmd) + cmd.unshift(bin_file) if Byebug.mode == :standalone + cmd + end + + def prepend_ruby_bin(cmd) + cmd.unshift(RbConfig.ruby) if which("ruby") != which(cmd.first) + cmd + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/save.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/save.rb new file mode 100644 index 00000000..bf1ad1c3 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/save.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require "byebug/command" + +module Byebug + # + # Save current settings to use them in another debug session. + # + class SaveCommand < Command + self.allow_in_control = true + self.allow_in_post_mortem = true + + def self.regexp + /^\s* sa(?:ve)? (?:\s+(\S+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + save[ FILE] + + #{short_description} + + Byebug state is saved as a script file. This includes breakpoints, + catchpoints, display expressions and some settings. If no filename is + given, byebug will fabricate one. + + Use the "source" command in another debug session to restore the saved + file. + DESCRIPTION + end + + def self.short_description + "Saves current byebug session to a file" + end + + def execute + file = File.open(@match[1] || Setting[:savefile], "w") + + save_breakpoints(file) + save_catchpoints(file) + save_displays(file) + save_settings(file) + + print pr("save.messages.done", path: file.path) + file.close + end + + private + + def save_breakpoints(file) + Byebug.breakpoints.each do |b| + file.puts "break #{b.source}:#{b.pos}#{" if #{b.expr}" if b.expr}" + end + end + + def save_catchpoints(file) + Byebug.catchpoints.each_key do |c| + file.puts "catch #{c}" + end + end + + def save_displays(file) + Byebug.displays.each { |d| file.puts "display #{d[1]}" if d[0] } + end + + def save_settings(file) + %w[autoirb autolist basename].each do |setting| + file.puts "set #{setting} #{Setting[setting.to_sym]}" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/set.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/set.rb new file mode 100644 index 00000000..18dfa175 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/set.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require "byebug/command" +require "byebug/helpers/parse" + +module Byebug + # + # Change byebug settings. + # + class SetCommand < Command + include Helpers::ParseHelper + + self.allow_in_control = true + self.allow_in_post_mortem = true + + def self.regexp + /^\s* set (?:\s+(?\w+))? (?:\s+(?\S+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + set + + #{short_description} + + Boolean values take "on", "off", "true", "false", "1" or "0". If you + don't specify a value, the boolean setting will be enabled. Conversely, + you can use "set no" to disable them. + + You can see these environment settings with the "show" command. + DESCRIPTION + end + + def self.short_description + "Modifies byebug settings" + end + + def self.help + super + Setting.help_all + end + + def execute + key = @match[:setting] + value = @match[:value] + return puts(help) if key.nil? && value.nil? + + setting = Setting.find(key) + return errmsg(pr("set.errors.unknown_setting", key: key)) unless setting + + if !setting.boolean? && value.nil? + err = pr("set.errors.must_specify_value", key: key) + elsif setting.boolean? + value, err = get_onoff(value, key =~ /^no/ ? false : true) + elsif setting.integer? + value, err = get_int(value, setting.to_sym, 1) + end + return errmsg(err) if value.nil? + + setting.value = value + + puts setting.to_s + end + + private + + def get_onoff(arg, default) + return default if arg.nil? + + case arg + when "1", "on", "true" + true + when "0", "off", "false" + false + else + [nil, pr("set.errors.on_off", arg: arg)] + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/show.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/show.rb new file mode 100644 index 00000000..987e47af --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/show.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require "byebug/command" + +module Byebug + # + # Show byebug settings. + # + class ShowCommand < Command + self.allow_in_control = true + self.allow_in_post_mortem = true + + def self.regexp + /^\s* show (?:\s+(?\w+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + show + + #{short_description} + + You can change them with the "set" command. + DESCRIPTION + end + + def self.short_description + "Shows byebug settings" + end + + def self.help + super + Setting.help_all + end + + def execute + key = @match[:setting] + return puts(help) unless key + + setting = Setting.find(key) + return errmsg(pr("show.errors.unknown_setting", key: key)) unless setting + + puts Setting.settings[setting.to_sym] + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/skip.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/skip.rb new file mode 100644 index 00000000..a01cc643 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/skip.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require "byebug/command" +require "byebug/helpers/parse" + +module Byebug + # + # Allows the user to continue execution until the next breakpoint, as + # long as it is different from the current one + # + class SkipCommand < Command + include Helpers::ParseHelper + + class << self + attr_writer :file_line, :file_path + attr_reader :previous_autolist + + def file_line + @file_line ||= 0 + end + + def file_path + @file_path ||= "" + end + + def setup_autolist(value) + @previous_autolist = ListCommand.always_run + ListCommand.always_run = value + end + + def restore_autolist + ListCommand.always_run = @previous_autolist + @previous_autolist = nil + end + end + + def self.regexp + /^\s* sk(?:ip)? \s*$/x + end + + def self.description + <<-DESCRIPTION + sk[ip] + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Runs until the next breakpoint as long as it is different from the current one" + end + + def initialize_attributes + self.class.always_run = 2 + self.class.setup_autolist(0) + self.class.file_path = frame.file + self.class.file_line = frame.line + end + + def keep_execution + [self.class.file_path, self.class.file_line] == [frame.file, frame.line] + end + + def reset_attributes + self.class.always_run = 0 + ListCommand.new(processor).execute if self.class.previous_autolist == 1 + self.class.restore_autolist + end + + def auto_run + return false unless self.class.always_run == 2 + + keep_execution ? processor.proceed! : reset_attributes + true + end + + def execute + return if auto_run + + initialize_attributes + processor.proceed! + Byebug.stop if Byebug.stoppable? + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/source.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/source.rb new file mode 100644 index 00000000..50a64bc2 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/source.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "byebug/command" + +module Byebug + # + # Execute a file containing byebug commands. + # + # It can be used to restore a previously saved debugging session. + # + class SourceCommand < Command + self.allow_in_control = true + self.allow_in_post_mortem = true + + def self.regexp + /^\s* so(?:urce)? (?:\s+(\S+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + source + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Restores a previously saved byebug session" + end + + def execute + return puts(help) unless @match[1] + + file = File.expand_path(@match[1]).strip + return errmsg(pr("source.errors.not_found", file: file)) unless File.exist?(file) + + processor.interface.read_file(file) + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/step.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/step.rb new file mode 100644 index 00000000..3a287116 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/step.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require "byebug/command" +require "byebug/helpers/parse" + +module Byebug + # + # Implements the step functionality. + # + # Allows the user the continue execution until the next instruction, possibily + # in a different frame. Use step to step into method calls or blocks. + # + class StepCommand < Command + include Helpers::ParseHelper + + def self.regexp + /^\s* s(?:tep)? (?:\s+(\S+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + s[tep][ times] + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Steps into blocks or methods one or more times" + end + + def execute + steps, err = parse_steps(@match[1], "Steps") + return errmsg(err) unless steps + + context.step_into(steps, context.frame.pos) + processor.proceed! + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/thread.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/thread.rb new file mode 100644 index 00000000..f93fe66d --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/thread.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "byebug/subcommands" + +require "byebug/commands/thread/current" +require "byebug/commands/thread/list" +require "byebug/commands/thread/resume" +require "byebug/commands/thread/stop" +require "byebug/commands/thread/switch" + +module Byebug + # + # Manipulation of Ruby threads + # + class ThreadCommand < Command + include Subcommands + + def self.regexp + /^\s* th(?:read)? (?:\s+ (.+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + th[read] + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Commands to manipulate threads" + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/thread/current.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/thread/current.rb new file mode 100644 index 00000000..532b41be --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/thread/current.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require "byebug/helpers/thread" + +module Byebug + # + # Reopens the +thread+ command to define the +current+ subcommand + # + class ThreadCommand < Command + # + # Information about the current thread + # + class CurrentCommand < Command + include Helpers::ThreadHelper + + def self.regexp + /^\s* c(?:urrent)? \s*$/x + end + + def self.description + <<-DESCRIPTION + th[read] c[urrent] + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Shows current thread information" + end + + def execute + display_context(context) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/thread/list.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/thread/list.rb new file mode 100644 index 00000000..06b97e6c --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/thread/list.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require "byebug/helpers/thread" + +module Byebug + # + # Reopens the +thread+ command to define the +list+ subcommand + # + class ThreadCommand < Command + # + # Information about threads + # + class ListCommand < Command + include Helpers::ThreadHelper + + def self.regexp + /^\s* l(?:ist)? \s*$/x + end + + def self.description + <<-DESCRIPTION + th[read] l[ist] + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Lists all threads" + end + + def execute + contexts = Byebug.contexts.sort_by(&:thnum) + + thread_list = prc("thread.context", contexts) do |context, _| + thread_arguments(context) + end + + print(thread_list) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/thread/resume.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/thread/resume.rb new file mode 100644 index 00000000..bb8a3237 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/thread/resume.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require "byebug/helpers/thread" + +module Byebug + # + # Reopens the +thread+ command to define the +resume+ subcommand + # + class ThreadCommand < Command + # + # Resumes the specified thread + # + class ResumeCommand < Command + include Helpers::ThreadHelper + + def self.regexp + /^\s* r(?:esume)? (?: \s* (\d+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + th[read] r[esume] + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Resumes execution of the specified thread" + end + + def execute + return puts(help) unless @match[1] + + context, err = context_from_thread(@match[1]) + return errmsg(err) if err + + return errmsg(pr("thread.errors.already_running")) unless context.suspended? + + context.resume + display_context(context) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/thread/stop.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/thread/stop.rb new file mode 100644 index 00000000..1fb1223f --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/thread/stop.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require "byebug/helpers/thread" + +module Byebug + # + # Reopens the +thread+ command to define the +stop+ subcommand + # + class ThreadCommand < Command + # + # Stops the specified thread + # + class StopCommand < Command + include Helpers::ThreadHelper + + def self.regexp + /^\s* st(?:op)? (?: \s* (\d+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + th[read] st[op] + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Stops the execution of the specified thread" + end + + def execute + return puts(help) unless @match[1] + + context, err = context_from_thread(@match[1]) + return errmsg(err) if err + + context.suspend + display_context(context) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/thread/switch.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/thread/switch.rb new file mode 100644 index 00000000..61648747 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/thread/switch.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require "byebug/helpers/thread" + +module Byebug + # + # Reopens the +thread+ command to define the +switch+ subcommand + # + class ThreadCommand < Command + # + # Switches to the specified thread + # + class SwitchCommand < Command + include Helpers::ThreadHelper + + def self.regexp + /^\s* sw(?:itch)? (?: \s* (\d+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + th[read] sw[itch] + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Switches execution to the specified thread" + end + + def execute + return puts(help) unless @match[1] + + context, err = context_from_thread(@match[1]) + return errmsg(err) if err + + display_context(context) + + context.switch + + processor.proceed! + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/tracevar.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/tracevar.rb new file mode 100644 index 00000000..60217ac8 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/tracevar.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require "byebug/command" + +module Byebug + # + # Show (and possibily stop) at every line that changes a global variable. + # + class TracevarCommand < Command + def self.regexp + /^\s* tr(?:acevar)? (?: \s+ (\S+))? # (variable-name)? + (?: \s+ (stop|nostop))? + \s*$/x + end + + def self.description + <<-DESCRIPTION + tr[acevar] [[no]stop] + + #{short_description} + + If "stop" is specified, execution will stop every time the variable + changes its value. If nothing or "nostop" is specified, execution won't + stop, changes will just be logged in byebug's output. + DESCRIPTION + end + + def self.short_description + "Enables tracing of a global variable" + end + + def execute + var = @match[1] + return errmsg(pr("trace.errors.needs_global_variable")) unless var + return errmsg(pr("trace.errors.var_is_not_global", name: var)) unless global_variables.include?(:"#{var}") + + stop = @match[2] && @match[2] !~ /nostop/ + + instance_eval do + trace_var(:"#{var}") { |val| on_change(var, val, stop) } + end + + puts pr("trace.messages.success", var: var) + end + + private + + def on_change(name, value, stop) + puts pr("trace.messages.on_change", name: name, value: value) + + context.step_out(1, false) if stop + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/undisplay.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/undisplay.rb new file mode 100644 index 00000000..5e108a0b --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/undisplay.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require "byebug/command" +require "byebug/helpers/parse" + +module Byebug + # + # Remove expressions from display list. + # + class UndisplayCommand < Command + include Helpers::ParseHelper + + self.allow_in_post_mortem = true + + def self.regexp + /^\s* undisp(?:lay)? (?:\s+(\S+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + undisp[lay][ nnn] + + #{short_description} + + Arguments are the code numbers of the expressions to stop displaying. No + argument means cancel all automatic-display expressions. Type "info + display" to see the current list of code numbers. + DESCRIPTION + end + + def self.short_description + "Stops displaying all or some expressions when program stops" + end + + def execute + if @match[1] + pos, err = get_int(@match[1], "Undisplay", 1, Byebug.displays.size) + return errmsg(err) unless err.nil? + + last_display = Byebug.displays[pos - 1] + return errmsg(pr("display.errors.undefined", expr: pos)) unless last_display + + last_display[0] = nil + else + return unless confirm(pr("display.confirmations.clear_all")) + + Byebug.displays.each { |d| d[0] = false } + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/untracevar.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/untracevar.rb new file mode 100644 index 00000000..77e6dc29 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/untracevar.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require "byebug/command" + +module Byebug + # + # Stop tracing a global variable. + # + class UntracevarCommand < Command + def self.regexp + /^\s* untr(?:acevar)? (?:\s+ (\S+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + untr[acevar] + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Stops tracing a global variable" + end + + def execute + var = @match[1] + if global_variables.include?(:"#{var}") + untrace_var(:"#{var}") + puts pr("trace.messages.undo", var: var) + else + errmsg pr("trace.errors.not_global", var: var) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/up.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/up.rb new file mode 100644 index 00000000..c6d2c0bc --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/up.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require "pathname" +require "byebug/command" +require "byebug/helpers/frame" +require "byebug/helpers/parse" + +module Byebug + # + # Move the current frame up in the backtrace. + # + class UpCommand < Command + include Helpers::FrameHelper + include Helpers::ParseHelper + + self.allow_in_post_mortem = true + + def self.regexp + /^\s* up (?:\s+(\S+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + up[ count] + + #{short_description} + + Use the "bt" command to find out where you want to go. + DESCRIPTION + end + + def self.short_description + "Moves to a higher frame in the stack trace" + end + + def execute + pos, err = parse_steps(@match[1], "Up") + return errmsg(err) unless pos + + jump_frames(pos) + + ListCommand.new(processor).execute if Setting[:autolist] + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/var.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/var.rb new file mode 100644 index 00000000..aed74df4 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/var.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require "byebug/subcommands" + +require "byebug/commands/var/all" +require "byebug/commands/var/args" +require "byebug/commands/var/const" +require "byebug/commands/var/instance" +require "byebug/commands/var/local" +require "byebug/commands/var/global" + +module Byebug + # + # Shows variables and its values + # + class VarCommand < Command + include Subcommands + + self.allow_in_post_mortem = true + + def self.regexp + /^\s* v(?:ar)? (?:\s+ (.+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + [v]ar + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Shows variables and its values" + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/var/all.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/var/all.rb new file mode 100644 index 00000000..8e84ff55 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/var/all.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require "byebug/helpers/var" + +module Byebug + # + # Reopens the +var+ command to define the +all+ subcommand + # + class VarCommand < Command + # + # Shows global, instance and local variables + # + class AllCommand < Command + include Helpers::VarHelper + + self.allow_in_post_mortem = true + + def self.regexp + /^\s* a(?:ll)? \s*$/x + end + + def self.description + <<-DESCRIPTION + v[ar] a[ll] + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Shows local, global and instance variables of self." + end + + def execute + var_global + var_instance("self") + var_local + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/var/args.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/var/args.rb new file mode 100644 index 00000000..191e4a8a --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/var/args.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require "byebug/helpers/var" + +module Byebug + # + # Reopens the +var+ command to define the +args+ subcommand + # + class VarCommand < Command + # + # Information about arguments of the current method/block + # + class ArgsCommand < Command + include Helpers::VarHelper + + self.allow_in_post_mortem = true + + def self.regexp + /^\s* a(?:rgs)? \s*$/x + end + + def self.description + <<-DESCRIPTION + v[ar] a[args] + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Information about arguments of the current scope" + end + + def execute + var_args + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/var/const.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/var/const.rb new file mode 100644 index 00000000..9a16c283 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/var/const.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require "byebug/helpers/eval" + +module Byebug + # + # Reopens the +var+ command to define the +const+ subcommand + # + class VarCommand < Command + # + # Shows constants + # + class ConstCommand < Command + include Helpers::EvalHelper + + self.allow_in_post_mortem = true + + def self.regexp + /^\s* c(?:onst)? (?:\s+ (.+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + v[ar] c[onstant] + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Shows constants of an object." + end + + def execute + obj = warning_eval(str_obj) + return errmsg(pr("variable.errors.not_module", object: str_obj)) unless obj.is_a?(Module) + + constants = warning_eval("#{str_obj}.constants") + puts prv(constants.sort.map { |c| [c, obj.const_get(c)] }, "constant") + end + + private + + def str_obj + @str_obj ||= @match[1] || "self.class" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/var/global.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/var/global.rb new file mode 100644 index 00000000..fb28bd28 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/var/global.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module Byebug + # + # Reopens the +var+ command to define the +global+ subcommand + # + class VarCommand < Command + # + # Shows global variables + # + class GlobalCommand < Command + include Helpers::VarHelper + + self.allow_in_post_mortem = true + + def self.regexp + /^\s* g(?:lobal)? \s*$/x + end + + def self.description + <<-DESCRIPTION + v[ar] g[lobal] + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Shows global variables." + end + + def execute + var_global + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/var/instance.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/var/instance.rb new file mode 100644 index 00000000..7b55f4af --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/var/instance.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require "byebug/helpers/var" + +module Byebug + # + # Reopens the +var+ command to define the +instance+ subcommand + # + class VarCommand < Command + # + # Shows instance variables + # + class InstanceCommand < Command + include Helpers::VarHelper + + self.allow_in_post_mortem = true + + def self.regexp + /^\s* i(?:nstance)? (?:\s+ (.+))? \s*$/x + end + + def self.description + <<-DESCRIPTION + v[ar] i[nstance][ ] + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Shows instance variables of self or a specific object." + end + + def execute + var_instance(@match[1]) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/var/local.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/var/local.rb new file mode 100644 index 00000000..8d8bbcf7 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/var/local.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require "byebug/helpers/var" + +module Byebug + # + # Reopens the +var+ command to define the +local+ subcommand + # + class VarCommand < Command + # + # Shows local variables in current scope + # + class LocalCommand < Command + include Helpers::VarHelper + + self.allow_in_post_mortem = true + + def self.regexp + /^\s* l(?:ocal)? \s*$/x + end + + def self.description + <<-DESCRIPTION + v[ar] l[ocal] + + #{short_description} + DESCRIPTION + end + + def self.short_description + "Shows local variables in current scope." + end + + def execute + var_local + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/where.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/where.rb new file mode 100644 index 00000000..bcb7948b --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/commands/where.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require "pathname" +require "byebug/command" +require "byebug/helpers/frame" + +module Byebug + # + # Show current backtrace. + # + class WhereCommand < Command + include Helpers::FrameHelper + + self.allow_in_post_mortem = true + + def self.regexp + /^\s* (?:w(?:here)?|bt|backtrace) \s*$/x + end + + def self.description + <<-DESCRIPTION + w[here]|bt|backtrace + + #{short_description} + + Print the entire stack frame. Each frame is numbered; the most recent + frame is 0. A frame number can be referred to in the "frame" command. + "up" and "down" add or subtract respectively to frame numbers shown. + The position of the current frame is marked with -->. C-frames hang + from their most immediate Ruby frame to indicate that they are not + navigable. + DESCRIPTION + end + + def self.short_description + "Displays the backtrace" + end + + def execute + print_backtrace + end + + private + + def print_backtrace + bt = prc("frame.line", (0...context.stack_size)) do |_, index| + Frame.new(context, index).to_hash + end + + print(bt) + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/context.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/context.rb new file mode 100644 index 00000000..b993b420 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/context.rb @@ -0,0 +1,157 @@ +# frozen_string_literal: true + +require "byebug/frame" +require "byebug/helpers/path" +require "byebug/helpers/file" +require "byebug/processors/command_processor" + +module Byebug + # + # Mantains context information for the debugger and it's the main + # communication point between the library and the C-extension through the + # at_breakpoint, at_catchpoint, at_tracing, at_line and at_return callbacks + # + class Context + include Helpers::FileHelper + + class << self + include Helpers::PathHelper + + attr_writer :ignored_files + + # + # List of files byebug will ignore while debugging + # + def ignored_files + @ignored_files ||= + Byebug.mode == :standalone ? lib_files + [bin_file] : lib_files + end + + attr_writer :interface + + def interface + @interface ||= LocalInterface.new + end + + attr_writer :processor + + def processor + @processor ||= CommandProcessor + end + end + + # + # Reader for the current frame + # + def frame + @frame ||= Frame.new(self, 0) + end + + # + # Writer for the current frame + # + def frame=(pos) + @frame = Frame.new(self, pos) + end + + extend Forwardable + def_delegators :frame, :file, :line + + # + # Current file & line information + # + def location + "#{normalize(file)}:#{line}" + end + + # + # Current file, line and source code information + # + def full_location + return location if virtual_file?(file) + + "#{location} #{get_line(file, line)}" + end + + # + # Context's stack size + # + def stack_size + return 0 unless backtrace + + backtrace.drop_while { |l| ignored_file?(l.first.path) } + .take_while { |l| !ignored_file?(l.first.path) } + .size + end + + def interrupt + step_into 1 + end + + # + # Line handler + # + def at_line + self.frame = 0 + return if ignored_file?(file) + + processor.at_line + end + + # + # Tracing handler + # + def at_tracing + return if ignored_file?(file) + + processor.at_tracing + end + + # + # Breakpoint handler + # + def at_breakpoint(breakpoint) + processor.at_breakpoint(breakpoint) + end + + # + # Catchpoint handler + # + def at_catchpoint(exception) + processor.at_catchpoint(exception) + end + + # + # Return handler + # + def at_return(return_value) + return if ignored_file?(file) + + processor.at_return(return_value) + end + + # + # End of class definition handler + # + def at_end + return if ignored_file?(file) + + processor.at_end + end + + private + + def processor + @processor ||= self.class.processor.new(self, self.class.interface) + end + + # + # Tells whether a file is ignored by the debugger. + # + # @param path [String] filename to be checked. + # + def ignored_file?(path) + self.class.ignored_files.include?(path) + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/core.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/core.rb new file mode 100644 index 00000000..e0c4849f --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/core.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +require "byebug/helpers/reflection" +require "byebug/byebug" +require "byebug/context" +require "byebug/breakpoint" +require "byebug/interface" +require "byebug/processors/script_processor" +require "byebug/processors/post_mortem_processor" +require "byebug/commands" +require "byebug/remote" +require "byebug/printers/plain" + +# +# Main debugger's container module. Everything is defined under this module +# +module Byebug + include Helpers::ReflectionHelper + + extend self + + # + # Configuration file used for startup commands. Default value is .byebugrc + # + attr_accessor :init_file + self.init_file = ".byebugrc" + + # + # Debugger's display expressions + # + attr_accessor :displays + self.displays = [] + + # + # Running mode of the debugger. Can be either: + # + # * :attached => Attached to a running program through the `byebug` method. + # * :standalone => Started through `byebug` script. + # * :off => Ignoring any `byebug` method calls. + # + attr_accessor :mode + + # + # Runs normal byebug initialization scripts. + # + # Reads and executes the commands from init file (if any) in the current + # working directory. This is only done if the current directory is different + # from your home directory. Thus, you can have more than one init file, one + # generic in your home directory, and another, specific to the program you + # are debugging, in the directory where you invoke byebug. + # + def run_init_script + rc_dirs.each do |dir| + rc_file = File.expand_path(File.join(dir, init_file)) + next unless File.exist?(rc_file) + + run_rc_file(rc_file) + end + end + + def self.load_settings + Dir.glob(File.join(__dir__, "settings", "*.rb")).each do |file| + require file + end + + constants.grep(/[a-z]Setting/).map do |name| + setting = const_get(name).new + Byebug::Setting.settings[setting.to_sym] = setting + end + end + + # + # Saves information about the unhandled exception and gives a byebug + # prompt back to the user before program termination. + # + def self.handle_post_mortem + return unless raised_exception + + context = raised_exception.__bb_context + + PostMortemProcessor.new(context).at_line + end + + at_exit { Byebug.handle_post_mortem if Byebug.post_mortem? } + + private + + # + # Runs a initialization script file + # + def run_rc_file(rc_file) + interface = ScriptInterface.new(rc_file) + + ScriptProcessor.new(nil, interface).process_commands + end + + # + # List of folders to load rc files from + # + # @note Files will be loaded in the order specified here. + # + def rc_dirs + [ENV["HOME"], Dir.pwd].compact.uniq + end +end + +Byebug.load_settings + +# +# Extends the extension class to be able to pass information about the +# debugging environment from the c-extension to the user. +# +class Exception + attr_reader :__bb_context +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/errors.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/errors.rb new file mode 100644 index 00000000..01ee4f4d --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/errors.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Byebug + # + # Custom exception exception to signal "command not found" errors + # + class CommandNotFound < NoMethodError + def initialize(input, parent = nil) + @input = input + @parent = parent + + super("Unknown command '#{name}'. Try '#{help}'") + end + + private + + def name + build_cmd(@parent, @input) + end + + def help + build_cmd("help", @parent) + end + + def build_cmd(*args) + args.compact.join(" ") + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/frame.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/frame.rb new file mode 100644 index 00000000..fbe81863 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/frame.rb @@ -0,0 +1,185 @@ +# frozen_string_literal: true + +require "byebug/helpers/file" + +module Byebug + # + # Represents a frame in the stack trace + # + class Frame + include Helpers::FileHelper + + attr_reader :pos + + def initialize(context, pos) + @context = context + @pos = pos + end + + def file + @context.frame_file(pos) + end + + def line + @context.frame_line(pos) + end + + def _self + @context.frame_self(pos) + end + + def _binding + @context.frame_binding(pos) + end + + def _class + @context.frame_class(pos) + end + + def _method + @context.frame_method(pos) + end + + def current? + @context.frame.pos == pos + end + + # + # Gets local variables for the frame. + # + def locals + return [] unless _binding + + _binding.local_variables.each_with_object({}) do |e, a| + a[e] = _binding.local_variable_get(e) + a + end + end + + # + # Gets current method arguments for the frame. + # + def args + return c_args unless _binding + + ruby_args + end + + # + # Returns the current class in the frame or an empty string if the current + # +callstyle+ setting is 'short' + # + def deco_class + Setting[:callstyle] == "short" || _class.to_s.empty? ? "" : "#{_class}." + end + + def deco_block + _method[/(?:block(?: \(\d+ levels\))?|rescue) in /] || "" + end + + def deco_method + _method[/((?:block(?: \(\d+ levels\))?|rescue) in )?(.*)/] + end + + # + # Builds a string containing all available args in the frame number, in a + # verbose or non verbose way according to the value of the +callstyle+ + # setting + # + def deco_args + return "" if args.empty? + + my_args = args.map do |arg| + prefix, default = prefix_and_default(arg[0]) + + kls = use_short_style?(arg) ? "" : "##{locals[arg[1]].class}" + + "#{prefix}#{arg[1] || default}#{kls}" + end + + "(#{my_args.join(', ')})" + end + + # + # Builds a formatted string containing information about current method call + # + def deco_call + deco_block + deco_class + deco_method + deco_args + end + + # + # Formatted filename in frame + # + def deco_file + Setting[:fullpath] ? File.expand_path(file) : shortpath(file) + end + + # + # Properly formatted frame number of frame + # + def deco_pos + format("%-2d", pos) + end + + # + # Formatted mark for the frame. + # + # --> marks the current frame + # ͱ-- marks c-frames + # marks regular frames + # + def mark + return "-->" if current? + return " ͱ--" if c_frame? + + " " + end + + # + # Checks whether the frame is a c-frame + # + def c_frame? + _binding.nil? + end + + def to_hash + { + mark: mark, + pos: deco_pos, + call: deco_call, + file: deco_file, + line: line, + full_path: File.expand_path(deco_file) + } + end + + private + + def c_args + return [] unless _self.to_s != "main" + + _class.instance_method(_method).parameters + end + + def ruby_args + meth_name = _binding.eval("__method__") + return [] unless meth_name + + meth_obj = _class.instance_method(meth_name) + return [] unless meth_obj + + meth_obj.parameters + end + + def use_short_style?(arg) + Setting[:callstyle] == "short" || arg[1].nil? || locals.empty? + end + + def prefix_and_default(arg_type) + return ["&", "block"] if arg_type == :block + return ["*", "args"] if arg_type == :rest + + ["", nil] + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/bin.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/bin.rb new file mode 100644 index 00000000..9d79966f --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/bin.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module Byebug + module Helpers + # + # Utilities for interaction with executables + # + module BinHelper + # + # Cross-platform way of finding an executable in the $PATH. + # Adapted from: https://gist.github.com/steakknife/88b6c3837a5e90a08296 + # + def which(cmd) + return File.expand_path(cmd) if File.exist?(cmd) + + [nil, *search_paths].each do |path| + exe = find_executable(path, cmd) + return exe if exe + end + + nil + end + + def find_executable(path, cmd) + executable_file_extensions.each do |ext| + exe = File.expand_path(cmd + ext, path) + + return exe if real_executable?(exe) + end + + nil + end + + def search_paths + ENV["PATH"].split(File::PATH_SEPARATOR) + end + + def executable_file_extensions + ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""] + end + + def real_executable?(file) + File.executable?(file) && !File.directory?(file) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/eval.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/eval.rb new file mode 100644 index 00000000..f1393c8c --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/eval.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +module Byebug + module Helpers + # + # Utilities to assist evaluation of code strings + # + module EvalHelper + # + # Evaluates an +expression+ in a separate thread. + # + # @param expression [String] Expression to evaluate + # + def separate_thread_eval(expression) + allowing_other_threads do + in_new_thread { warning_eval(expression) } + end + end + + # + # Evaluates an +expression+ that might use or defer execution to threads + # other than the current one. + # + # @note This is necessary because when in byebug's prompt, every thread is + # "frozen" so that nothing gets run. So we need to unlock threads prior + # to evaluation or we will run into a deadlock. + # + # @param expression [String] Expression to evaluate + # + def multiple_thread_eval(expression) + allowing_other_threads { warning_eval(expression) } + end + + # + # Evaluates a string containing Ruby code in a specific binding, + # returning nil in an error happens. + # + def silent_eval(str, binding = frame._binding) + safe_eval(str, binding) { |_e| nil } + end + + # + # Evaluates a string containing Ruby code in a specific binding, + # handling the errors at an error level. + # + def error_eval(str, binding = frame._binding) + safe_eval(str, binding) { |e| raise(e, msg(e)) } + end + + # + # Evaluates a string containing Ruby code in a specific binding, + # handling the errors at a warning level. + # + def warning_eval(str, binding = frame._binding) + safe_eval(str, binding) { |e| errmsg(msg(e)) } + end + + private + + def safe_eval(str, binding) + binding.eval(str.gsub(/\Aeval /, ""), "(byebug)", 1) + rescue StandardError, ScriptError => e + yield(e) + end + + def msg(exception) + msg = Setting[:stack_on_error] ? error_msg(exception) : warning_msg(exception) + + pr("eval.exception", text_message: msg) + end + + def error_msg(exception) + at = exception.backtrace + + locations = ["#{at.shift}: #{warning_msg(exception)}"] + locations += at.map { |path| " from #{path}" } + locations.join("\n") + end + + def warning_msg(exception) + "#{exception.class} Exception: #{exception.message}" + end + + # + # Run block temporarily ignoring all TracePoint events. + # + # Used to evaluate stuff within Byebug's prompt. Otherwise, any code + # creating new threads won't be properly evaluated because new threads + # will get blocked by byebug's main thread. + # + def allowing_other_threads + Byebug.unlock + + res = yield + + Byebug.lock + + res + end + + # + # Runs the given block in a new thread, waits for it to finish and + # returns the new thread's result. + # + def in_new_thread + res = nil + + Thread.new { res = yield }.join + + res + end + + def safe_inspect(var) + var.inspect + rescue StandardError + safe_to_s(var) + end + + def safe_to_s(var) + var.to_s + rescue StandardError + "*Error in evaluation*" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/file.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/file.rb new file mode 100644 index 00000000..636b1e0f --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/file.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module Byebug + module Helpers + # + # Utilities for interaction with files + # + module FileHelper + # + # Reads lines of source file +filename+ into an array + # + def get_lines(filename) + File.foreach(filename).reduce([]) { |acc, elem| acc << elem.chomp } + end + + # + # Reads line number +lineno+ from file named +filename+ + # + def get_line(filename, lineno) + File.open(filename) do |f| + f.gets until f.lineno == lineno - 1 + f.gets + end + end + + # + # Returns the number of lines in file +filename+ in a portable, + # one-line-at-a-time way. + # + def n_lines(filename) + File.foreach(filename).reduce(0) { |acc, _elem| acc + 1 } + end + + # + # Regularize file name. + # + def normalize(filename) + return filename if virtual_file?(filename) + + return File.basename(filename) if Setting[:basename] + + File.exist?(filename) ? File.realpath(filename) : filename + end + + # + # A short version of a long path + # + def shortpath(fullpath) + components = Pathname(fullpath).each_filename.to_a + return fullpath if components.size <= 2 + + File.join("...", components[-3..-1]) + end + + # + # True for special files like -e, false otherwise + # + def virtual_file?(name) + ["(irb)", "-e", "(byebug)", "(eval)"].include?(name) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/frame.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/frame.rb new file mode 100644 index 00000000..64a5f8e5 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/frame.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +module Byebug + module Helpers + # + # Utilities to assist frame navigation + # + module FrameHelper + def switch_to_frame(frame) + new_frame = index_from_start(frame) + return frame_err("c_frame") if Frame.new(context, new_frame).c_frame? + + adjust_frame(new_frame) + end + + def jump_frames(steps) + adjust_frame(navigate_to_frame(steps)) + end + + private + + def adjust_frame(new_frame) + return frame_err("too_low") if new_frame >= context.stack_size + return frame_err("too_high") if new_frame.negative? + + context.frame = new_frame + processor.prev_line = nil + end + + def navigate_to_frame(jump_no) + current_jumps = 0 + current_pos = context.frame.pos + + loop do + current_pos += direction(jump_no) + break if out_of_bounds?(current_pos) + + next if Frame.new(context, current_pos).c_frame? + + current_jumps += 1 + break if current_jumps == jump_no.abs + end + + current_pos + end + + def out_of_bounds?(pos) + !(0...context.stack_size).cover?(pos) + end + + def frame_err(msg) + errmsg(pr("frame.errors.#{msg}")) + end + + # + # @param step [Integer] A positive or negative integer + # + # @return [Integer] +1 if step is positive / -1 if negative + # + def direction(step) + step / step.abs + end + + # + # Convert a possibly negative index to a positive index from the start + # of the callstack. -1 is the last position in the stack and so on. + # + # @param i [Integer] Integer to be converted in a proper positive index. + # + def index_from_start(index) + index >= 0 ? index : context.stack_size + index + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/parse.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/parse.rb new file mode 100644 index 00000000..188187aa --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/parse.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +module Byebug + module Helpers + # + # Utilities to assist command parsing + # + module ParseHelper + # + # Parses +str+ of command +cmd+ as an integer between +min+ and +max+. + # + # If either +min+ or +max+ is nil, that value has no bound. + # + # @todo Remove the `cmd` parameter. It has nothing to do with the method's + # purpose. + # + def get_int(str, cmd, min = nil, max = nil) + return nil, pr("parse.errors.int.not_number", cmd: cmd, str: str) if str !~ /\A-?[0-9]+\z/ + + int = str.to_i + if min && int < min + err = pr("parse.errors.int.too_low", cmd: cmd, str: str, min: min) + return nil, err + elsif max && int > max + err = pr("parse.errors.int.too_high", cmd: cmd, str: str, max: max) + return nil, err + end + + int + end + + # + # @return true if code is syntactically correct for Ruby, false otherwise + # + def syntax_valid?(code) + return true unless code + + without_stderr do + begin + RubyVM::InstructionSequence.compile(code) + true + rescue SyntaxError + false + end + end + end + + # + # @return +str+ as an integer or 1 if +str+ is empty. + # + def parse_steps(str, cmd) + return 1 unless str + + steps, err = get_int(str, cmd, 1) + return nil, err unless steps + + steps + end + + private + + # + # Temporarily disable output to $stderr + # + def without_stderr + old_stderr = $stderr + $stderr = StringIO.new + + yield + ensure + $stderr = old_stderr + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/path.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/path.rb new file mode 100644 index 00000000..c2422492 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/path.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Byebug + module Helpers + # + # Utilities for managing gem paths + # + module PathHelper + def bin_file + @bin_file ||= File.join(root_path, "exe", "byebug") + end + + def root_path + @root_path ||= File.expand_path(File.join("..", "..", ".."), __dir__) + end + + def lib_files + @lib_files ||= glob_for("lib") + end + + def test_files + @test_files ||= glob_for("test") + end + + def gem_files + @gem_files ||= [bin_file] + lib_files + end + + def all_files + @all_files ||= gem_files + test_files + end + + private + + def glob_for(dir) + Dir.glob(File.join(root_path, dir, "**", "*.rb")) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/reflection.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/reflection.rb new file mode 100644 index 00000000..ceb8fb48 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/reflection.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Byebug + module Helpers + # + # Reflection utilitie + # + module ReflectionHelper + # + # List of "command" classes in the including module + # + def commands + constants(false) + .map { |const| const_get(const, false) } + .select { |c| c.is_a?(Class) && c.name =~ /[a-z]Command$/ } + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/string.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/string.rb new file mode 100644 index 00000000..d265d969 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/string.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Byebug + module Helpers + # + # Utilities for interaction with strings + # + module StringHelper + # + # Converts +str+ from an_underscored-or-dasherized_string to + # ACamelizedString. + # + def camelize(str) + str.dup.split(/[_-]/).map(&:capitalize).join("") + end + + # + # Improves indentation and spacing in +str+ for readability in Byebug's + # command prompt. + # + def prettify(str) + "\n" + deindent(str) + "\n" + end + + # + # Removes a number of leading whitespace for each input line. + # + def deindent(str, leading_spaces: 6) + str.gsub(/^ {#{leading_spaces}}/, "") + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/thread.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/thread.rb new file mode 100644 index 00000000..02c9f8e8 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/thread.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +module Byebug + module Helpers + # + # Utilities for thread subcommands + # + module ThreadHelper + def display_context(ctx) + puts pr("thread.context", thread_arguments(ctx)) + end + + def thread_arguments(ctx) + { + status_flag: status_flag(ctx), + debug_flag: debug_flag(ctx), + id: ctx.thnum, + thread: ctx.thread.inspect, + file_line: location(ctx), + pid: Process.pid, + status: ctx.thread.status, + current: current_thread?(ctx) + } + end + + def current_thread?(ctx) + ctx.thread == Thread.current + end + + def context_from_thread(thnum) + ctx = Byebug.contexts.find { |c| c.thnum.to_s == thnum } + + err = if ctx.nil? + pr("thread.errors.no_thread") + elsif ctx == context + pr("thread.errors.current_thread") + elsif ctx.ignored? + pr("thread.errors.ignored", arg: thnum) + end + + [ctx, err] + end + + private + + # @todo Check whether it is Byebug.current_context or context + def location(ctx) + return context.location if ctx == Byebug.current_context + + backtrace = ctx.thread.backtrace_locations + return "" unless backtrace && backtrace[0] + + "#{backtrace[0].path}:#{backtrace[0].lineno}" + end + + def status_flag(ctx) + return "$" if ctx.suspended? + + current_thread?(ctx) ? "+" : " " + end + + def debug_flag(ctx) + ctx.ignored? ? "!" : " " + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/toggle.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/toggle.rb new file mode 100644 index 00000000..0ea2b0b4 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/toggle.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require "byebug/helpers/parse" + +module Byebug + module Helpers + # + # Utilities to assist breakpoint/display enabling/disabling. + # + module ToggleHelper + include ParseHelper + + def enable_disable_breakpoints(is_enable, args) + raise pr("toggle.errors.no_breakpoints") if Breakpoint.none? + + select_breakpoints(is_enable, args).each do |b| + enabled = (is_enable == "enable") + raise pr("toggle.errors.expression", expr: b.expr) if enabled && !syntax_valid?(b.expr) + + puts pr("toggle.messages.toggled", bpnum: b.id, + endis: enabled ? "en" : "dis") + b.enabled = enabled + end + end + + def enable_disable_display(is_enable, args) + raise pr("toggle.errors.no_display") if n_displays.zero? + + selected_displays = args ? args.split(/ +/) : [1..n_displays + 1] + + selected_displays.each do |pos| + pos, err = get_int(pos, "#{is_enable} display", 1, n_displays) + raise err unless err.nil? + + Byebug.displays[pos - 1][0] = (is_enable == "enable") + end + end + + private + + def select_breakpoints(is_enable, args) + all_breakpoints = Byebug.breakpoints.sort_by(&:id) + return all_breakpoints if args.nil? + + selected_ids = [] + args.split(/ +/).each do |pos| + last_id = all_breakpoints.last.id + pos, err = get_int(pos, "#{is_enable} breakpoints", 1, last_id) + raise(ArgumentError, err) unless pos + + selected_ids << pos + end + + all_breakpoints.select { |b| selected_ids.include?(b.id) } + end + + def n_displays + Byebug.displays.size + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/var.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/var.rb new file mode 100644 index 00000000..e6fc9e7b --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/helpers/var.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require "byebug/helpers/eval" + +module Byebug + module Helpers + # + # Utilities for variable subcommands + # + module VarHelper + include EvalHelper + + def var_list(ary, binding = context.frame._binding) + vars = ary.sort.map do |name| + [name, safe_inspect(silent_eval(name.to_s, binding))] + end + + puts prv(vars, "instance") + end + + def var_global + globals = global_variables.reject do |v| + %i[$IGNORECASE $= $KCODE $-K $binding].include?(v) + end + + var_list(globals) + end + + def var_instance(str) + obj = warning_eval(str || "self") + + var_list(obj.instance_variables, obj.instance_eval { binding }) + end + + def var_local + locals = context.frame.locals + cur_self = context.frame._self + locals[:self] = cur_self unless cur_self.to_s == "main" + puts prv(locals.keys.sort.map { |k| [k, locals[k]] }, "instance") + end + + def var_args + args = context.frame.args + return if args == [[:rest]] + + all_locals = context.frame.locals + arg_values = args.map { |arg| arg[1] } + + locals = all_locals.select { |k, _| arg_values.include?(k) } + puts prv(locals.keys.sort.map { |k| [k, locals[k]] }, "instance") + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/history.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/history.rb new file mode 100644 index 00000000..40bf6c71 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/history.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +begin + require "readline" +rescue LoadError + warn <<-MESSAGE + Sorry, you can't use byebug without Readline. To solve this, you need to + rebuild Ruby with Readline support. If using Ubuntu, try `sudo apt-get + install libreadline-dev` and then reinstall your Ruby. + MESSAGE + + raise +end + +module Byebug + # + # Handles byebug's history of commands. + # + class History + attr_accessor :size + + def initialize + self.size = 0 + end + + # + # Array holding the list of commands in history + # + def buffer + Readline::HISTORY.to_a + end + + # + # Restores history from disk. + # + def restore + return unless File.exist?(Setting[:histfile]) + + File.readlines(Setting[:histfile]).reverse_each { |l| push(l.chomp) } + end + + # + # Saves history to disk. + # + def save + n_cmds = Setting[:histsize] > size ? size : Setting[:histsize] + + File.open(Setting[:histfile], "w") do |file| + n_cmds.times { file.puts(pop) } + end + + clear + end + + # + # Discards history. + # + def clear + size.times { pop } + end + + # + # Adds a new command to Readline's history. + # + def push(cmd) + return if ignore?(cmd) + + self.size += 1 + Readline::HISTORY.push(cmd) + end + + # + # Removes a command from Readline's history. + # + def pop + self.size -= 1 + Readline::HISTORY.pop + end + + # + # Prints the requested numbers of history entries. + # + def to_s(n_cmds) + show_size = n_cmds ? specific_max_size(n_cmds) : default_max_size + + commands = buffer.last(show_size) + + last_ids(show_size).zip(commands).map do |l| + format("%5d %s", position: l[0], command: l[1]) + end.join("\n") + "\n" + end + + # + # Array of ids of the last +number+ commands. + # + def last_ids(number) + (1 + size - number..size).to_a + end + + # + # Max number of commands to be displayed when no size has been specified. + # + # Never more than Setting[:histsize]. + # + def default_max_size + [Setting[:histsize], self.size].min + end + + # + # Max number of commands to be displayed when a size has been specified. + # + # The only bound here is not showing more items than available. + # + def specific_max_size(number) + [self.size, number].min + end + + # + # Whether a specific command should not be stored in history. + # + # For now, empty lines and consecutive duplicates. + # + def ignore?(buf) + return true if /^\s*$/ =~ buf + return false if Readline::HISTORY.empty? + + buffer[Readline::HISTORY.length - 1] == buf + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/interface.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/interface.rb new file mode 100644 index 00000000..c5fd7c64 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/interface.rb @@ -0,0 +1,146 @@ +# frozen_string_literal: true + +require "byebug/setting" +require "byebug/history" +require "byebug/helpers/file" + +# +# Namespace for all of byebug's code +# +module Byebug + # + # Main Interface class + # + # Contains common functionality to all implemented interfaces. + # + class Interface + include Helpers::FileHelper + + attr_accessor :command_queue, :history + attr_reader :input, :output, :error + + def initialize + @command_queue = [] + @history = History.new + @last_line = "" + end + + def last_if_empty(input) + @last_line = input.empty? ? @last_line : input + end + + # + # Pops a command from the input stream. + # + def read_command(prompt) + return command_queue.shift unless command_queue.empty? + + read_input(prompt) + end + + # + # Pushes lines in +filename+ to the command queue. + # + def read_file(filename) + command_queue.concat(get_lines(filename)) + end + + # + # Reads a new line from the interface's input stream, parses it into + # commands and saves it to history. + # + # @return [String] Representing something to be run by the debugger. + # + def read_input(prompt, save_hist = true) + line = prepare_input(prompt) + return unless line + + history.push(line) if save_hist + + command_queue.concat(split_commands(line)) + command_queue.shift + end + + # + # Reads a new line from the interface's input stream. + # + # @return [String] New string read or the previous string if the string + # read now was empty. + # + def prepare_input(prompt) + line = readline(prompt) + return unless line + + last_if_empty(line) + end + + # + # Prints an error message to the error stream. + # + def errmsg(message) + error.print("*** #{message}\n") + end + + # + # Prints an output message to the output stream. + # + def puts(message) + output.puts(message) + end + + # + # Prints an output message to the output stream without a final "\n". + # + def print(message) + output.print(message) + end + + # + # Confirms user introduced an affirmative response to the input stream. + # + def confirm(prompt) + readline(prompt) == "y" + end + + def close + end + + # + # Saves or clears history according to +autosave+ setting. + # + def autosave + Setting[:autosave] ? history.save : history.clear + end + + # + # Restores history according to +autosave+ setting. + # + def autorestore + history.restore if Setting[:autosave] + end + + private + + # + # Splits a command line of the form "cmd1 ; cmd2 ; ... ; cmdN" into an + # array of commands: [cmd1, cmd2, ..., cmdN] + # + def split_commands(cmd_line) + return [""] if cmd_line.empty? + + cmd_line.split(/;/).each_with_object([]) do |v, m| + if m.empty? || m.last[-1] != '\\' + m << v.strip + next + end + + m.last[-1, 1] = "" + m.last << ";" << v + end + end + end +end + +require "byebug/interfaces/local_interface" +require "byebug/interfaces/script_interface" +require "byebug/interfaces/remote_interface" diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/interfaces/local_interface.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/interfaces/local_interface.rb new file mode 100644 index 00000000..65b7d67c --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/interfaces/local_interface.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module Byebug + # + # Interface class for standard byebug use. + # + class LocalInterface < Interface + EOF_ALIAS = "continue" + + def initialize + super() + @input = $stdin + @output = $stdout + @error = $stderr + end + + # + # Reads a single line of input using Readline. If Ctrl-D is pressed, it + # returns "continue", meaning that program's execution will go on. + # + # @param prompt Prompt to be displayed. + # + def readline(prompt) + with_repl_like_sigint { Readline.readline(prompt) || EOF_ALIAS } + end + + # + # Yields the block handling Ctrl-C the following way: if pressed while + # waiting for input, the line is reset to only the prompt and we ask for + # input again. + # + # @note Any external 'INT' traps are overriden during this method. + # + def with_repl_like_sigint + orig_handler = trap("INT") { raise Interrupt } + yield + rescue Interrupt + puts("^C") + retry + ensure + trap("INT", orig_handler) + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/interfaces/remote_interface.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/interfaces/remote_interface.rb new file mode 100644 index 00000000..aca54415 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/interfaces/remote_interface.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "byebug/history" + +module Byebug + # + # Interface class for remote use of byebug. + # + class RemoteInterface < Interface + def initialize(socket) + super() + @input = socket + @output = socket + @error = socket + end + + def read_command(prompt) + super("PROMPT #{prompt}") + rescue Errno::EPIPE, Errno::ECONNABORTED + "continue" + end + + def confirm(prompt) + super("CONFIRM #{prompt}") + rescue Errno::EPIPE, Errno::ECONNABORTED + false + end + + def print(message) + super(message) + rescue Errno::EPIPE, Errno::ECONNABORTED + nil + end + + def puts(message) + super(message) + rescue Errno::EPIPE, Errno::ECONNABORTED + nil + end + + def close + output.close + end + + def readline(prompt) + puts(prompt) + (input.gets || "continue").chomp + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/interfaces/script_interface.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/interfaces/script_interface.rb new file mode 100644 index 00000000..0b847c00 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/interfaces/script_interface.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Byebug + # + # Interface class for command execution from script files. + # + class ScriptInterface < Interface + def initialize(file, verbose = false) + super() + @verbose = verbose + @input = File.open(file) + @output = verbose ? $stdout : StringIO.new + @error = $stderr + end + + def read_command(prompt) + readline(prompt, false) + end + + def close + input.close + end + + def readline(*) + while (result = input.gets) + output.puts "+ #{result}" if @verbose + next if result =~ /^\s*#/ + + return result.chomp + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/interfaces/test_interface.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/interfaces/test_interface.rb new file mode 100644 index 00000000..d732d5f7 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/interfaces/test_interface.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +module Byebug + # + # Custom interface for easier assertions + # + class TestInterface < Interface + attr_accessor :test_block + + def initialize + super() + + clear + end + + def errmsg(message) + error.concat(prepare(message)) + end + + def print(message) + output.concat(prepare(message)) + end + + def puts(message) + output.concat(prepare(message)) + end + + def read_command(prompt) + cmd = super(prompt) + + return cmd unless cmd.nil? && test_block + + test_block.call + self.test_block = nil + end + + def clear + @input = [] + @output = [] + @error = [] + history.clear + end + + def inspect + [ + "Input:", input.join("\n"), + "Output:", output.join("\n"), + "Error:", error.join("\n") + ].join("\n") + end + + def readline(prompt) + puts(prompt) + + cmd = input.shift + cmd.is_a?(Proc) ? cmd.call : cmd + end + + private + + def prepare(message) + return message.map(&:to_s) if message.respond_to?(:map) + + message.to_s.split("\n") + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/option_setter.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/option_setter.rb new file mode 100644 index 00000000..d86537c3 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/option_setter.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +module Byebug + # + # Handles byebug's command line options + # + class OptionSetter + def initialize(runner, opts) + @runner = runner + @opts = opts + end + + def setup + debug + include_flag + post_mortem + quit + rc + stop + require_flag + remote + trace + version + help + end + + private + + def debug + @opts.on "-d", "--debug", "Set $DEBUG=true" do + $DEBUG = true + end + end + + def include_flag + @opts.on "-I", "--include list", "Add to paths to $LOAD_PATH" do |list| + $LOAD_PATH.push(list.split(":")).flatten! + end + end + + def post_mortem + @opts.on "-m", "--[no-]post-mortem", "Use post-mortem mode" do |v| + Setting[:post_mortem] = v + end + end + + def quit + @opts.on "-q", "--[no-]quit", "Quit when script finishes" do |v| + @runner.quit = v + end + end + + def rc + @opts.on "-x", "--[no-]rc", "Run byebug initialization file" do |v| + @runner.init_script = v + end + end + + def stop + @opts.on "-s", "--[no-]stop", "Stop when script is loaded" do |v| + @runner.stop = v + end + end + + def require_flag + @opts.on "-r", "--require file", "Require library before script" do |lib| + require lib + end + end + + def remote + @opts.on "-R", "--remote [host:]port", "Remote debug [host:]port" do |p| + @runner.remote = p + end + end + + def trace + @opts.on "-t", "--[no-]trace", "Turn on line tracing" do |v| + Setting[:linetrace] = v + end + end + + def version + @opts.on "-v", "--version", "Print program version" do + @runner.version = Byebug::VERSION + end + end + + def help + @opts.on "-h", "--help", "Display this message" do + @runner.help = @opts.help + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/printers/base.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/printers/base.rb new file mode 100644 index 00000000..88bfab0d --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/printers/base.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require "yaml" + +module Byebug + module Printers + # + # Base printer + # + class Base + class MissedPath < StandardError; end + class MissedArgument < StandardError; end + + SEPARATOR = "." + + def type + self.class.name.split("::").last.downcase + end + + private + + def locate(path) + result = nil + contents.each_value do |contents| + result = parts(path).reduce(contents) do |r, part| + r&.key?(part) ? r[part] : nil + end + break if result + end + raise MissedPath, "Can't find part path '#{path}'" unless result + + result + end + + def translate(string, args = {}) + # they may contain #{} string interpolation + string.gsub(/\|\w+$/, "").gsub(/([^#]?){([^}]*)}/) do + key = Regexp.last_match[2].to_s + raise MissedArgument, "Missed argument #{key} for '#{string}'" unless args.key?(key.to_sym) + + "#{Regexp.last_match[1]}#{args[key.to_sym]}" + end + end + + def parts(path) + path.split(SEPARATOR) + end + + def contents + @contents ||= contents_files.each_with_object({}) do |filename, hash| + hash[filename] = YAML.load_file(filename) || {} + end + end + + def array_of_args(collection, &_block) + collection_with_index = collection.each.with_index + collection_with_index.each_with_object([]) do |(item, index), array| + args = yield item, index + array << args if args + end + end + + def contents_files + [File.join(__dir__, "texts", "base.yml")] + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/printers/plain.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/printers/plain.rb new file mode 100644 index 00000000..c6da2014 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/printers/plain.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require "byebug/printers/base" + +module Byebug + module Printers + # + # Plain text printer + # + class Plain < Base + def print(path, args = {}) + message = translate(locate(path), args) + tail = parts(path).include?("confirmations") ? " (y/n) " : "\n" + message << tail + end + + def print_collection(path, collection, &block) + lines = array_of_args(collection, &block).map do |args| + print(path, args) + end + + lines.join + end + + def print_variables(variables, *_unused) + print_collection("variable.variable", variables) do |(key, value), _| + value = value.nil? ? "nil" : value.to_s + if "#{key} = #{value}".size > Setting[:width] + key_size = "#{key} = ".size + value = value[0..Setting[:width] - key_size - 4] + "..." + end + + { key: key, value: value } + end + end + + private + + def contents_files + [File.join(__dir__, "texts", "plain.yml")] + super + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/printers/texts/base.yml b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/printers/texts/base.yml new file mode 100644 index 00000000..97778e37 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/printers/texts/base.yml @@ -0,0 +1,115 @@ +base: + errors: + only_local: "Command is available only in local mode." + +break: + errors: + line: "Line {line} is not a valid breakpoint in file {file}.\n\nValid break points are:\n{valid_breakpoints}" + location: "Invalid breakpoint location" + state: "We are not in a state that has an associated file" + class: "Unknown class {klass}" + far_line: "There are only {lines} lines in file {file}" + source: "No file named {file}" + expression: "Incorrect expression \"{expr}\"; breakpoint disabled" + no_breakpoint: "Invalid breakpoint id. Use \"info breakpoint\" to find out the correct id" + no_breakpoint_delete: "No breakpoint number {pos}" + not_changed: "Incorrect expression \"{expr}\", breakpoint not changed" + confirmations: + delete_all: "Delete all breakpoints?" + messages: + breakpoint_deleted: "Deleted breakpoint {pos}" + +catch: + added: "Catching exception {exception}." + removed: "Catch for exception {exception} removed" + errors: + off: "Off expected. Got {off}" + not_class: "Warning {class} is not known to be a Class" + not_found: "Catch for exception {exception} not found" + confirmations: + delete_all: "Delete all catchpoints? (y or n) " + +condition: + errors: + no_breakpoints: "No breakpoints have been set" + +continue: + errors: + unstopped_line: "Line {line} is not a valid stopping point in file" + +display: + confirmations: + clear_all: "Clear all expressions?" + errors: + undefined: "Display expression {expr} is not defined" + +edit: + errors: + state: "We are not in a state that has an associated file" + file_line: "Invalid file[:line] number specification: {file_line}" + not_readable: "File {file} is not readable." + not_exist: "File {file} does not exist." + +frame: + errors: + too_low: "Can't navigate beyond the oldest frame" + too_high: "Can't navigate beyond the newest frame" + c_frame: "Can't navigate to c-frame" + +info: + errors: + undefined_file: "{file} is not a valid source file" + +pry: + errors: + not_installed: "You need to install pry in order to run this command" + +quit: + confirmations: + really: "Really quit?" + +save: + messages: + done: "Saved to '{path}'" + +set: + errors: + unknown_setting: "Unknown setting :{key}" + must_specify_value: "You must specify a value for setting :{key}" + on_off: "Expecting 'on', 1, true, 'off', 0, false. Got: {arg}." + +show: + errors: + unknown_setting: "Unknown setting :{key}" + +source: + errors: + not_found: "File \"{file}\" not found" + +thread: + errors: + no_thread: "No such thread" + current_thread: "It's the current thread" + wrong_action: "Can't {subcmd} thread {arg}" + already_running: "Already running" + +toggle: + errors: + no_breakpoints: "No breakpoints have been set" + no_display: "No display expressions have been set" + syntax: "\"{toggle}\" must be followed by \"display\", \"breakpoints\" or breakpoint ids" + expression: "Expression \"{expr}\" syntactically incorrect; breakpoint remains disabled." + messages: + toggled: "Breakpoint {bpnum} {endis}abled" + +parse: + errors: + int: + too_low: "\"{cmd}\" argument \"{str}\" needs to be at least {min}" + too_high: "\"{cmd}\" argument \"{str}\" needs to be at most {max}" + not_number: "\"{cmd}\" argument \"{str}\" needs to be a number" + +variable: + errors: + not_module: "Should be Class/Module: {object}" + cant_get_class_vars: "can't get class variables here.\n" diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/printers/texts/plain.yml b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/printers/texts/plain.yml new file mode 100644 index 00000000..9ede0f7a --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/printers/texts/plain.yml @@ -0,0 +1,33 @@ +break: + created: "Created breakpoint {id} at {file}:{line}" + +display: + result: "{n}: {exp} = {result}" + +eval: + exception: "{text_message}" + result: "{result}" + +frame: + line: "{mark} #{pos} {call} at {file}:{line}" + +method: + methods: "{name}|c" + +restart: + success: "Re exec'ing:\n {cmd}" + +thread: + context: "{status_flag}{debug_flag}{id} {thread} {file_line}" + +trace: + messages: + success: "Tracing global variable \"{var}\"." + on_change: "traced global variable '{name}' has value '{value}'" + undo: "Not tracing global variable \"{var}\" anymore." + errors: + var_is_not_global: "'{name}' is not a global variable." + needs_global_variable: "tracevar needs a global variable name" + +variable: + variable: "{key} = {value}" diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/processors/command_processor.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/processors/command_processor.rb new file mode 100644 index 00000000..62fe34e1 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/processors/command_processor.rb @@ -0,0 +1,173 @@ +# frozen_string_literal: true + +require "forwardable" + +require "byebug/helpers/eval" +require "byebug/errors" + +module Byebug + # + # Processes commands in regular mode. + # + # You can override this class to create your own command processor that, for + # example, whitelists only certain commands to be executed. + # + # @see PostMortemProcessor for a example + # + class CommandProcessor + include Helpers::EvalHelper + + attr_accessor :prev_line + attr_reader :context, :interface + + def initialize(context, interface = LocalInterface.new) + @context = context + @interface = interface + + @proceed = false + @prev_line = nil + end + + def printer + @printer ||= Printers::Plain.new + end + + extend Forwardable + + def_delegators :@context, :frame + + def_delegator :printer, :print, :pr + def_delegator :printer, :print_collection, :prc + def_delegator :printer, :print_variables, :prv + + def_delegators :interface, :errmsg, :puts, :confirm + + def_delegators :Byebug, :commands + + # + # Available commands + # + def command_list + @command_list ||= CommandList.new(commands) + end + + def at_line + process_commands + end + + def at_tracing + puts "Tracing: #{context.full_location}" + + run_auto_cmds(2) + end + + def at_breakpoint(brkpt) + number = Byebug.breakpoints.index(brkpt) + 1 + + puts "Stopped by breakpoint #{number} at #{frame.file}:#{frame.line}" + end + + def at_catchpoint(exception) + puts "Catchpoint at #{context.location}: `#{exception}'" + end + + def at_return(return_value) + puts "Return value is: #{safe_inspect(return_value)}" + + process_commands + end + + def at_end + process_commands + end + + # + # Let the execution continue + # + def proceed! + @proceed = true + end + + # + # Handle byebug commands. + # + def process_commands + before_repl + + repl + ensure + after_repl + end + + protected + + # + # Prompt shown before reading a command. + # + def prompt + "(byebug) " + end + + def before_repl + @proceed = false + @prev_line = nil + + run_auto_cmds(1) + interface.autorestore + end + + def after_repl + interface.autosave + end + + # + # Main byebug's REPL + # + def repl + until @proceed + cmd = interface.read_command(prompt) + return if cmd.nil? + + next if cmd == "" + + run_cmd(cmd) + end + end + + private + + def auto_cmds_for(run_level) + command_list.select { |cmd| cmd.always_run >= run_level } + end + + # + # Run permanent commands. + # + def run_auto_cmds(run_level) + safely do + auto_cmds_for(run_level).each { |cmd| cmd.new(self).execute } + end + end + + # + # Executes the received input + # + # Instantiates a command matching the input and runs it. If a matching + # command is not found, it evaluates the unknown input. + # + def run_cmd(input) + safely do + command = command_list.match(input) + return command.new(self, input).execute if command + + puts safe_inspect(multiple_thread_eval(input)) + end + end + + def safely + yield + rescue StandardError => e + errmsg(e.message) + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/processors/control_processor.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/processors/control_processor.rb new file mode 100644 index 00000000..a20290c0 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/processors/control_processor.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "byebug/processors/command_processor" + +module Byebug + # + # Processes commands when there's not program running + # + class ControlProcessor < CommandProcessor + # + # Available commands + # + def commands + super.select(&:allow_in_control) + end + + # + # Prompt shown before reading a command. + # + def prompt + "(byebug:ctrl) " + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/processors/post_mortem_processor.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/processors/post_mortem_processor.rb new file mode 100644 index 00000000..5529b878 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/processors/post_mortem_processor.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require "byebug/processors/command_processor" + +module Byebug + # + # Processes commands in post_mortem mode + # + class PostMortemProcessor < CommandProcessor + def commands + super.select(&:allow_in_post_mortem) + end + + def prompt + "(byebug:post_mortem) " + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/processors/script_processor.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/processors/script_processor.rb new file mode 100644 index 00000000..7cf73419 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/processors/script_processor.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require "byebug/processors/command_processor" + +module Byebug + # + # Processes commands from a file + # + class ScriptProcessor < CommandProcessor + # + # Available commands + # + def commands + super.select(&:allow_in_control) + end + + def repl + while (input = interface.read_command(prompt)) + safely do + command = command_list.match(input) + raise CommandNotFound.new(input) unless command + + command.new(self, input).execute + end + end + end + + def after_repl + super + + interface.close + end + + # + # Prompt shown before reading a command. + # + def prompt + "(byebug:ctrl) " + end + + private + + def without_exceptions + yield + rescue StandardError + nil + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/remote.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/remote.rb new file mode 100644 index 00000000..1a45920d --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/remote.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require "socket" +require "byebug/processors/control_processor" +require "byebug/remote/server" +require "byebug/remote/client" + +# +# Remote debugging functionality. +# +module Byebug + # Port number used for remote debugging + PORT = 8989 unless defined?(PORT) + + class << self + # If in remote mode, wait for the remote connection + attr_accessor :wait_connection + + # The actual port that the server is started at + def actual_port + server.actual_port + end + + # The actual port that the control server is started at + def actual_control_port + control.actual_port + end + + # + # Interrupts the current thread + # + def interrupt + current_context.interrupt + end + + # + # Starts the remote server main thread + # + def start_server(host = nil, port = PORT) + start_control(host, port.zero? ? 0 : port + 1) + + server.start(host, port) + end + + # + # Starts the remote server control thread + # + def start_control(host = nil, port = PORT + 1) + control.start(host, port) + end + + # + # Connects to the remote byebug + # + def start_client(host = "localhost", port = PORT) + client.start(host, port) + end + + def parse_host_and_port(host_port_spec) + location = host_port_spec.split(":") + location[1] ? [location[0], location[1].to_i] : ["localhost", location[0]] + end + + private + + def client + @client ||= Remote::Client.new(Context.interface) + end + + def server + @server ||= Remote::Server.new(wait_connection: wait_connection) do |s| + Context.interface = RemoteInterface.new(s) + end + end + + def control + @control ||= Remote::Server.new(wait_connection: false) do |s| + context = Byebug.current_context + interface = RemoteInterface.new(s) + + ControlProcessor.new(context, interface).process_commands + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/remote/client.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/remote/client.rb new file mode 100644 index 00000000..0c762f06 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/remote/client.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require "socket" + +module Byebug + module Remote + # + # Client for remote debugging + # + class Client + attr_reader :interface, :socket + + def initialize(interface) + @interface = interface + @socket = nil + end + + # + # Connects to the remote byebug + # + def start(host = "localhost", port = PORT) + connect_at(host, port) + + while (line = socket.gets) + case line + when /^PROMPT (.*)$/ + input = interface.read_command(Regexp.last_match[1]) + break unless input + + socket.puts input + when /^CONFIRM (.*)$/ + input = interface.readline(Regexp.last_match[1]) + break unless input + + socket.puts input + else + interface.puts line + end + end + + socket.close + end + + def started? + !socket.nil? + end + + private + + def connect_at(host, port) + interface.puts "Connecting to byebug server at #{host}:#{port}..." + @socket = TCPSocket.new(host, port) + interface.puts "Connected." + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/remote/server.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/remote/server.rb new file mode 100644 index 00000000..e027a5df --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/remote/server.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require "socket" + +module Byebug + module Remote + # + # Server for remote debugging + # + class Server + attr_reader :actual_port, :wait_connection + + def initialize(wait_connection:, &block) + @thread = nil + @wait_connection = wait_connection + @main_loop = block + end + + # + # Start the remote debugging server + # + def start(host, port) + return if @thread + + if wait_connection + mutex = Mutex.new + proceed = ConditionVariable.new + end + + server = TCPServer.new(host, port) + @actual_port = server.addr[1] + + yield if block_given? + + @thread = DebugThread.new do + while (session = server.accept) + @main_loop.call(session) + + mutex.synchronize { proceed.signal } if wait_connection + end + end + + mutex.synchronize { proceed.wait(mutex) } if wait_connection + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/runner.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/runner.rb new file mode 100644 index 00000000..baf438c8 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/runner.rb @@ -0,0 +1,198 @@ +# frozen_string_literal: true + +require "optparse" +require "English" +require "byebug/core" +require "byebug/version" +require "byebug/helpers/bin" +require "byebug/helpers/parse" +require "byebug/helpers/string" +require "byebug/option_setter" +require "byebug/processors/control_processor" + +module Byebug + # + # Responsible for starting the debugger when started from the command line. + # + class Runner + include Helpers::BinHelper + include Helpers::ParseHelper + include Helpers::StringHelper + + # + # Special working modes that don't actually start the debugger. + # + attr_reader :help, :version, :remote + + # + # Signals that we should exit after the debugged program is finished. + # + attr_accessor :quit + + # + # Signals that we should stop before program starts + # + attr_accessor :stop + + # + # Signals that we should run rc scripts before program starts + # + attr_writer :init_script + + # + # @param stop [Boolean] Whether the runner should stop right before + # starting the program. + # + # @param quit [Boolean] Whether the runner should quit right after + # finishing the program. + # + def initialize(stop = true, quit = true) + @stop = stop + @quit = quit + end + + def help=(text) + @help ||= text + + interface.puts("#{text}\n") + end + + def version=(number) + @version ||= number + + interface.puts prettify <<-VERSION + Running byebug #{number} + VERSION + end + + def remote=(host_and_port) + @remote ||= Byebug.parse_host_and_port(host_and_port) + + Byebug.start_client(*@remote) + end + + def init_script + defined?(@init_script) ? @init_script : true + end + + # + # Usage banner. + # + def banner + prettify <<-BANNER + byebug #{Byebug::VERSION} + + Usage: byebug [options] -- + BANNER + end + + # + # Starts byebug to debug a program. + # + def run + Byebug.mode = :standalone + + option_parser.order!($ARGV) + return if non_script_option? || error_in_script? + + $PROGRAM_NAME = program + + Byebug.run_init_script if init_script + + loop do + debug_program + + break if quit + + ControlProcessor.new(nil, interface).process_commands + end + end + + def interface + @interface ||= Context.interface + end + + # + # Processes options passed from the command line. + # + def option_parser + @option_parser ||= OptionParser.new(banner, 25) do |opts| + opts.banner = banner + + OptionSetter.new(self, opts).setup + end + end + + def program + @program ||= begin + candidate = which($ARGV.shift) + + if [which("ruby"), RbConfig.ruby].include?(candidate) + which($ARGV.shift) + else + candidate + end + end + end + + # + # An option that doesn't need a script specified was given + # + def non_script_option? + version || help || remote + end + + # + # There is an error with the specified script + # + def error_in_script? + no_script? || non_existing_script? || invalid_script? + end + + # + # No script to debug specified + # + def no_script? + return false unless $ARGV.empty? + + print_error("You must specify a program to debug") + true + end + + # + # Extracts debugged program from command line args. + # + def non_existing_script? + return false if program + + print_error("The script doesn't exist") + true + end + + # + # Checks the debugged script has correct syntax + # + def invalid_script? + return false if syntax_valid?(File.read(program)) + + print_error("The script has incorrect syntax") + true + end + + # + # Debugs a script only if syntax checks okay. + # + def debug_program + error = Byebug.debug_load(program, stop) + puts "#{error}\n#{error.backtrace}" if error + end + + # + # Prints an error message and a help string + # + def print_error(msg) + interface.errmsg(msg) + interface.puts(option_parser.help) + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/setting.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/setting.rb new file mode 100644 index 00000000..d681004b --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/setting.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require "byebug/helpers/string" + +module Byebug + # + # Parent class for all byebug settings. + # + class Setting + attr_accessor :value + + DEFAULT = false + + def initialize + @value = self.class::DEFAULT + end + + def boolean? + [true, false].include?(value) + end + + def integer? + Integer(value) ? true : false + rescue ArgumentError + false + end + + def help + prettify(banner) + end + + def to_sym + name = self.class.name.gsub(/^Byebug::/, "").gsub(/Setting$/, "") + name.gsub(/(.)([A-Z])/, '\1_\2').downcase.to_sym + end + + def to_s + "#{to_sym} is #{value ? 'on' : 'off'}\n" + end + + class << self + def settings + @settings ||= {} + end + + def [](name) + settings[name].value + end + + def []=(name, value) + settings[name].value = value + end + + def find(shortcut) + abbr = shortcut =~ /^no/ ? shortcut[2..-1] : shortcut + matches = settings.select do |key, value| + key =~ (value.boolean? ? /#{abbr}/ : /#{shortcut}/) + end + matches.size == 1 ? matches.values.first : nil + end + + # + # @todo DRY this up. Very similar code exists in the CommandList class + # + def help_all + output = " List of supported settings:\n\n" + width = settings.keys.max_by(&:size).size + settings.each_value do |sett| + output += format( + " %-#{width}s -- %s\n", + name: sett.to_sym, + description: sett.banner + ) + end + output + "\n" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/autoirb.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/autoirb.rb new file mode 100644 index 00000000..99cb2e91 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/autoirb.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require "byebug/setting" +require "byebug/commands/irb" + +module Byebug + # + # Setting for automatically invoking IRB on every stop. + # + class AutoirbSetting < Setting + DEFAULT = 0 + + def initialize + IrbCommand.always_run = DEFAULT + end + + def banner + "Invoke IRB on every stop" + end + + def value=(val) + IrbCommand.always_run = val ? 1 : 0 + end + + def value + IrbCommand.always_run == 1 + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/autolist.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/autolist.rb new file mode 100644 index 00000000..308230f9 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/autolist.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require "byebug/setting" +require "byebug/commands/list" + +module Byebug + # + # Setting for automatically listing source code on every stop. + # + class AutolistSetting < Setting + DEFAULT = 1 + + def initialize + ListCommand.always_run = DEFAULT + end + + def banner + "Invoke list command on every stop" + end + + def value=(val) + ListCommand.always_run = val ? 1 : 0 + end + + def value + ListCommand.always_run == 1 + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/autopry.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/autopry.rb new file mode 100644 index 00000000..76519854 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/autopry.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require "byebug/setting" +require "byebug/commands/pry" + +module Byebug + # + # Setting for automatically invoking Pry on every stop. + # + class AutoprySetting < Setting + DEFAULT = 0 + + def initialize + PryCommand.always_run = DEFAULT + end + + def banner + "Invoke Pry on every stop" + end + + def value=(val) + PryCommand.always_run = val ? 1 : 0 + end + + def value + PryCommand.always_run == 1 + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/autosave.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/autosave.rb new file mode 100644 index 00000000..3378827e --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/autosave.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require "byebug/setting" + +module Byebug + # + # Setting for automatically saving previously entered commands to history + # when exiting the debugger. + # + class AutosaveSetting < Setting + DEFAULT = true + + def banner + "Automatically save command history record on exit" + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/basename.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/basename.rb new file mode 100644 index 00000000..9b442dac --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/basename.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "byebug/setting" + +module Byebug + # + # Command to display short paths in file names. + # + # For example, when displaying source code information. + # + class BasenameSetting < Setting + def banner + ": information after every stop uses short paths" + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/callstyle.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/callstyle.rb new file mode 100644 index 00000000..7d56ec12 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/callstyle.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require "byebug/setting" + +module Byebug + # + # Setting to customize the verbosity level for stack frames. + # + class CallstyleSetting < Setting + DEFAULT = "long" + + def banner + "Set how you want method call parameters to be displayed" + end + + def to_s + "Frame display callstyle is '#{value}'" + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/fullpath.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/fullpath.rb new file mode 100644 index 00000000..546887e1 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/fullpath.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "byebug/setting" + +module Byebug + # + # Setting to display full paths in backtraces. + # + class FullpathSetting < Setting + DEFAULT = true + + def banner + "Display full file names in backtraces" + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/histfile.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/histfile.rb new file mode 100644 index 00000000..d5caa2e2 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/histfile.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require "byebug/setting" + +module Byebug + # + # Setting to customize the file where byebug's history is saved. + # + class HistfileSetting < Setting + DEFAULT = File.expand_path(".byebug_history") + + def banner + "File where cmd history is saved to. Default: ./.byebug_history" + end + + def to_s + "The command history file is #{value}\n" + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/histsize.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/histsize.rb new file mode 100644 index 00000000..07022bcb --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/histsize.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require "byebug/setting" + +module Byebug + # + # Setting to customize the number of byebug commands to be saved in history. + # + class HistsizeSetting < Setting + DEFAULT = 256 + + def banner + "Maximum number of commands that can be stored in byebug history" + end + + def to_s + "Maximum size of byebug's command history is #{value}" + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/linetrace.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/linetrace.rb new file mode 100644 index 00000000..ef0e9eb4 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/linetrace.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require "byebug/setting" + +module Byebug + # + # Setting to enable/disable linetracing. + # + class LinetraceSetting < Setting + def banner + "Enable line execution tracing" + end + + def value=(val) + Byebug.tracing = val + end + + def value + Byebug.tracing? + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/listsize.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/listsize.rb new file mode 100644 index 00000000..2ba175c6 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/listsize.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require "byebug/setting" + +module Byebug + # + # Setting to customize the number of source code lines to be displayed every + # time the "list" command is invoked. + # + class ListsizeSetting < Setting + DEFAULT = 10 + + def banner + "Set number of source lines to list by default" + end + + def to_s + "Number of source lines to list is #{value}\n" + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/post_mortem.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/post_mortem.rb new file mode 100644 index 00000000..da553492 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/post_mortem.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require "byebug/setting" + +module Byebug + # + # Setting to enable/disable post_mortem mode, i.e., a debugger prompt after + # program termination by unhandled exception. + # + class PostMortemSetting < Setting + def initialize + Byebug.post_mortem = DEFAULT + end + + def banner + "Enable/disable post-mortem mode" + end + + def value=(val) + Byebug.post_mortem = val + end + + def value + Byebug.post_mortem? + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/savefile.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/savefile.rb new file mode 100644 index 00000000..93540b6d --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/savefile.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require "byebug/setting" + +module Byebug + # + # Setting to customize the file where byebug's history is saved. + # + class SavefileSetting < Setting + DEFAULT = File.expand_path("#{ENV['HOME'] || '.'}/.byebug_save") + + def banner + "File where settings are saved to. Default: ~/.byebug_save" + end + + def to_s + "The command history file is #{value}\n" + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/stack_on_error.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/stack_on_error.rb new file mode 100644 index 00000000..45594298 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/stack_on_error.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require "byebug/setting" + +module Byebug + # + # Setting to enable/disable the display of backtraces when evaluations raise + # errors. + # + class StackOnErrorSetting < Setting + def banner + "Display stack trace when `eval` raises an exception" + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/width.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/width.rb new file mode 100644 index 00000000..49a0ebe8 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/settings/width.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require "byebug/setting" + +module Byebug + # + # Setting to customize the maximum width of byebug's output. + # + class WidthSetting < Setting + DEFAULT = 160 + + def banner + "Number of characters per line in byebug's output" + end + + def to_s + "Maximum width of byebug's output is #{value}" + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/source_file_formatter.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/source_file_formatter.rb new file mode 100644 index 00000000..fdebbf18 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/source_file_formatter.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require "byebug/helpers/file" +require "byebug/setting" + +module Byebug + # + # Formats specific line ranges in a source file + # + class SourceFileFormatter + include Helpers::FileHelper + + attr_reader :file, :annotator + + def initialize(file, annotator) + @file = file + @annotator = annotator + end + + def lines(min, max) + File.foreach(file).with_index.map do |line, lineno| + next unless (min..max).cover?(lineno + 1) + + format( + "%s %#{max.to_s.size}d: %s", + annotation: annotator.call(lineno + 1), + lineno: lineno + 1, + source: line + ) + end + end + + def lines_around(center) + lines(*range_around(center)) + end + + def range_around(center) + range_from(center - size / 2) + end + + def range_from(min) + first = amend_initial(min) + + [first, first + size - 1] + end + + def amend_initial(line) + amend(line, max_initial_line) + end + + def amend_final(line) + amend(line, max_line) + end + + def max_initial_line + max_line - size + 1 + end + + def max_line + @max_line ||= n_lines(file) + end + + def size + [Setting[:listsize], max_line].min + end + + def amend(line, ceiling) + [ceiling, [1, line].max].min + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/subcommands.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/subcommands.rb new file mode 100644 index 00000000..9aa39b2b --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/subcommands.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require "forwardable" + +require "byebug/helpers/reflection" +require "byebug/command_list" + +module Byebug + # + # Subcommand additions. + # + module Subcommands + def self.included(command) + command.extend(ClassMethods) + end + + extend Forwardable + def_delegators "self.class", :subcommand_list + + # + # Delegates to subcommands or prints help if no subcommand specified. + # + def execute + subcmd_name = @match[1] + return puts(help) unless subcmd_name + + subcmd = subcommand_list.match(subcmd_name) + raise CommandNotFound.new(subcmd_name, self.class) unless subcmd + + subcmd.new(processor, arguments).execute + end + + # + # Class methods added to subcommands + # + module ClassMethods + include Helpers::ReflectionHelper + + # + # Default help text for a command with subcommands + # + def help + super + subcommand_list.to_s + end + + # + # Command's subcommands. + # + def subcommand_list + @subcommand_list ||= CommandList.new(commands) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/version.rb b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/version.rb new file mode 100644 index 00000000..5efefac6 --- /dev/null +++ b/path/ruby/2.6.0/gems/byebug-11.0.1/lib/byebug/version.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +# +# Reopen main module to define the library version +# +module Byebug + VERSION = "11.0.1" +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/.yardopts b/path/ruby/2.6.0/gems/capybara-3.29.0/.yardopts new file mode 100644 index 00000000..29c933bc --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/.yardopts @@ -0,0 +1 @@ +--markup markdown diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/History.md b/path/ruby/2.6.0/gems/capybara-3.29.0/History.md new file mode 100644 index 00000000..457d4c54 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/History.md @@ -0,0 +1,1743 @@ +# Version 3.29.0 +Release date: Unreleased + +### Added + +* Allow clicking on file input when using the block version of `attach_file` with Chrome and Firefox +* Spatial filters (`left_of`, `right_of`, `above`, `below`, `near`) +* rack_test driver now supports clicking on details elements to open/close them + +### Fixed + +* rack_test driver correctly determines visibility for open details elements descendants + +### Changed + +* Results will now be lazily evaluated when using JRuby >= 9.2.8.0 + + +# Version 3.28.0 +Release date: 2019-08-03 + +### Added + +* Allow forcing HTML5 or legacy dragging via the `:html5` option to `drag_to` when using Selenium with Chrome or Firefox +* Autodetection of drag type interprets not seeing the mousedown event as legacy. +* HTML5 form validation `:valid` node filter added to `:field` and `:fillable_field` selectors +* When using Capybara registered :puma server - patches Puma 4.0.x to fix SSL connection behavior. Removes + default `queue_requests` setting - Issue #2227 + +# Version 3.27.0 +Release date: 2019-07-28 + +### Added + +* Allow to use chromedriver/geckodriver native `is_element_displayed` endpoint via Selenium + driver `native_displayed` option for performance reasons. Disabled by default due to endpoints + currently not handling <details> element descendants visibility correctly. + +### Fixed + +* Ignore negative lookahead/lookbehind regex when performing initial XPath text matching +* Reloading of elements found via `ancestor` and `sibling` +* Only default puma settings to `queue_requests: false` when using SSL +* Visibility of descendants of <details> elements is correctly determined when using rack_test + and the selenium driver with Capybara optimized atoms +* local/session storage clearance in Chrome when clearing only one of them - Issue #2233 + +# Version 3.26.0 +Release date: 2019-07-15 + +### Added + +* `w3c_click_offset` configuration option applies to `right_click` and `double_click` as well as `click` +* Warning when passing `nil` to the text/content assertions/expectations +* `Session#server_url` returns the base url the AUT is being run at (when controlled by Capybara) +* `option` selector type accepts an integer as locator + +### Fixed + +* Default puma server registration now specifies `queue_requests: false` - Issue #2227 +* Workaround issue with FF 68 and hanging during reset if a system modal is visible +* Don't expand file path if it's already absolute - Issue #2228 + +# Version 3.25.0 +Release date: 2019-06-27 + +### Added + +* Animation disabler also disables before and after pseudoelements - Issue #2221 [Daniel Heath] +* `w3c_click_offset` configuration option to determine whether click offsets are calculated from element + center or top left corner + +### Fixed + +* Woraround issue with chromedriver 76/77 in W3C mode losing mouse state during legacy drag. Only fixed if + both source and target are simultaenously inside the viewport - Issue #2223 +* Negative ancestor expectations/predicates were incorrectly checking siblings rather than ancestors + +# Version 3.24.0 +Release date: 2019-06-13 + +### Added + +* Log access when using the Selenium driver with Chrome 75 in W3C mode has been reenabled. + +### Changed + +* Selenium driver now selects all current content and then sends keys rather than clearing field by JS + and then sending keys when setting values to text inputs in order to more closely simulate user behavior + +### Fixed + +* Relative paths passed to `attach_file` will be assumed to be relative to the current working directory when using the + Selenium driver + +# Version 3.23.0 +Release date: 2019-06-10 + +### Added + +* Improved error message when using Chrome in W3C mode and attempting to access logs +* Support driver specific options for Element#drag_to +* Support setting `` elements with the selenium driver + +### Fixed + +* Tightened conditions when in expression text option matching will be used +* Improved Selenium drivers HTML5 drag and drop emulation compatibility with SortableJS library (and others) + +# Version 3.22.0 +Release date: 2019-05-29 + +### Added + +* `ancestor`/`sibling` assertions and matchers added +* Documentation Updates and Fixes - Many thanks again to Masafumi Koba! [Masafumi Koba] +* Added `:with` alias for `:option` filter on `:checkbox` and `:radio_button` selectors + +### Changed + +* Selenium driver with Chrome >= 73 now resets cookies and local/session storage after navigating + to 'about:blank' when possible to minimize potential race condition + +# Version 3.21.0 +Release date: 2019-05-24 + +### Added + +* Element#drop - Chrome and Firefox, via the selenium driver, support dropping files/data on elements +* Default CSS used for `attach_file` `make_visible: true` now includes auto for + height and width to handle more ways of hiding the file input element +* Documentation Updates and Fixes - Many thanks to Masafumi Koba! [Masafumi Koba] + +### Changed + +* Deprecate support for CSS locator being a Symbol + +# Version 3.20.2 +Release date: 2019-05-19 + +### Fixed + +* Move `uglifier` from runtime to development dependency [miyucy] + +# Version 3.20.1 +Release date: 2019-05-17 + +### Fixed + +* RackTest driver considers <template> elements to be non-visible and ignores the contents + +# Version 3.20.0 +Release date: 2019-05-14 + +### Added + +* `Node#obscured?` to check viewport presence and element overlap +* `:obscured` system filter to check whether elements are obscured in finders, assertions, and expectations +* :label selector :for option can be a regexp +* Significantly smaller `isDisplayed`/`getAttribute` atoms for selenium driver. If these produce issues you can disable their use + by setting an environment variable named 'DISABLE_CAPYBARA_SELENIUM_OPTIMIZATIONS' (Please also report any issues). +* `href: false` option with `find_link`/`click_link`/:link selector ignores `href` presence/absence + +### Fixed + +* Workaround Safari issue with send_keys not correctly using top level modifiers +* Workaround Safari not retrying click due to incorrect error type +* Fix Safari attach_file block mode when clicking elements associated to the file input +* Workaround Safari issue with repeated hover + +# Version 3.19.1 +Release date: 2019-05-11 + +### Fixed + +* Fix access to specializations when Selenium::Driver is subclassed [James Mead] + +# Version 3.19.0 +Release date: 2019-05-09 + +### Added + + +* Syntactic sugar `#once`, `#twice`, `#thrice`, `#exactly`, `#at_least`, `#at_most`, and `#times` + added to `have_selector`, `have_css`, `have_xpath`, and `have_text` RSpec matchers +* Support for multiple expression types in Selector definitions +* Reduced wirecalls for common actions in Selenium driver + +### Fixed + +* Workaround Chrome 75 appending files to multiple file inputs +* Suppressed retry when detecting http vs https server connection + +# Version 3.18.0 +Release date: 2019-04-22 + +### Added + +* XPath Selector query optimized to make use of Regexp :text option in initial element find + +### Fixed + +* Workaround issue where Chrome/chromedriver 74 can return the wrong error type when a click is intercepted + +# Version 3.17.0 +Release date: 2019-04-18 + +### Added + +* Initial support for selenium-webdriver 4.0.0.alpha1 +* :button selector will now also match on `name` attribute + +### Fixed + +* Suppress warnings generated by using selenium-webdriver 3.141.5926 +* Mask Appium issue with finder visibility optimizations (non-optimal) + +# Version 3.16.2 +Release date: 2019-04-10 + +### Fixed + +* Fix Session#quit resetting of memoized document + +# Version 3.16.1 +Release date: 2019-03-30 + +### Fixed + +* Fix potential 'uninitialized constant' error when using the :selenium_chrome driver [jeffclemens-ab] + +# Version 3.16 +Release date: 2019-03-28 + +### Changed + +* Ruby 2.4.0+ is now required +* Selenium driver now defaults to using a persistent http client connection + +### Added + +* :wait option in predicates now accepts `true` to selectively override when `Capybara.predicates_wait == false` + +# Version 3.15 +Release date: 2019-03-19 + +### Added + +* `attach_file` now supports a block mode on JS capable drivers to more accurately test user behavior when file inputs are hidden (beta) +* :table selector now supports `with_rows`, 'rows', `with_cols`, and 'cols' filters + +### Fixed + +* Fix link selector when `Capybara.test_id` is set - Issue #2166 [bingjyang] + + +# Version 3.14 +Release date: 2019-02-25 + +### Added + +* rack_test driver now supports reloading elements when the document changes - Issue #2157 +* Selenium driver HTML5 drag-drop emulation now emits multiple move events so drag direction + is determinable [Erkki Eilonen, Thomas Walpole] +* Capybara.server_errors now defaults to [Exception] - Issue #2160 [Edgars Beigarts] +### Fixed + +* Workaround hover issue with FF 65 - Issue #2156 +* Workaround chromedriver issue when setting blank strings to react controlled text fields +* Workaround chromedriver issue with popup windows not loading content - https://bugs.chromium.org/p/chromedriver/issues/detail?id=2650&q=load&sort=-id&colspec=ID%20Status%20Pri%20Owner%20Summary + +# Version 3.13.2 +Release date: 2019-01-24 + +### Fixed + +* Remove extraneous output + +# Version 3.13.1 +Release date: 2019-01-24 + +### Fixed + +* Only use Selenium visibility optimization when JS atom is available - Issue #2151 + +# Version 3.13.0 +Release date: 2019-01-23 + +### Added + +* Session#quit added +* #scroll_to added to allow scrolling page/elements to specified locations +* Speed optimizations around multiple element location and path generation when using the Selenium driver +* Support for locator type checking in custom selectors +* Allow configuration of gumbo use - defaults to off +* `assert_style`/`has_style`/`have_style` deprecated in favor of `assert_matches_style`/`matches_styles?`/`match_style` +* :style filter added to selectors + +# Version 3.12.0 +Release date: 2018-11-28 + +### Added + +* Support Ruby 2.6 endless range in Result#[] and query `:between` option +* Pre-registered headless firefox driver :selenium_headless [Andrew Havens] +* Selenium driver now defaults to clearing `sessionStorage` and `localStorage`. To disable pass `clear_local_storage: false` and/or `clear_session_storage: false` when creating Capybara::Selenium::Driver instance in your driver registration + +### Fixed + +* Raise error if only :x or :y are passed as an offset to click methods + +### Removed + +* Support for RSpec < 3.5 + +# Version 3.11.1 +Release date: 2018-11-16 + +### Fixed + +* Fixed :link_or_button XPath generation when it has had an expression filter added + +# Version 3.11.0 +Release date: 2018-11-14 + +### Added + +* Ability for node filters to set detailed error messages +* `Capybara::HTML` Will use `nokogumbo` for HTML parsing if installed +* `Selector#locator_filter` added to allow for dynamic locator in selectors + +### Fixed + +* Node filters are evaluated in the context of the Selector they are used in to ensure the correct options are used + +# Version 3.10.1 +Release date: 2018-11-03 + +### Fixed + +* Fix `aria-label` and `test_id` matching for `link_or_button` selector type - Issue #2125 +* Fixed crash in element path creation for matcher failure messages - Issue #2120 + +# Version 3.10.0 +Release date: 2018-10-23 + +### Added + +* :class filter can now check for class names starting with ! +* Selector `xpath`/`css` expression definitions will get filter names from block parameters if not explicitly provided +* `any_of_selectors` assertions and matchers to complement `all_of_selectors` and `none_of_selectors` + +### Fixed + +* Selector `css` expression definiton declared filters now work again +* Cleaned up warnings [Yuji Yaginuma] +* Workaround installation of rspec matcher proxies under jruby by reverting to the old solution not using prepend, so jruby bugs are not hit - Issue #2115 + +# Version 3.9.0 +Release date: 2018-10-03 + +### Added + +* Selenium with Chrome removes all cookies at session reset instead of just cookies from current domain if possible +* Support for Regexp for system :id and :class filters where possible +* `using_session` now accepts a session object as well as the name of the session for users who manually manage sessions +* The `:field` selector will now find `type = "hidden"` fields if the `type: "hidden"` filter option is provided + +# Version 3.8.2 +Release date: 2018-09-26 + +### Fixed + +* Fixed negated class selector option - Issue #2103 + +# Version 3.8.1 +Release date: 2018-09-22 + +### Fixed + +* Filling in of date fields with a string when using selenium chrome regression [Micah Geisel] + +# Version 3.8.0 +Release date: 2018-09-20 + +### Added + +* Workaround geckodriver 0.22 issue with undefined pause durations +* :element selector ignores XML namespaces + +### Fixed + +* Added Errno::ECONNRESET to the errors which will allows https server detection + +# Version 3.7.2 +Release date: 2018-09-12 + +### Fixed + +* Fix MatchQuery based matchers when used on a root element found using any type of parent/ancestor query - Issue #2097 + +* Fix Chrome/FF HTML5 drag simulation for elements (a, img) which default to draggable - Issue #2098 + +# Version 3.7.1 +Release date: 2018-09-05 + +### Fixed + +* Restored ability to pass symbol as the CSS selector when calling `has_css?`/`have_css`/etc - Issue #2093 + +# Version 3.7.0 +Release date: 2018-09-02 + +### Added + +* `Capybara.disable_animation` can be set to a CSS selector to identify which elements will have animation disabled [Michael Glass] +* `Capybara.default_normalize_ws` option which sets whether or not text predicates and matchers (`has_text?`, `has_content?`, `assert_text`, etc) use `normalize_ws` option by default. Defaults to false. [Stegalin Ivan] +* Selector based predicates, matchers, and finders now support the `:normalize_ws` option for the `:text`/`:exact_text` filters. Defaults to the `Capybara.default_normalize_ws`setting above. +* Element `choose`/`check`/`uncheck`/`attach_file`/`fill_in` can now operate on the element they're called on or a descendant if no locator is passed. + +### Fixed + +* All CSS styles applied by the `Element#attach_file` `:make_visible` option will now have `!important` priority set to ensure they override any other specified style. +* Firefox file inputs are only manually cleared when necessary. + +# Version 3.6.0 +Release date: 2018-08-14 + +### Added + +* Workaround geckodriver/firefox send_keys issues as much as possible using the Selenium actions API +* Workaround lack of HTML5 native drag and drop events when using Selenium driver with Chrome and FF >= 62 +* `Capybara.predicates_wait` option which sets whether or not Capybaras matcher predicate methods (`has_css?`, `has_selector?`, `has_text?`, etc.) default to using waiting/retrying behavior (defaults to true) + +# Version 3.5.1 +Release date: 2018-08-03 + +### Fixed + +* Fixed misspelled method name `refute_matches_elector` => `refute_matches_selector` + +# Version 3.5.0 +Release date: 2018-08-01 + +### Added + +* text predicates and matchers (`has_text?`, `has_content?`, `assert_text`, etc) now support a `normalize_ws` option + +### Fixed + +* `attach_file` with Selenium and local Firefox 62+ now correctly generates only one change event when attaching multiple files + +# Version 3.4.2 +Release date: 2018-07-24 + +### Fixed + +* `match_xxx` selectors and `matches_xxx?` predicates work correctly with elements found using a sibling selector - Issue #2073 + +# Version 3.4.1 +Release date: 2018-07-20 + +### Fixed + +* `Session#evaluate_script` now strips the script in `Session` rather than only in the Selenium driver + +# Version 3.4.0 +Release date: 2018-07-19 + +### Fixed + +* Make selenium driver :backspace clear stategy work even if caret location is in middle of field content [Champier Cyril] +* Selenium issue with fieldset nested in disabled fieldset not being considered disabled +* `Session#evaluate_script` and `Element#evaluate_script` now strip leading/trailing whitespace from scripts [Ian Lesperance] + +### Added + +* Work around Selenium lack of support for `file_detector` with remote geckodriver +* `#within_frame` locator is optional when only one frame exists +* `Capybara.test_id` option that allows for matching the Capybara provided selector types on an arbitrary attribute + (defaults to nil), set to your test id attribute ('data-test-id, etc) if using test id attributes in your project + +# Version 3.3.1 +Release date: 2018-06-27 + +### Fixed + +* `selenium-webdriver` version check [ahorek] +* Selenium driver correctly responds to `disabled?` for fieldset elements - Issue #2059 [Thomas Walpole] + +# Version 3.3.0 +Release date: 2018-06-25 + +### Added + +* RackTest driver now handles 307/308 redirects +* `execute_async_script` can now be called on elements to run the JS in the context of the element +* `:download` filter option on `:link' selector +* `Window#fullscreen` +* `Element#style` and associated matchers + +### Changed + +* Minimum "supported" `selenium-webdriver` is raised to 3.5.0 (but you really should be using newer than that) + +### Fixes + +* Selenium driver with Firefox workaround for clicking on table row - https://github.com/mozilla/geckodriver/issues/1228 +* :class and :id filters applied to CSS based selectors now correctly handle the CSS comma +* Selenium driver handles namespaces when generating an elements `#path` - Issue #2048 + +# Version 3.2.1 +Release date: 2018-06-04 + +### Fixes + +* Only split CSS selectors when :class or :id options are given. Restores 3.1.1 functionality for now but the underlying issue + will require a larger fix, hopefully coming soon. - Issue #2044 [Thomas Walpole] + +# Version 3.2.0 +Release date: 2018-06-01 + +### Changed + +* Ruby 2.3.0+ is now required +* `ElementNotFound` errors raised in selector filters are interpreted as non-matches + +### Added + +* New global configuration `default_set_options` used in `Capybara::Node::Element#set` as default `options` hash [Champier Cyril] +* `execute_script` and `evaluate_script` can now be called on elements to run the JS in the context of the element [Thomas Walpole] +* Filters in custom selectors now support a `matcher` Regexp to handle multiple filter options [Thomas Walpole] +* `:element` selector type which will match on any attribute (other than the reserved names) passed as a filter option [Thomas Walpole] +* `:class` filter option now supports preceding class names with `!` to indicate not having that class [Thomas Walpole] +* `:class` and `:id` filter options now accept `XPath::Expression` objects to allow for more flexibility in matching [Thomas Walpole] +* `Capybara.disable_animation` setting which triggers loading of a middleware that attempts to disable animations in pages. + This is very much a beta feature and may change/disappear in the future. [Thomas Walpole] + +# Version 3.1.1 +Release date: 2018-05-25 + +### Fixes + +* Ensure keystrokes are sent when setting time/date fields to a string with the Selenium driver [Thomas Walpole] + +# Version 3.1.0 +Release date: 2018-05-10 + +### Added + +* Support for using `select` with text inputs associated with a datalist element +* `type` filter on `:button` selector +* Support for server operating in https mode +* Selenium driver now uses JS to fill_in/set date and time fields when passed date or time objects [Aleksei Gusev, Thomas Walpole] + +# Version 3.0.3 +Release date: 2018-04-30 + +### Fixes + +* Issue in `check` where the locator string could not be omitted +* Selenium browser type detection when using remote [Ian Ker-Seymer] +* Potential hang when waiting for requests to complete [Chris Zetter] + +# Version 3.0.2 +Release date: 2018-04-13 + +### Fixes + +* Fix expression filter descriptions in some selector failure messages +* Fix compounding of negated matechers - Issue #2010 + +# Version 3.0.1 +Release date: 2018-04-06 + +### Changed + +* Restored ability for `Capybara.server=` to accept a proc which was accidentally removed in 3.0.0 + +# Version 3.0.0 +Release date: 2018-04-05 + +### Changed + +* Selenium driver only closes extra windows for browsers where that is known to work (Firefox, Chrome) +* "threadsafe" mode is no longer considered beta + +### Fixes + +* Multiple file attach_file with Firefox +* Use Puma::Server directly rather than Rack::Handler::Puma so signal handlers don't prevent test quitting + +# Version 3.0.0.rc2 +Release date: 2018-03-23 + +### Changed + +* Visibile text whitespace is no longer fully normalized in favor of being more in line with the WebDriver spec for visible text +* Drivers are expected to close extra windows when resetting the session +* Selenium driver supports Date/Time when filling in date/time/datetime-local inputs +* `current_url` returns the url for the top level browsing context +* `title` returns the title for the top level browsing context + +### Added + +* `Driver#frame_url` returns the url for the current frame +* `Driver#frame_title` returns the title for the current frame + +# Version 3.0.0.rc1 +Release date: 2018-03-02 + +### Added +* Support for libraries wrapping Capybara elements and providing a `#to_capybara_node` method + +### Changed + +* `first` now raises ElementNotFound, by default, instead of returning nil when no matches are found - Issue #1507 +* 'all' now waits for at least one matching element by default. Pass `wait: false` if you want the previous + behavior where an empty result would be returned immediately if no matching elements exist yet. +* ArgumentError raised if extra parameters passed to selector queries + +### Removed + +* Ruby < 2.2.2 support +* `Capybara.exact_options` no longer exists. Just use `exact: true` on relevant actions/finders if necessary. +* All previously deprecated methods removed +* RSpec 2.x support +* selenium-webdriver 2.x support +* Nokogiri < 1.8 support +* `field_labeled` alias for `find_field` + +# Version 2.18.0 +Release date: 2018-02-12 + +### Fixed + +* Firefox/geckodriver setting of contenteditable childs contents +* Ignore Selenium::WebDriver::Error::SessionNotCreatedError when quitting driver [Tim Connor] + +### Removed + +* Headless chrome modal JS injection that is no longer needed for Chrome 64+/chromedriver 2.35+ + + +# Version 2.17.0 +Release date: 2018-01-02 + +### Added + +* `have_all_of_selectors`, `have_none_of_selectors` RSpec matchers for parity with minitest assertions [Thomas Walpole] + +### Fixed + +* Allow xpath 3.x gem [Thomas Walpole] +* Issue when drivers returned nil for `current_path` and a matcher was used with a Regexp [Thomas Walpole] +* Error message when visible element not found, but non-visible was [Andy Klimczak] + +# Version 2.16.1 +Release date: 2017-11-20 + +### Fixed + +* Fix rack_test driver for rack_test 0.7.1/0.8.0 [Thomas Walpole] +* `accept_prompt` response text can contain quotes when using selenium with headless chrome [Thomas Walpole] + +# Version 2.16.0 +Release date: 2017-11-13 + +### Added + +* Attempt to move element into view when selenium doesn't correctly do it - See PR #1917 [Thomas Walpole] +* `current_path` matchers will now autodetect path vs url based on string to be matched. Deprecates + `:only_path` in favor of `:ignore_query` option [Thomas Walpole] +* Session#evaluate_async_script [Thomas Walpole] + +### Fixed + +* Default prompt value when using headless Chrome works correctly [Thomas Walpole] +* Support new modal error returned by selenium-webdriver 3.7 for W3C drivers [Thomas Walpole] +* Calling `respond_to?` on the object passed to `Capybara.configure` block - Issue #1935 + +# Version 2.15.4 +Release date: 2017-10-07 + +### Fixed +* Visiting an absolute URL shouldn't overwrite the port when no server or always_include_port=false - Issue #1921 + +# Version 2.15.3 +Release date: 2017-10-03 + +### Fixed +* Visiting '/' when Capybara.app_host has a trailing '/' - Issue #1918 [Thomas Walpole] + +# Version 2.15.2 +Release date: 2017-10-02 + +### Fixed + +* Include within scope description in element not found/ambiguous errors [Thomas Walpole] +* Raise error when no activation block is passed to modal methods if using headless chrome [Thomas Walpole] +* Don't retry element access when inspecting [Ivan Neverov] +* Don't override a specified port (even if it is default port) in visited url [Thomas Walpole] + +# Version 2.15.1 + +Release date: 2017-08-04 + +### Fixed + +* `attach_file` with no extension/MIME type when using the `:rack_test` driver [Thomas Walpole] + +# Version 2.15.0 + +Release date: 2017-08-04 + +### Added + +* `sibling` and `ancestor` finders added [Thomas Walpole] +* Added ability to pass options to registered servers when setting +* Added basic built-in driver registrations `:selenium_chrome` and `:selenium_chrome_headless` [Thomas Walpole] +* Add `and_then` to Capybara RSpec matchers which behaves like the previous `and` compounder. [Thomas Walpole] +* Compound RSpec expectations with Capybara matchers now run both matchers inside a retry loop rather + than waiting for one to pass/fail before checking the second. Will make `#or` more performant and confirm + both conditions are true "simultaneously" for `and`. [Thomas Walpole] + If you still want the +* Default filter values are now included in error descriptions [Thomas Walpole] +* Add `Session#refresh` [Thomas Walpole] +* Loosened restrictions on where `Session#within_window` can be called from [Thomas Walpole] +* Switched from `mime-types` dependency to `mini_mime` [Jason Frey] + +# Version 2.14.4 + +Release date: 2017-06-27 + +### Fixed + +* Fix retrieval of session_options for HaveSelector matcher descriptions - Issue #1883 + +# Version 2.14.3 + +Release date: 2017-06-15 + +### Fixed + +* Minitest assertions now raise the correct error type - Issue #1879 [Thomas Walpole] +* Improve flexibility of detecting Chrome headless mode [Thomas Walpole] + +# Version 2.14.2 + +Release date: 2017-06-09 + +### Fixed + +* Workaround for system modals when using headless Chrome now works if the page changes + +# Version 2.14.1 + +Release date: 2017-06-07 + +### Fixed + +* Catch correct error when unexpected system modals are discovered in latest selenium [Thomas Walpole] +* Update default `puma` server registration to encourage it to run in single mode [Thomas Walpole] +* Suppress invalid element errors raised while lazily evaluating the results of `all` [Thomas Walpole] +* Added missing `with_selected` option to the :select selector to match `options`/`with_options` options - Issue #1865 [Bartosz Nowak] +* Workaround broken system modals when using selenium with headless Chrome + +# Version 2.14.0 + +Release date: 2017-05-01 + +### Added + +* "threadsafe" mode that allows per-session configuration +* `:type` filter added to the `:fillable_field` selector +* Proxy methods when using RSpec for `all`/`within` that call either the Capybara::DSL or RSpec matchers + depending on arguments passed +* Support for the new errors in selenium-webdriver 3.4 + +### Fixed + +* Element#inspect doesn't raise an error on obsolete elements +* Setting a contenteditable element with Selenium and Chrome 59 +* Workaround a hang while setting the window size when using geckodriver 0.16 and Firefox 53 +* Clicking on url with a blank href goes to the current url when using the RackTest driver + +# Version 2.13.0 + +Release date: 2017-03-16 + +### Added + +* Selenium driver supports returning element(s) from evaluate_script [Thomas Walpole] +* rack_test driver supports click on checkboxes and radio buttons to change their states [Thomas Walpole] +* Support RSpec equivalent assertions and expectations for MiniTest [Thomas Walpole] + +### Fixed + +* Editing of content editable children with selenium + +# Version 2.12.1 + +Release date: 2017-02-16 + +### Fixed +* Disable lazy Capybara::Results evaluation for JRuby due to ongoing issues + +# Version 2.12.0 + +Release date: 2017-01-22 + +### Added + +* Session#switch_to_frame for manually handling frame switching - Issue #1365 [Thomas Walpole] +* Session#within_frame now accepts a selector type (defaults to :frame) and locator [Thomas Walpole] +* Session#execute_script and Session#evaluate_script now accept optional arguments that will be passed to the JS function. This may not be supported + by all drivers, and the types of arguments that may be passed is limited. If drivers opt to support this feature they should support passing page elements. [Thomas Walpole] +* :exact option for text and title matchers - Issue #1256 [Thomas Walpole] +* :exact_text option for selector finders/minders - Issue #1256 [Thomas Walpole] +* Capybara.exact_text setting that affects the text matchers and :text options passed to selector finders/matchers. Issue #1256 [Thomas Walpole] +* :make_visible option for #attach_file that allows for convenient changing of the CSS style of a file input element before attaching the file to it. Requires driver + support for passing page elements to Session#execute_script [Thomas Walpole] +* assert_all_selectors/assert_none_of_selectors assertions added +* :link selector (used by find_link/click_link) now supports finding hyperlink placeholders (no href attribute) when href: nil option is specified [Thomas Walpole] +* `within_element` as an alias of `within` due to RSpec collision + +### Fixed + +* Fields inside a disabled fieldset are now correctly considered disabled - Issue #1816 [Thomas Walpole] +* Lazy Capybara::Results evaluation enabled for JRuby 9.1.6.0+ +* A driver returning nil for #current_url won't raise an exception when calling #current_path [Dylan Reichstadt] +* Support Ruby 2.4.0 unified Integer [Koichi ITO] +* RackTest driver no longer modifies the text content of textarea elements in order to behave more like a real browser [Thomas Walpole] +* TextQuery (assert_text/have_text/etc) now ignores errors when trying to generate more helpful errors messages so the original error isn't hidden [Thomas Walpole] + +# Version 2.11.0 + +Release date: 2016-12-05 + +### Added + +* Options for clearing session/local storage on reset added to the Selenium driver +* Window size changes wait for the size to stabilize +* Defined return value for most actions +* Ignore specific error when qutting selenium driver instance - Issue #1773 [Dylan Reichstadt, Thomas Walpole] +* Warn on selenium unknown errors rather than raising when quitting driver [Adam Pohorecki, Thomas Walpole] +* Capybara::Result#each now returns an `Enumerator` when called without a block - Issue #1777 [Thomas Walpole] + +### Fixed + +* Selenium driver with Chrome should support multiple file upload [Thomas Walpole] +* Fix visible: :hidden with :text option behavior [Thomas Walpole] + +# Version 2.10.2 + +Release date: 2016-11-30 + +### Fixed + +* App exceptions with multiple parameter initializers now re-raised correctly - Issue #1785 [Michael Lutsiuk] +* Use Addressable::URI when parsing current_path since it's more lenient of technically invalid URLs - Issue #1801 [Marcos Duque, Thomas Walpole] + +# Version 2.10.1 + +Release date: 2016-10-08 + +### Fixed +* App errors are now correctly raised with the explanatory cause in JRuby [Thomas Walpole] +* Capybara::Result optimization disabled in JRuby due to issue with lazy enumerator evaluation [Thomas Walpole] + See: https://github.com/jruby/jruby/issues/4212 + +# Version 2.10.0 + +Release date: 2016-10-05 + +### Added + +* Select `").addClass(this._triggerClass).html(a?e("").attr({src:a,alt:n,title:n}):n)),t[r?"before":"after"](i.trigger),i.trigger.click(function(){return e.datepicker._datepickerShowing&&e.datepicker._lastInput===t[0]?e.datepicker._hideDatepicker():e.datepicker._datepickerShowing&&e.datepicker._lastInput!==t[0]?(e.datepicker._hideDatepicker(),e.datepicker._showDatepicker(t[0])):e.datepicker._showDatepicker(t[0]),!1}))},_autoSize:function(e){if(this._get(e,"autoSize")&&!e.inline){var t,i,s,n,a=new Date(2009,11,20),o=this._get(e,"dateFormat");o.match(/[DM]/)&&(t=function(e){for(i=0,s=0,n=0;e.length>n;n++)e[n].length>i&&(i=e[n].length,s=n);return s},a.setMonth(t(this._get(e,o.match(/MM/)?"monthNames":"monthNamesShort"))),a.setDate(t(this._get(e,o.match(/DD/)?"dayNames":"dayNamesShort"))+20-a.getDay())),e.input.attr("size",this._formatDate(e,a).length)}},_inlineDatepicker:function(t,i){var s=e(t);s.hasClass(this.markerClassName)||(s.addClass(this.markerClassName).append(i.dpDiv),e.data(t,"datepicker",i),this._setDate(i,this._getDefaultDate(i),!0),this._updateDatepicker(i),this._updateAlternate(i),i.settings.disabled&&this._disableDatepicker(t),i.dpDiv.css("display","block"))},_dialogDatepicker:function(t,i,s,n,a){var o,h,l,u,d,c=this._dialogInst;return c||(this.uuid+=1,o="dp"+this.uuid,this._dialogInput=e(""),this._dialogInput.keydown(this._doKeyDown),e("body").append(this._dialogInput),c=this._dialogInst=this._newInst(this._dialogInput,!1),c.settings={},e.data(this._dialogInput[0],"datepicker",c)),r(c.settings,n||{}),i=i&&i.constructor===Date?this._formatDate(c,i):i,this._dialogInput.val(i),this._pos=a?a.length?a:[a.pageX,a.pageY]:null,this._pos||(h=document.documentElement.clientWidth,l=document.documentElement.clientHeight,u=document.documentElement.scrollLeft||document.body.scrollLeft,d=document.documentElement.scrollTop||document.body.scrollTop,this._pos=[h/2-100+u,l/2-150+d]),this._dialogInput.css("left",this._pos[0]+20+"px").css("top",this._pos[1]+"px"),c.settings.onSelect=s,this._inDialog=!0,this.dpDiv.addClass(this._dialogClass),this._showDatepicker(this._dialogInput[0]),e.blockUI&&e.blockUI(this.dpDiv),e.data(this._dialogInput[0],"datepicker",c),this},_destroyDatepicker:function(t){var i,s=e(t),n=e.data(t,"datepicker");s.hasClass(this.markerClassName)&&(i=t.nodeName.toLowerCase(),e.removeData(t,"datepicker"),"input"===i?(n.append.remove(),n.trigger.remove(),s.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)):("div"===i||"span"===i)&&s.removeClass(this.markerClassName).empty(),v===n&&(v=null))},_enableDatepicker:function(t){var i,s,n=e(t),a=e.data(t,"datepicker");n.hasClass(this.markerClassName)&&(i=t.nodeName.toLowerCase(),"input"===i?(t.disabled=!1,a.trigger.filter("button").each(function(){this.disabled=!1}).end().filter("img").css({opacity:"1.0",cursor:""})):("div"===i||"span"===i)&&(s=n.children("."+this._inlineClass),s.children().removeClass("ui-state-disabled"),s.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",!1)),this._disabledInputs=e.map(this._disabledInputs,function(e){return e===t?null:e}))},_disableDatepicker:function(t){var i,s,n=e(t),a=e.data(t,"datepicker");n.hasClass(this.markerClassName)&&(i=t.nodeName.toLowerCase(),"input"===i?(t.disabled=!0,a.trigger.filter("button").each(function(){this.disabled=!0}).end().filter("img").css({opacity:"0.5",cursor:"default"})):("div"===i||"span"===i)&&(s=n.children("."+this._inlineClass),s.children().addClass("ui-state-disabled"),s.find("select.ui-datepicker-month, select.ui-datepicker-year").prop("disabled",!0)),this._disabledInputs=e.map(this._disabledInputs,function(e){return e===t?null:e}),this._disabledInputs[this._disabledInputs.length]=t)},_isDisabledDatepicker:function(e){if(!e)return!1;for(var t=0;this._disabledInputs.length>t;t++)if(this._disabledInputs[t]===e)return!0;return!1},_getInst:function(t){try{return e.data(t,"datepicker")}catch(i){throw"Missing instance data for this datepicker"}},_optionDatepicker:function(t,i,s){var n,a,o,h,l=this._getInst(t);return 2===arguments.length&&"string"==typeof i?"defaults"===i?e.extend({},e.datepicker._defaults):l?"all"===i?e.extend({},l.settings):this._get(l,i):null:(n=i||{},"string"==typeof i&&(n={},n[i]=s),l&&(this._curInst===l&&this._hideDatepicker(),a=this._getDateDatepicker(t,!0),o=this._getMinMaxDate(l,"min"),h=this._getMinMaxDate(l,"max"),r(l.settings,n),null!==o&&void 0!==n.dateFormat&&void 0===n.minDate&&(l.settings.minDate=this._formatDate(l,o)),null!==h&&void 0!==n.dateFormat&&void 0===n.maxDate&&(l.settings.maxDate=this._formatDate(l,h)),"disabled"in n&&(n.disabled?this._disableDatepicker(t):this._enableDatepicker(t)),this._attachments(e(t),l),this._autoSize(l),this._setDate(l,a),this._updateAlternate(l),this._updateDatepicker(l)),void 0)},_changeDatepicker:function(e,t,i){this._optionDatepicker(e,t,i)},_refreshDatepicker:function(e){var t=this._getInst(e);t&&this._updateDatepicker(t)},_setDateDatepicker:function(e,t){var i=this._getInst(e);i&&(this._setDate(i,t),this._updateDatepicker(i),this._updateAlternate(i))},_getDateDatepicker:function(e,t){var i=this._getInst(e);return i&&!i.inline&&this._setDateFromField(i,t),i?this._getDate(i):null},_doKeyDown:function(t){var i,s,n,a=e.datepicker._getInst(t.target),o=!0,r=a.dpDiv.is(".ui-datepicker-rtl");if(a._keyEvent=!0,e.datepicker._datepickerShowing)switch(t.keyCode){case 9:e.datepicker._hideDatepicker(),o=!1;break;case 13:return n=e("td."+e.datepicker._dayOverClass+":not(."+e.datepicker._currentClass+")",a.dpDiv),n[0]&&e.datepicker._selectDay(t.target,a.selectedMonth,a.selectedYear,n[0]),i=e.datepicker._get(a,"onSelect"),i?(s=e.datepicker._formatDate(a),i.apply(a.input?a.input[0]:null,[s,a])):e.datepicker._hideDatepicker(),!1;case 27:e.datepicker._hideDatepicker();break;case 33:e.datepicker._adjustDate(t.target,t.ctrlKey?-e.datepicker._get(a,"stepBigMonths"):-e.datepicker._get(a,"stepMonths"),"M");break;case 34:e.datepicker._adjustDate(t.target,t.ctrlKey?+e.datepicker._get(a,"stepBigMonths"):+e.datepicker._get(a,"stepMonths"),"M");break;case 35:(t.ctrlKey||t.metaKey)&&e.datepicker._clearDate(t.target),o=t.ctrlKey||t.metaKey;break;case 36:(t.ctrlKey||t.metaKey)&&e.datepicker._gotoToday(t.target),o=t.ctrlKey||t.metaKey;break;case 37:(t.ctrlKey||t.metaKey)&&e.datepicker._adjustDate(t.target,r?1:-1,"D"),o=t.ctrlKey||t.metaKey,t.originalEvent.altKey&&e.datepicker._adjustDate(t.target,t.ctrlKey?-e.datepicker._get(a,"stepBigMonths"):-e.datepicker._get(a,"stepMonths"),"M");break;case 38:(t.ctrlKey||t.metaKey)&&e.datepicker._adjustDate(t.target,-7,"D"),o=t.ctrlKey||t.metaKey;break;case 39:(t.ctrlKey||t.metaKey)&&e.datepicker._adjustDate(t.target,r?-1:1,"D"),o=t.ctrlKey||t.metaKey,t.originalEvent.altKey&&e.datepicker._adjustDate(t.target,t.ctrlKey?+e.datepicker._get(a,"stepBigMonths"):+e.datepicker._get(a,"stepMonths"),"M");break;case 40:(t.ctrlKey||t.metaKey)&&e.datepicker._adjustDate(t.target,7,"D"),o=t.ctrlKey||t.metaKey;break;default:o=!1}else 36===t.keyCode&&t.ctrlKey?e.datepicker._showDatepicker(this):o=!1;o&&(t.preventDefault(),t.stopPropagation())},_doKeyPress:function(t){var i,s,n=e.datepicker._getInst(t.target); +return e.datepicker._get(n,"constrainInput")?(i=e.datepicker._possibleChars(e.datepicker._get(n,"dateFormat")),s=String.fromCharCode(null==t.charCode?t.keyCode:t.charCode),t.ctrlKey||t.metaKey||" ">s||!i||i.indexOf(s)>-1):void 0},_doKeyUp:function(t){var i,s=e.datepicker._getInst(t.target);if(s.input.val()!==s.lastVal)try{i=e.datepicker.parseDate(e.datepicker._get(s,"dateFormat"),s.input?s.input.val():null,e.datepicker._getFormatConfig(s)),i&&(e.datepicker._setDateFromField(s),e.datepicker._updateAlternate(s),e.datepicker._updateDatepicker(s))}catch(n){}return!0},_showDatepicker:function(t){if(t=t.target||t,"input"!==t.nodeName.toLowerCase()&&(t=e("input",t.parentNode)[0]),!e.datepicker._isDisabledDatepicker(t)&&e.datepicker._lastInput!==t){var i,n,a,o,h,l,u;i=e.datepicker._getInst(t),e.datepicker._curInst&&e.datepicker._curInst!==i&&(e.datepicker._curInst.dpDiv.stop(!0,!0),i&&e.datepicker._datepickerShowing&&e.datepicker._hideDatepicker(e.datepicker._curInst.input[0])),n=e.datepicker._get(i,"beforeShow"),a=n?n.apply(t,[t,i]):{},a!==!1&&(r(i.settings,a),i.lastVal=null,e.datepicker._lastInput=t,e.datepicker._setDateFromField(i),e.datepicker._inDialog&&(t.value=""),e.datepicker._pos||(e.datepicker._pos=e.datepicker._findPos(t),e.datepicker._pos[1]+=t.offsetHeight),o=!1,e(t).parents().each(function(){return o|="fixed"===e(this).css("position"),!o}),h={left:e.datepicker._pos[0],top:e.datepicker._pos[1]},e.datepicker._pos=null,i.dpDiv.empty(),i.dpDiv.css({position:"absolute",display:"block",top:"-1000px"}),e.datepicker._updateDatepicker(i),h=e.datepicker._checkOffset(i,h,o),i.dpDiv.css({position:e.datepicker._inDialog&&e.blockUI?"static":o?"fixed":"absolute",display:"none",left:h.left+"px",top:h.top+"px"}),i.inline||(l=e.datepicker._get(i,"showAnim"),u=e.datepicker._get(i,"duration"),i.dpDiv.css("z-index",s(e(t))+1),e.datepicker._datepickerShowing=!0,e.effects&&e.effects.effect[l]?i.dpDiv.show(l,e.datepicker._get(i,"showOptions"),u):i.dpDiv[l||"show"](l?u:null),e.datepicker._shouldFocusInput(i)&&i.input.focus(),e.datepicker._curInst=i))}},_updateDatepicker:function(t){this.maxRows=4,v=t,t.dpDiv.empty().append(this._generateHTML(t)),this._attachHandlers(t);var i,s=this._getNumberOfMonths(t),n=s[1],a=17,r=t.dpDiv.find("."+this._dayOverClass+" a");r.length>0&&o.apply(r.get(0)),t.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width(""),n>1&&t.dpDiv.addClass("ui-datepicker-multi-"+n).css("width",a*n+"em"),t.dpDiv[(1!==s[0]||1!==s[1]?"add":"remove")+"Class"]("ui-datepicker-multi"),t.dpDiv[(this._get(t,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl"),t===e.datepicker._curInst&&e.datepicker._datepickerShowing&&e.datepicker._shouldFocusInput(t)&&t.input.focus(),t.yearshtml&&(i=t.yearshtml,setTimeout(function(){i===t.yearshtml&&t.yearshtml&&t.dpDiv.find("select.ui-datepicker-year:first").replaceWith(t.yearshtml),i=t.yearshtml=null},0))},_shouldFocusInput:function(e){return e.input&&e.input.is(":visible")&&!e.input.is(":disabled")&&!e.input.is(":focus")},_checkOffset:function(t,i,s){var n=t.dpDiv.outerWidth(),a=t.dpDiv.outerHeight(),o=t.input?t.input.outerWidth():0,r=t.input?t.input.outerHeight():0,h=document.documentElement.clientWidth+(s?0:e(document).scrollLeft()),l=document.documentElement.clientHeight+(s?0:e(document).scrollTop());return i.left-=this._get(t,"isRTL")?n-o:0,i.left-=s&&i.left===t.input.offset().left?e(document).scrollLeft():0,i.top-=s&&i.top===t.input.offset().top+r?e(document).scrollTop():0,i.left-=Math.min(i.left,i.left+n>h&&h>n?Math.abs(i.left+n-h):0),i.top-=Math.min(i.top,i.top+a>l&&l>a?Math.abs(a+r):0),i},_findPos:function(t){for(var i,s=this._getInst(t),n=this._get(s,"isRTL");t&&("hidden"===t.type||1!==t.nodeType||e.expr.filters.hidden(t));)t=t[n?"previousSibling":"nextSibling"];return i=e(t).offset(),[i.left,i.top]},_hideDatepicker:function(t){var i,s,n,a,o=this._curInst;!o||t&&o!==e.data(t,"datepicker")||this._datepickerShowing&&(i=this._get(o,"showAnim"),s=this._get(o,"duration"),n=function(){e.datepicker._tidyDialog(o)},e.effects&&(e.effects.effect[i]||e.effects[i])?o.dpDiv.hide(i,e.datepicker._get(o,"showOptions"),s,n):o.dpDiv["slideDown"===i?"slideUp":"fadeIn"===i?"fadeOut":"hide"](i?s:null,n),i||n(),this._datepickerShowing=!1,a=this._get(o,"onClose"),a&&a.apply(o.input?o.input[0]:null,[o.input?o.input.val():"",o]),this._lastInput=null,this._inDialog&&(this._dialogInput.css({position:"absolute",left:"0",top:"-100px"}),e.blockUI&&(e.unblockUI(),e("body").append(this.dpDiv))),this._inDialog=!1)},_tidyDialog:function(e){e.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(t){if(e.datepicker._curInst){var i=e(t.target),s=e.datepicker._getInst(i[0]);(i[0].id!==e.datepicker._mainDivId&&0===i.parents("#"+e.datepicker._mainDivId).length&&!i.hasClass(e.datepicker.markerClassName)&&!i.closest("."+e.datepicker._triggerClass).length&&e.datepicker._datepickerShowing&&(!e.datepicker._inDialog||!e.blockUI)||i.hasClass(e.datepicker.markerClassName)&&e.datepicker._curInst!==s)&&e.datepicker._hideDatepicker()}},_adjustDate:function(t,i,s){var n=e(t),a=this._getInst(n[0]);this._isDisabledDatepicker(n[0])||(this._adjustInstDate(a,i+("M"===s?this._get(a,"showCurrentAtPos"):0),s),this._updateDatepicker(a))},_gotoToday:function(t){var i,s=e(t),n=this._getInst(s[0]);this._get(n,"gotoCurrent")&&n.currentDay?(n.selectedDay=n.currentDay,n.drawMonth=n.selectedMonth=n.currentMonth,n.drawYear=n.selectedYear=n.currentYear):(i=new Date,n.selectedDay=i.getDate(),n.drawMonth=n.selectedMonth=i.getMonth(),n.drawYear=n.selectedYear=i.getFullYear()),this._notifyChange(n),this._adjustDate(s)},_selectMonthYear:function(t,i,s){var n=e(t),a=this._getInst(n[0]);a["selected"+("M"===s?"Month":"Year")]=a["draw"+("M"===s?"Month":"Year")]=parseInt(i.options[i.selectedIndex].value,10),this._notifyChange(a),this._adjustDate(n)},_selectDay:function(t,i,s,n){var a,o=e(t);e(n).hasClass(this._unselectableClass)||this._isDisabledDatepicker(o[0])||(a=this._getInst(o[0]),a.selectedDay=a.currentDay=e("a",n).html(),a.selectedMonth=a.currentMonth=i,a.selectedYear=a.currentYear=s,this._selectDate(t,this._formatDate(a,a.currentDay,a.currentMonth,a.currentYear)))},_clearDate:function(t){var i=e(t);this._selectDate(i,"")},_selectDate:function(t,i){var s,n=e(t),a=this._getInst(n[0]);i=null!=i?i:this._formatDate(a),a.input&&a.input.val(i),this._updateAlternate(a),s=this._get(a,"onSelect"),s?s.apply(a.input?a.input[0]:null,[i,a]):a.input&&a.input.trigger("change"),a.inline?this._updateDatepicker(a):(this._hideDatepicker(),this._lastInput=a.input[0],"object"!=typeof a.input[0]&&a.input.focus(),this._lastInput=null)},_updateAlternate:function(t){var i,s,n,a=this._get(t,"altField");a&&(i=this._get(t,"altFormat")||this._get(t,"dateFormat"),s=this._getDate(t),n=this.formatDate(i,s,this._getFormatConfig(t)),e(a).each(function(){e(this).val(n)}))},noWeekends:function(e){var t=e.getDay();return[t>0&&6>t,""]},iso8601Week:function(e){var t,i=new Date(e.getTime());return i.setDate(i.getDate()+4-(i.getDay()||7)),t=i.getTime(),i.setMonth(0),i.setDate(1),Math.floor(Math.round((t-i)/864e5)/7)+1},parseDate:function(t,i,s){if(null==t||null==i)throw"Invalid arguments";if(i="object"==typeof i?""+i:i+"",""===i)return null;var n,a,o,r,h=0,l=(s?s.shortYearCutoff:null)||this._defaults.shortYearCutoff,u="string"!=typeof l?l:(new Date).getFullYear()%100+parseInt(l,10),d=(s?s.dayNamesShort:null)||this._defaults.dayNamesShort,c=(s?s.dayNames:null)||this._defaults.dayNames,p=(s?s.monthNamesShort:null)||this._defaults.monthNamesShort,f=(s?s.monthNames:null)||this._defaults.monthNames,m=-1,g=-1,v=-1,y=-1,b=!1,_=function(e){var i=t.length>n+1&&t.charAt(n+1)===e;return i&&n++,i},x=function(e){var t=_(e),s="@"===e?14:"!"===e?20:"y"===e&&t?4:"o"===e?3:2,n="y"===e?s:1,a=RegExp("^\\d{"+n+","+s+"}"),o=i.substring(h).match(a);if(!o)throw"Missing number at position "+h;return h+=o[0].length,parseInt(o[0],10)},w=function(t,s,n){var a=-1,o=e.map(_(t)?n:s,function(e,t){return[[t,e]]}).sort(function(e,t){return-(e[1].length-t[1].length)});if(e.each(o,function(e,t){var s=t[1];return i.substr(h,s.length).toLowerCase()===s.toLowerCase()?(a=t[0],h+=s.length,!1):void 0}),-1!==a)return a+1;throw"Unknown name at position "+h},k=function(){if(i.charAt(h)!==t.charAt(n))throw"Unexpected literal at position "+h;h++};for(n=0;t.length>n;n++)if(b)"'"!==t.charAt(n)||_("'")?k():b=!1;else switch(t.charAt(n)){case"d":v=x("d");break;case"D":w("D",d,c);break;case"o":y=x("o");break;case"m":g=x("m");break;case"M":g=w("M",p,f);break;case"y":m=x("y");break;case"@":r=new Date(x("@")),m=r.getFullYear(),g=r.getMonth()+1,v=r.getDate();break;case"!":r=new Date((x("!")-this._ticksTo1970)/1e4),m=r.getFullYear(),g=r.getMonth()+1,v=r.getDate();break;case"'":_("'")?k():b=!0;break;default:k()}if(i.length>h&&(o=i.substr(h),!/^\s+/.test(o)))throw"Extra/unparsed characters found in date: "+o;if(-1===m?m=(new Date).getFullYear():100>m&&(m+=(new Date).getFullYear()-(new Date).getFullYear()%100+(u>=m?0:-100)),y>-1)for(g=1,v=y;;){if(a=this._getDaysInMonth(m,g-1),a>=v)break;g++,v-=a}if(r=this._daylightSavingAdjust(new Date(m,g-1,v)),r.getFullYear()!==m||r.getMonth()+1!==g||r.getDate()!==v)throw"Invalid date";return r},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:1e7*60*60*24*(718685+Math.floor(492.5)-Math.floor(19.7)+Math.floor(4.925)),formatDate:function(e,t,i){if(!t)return"";var s,n=(i?i.dayNamesShort:null)||this._defaults.dayNamesShort,a=(i?i.dayNames:null)||this._defaults.dayNames,o=(i?i.monthNamesShort:null)||this._defaults.monthNamesShort,r=(i?i.monthNames:null)||this._defaults.monthNames,h=function(t){var i=e.length>s+1&&e.charAt(s+1)===t;return i&&s++,i},l=function(e,t,i){var s=""+t;if(h(e))for(;i>s.length;)s="0"+s;return s},u=function(e,t,i,s){return h(e)?s[t]:i[t]},d="",c=!1;if(t)for(s=0;e.length>s;s++)if(c)"'"!==e.charAt(s)||h("'")?d+=e.charAt(s):c=!1;else switch(e.charAt(s)){case"d":d+=l("d",t.getDate(),2);break;case"D":d+=u("D",t.getDay(),n,a);break;case"o":d+=l("o",Math.round((new Date(t.getFullYear(),t.getMonth(),t.getDate()).getTime()-new Date(t.getFullYear(),0,0).getTime())/864e5),3);break;case"m":d+=l("m",t.getMonth()+1,2);break;case"M":d+=u("M",t.getMonth(),o,r);break;case"y":d+=h("y")?t.getFullYear():(10>t.getYear()%100?"0":"")+t.getYear()%100;break;case"@":d+=t.getTime();break;case"!":d+=1e4*t.getTime()+this._ticksTo1970;break;case"'":h("'")?d+="'":c=!0;break;default:d+=e.charAt(s)}return d},_possibleChars:function(e){var t,i="",s=!1,n=function(i){var s=e.length>t+1&&e.charAt(t+1)===i;return s&&t++,s};for(t=0;e.length>t;t++)if(s)"'"!==e.charAt(t)||n("'")?i+=e.charAt(t):s=!1;else switch(e.charAt(t)){case"d":case"m":case"y":case"@":i+="0123456789";break;case"D":case"M":return null;case"'":n("'")?i+="'":s=!0;break;default:i+=e.charAt(t)}return i},_get:function(e,t){return void 0!==e.settings[t]?e.settings[t]:this._defaults[t]},_setDateFromField:function(e,t){if(e.input.val()!==e.lastVal){var i=this._get(e,"dateFormat"),s=e.lastVal=e.input?e.input.val():null,n=this._getDefaultDate(e),a=n,o=this._getFormatConfig(e);try{a=this.parseDate(i,s,o)||n}catch(r){s=t?"":s}e.selectedDay=a.getDate(),e.drawMonth=e.selectedMonth=a.getMonth(),e.drawYear=e.selectedYear=a.getFullYear(),e.currentDay=s?a.getDate():0,e.currentMonth=s?a.getMonth():0,e.currentYear=s?a.getFullYear():0,this._adjustInstDate(e)}},_getDefaultDate:function(e){return this._restrictMinMax(e,this._determineDate(e,this._get(e,"defaultDate"),new Date))},_determineDate:function(t,i,s){var n=function(e){var t=new Date;return t.setDate(t.getDate()+e),t},a=function(i){try{return e.datepicker.parseDate(e.datepicker._get(t,"dateFormat"),i,e.datepicker._getFormatConfig(t))}catch(s){}for(var n=(i.toLowerCase().match(/^c/)?e.datepicker._getDate(t):null)||new Date,a=n.getFullYear(),o=n.getMonth(),r=n.getDate(),h=/([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,l=h.exec(i);l;){switch(l[2]||"d"){case"d":case"D":r+=parseInt(l[1],10);break;case"w":case"W":r+=7*parseInt(l[1],10);break;case"m":case"M":o+=parseInt(l[1],10),r=Math.min(r,e.datepicker._getDaysInMonth(a,o));break;case"y":case"Y":a+=parseInt(l[1],10),r=Math.min(r,e.datepicker._getDaysInMonth(a,o))}l=h.exec(i)}return new Date(a,o,r)},o=null==i||""===i?s:"string"==typeof i?a(i):"number"==typeof i?isNaN(i)?s:n(i):new Date(i.getTime());return o=o&&"Invalid Date"==""+o?s:o,o&&(o.setHours(0),o.setMinutes(0),o.setSeconds(0),o.setMilliseconds(0)),this._daylightSavingAdjust(o)},_daylightSavingAdjust:function(e){return e?(e.setHours(e.getHours()>12?e.getHours()+2:0),e):null},_setDate:function(e,t,i){var s=!t,n=e.selectedMonth,a=e.selectedYear,o=this._restrictMinMax(e,this._determineDate(e,t,new Date));e.selectedDay=e.currentDay=o.getDate(),e.drawMonth=e.selectedMonth=e.currentMonth=o.getMonth(),e.drawYear=e.selectedYear=e.currentYear=o.getFullYear(),n===e.selectedMonth&&a===e.selectedYear||i||this._notifyChange(e),this._adjustInstDate(e),e.input&&e.input.val(s?"":this._formatDate(e))},_getDate:function(e){var t=!e.currentYear||e.input&&""===e.input.val()?null:this._daylightSavingAdjust(new Date(e.currentYear,e.currentMonth,e.currentDay));return t},_attachHandlers:function(t){var i=this._get(t,"stepMonths"),s="#"+t.id.replace(/\\\\/g,"\\");t.dpDiv.find("[data-handler]").map(function(){var t={prev:function(){e.datepicker._adjustDate(s,-i,"M")},next:function(){e.datepicker._adjustDate(s,+i,"M")},hide:function(){e.datepicker._hideDatepicker()},today:function(){e.datepicker._gotoToday(s)},selectDay:function(){return e.datepicker._selectDay(s,+this.getAttribute("data-month"),+this.getAttribute("data-year"),this),!1},selectMonth:function(){return e.datepicker._selectMonthYear(s,this,"M"),!1},selectYear:function(){return e.datepicker._selectMonthYear(s,this,"Y"),!1}};e(this).bind(this.getAttribute("data-event"),t[this.getAttribute("data-handler")])})},_generateHTML:function(e){var t,i,s,n,a,o,r,h,l,u,d,c,p,f,m,g,v,y,b,_,x,w,k,T,D,S,M,C,N,A,P,I,H,z,F,E,O,j,W,L=new Date,R=this._daylightSavingAdjust(new Date(L.getFullYear(),L.getMonth(),L.getDate())),Y=this._get(e,"isRTL"),B=this._get(e,"showButtonPanel"),J=this._get(e,"hideIfNoPrevNext"),q=this._get(e,"navigationAsDateFormat"),K=this._getNumberOfMonths(e),V=this._get(e,"showCurrentAtPos"),U=this._get(e,"stepMonths"),Q=1!==K[0]||1!==K[1],G=this._daylightSavingAdjust(e.currentDay?new Date(e.currentYear,e.currentMonth,e.currentDay):new Date(9999,9,9)),X=this._getMinMaxDate(e,"min"),$=this._getMinMaxDate(e,"max"),Z=e.drawMonth-V,et=e.drawYear;if(0>Z&&(Z+=12,et--),$)for(t=this._daylightSavingAdjust(new Date($.getFullYear(),$.getMonth()-K[0]*K[1]+1,$.getDate())),t=X&&X>t?X:t;this._daylightSavingAdjust(new Date(et,Z,1))>t;)Z--,0>Z&&(Z=11,et--);for(e.drawMonth=Z,e.drawYear=et,i=this._get(e,"prevText"),i=q?this.formatDate(i,this._daylightSavingAdjust(new Date(et,Z-U,1)),this._getFormatConfig(e)):i,s=this._canAdjustMonth(e,-1,et,Z)?""+i+"":J?"":""+i+"",n=this._get(e,"nextText"),n=q?this.formatDate(n,this._daylightSavingAdjust(new Date(et,Z+U,1)),this._getFormatConfig(e)):n,a=this._canAdjustMonth(e,1,et,Z)?""+n+"":J?"":""+n+"",o=this._get(e,"currentText"),r=this._get(e,"gotoCurrent")&&e.currentDay?G:R,o=q?this.formatDate(o,r,this._getFormatConfig(e)):o,h=e.inline?"":"",l=B?"
    "+(Y?h:"")+(this._isInRange(e,r)?"":"")+(Y?"":h)+"
    ":"",u=parseInt(this._get(e,"firstDay"),10),u=isNaN(u)?0:u,d=this._get(e,"showWeek"),c=this._get(e,"dayNames"),p=this._get(e,"dayNamesMin"),f=this._get(e,"monthNames"),m=this._get(e,"monthNamesShort"),g=this._get(e,"beforeShowDay"),v=this._get(e,"showOtherMonths"),y=this._get(e,"selectOtherMonths"),b=this._getDefaultDate(e),_="",w=0;K[0]>w;w++){for(k="",this.maxRows=4,T=0;K[1]>T;T++){if(D=this._daylightSavingAdjust(new Date(et,Z,e.selectedDay)),S=" ui-corner-all",M="",Q){if(M+="
    "}for(M+="
    "+(/all|left/.test(S)&&0===w?Y?a:s:"")+(/all|right/.test(S)&&0===w?Y?s:a:"")+this._generateMonthYearHeader(e,Z,et,X,$,w>0||T>0,f,m)+"
    "+"",C=d?"":"",x=0;7>x;x++)N=(x+u)%7,C+="";for(M+=C+"",A=this._getDaysInMonth(et,Z),et===e.selectedYear&&Z===e.selectedMonth&&(e.selectedDay=Math.min(e.selectedDay,A)),P=(this._getFirstDayOfMonth(et,Z)-u+7)%7,I=Math.ceil((P+A)/7),H=Q?this.maxRows>I?this.maxRows:I:I,this.maxRows=H,z=this._daylightSavingAdjust(new Date(et,Z,1-P)),F=0;H>F;F++){for(M+="",E=d?"":"",x=0;7>x;x++)O=g?g.apply(e.input?e.input[0]:null,[z]):[!0,""],j=z.getMonth()!==Z,W=j&&!y||!O[0]||X&&X>z||$&&z>$,E+="",z.setDate(z.getDate()+1),z=this._daylightSavingAdjust(z);M+=E+""}Z++,Z>11&&(Z=0,et++),M+="
    "+this._get(e,"weekHeader")+"=5?" class='ui-datepicker-week-end'":"")+">"+""+p[N]+"
    "+this._get(e,"calculateWeek")(z)+""+(j&&!v?" ":W?""+z.getDate()+"":""+z.getDate()+"")+"
    "+(Q?"
    "+(K[0]>0&&T===K[1]-1?"
    ":""):""),k+=M}_+=k}return _+=l,e._keyEvent=!1,_},_generateMonthYearHeader:function(e,t,i,s,n,a,o,r){var h,l,u,d,c,p,f,m,g=this._get(e,"changeMonth"),v=this._get(e,"changeYear"),y=this._get(e,"showMonthAfterYear"),b="
    ",_="";if(a||!g)_+=""+o[t]+"";else{for(h=s&&s.getFullYear()===i,l=n&&n.getFullYear()===i,_+=""}if(y||(b+=_+(!a&&g&&v?"":" ")),!e.yearshtml)if(e.yearshtml="",a||!v)b+=""+i+"";else{for(d=this._get(e,"yearRange").split(":"),c=(new Date).getFullYear(),p=function(e){var t=e.match(/c[+\-].*/)?i+parseInt(e.substring(1),10):e.match(/[+\-].*/)?c+parseInt(e,10):parseInt(e,10);return isNaN(t)?c:t},f=p(d[0]),m=Math.max(f,p(d[1]||"")),f=s?Math.max(f,s.getFullYear()):f,m=n?Math.min(m,n.getFullYear()):m,e.yearshtml+="",b+=e.yearshtml,e.yearshtml=null}return b+=this._get(e,"yearSuffix"),y&&(b+=(!a&&g&&v?"":" ")+_),b+="
    "},_adjustInstDate:function(e,t,i){var s=e.drawYear+("Y"===i?t:0),n=e.drawMonth+("M"===i?t:0),a=Math.min(e.selectedDay,this._getDaysInMonth(s,n))+("D"===i?t:0),o=this._restrictMinMax(e,this._daylightSavingAdjust(new Date(s,n,a)));e.selectedDay=o.getDate(),e.drawMonth=e.selectedMonth=o.getMonth(),e.drawYear=e.selectedYear=o.getFullYear(),("M"===i||"Y"===i)&&this._notifyChange(e)},_restrictMinMax:function(e,t){var i=this._getMinMaxDate(e,"min"),s=this._getMinMaxDate(e,"max"),n=i&&i>t?i:t;return s&&n>s?s:n},_notifyChange:function(e){var t=this._get(e,"onChangeMonthYear");t&&t.apply(e.input?e.input[0]:null,[e.selectedYear,e.selectedMonth+1,e])},_getNumberOfMonths:function(e){var t=this._get(e,"numberOfMonths");return null==t?[1,1]:"number"==typeof t?[1,t]:t},_getMinMaxDate:function(e,t){return this._determineDate(e,this._get(e,t+"Date"),null)},_getDaysInMonth:function(e,t){return 32-this._daylightSavingAdjust(new Date(e,t,32)).getDate()},_getFirstDayOfMonth:function(e,t){return new Date(e,t,1).getDay()},_canAdjustMonth:function(e,t,i,s){var n=this._getNumberOfMonths(e),a=this._daylightSavingAdjust(new Date(i,s+(0>t?t:n[0]*n[1]),1));return 0>t&&a.setDate(this._getDaysInMonth(a.getFullYear(),a.getMonth())),this._isInRange(e,a)},_isInRange:function(e,t){var i,s,n=this._getMinMaxDate(e,"min"),a=this._getMinMaxDate(e,"max"),o=null,r=null,h=this._get(e,"yearRange");return h&&(i=h.split(":"),s=(new Date).getFullYear(),o=parseInt(i[0],10),r=parseInt(i[1],10),i[0].match(/[+\-].*/)&&(o+=s),i[1].match(/[+\-].*/)&&(r+=s)),(!n||t.getTime()>=n.getTime())&&(!a||t.getTime()<=a.getTime())&&(!o||t.getFullYear()>=o)&&(!r||r>=t.getFullYear())},_getFormatConfig:function(e){var t=this._get(e,"shortYearCutoff");return t="string"!=typeof t?t:(new Date).getFullYear()%100+parseInt(t,10),{shortYearCutoff:t,dayNamesShort:this._get(e,"dayNamesShort"),dayNames:this._get(e,"dayNames"),monthNamesShort:this._get(e,"monthNamesShort"),monthNames:this._get(e,"monthNames")}},_formatDate:function(e,t,i,s){t||(e.currentDay=e.selectedDay,e.currentMonth=e.selectedMonth,e.currentYear=e.selectedYear);var n=t?"object"==typeof t?t:this._daylightSavingAdjust(new Date(s,i,t)):this._daylightSavingAdjust(new Date(e.currentYear,e.currentMonth,e.currentDay));return this.formatDate(this._get(e,"dateFormat"),n,this._getFormatConfig(e))}}),e.fn.datepicker=function(t){if(!this.length)return this;e.datepicker.initialized||(e(document).mousedown(e.datepicker._checkExternalClick),e.datepicker.initialized=!0),0===e("#"+e.datepicker._mainDivId).length&&e("body").append(e.datepicker.dpDiv);var i=Array.prototype.slice.call(arguments,1);return"string"!=typeof t||"isDisabled"!==t&&"getDate"!==t&&"widget"!==t?"option"===t&&2===arguments.length&&"string"==typeof arguments[1]?e.datepicker["_"+t+"Datepicker"].apply(e.datepicker,[this[0]].concat(i)):this.each(function(){"string"==typeof t?e.datepicker["_"+t+"Datepicker"].apply(e.datepicker,[this].concat(i)):e.datepicker._attachDatepicker(this,t)}):e.datepicker["_"+t+"Datepicker"].apply(e.datepicker,[this[0]].concat(i))},e.datepicker=new n,e.datepicker.initialized=!1,e.datepicker.uuid=(new Date).getTime(),e.datepicker.version="1.11.4",e.datepicker,e.widget("ui.draggable",e.ui.mouse,{version:"1.11.4",widgetEventPrefix:"drag",options:{addClasses:!0,appendTo:"parent",axis:!1,connectToSortable:!1,containment:!1,cursor:"auto",cursorAt:!1,grid:!1,handle:!1,helper:"original",iframeFix:!1,opacity:!1,refreshPositions:!1,revert:!1,revertDuration:500,scope:"default",scroll:!0,scrollSensitivity:20,scrollSpeed:20,snap:!1,snapMode:"both",snapTolerance:20,stack:!1,zIndex:!1,drag:null,start:null,stop:null},_create:function(){"original"===this.options.helper&&this._setPositionRelative(),this.options.addClasses&&this.element.addClass("ui-draggable"),this.options.disabled&&this.element.addClass("ui-draggable-disabled"),this._setHandleClassName(),this._mouseInit()},_setOption:function(e,t){this._super(e,t),"handle"===e&&(this._removeHandleClassName(),this._setHandleClassName())},_destroy:function(){return(this.helper||this.element).is(".ui-draggable-dragging")?(this.destroyOnClear=!0,void 0):(this.element.removeClass("ui-draggable ui-draggable-dragging ui-draggable-disabled"),this._removeHandleClassName(),this._mouseDestroy(),void 0)},_mouseCapture:function(t){var i=this.options;return this._blurActiveElement(t),this.helper||i.disabled||e(t.target).closest(".ui-resizable-handle").length>0?!1:(this.handle=this._getHandle(t),this.handle?(this._blockFrames(i.iframeFix===!0?"iframe":i.iframeFix),!0):!1)},_blockFrames:function(t){this.iframeBlocks=this.document.find(t).map(function(){var t=e(this);return e("
    ").css("position","absolute").appendTo(t.parent()).outerWidth(t.outerWidth()).outerHeight(t.outerHeight()).offset(t.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_blurActiveElement:function(t){var i=this.document[0];if(this.handleElement.is(t.target))try{i.activeElement&&"body"!==i.activeElement.nodeName.toLowerCase()&&e(i.activeElement).blur()}catch(s){}},_mouseStart:function(t){var i=this.options;return this.helper=this._createHelper(t),this.helper.addClass("ui-draggable-dragging"),this._cacheHelperProportions(),e.ui.ddmanager&&(e.ui.ddmanager.current=this),this._cacheMargins(),this.cssPosition=this.helper.css("position"),this.scrollParent=this.helper.scrollParent(!0),this.offsetParent=this.helper.offsetParent(),this.hasFixedAncestor=this.helper.parents().filter(function(){return"fixed"===e(this).css("position")}).length>0,this.positionAbs=this.element.offset(),this._refreshOffsets(t),this.originalPosition=this.position=this._generatePosition(t,!1),this.originalPageX=t.pageX,this.originalPageY=t.pageY,i.cursorAt&&this._adjustOffsetFromHelper(i.cursorAt),this._setContainment(),this._trigger("start",t)===!1?(this._clear(),!1):(this._cacheHelperProportions(),e.ui.ddmanager&&!i.dropBehaviour&&e.ui.ddmanager.prepareOffsets(this,t),this._normalizeRightBottom(),this._mouseDrag(t,!0),e.ui.ddmanager&&e.ui.ddmanager.dragStart(this,t),!0)},_refreshOffsets:function(e){this.offset={top:this.positionAbs.top-this.margins.top,left:this.positionAbs.left-this.margins.left,scroll:!1,parent:this._getParentOffset(),relative:this._getRelativeOffset()},this.offset.click={left:e.pageX-this.offset.left,top:e.pageY-this.offset.top}},_mouseDrag:function(t,i){if(this.hasFixedAncestor&&(this.offset.parent=this._getParentOffset()),this.position=this._generatePosition(t,!0),this.positionAbs=this._convertPositionTo("absolute"),!i){var s=this._uiHash();if(this._trigger("drag",t,s)===!1)return this._mouseUp({}),!1;this.position=s.position}return this.helper[0].style.left=this.position.left+"px",this.helper[0].style.top=this.position.top+"px",e.ui.ddmanager&&e.ui.ddmanager.drag(this,t),!1},_mouseStop:function(t){var i=this,s=!1;return e.ui.ddmanager&&!this.options.dropBehaviour&&(s=e.ui.ddmanager.drop(this,t)),this.dropped&&(s=this.dropped,this.dropped=!1),"invalid"===this.options.revert&&!s||"valid"===this.options.revert&&s||this.options.revert===!0||e.isFunction(this.options.revert)&&this.options.revert.call(this.element,s)?e(this.helper).animate(this.originalPosition,parseInt(this.options.revertDuration,10),function(){i._trigger("stop",t)!==!1&&i._clear()}):this._trigger("stop",t)!==!1&&this._clear(),!1},_mouseUp:function(t){return this._unblockFrames(),e.ui.ddmanager&&e.ui.ddmanager.dragStop(this,t),this.handleElement.is(t.target)&&this.element.focus(),e.ui.mouse.prototype._mouseUp.call(this,t)},cancel:function(){return this.helper.is(".ui-draggable-dragging")?this._mouseUp({}):this._clear(),this},_getHandle:function(t){return this.options.handle?!!e(t.target).closest(this.element.find(this.options.handle)).length:!0},_setHandleClassName:function(){this.handleElement=this.options.handle?this.element.find(this.options.handle):this.element,this.handleElement.addClass("ui-draggable-handle")},_removeHandleClassName:function(){this.handleElement.removeClass("ui-draggable-handle")},_createHelper:function(t){var i=this.options,s=e.isFunction(i.helper),n=s?e(i.helper.apply(this.element[0],[t])):"clone"===i.helper?this.element.clone().removeAttr("id"):this.element;return n.parents("body").length||n.appendTo("parent"===i.appendTo?this.element[0].parentNode:i.appendTo),s&&n[0]===this.element[0]&&this._setPositionRelative(),n[0]===this.element[0]||/(fixed|absolute)/.test(n.css("position"))||n.css("position","absolute"),n},_setPositionRelative:function(){/^(?:r|a|f)/.test(this.element.css("position"))||(this.element[0].style.position="relative")},_adjustOffsetFromHelper:function(t){"string"==typeof t&&(t=t.split(" ")),e.isArray(t)&&(t={left:+t[0],top:+t[1]||0}),"left"in t&&(this.offset.click.left=t.left+this.margins.left),"right"in t&&(this.offset.click.left=this.helperProportions.width-t.right+this.margins.left),"top"in t&&(this.offset.click.top=t.top+this.margins.top),"bottom"in t&&(this.offset.click.top=this.helperProportions.height-t.bottom+this.margins.top)},_isRootNode:function(e){return/(html|body)/i.test(e.tagName)||e===this.document[0]},_getParentOffset:function(){var t=this.offsetParent.offset(),i=this.document[0];return"absolute"===this.cssPosition&&this.scrollParent[0]!==i&&e.contains(this.scrollParent[0],this.offsetParent[0])&&(t.left+=this.scrollParent.scrollLeft(),t.top+=this.scrollParent.scrollTop()),this._isRootNode(this.offsetParent[0])&&(t={top:0,left:0}),{top:t.top+(parseInt(this.offsetParent.css("borderTopWidth"),10)||0),left:t.left+(parseInt(this.offsetParent.css("borderLeftWidth"),10)||0)}},_getRelativeOffset:function(){if("relative"!==this.cssPosition)return{top:0,left:0};var e=this.element.position(),t=this._isRootNode(this.scrollParent[0]);return{top:e.top-(parseInt(this.helper.css("top"),10)||0)+(t?0:this.scrollParent.scrollTop()),left:e.left-(parseInt(this.helper.css("left"),10)||0)+(t?0:this.scrollParent.scrollLeft())}},_cacheMargins:function(){this.margins={left:parseInt(this.element.css("marginLeft"),10)||0,top:parseInt(this.element.css("marginTop"),10)||0,right:parseInt(this.element.css("marginRight"),10)||0,bottom:parseInt(this.element.css("marginBottom"),10)||0}},_cacheHelperProportions:function(){this.helperProportions={width:this.helper.outerWidth(),height:this.helper.outerHeight()}},_setContainment:function(){var t,i,s,n=this.options,a=this.document[0];return this.relativeContainer=null,n.containment?"window"===n.containment?(this.containment=[e(window).scrollLeft()-this.offset.relative.left-this.offset.parent.left,e(window).scrollTop()-this.offset.relative.top-this.offset.parent.top,e(window).scrollLeft()+e(window).width()-this.helperProportions.width-this.margins.left,e(window).scrollTop()+(e(window).height()||a.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],void 0):"document"===n.containment?(this.containment=[0,0,e(a).width()-this.helperProportions.width-this.margins.left,(e(a).height()||a.body.parentNode.scrollHeight)-this.helperProportions.height-this.margins.top],void 0):n.containment.constructor===Array?(this.containment=n.containment,void 0):("parent"===n.containment&&(n.containment=this.helper[0].parentNode),i=e(n.containment),s=i[0],s&&(t=/(scroll|auto)/.test(i.css("overflow")),this.containment=[(parseInt(i.css("borderLeftWidth"),10)||0)+(parseInt(i.css("paddingLeft"),10)||0),(parseInt(i.css("borderTopWidth"),10)||0)+(parseInt(i.css("paddingTop"),10)||0),(t?Math.max(s.scrollWidth,s.offsetWidth):s.offsetWidth)-(parseInt(i.css("borderRightWidth"),10)||0)-(parseInt(i.css("paddingRight"),10)||0)-this.helperProportions.width-this.margins.left-this.margins.right,(t?Math.max(s.scrollHeight,s.offsetHeight):s.offsetHeight)-(parseInt(i.css("borderBottomWidth"),10)||0)-(parseInt(i.css("paddingBottom"),10)||0)-this.helperProportions.height-this.margins.top-this.margins.bottom],this.relativeContainer=i),void 0):(this.containment=null,void 0) +},_convertPositionTo:function(e,t){t||(t=this.position);var i="absolute"===e?1:-1,s=this._isRootNode(this.scrollParent[0]);return{top:t.top+this.offset.relative.top*i+this.offset.parent.top*i-("fixed"===this.cssPosition?-this.offset.scroll.top:s?0:this.offset.scroll.top)*i,left:t.left+this.offset.relative.left*i+this.offset.parent.left*i-("fixed"===this.cssPosition?-this.offset.scroll.left:s?0:this.offset.scroll.left)*i}},_generatePosition:function(e,t){var i,s,n,a,o=this.options,r=this._isRootNode(this.scrollParent[0]),h=e.pageX,l=e.pageY;return r&&this.offset.scroll||(this.offset.scroll={top:this.scrollParent.scrollTop(),left:this.scrollParent.scrollLeft()}),t&&(this.containment&&(this.relativeContainer?(s=this.relativeContainer.offset(),i=[this.containment[0]+s.left,this.containment[1]+s.top,this.containment[2]+s.left,this.containment[3]+s.top]):i=this.containment,e.pageX-this.offset.click.lefti[2]&&(h=i[2]+this.offset.click.left),e.pageY-this.offset.click.top>i[3]&&(l=i[3]+this.offset.click.top)),o.grid&&(n=o.grid[1]?this.originalPageY+Math.round((l-this.originalPageY)/o.grid[1])*o.grid[1]:this.originalPageY,l=i?n-this.offset.click.top>=i[1]||n-this.offset.click.top>i[3]?n:n-this.offset.click.top>=i[1]?n-o.grid[1]:n+o.grid[1]:n,a=o.grid[0]?this.originalPageX+Math.round((h-this.originalPageX)/o.grid[0])*o.grid[0]:this.originalPageX,h=i?a-this.offset.click.left>=i[0]||a-this.offset.click.left>i[2]?a:a-this.offset.click.left>=i[0]?a-o.grid[0]:a+o.grid[0]:a),"y"===o.axis&&(h=this.originalPageX),"x"===o.axis&&(l=this.originalPageY)),{top:l-this.offset.click.top-this.offset.relative.top-this.offset.parent.top+("fixed"===this.cssPosition?-this.offset.scroll.top:r?0:this.offset.scroll.top),left:h-this.offset.click.left-this.offset.relative.left-this.offset.parent.left+("fixed"===this.cssPosition?-this.offset.scroll.left:r?0:this.offset.scroll.left)}},_clear:function(){this.helper.removeClass("ui-draggable-dragging"),this.helper[0]===this.element[0]||this.cancelHelperRemoval||this.helper.remove(),this.helper=null,this.cancelHelperRemoval=!1,this.destroyOnClear&&this.destroy()},_normalizeRightBottom:function(){"y"!==this.options.axis&&"auto"!==this.helper.css("right")&&(this.helper.width(this.helper.width()),this.helper.css("right","auto")),"x"!==this.options.axis&&"auto"!==this.helper.css("bottom")&&(this.helper.height(this.helper.height()),this.helper.css("bottom","auto"))},_trigger:function(t,i,s){return s=s||this._uiHash(),e.ui.plugin.call(this,t,[i,s,this],!0),/^(drag|start|stop)/.test(t)&&(this.positionAbs=this._convertPositionTo("absolute"),s.offset=this.positionAbs),e.Widget.prototype._trigger.call(this,t,i,s)},plugins:{},_uiHash:function(){return{helper:this.helper,position:this.position,originalPosition:this.originalPosition,offset:this.positionAbs}}}),e.ui.plugin.add("draggable","connectToSortable",{start:function(t,i,s){var n=e.extend({},i,{item:s.element});s.sortables=[],e(s.options.connectToSortable).each(function(){var i=e(this).sortable("instance");i&&!i.options.disabled&&(s.sortables.push(i),i.refreshPositions(),i._trigger("activate",t,n))})},stop:function(t,i,s){var n=e.extend({},i,{item:s.element});s.cancelHelperRemoval=!1,e.each(s.sortables,function(){var e=this;e.isOver?(e.isOver=0,s.cancelHelperRemoval=!0,e.cancelHelperRemoval=!1,e._storedCSS={position:e.placeholder.css("position"),top:e.placeholder.css("top"),left:e.placeholder.css("left")},e._mouseStop(t),e.options.helper=e.options._helper):(e.cancelHelperRemoval=!0,e._trigger("deactivate",t,n))})},drag:function(t,i,s){e.each(s.sortables,function(){var n=!1,a=this;a.positionAbs=s.positionAbs,a.helperProportions=s.helperProportions,a.offset.click=s.offset.click,a._intersectsWith(a.containerCache)&&(n=!0,e.each(s.sortables,function(){return this.positionAbs=s.positionAbs,this.helperProportions=s.helperProportions,this.offset.click=s.offset.click,this!==a&&this._intersectsWith(this.containerCache)&&e.contains(a.element[0],this.element[0])&&(n=!1),n})),n?(a.isOver||(a.isOver=1,s._parent=i.helper.parent(),a.currentItem=i.helper.appendTo(a.element).data("ui-sortable-item",!0),a.options._helper=a.options.helper,a.options.helper=function(){return i.helper[0]},t.target=a.currentItem[0],a._mouseCapture(t,!0),a._mouseStart(t,!0,!0),a.offset.click.top=s.offset.click.top,a.offset.click.left=s.offset.click.left,a.offset.parent.left-=s.offset.parent.left-a.offset.parent.left,a.offset.parent.top-=s.offset.parent.top-a.offset.parent.top,s._trigger("toSortable",t),s.dropped=a.element,e.each(s.sortables,function(){this.refreshPositions()}),s.currentItem=s.element,a.fromOutside=s),a.currentItem&&(a._mouseDrag(t),i.position=a.position)):a.isOver&&(a.isOver=0,a.cancelHelperRemoval=!0,a.options._revert=a.options.revert,a.options.revert=!1,a._trigger("out",t,a._uiHash(a)),a._mouseStop(t,!0),a.options.revert=a.options._revert,a.options.helper=a.options._helper,a.placeholder&&a.placeholder.remove(),i.helper.appendTo(s._parent),s._refreshOffsets(t),i.position=s._generatePosition(t,!0),s._trigger("fromSortable",t),s.dropped=!1,e.each(s.sortables,function(){this.refreshPositions()}))})}}),e.ui.plugin.add("draggable","cursor",{start:function(t,i,s){var n=e("body"),a=s.options;n.css("cursor")&&(a._cursor=n.css("cursor")),n.css("cursor",a.cursor)},stop:function(t,i,s){var n=s.options;n._cursor&&e("body").css("cursor",n._cursor)}}),e.ui.plugin.add("draggable","opacity",{start:function(t,i,s){var n=e(i.helper),a=s.options;n.css("opacity")&&(a._opacity=n.css("opacity")),n.css("opacity",a.opacity)},stop:function(t,i,s){var n=s.options;n._opacity&&e(i.helper).css("opacity",n._opacity)}}),e.ui.plugin.add("draggable","scroll",{start:function(e,t,i){i.scrollParentNotHidden||(i.scrollParentNotHidden=i.helper.scrollParent(!1)),i.scrollParentNotHidden[0]!==i.document[0]&&"HTML"!==i.scrollParentNotHidden[0].tagName&&(i.overflowOffset=i.scrollParentNotHidden.offset())},drag:function(t,i,s){var n=s.options,a=!1,o=s.scrollParentNotHidden[0],r=s.document[0];o!==r&&"HTML"!==o.tagName?(n.axis&&"x"===n.axis||(s.overflowOffset.top+o.offsetHeight-t.pageY=0;c--)h=s.snapElements[c].left-s.margins.left,l=h+s.snapElements[c].width,u=s.snapElements[c].top-s.margins.top,d=u+s.snapElements[c].height,h-m>v||g>l+m||u-m>b||y>d+m||!e.contains(s.snapElements[c].item.ownerDocument,s.snapElements[c].item)?(s.snapElements[c].snapping&&s.options.snap.release&&s.options.snap.release.call(s.element,t,e.extend(s._uiHash(),{snapItem:s.snapElements[c].item})),s.snapElements[c].snapping=!1):("inner"!==f.snapMode&&(n=m>=Math.abs(u-b),a=m>=Math.abs(d-y),o=m>=Math.abs(h-v),r=m>=Math.abs(l-g),n&&(i.position.top=s._convertPositionTo("relative",{top:u-s.helperProportions.height,left:0}).top),a&&(i.position.top=s._convertPositionTo("relative",{top:d,left:0}).top),o&&(i.position.left=s._convertPositionTo("relative",{top:0,left:h-s.helperProportions.width}).left),r&&(i.position.left=s._convertPositionTo("relative",{top:0,left:l}).left)),p=n||a||o||r,"outer"!==f.snapMode&&(n=m>=Math.abs(u-y),a=m>=Math.abs(d-b),o=m>=Math.abs(h-g),r=m>=Math.abs(l-v),n&&(i.position.top=s._convertPositionTo("relative",{top:u,left:0}).top),a&&(i.position.top=s._convertPositionTo("relative",{top:d-s.helperProportions.height,left:0}).top),o&&(i.position.left=s._convertPositionTo("relative",{top:0,left:h}).left),r&&(i.position.left=s._convertPositionTo("relative",{top:0,left:l-s.helperProportions.width}).left)),!s.snapElements[c].snapping&&(n||a||o||r||p)&&s.options.snap.snap&&s.options.snap.snap.call(s.element,t,e.extend(s._uiHash(),{snapItem:s.snapElements[c].item})),s.snapElements[c].snapping=n||a||o||r||p)}}),e.ui.plugin.add("draggable","stack",{start:function(t,i,s){var n,a=s.options,o=e.makeArray(e(a.stack)).sort(function(t,i){return(parseInt(e(t).css("zIndex"),10)||0)-(parseInt(e(i).css("zIndex"),10)||0)});o.length&&(n=parseInt(e(o[0]).css("zIndex"),10)||0,e(o).each(function(t){e(this).css("zIndex",n+t)}),this.css("zIndex",n+o.length))}}),e.ui.plugin.add("draggable","zIndex",{start:function(t,i,s){var n=e(i.helper),a=s.options;n.css("zIndex")&&(a._zIndex=n.css("zIndex")),n.css("zIndex",a.zIndex)},stop:function(t,i,s){var n=s.options;n._zIndex&&e(i.helper).css("zIndex",n._zIndex)}}),e.ui.draggable,e.widget("ui.resizable",e.ui.mouse,{version:"1.11.4",widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:90,resize:null,start:null,stop:null},_num:function(e){return parseInt(e,10)||0},_isNumber:function(e){return!isNaN(parseInt(e,10))},_hasScroll:function(t,i){if("hidden"===e(t).css("overflow"))return!1;var s=i&&"left"===i?"scrollLeft":"scrollTop",n=!1;return t[s]>0?!0:(t[s]=1,n=t[s]>0,t[s]=0,n)},_create:function(){var t,i,s,n,a,o=this,r=this.options;if(this.element.addClass("ui-resizable"),e.extend(this,{_aspectRatio:!!r.aspectRatio,aspectRatio:r.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:r.helper||r.ghost||r.animate?r.helper||"ui-resizable-helper":null}),this.element[0].nodeName.match(/^(canvas|textarea|input|select|button|img)$/i)&&(this.element.wrap(e("
    ").css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("ui-resizable",this.element.resizable("instance")),this.elementIsWrapper=!0,this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")}),this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0}),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css({margin:this.originalElement.css("margin")}),this._proportionallyResize()),this.handles=r.handles||(e(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se"),this._handles=e(),this.handles.constructor===String)for("all"===this.handles&&(this.handles="n,e,s,w,se,sw,ne,nw"),t=this.handles.split(","),this.handles={},i=0;t.length>i;i++)s=e.trim(t[i]),a="ui-resizable-"+s,n=e("
    "),n.css({zIndex:r.zIndex}),"se"===s&&n.addClass("ui-icon ui-icon-gripsmall-diagonal-se"),this.handles[s]=".ui-resizable-"+s,this.element.append(n);this._renderAxis=function(t){var i,s,n,a;t=t||this.element;for(i in this.handles)this.handles[i].constructor===String?this.handles[i]=this.element.children(this.handles[i]).first().show():(this.handles[i].jquery||this.handles[i].nodeType)&&(this.handles[i]=e(this.handles[i]),this._on(this.handles[i],{mousedown:o._mouseDown})),this.elementIsWrapper&&this.originalElement[0].nodeName.match(/^(textarea|input|select|button)$/i)&&(s=e(this.handles[i],this.element),a=/sw|ne|nw|se|n|s/.test(i)?s.outerHeight():s.outerWidth(),n=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join(""),t.css(n,a),this._proportionallyResize()),this._handles=this._handles.add(this.handles[i])},this._renderAxis(this.element),this._handles=this._handles.add(this.element.find(".ui-resizable-handle")),this._handles.disableSelection(),this._handles.mouseover(function(){o.resizing||(this.className&&(n=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)),o.axis=n&&n[1]?n[1]:"se")}),r.autoHide&&(this._handles.hide(),e(this.element).addClass("ui-resizable-autohide").mouseenter(function(){r.disabled||(e(this).removeClass("ui-resizable-autohide"),o._handles.show())}).mouseleave(function(){r.disabled||o.resizing||(e(this).addClass("ui-resizable-autohide"),o._handles.hide())})),this._mouseInit()},_destroy:function(){this._mouseDestroy();var t,i=function(t){e(t).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};return this.elementIsWrapper&&(i(this.element),t=this.element,this.originalElement.css({position:t.css("position"),width:t.outerWidth(),height:t.outerHeight(),top:t.css("top"),left:t.css("left")}).insertAfter(t),t.remove()),this.originalElement.css("resize",this.originalResizeStyle),i(this.originalElement),this},_mouseCapture:function(t){var i,s,n=!1;for(i in this.handles)s=e(this.handles[i])[0],(s===t.target||e.contains(s,t.target))&&(n=!0);return!this.options.disabled&&n},_mouseStart:function(t){var i,s,n,a=this.options,o=this.element;return this.resizing=!0,this._renderProxy(),i=this._num(this.helper.css("left")),s=this._num(this.helper.css("top")),a.containment&&(i+=e(a.containment).scrollLeft()||0,s+=e(a.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:i,top:s},this.size=this._helper?{width:this.helper.width(),height:this.helper.height()}:{width:o.width(),height:o.height()},this.originalSize=this._helper?{width:o.outerWidth(),height:o.outerHeight()}:{width:o.width(),height:o.height()},this.sizeDiff={width:o.outerWidth()-o.width(),height:o.outerHeight()-o.height()},this.originalPosition={left:i,top:s},this.originalMousePosition={left:t.pageX,top:t.pageY},this.aspectRatio="number"==typeof a.aspectRatio?a.aspectRatio:this.originalSize.width/this.originalSize.height||1,n=e(".ui-resizable-"+this.axis).css("cursor"),e("body").css("cursor","auto"===n?this.axis+"-resize":n),o.addClass("ui-resizable-resizing"),this._propagate("start",t),!0},_mouseDrag:function(t){var i,s,n=this.originalMousePosition,a=this.axis,o=t.pageX-n.left||0,r=t.pageY-n.top||0,h=this._change[a];return this._updatePrevProperties(),h?(i=h.apply(this,[t,o,r]),this._updateVirtualBoundaries(t.shiftKey),(this._aspectRatio||t.shiftKey)&&(i=this._updateRatio(i,t)),i=this._respectSize(i,t),this._updateCache(i),this._propagate("resize",t),s=this._applyChanges(),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),e.isEmptyObject(s)||(this._updatePrevProperties(),this._trigger("resize",t,this.ui()),this._applyChanges()),!1):!1},_mouseStop:function(t){this.resizing=!1;var i,s,n,a,o,r,h,l=this.options,u=this;return this._helper&&(i=this._proportionallyResizeElements,s=i.length&&/textarea/i.test(i[0].nodeName),n=s&&this._hasScroll(i[0],"left")?0:u.sizeDiff.height,a=s?0:u.sizeDiff.width,o={width:u.helper.width()-a,height:u.helper.height()-n},r=parseInt(u.element.css("left"),10)+(u.position.left-u.originalPosition.left)||null,h=parseInt(u.element.css("top"),10)+(u.position.top-u.originalPosition.top)||null,l.animate||this.element.css(e.extend(o,{top:h,left:r})),u.helper.height(u.size.height),u.helper.width(u.size.width),this._helper&&!l.animate&&this._proportionallyResize()),e("body").css("cursor","auto"),this.element.removeClass("ui-resizable-resizing"),this._propagate("stop",t),this._helper&&this.helper.remove(),!1},_updatePrevProperties:function(){this.prevPosition={top:this.position.top,left:this.position.left},this.prevSize={width:this.size.width,height:this.size.height}},_applyChanges:function(){var e={};return this.position.top!==this.prevPosition.top&&(e.top=this.position.top+"px"),this.position.left!==this.prevPosition.left&&(e.left=this.position.left+"px"),this.size.width!==this.prevSize.width&&(e.width=this.size.width+"px"),this.size.height!==this.prevSize.height&&(e.height=this.size.height+"px"),this.helper.css(e),e},_updateVirtualBoundaries:function(e){var t,i,s,n,a,o=this.options;a={minWidth:this._isNumber(o.minWidth)?o.minWidth:0,maxWidth:this._isNumber(o.maxWidth)?o.maxWidth:1/0,minHeight:this._isNumber(o.minHeight)?o.minHeight:0,maxHeight:this._isNumber(o.maxHeight)?o.maxHeight:1/0},(this._aspectRatio||e)&&(t=a.minHeight*this.aspectRatio,s=a.minWidth/this.aspectRatio,i=a.maxHeight*this.aspectRatio,n=a.maxWidth/this.aspectRatio,t>a.minWidth&&(a.minWidth=t),s>a.minHeight&&(a.minHeight=s),a.maxWidth>i&&(a.maxWidth=i),a.maxHeight>n&&(a.maxHeight=n)),this._vBoundaries=a},_updateCache:function(e){this.offset=this.helper.offset(),this._isNumber(e.left)&&(this.position.left=e.left),this._isNumber(e.top)&&(this.position.top=e.top),this._isNumber(e.height)&&(this.size.height=e.height),this._isNumber(e.width)&&(this.size.width=e.width)},_updateRatio:function(e){var t=this.position,i=this.size,s=this.axis;return this._isNumber(e.height)?e.width=e.height*this.aspectRatio:this._isNumber(e.width)&&(e.height=e.width/this.aspectRatio),"sw"===s&&(e.left=t.left+(i.width-e.width),e.top=null),"nw"===s&&(e.top=t.top+(i.height-e.height),e.left=t.left+(i.width-e.width)),e},_respectSize:function(e){var t=this._vBoundaries,i=this.axis,s=this._isNumber(e.width)&&t.maxWidth&&t.maxWidthe.width,o=this._isNumber(e.height)&&t.minHeight&&t.minHeight>e.height,r=this.originalPosition.left+this.originalSize.width,h=this.position.top+this.size.height,l=/sw|nw|w/.test(i),u=/nw|ne|n/.test(i);return a&&(e.width=t.minWidth),o&&(e.height=t.minHeight),s&&(e.width=t.maxWidth),n&&(e.height=t.maxHeight),a&&l&&(e.left=r-t.minWidth),s&&l&&(e.left=r-t.maxWidth),o&&u&&(e.top=h-t.minHeight),n&&u&&(e.top=h-t.maxHeight),e.width||e.height||e.left||!e.top?e.width||e.height||e.top||!e.left||(e.left=null):e.top=null,e},_getPaddingPlusBorderDimensions:function(e){for(var t=0,i=[],s=[e.css("borderTopWidth"),e.css("borderRightWidth"),e.css("borderBottomWidth"),e.css("borderLeftWidth")],n=[e.css("paddingTop"),e.css("paddingRight"),e.css("paddingBottom"),e.css("paddingLeft")];4>t;t++)i[t]=parseInt(s[t],10)||0,i[t]+=parseInt(n[t],10)||0;return{height:i[0]+i[2],width:i[1]+i[3]}},_proportionallyResize:function(){if(this._proportionallyResizeElements.length)for(var e,t=0,i=this.helper||this.element;this._proportionallyResizeElements.length>t;t++)e=this._proportionallyResizeElements[t],this.outerDimensions||(this.outerDimensions=this._getPaddingPlusBorderDimensions(e)),e.css({height:i.height()-this.outerDimensions.height||0,width:i.width()-this.outerDimensions.width||0})},_renderProxy:function(){var t=this.element,i=this.options;this.elementOffset=t.offset(),this._helper?(this.helper=this.helper||e("
    "),this.helper.addClass(this._helper).css({width:this.element.outerWidth()-1,height:this.element.outerHeight()-1,position:"absolute",left:this.elementOffset.left+"px",top:this.elementOffset.top+"px",zIndex:++i.zIndex}),this.helper.appendTo("body").disableSelection()):this.helper=this.element},_change:{e:function(e,t){return{width:this.originalSize.width+t}},w:function(e,t){var i=this.originalSize,s=this.originalPosition;return{left:s.left+t,width:i.width-t}},n:function(e,t,i){var s=this.originalSize,n=this.originalPosition;return{top:n.top+i,height:s.height-i}},s:function(e,t,i){return{height:this.originalSize.height+i}},se:function(t,i,s){return e.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[t,i,s]))},sw:function(t,i,s){return e.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[t,i,s]))},ne:function(t,i,s){return e.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[t,i,s]))},nw:function(t,i,s){return e.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[t,i,s]))}},_propagate:function(t,i){e.ui.plugin.call(this,t,[i,this.ui()]),"resize"!==t&&this._trigger(t,i,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),e.ui.plugin.add("resizable","animate",{stop:function(t){var i=e(this).resizable("instance"),s=i.options,n=i._proportionallyResizeElements,a=n.length&&/textarea/i.test(n[0].nodeName),o=a&&i._hasScroll(n[0],"left")?0:i.sizeDiff.height,r=a?0:i.sizeDiff.width,h={width:i.size.width-r,height:i.size.height-o},l=parseInt(i.element.css("left"),10)+(i.position.left-i.originalPosition.left)||null,u=parseInt(i.element.css("top"),10)+(i.position.top-i.originalPosition.top)||null;i.element.animate(e.extend(h,u&&l?{top:u,left:l}:{}),{duration:s.animateDuration,easing:s.animateEasing,step:function(){var s={width:parseInt(i.element.css("width"),10),height:parseInt(i.element.css("height"),10),top:parseInt(i.element.css("top"),10),left:parseInt(i.element.css("left"),10)};n&&n.length&&e(n[0]).css({width:s.width,height:s.height}),i._updateCache(s),i._propagate("resize",t)}})}}),e.ui.plugin.add("resizable","containment",{start:function(){var t,i,s,n,a,o,r,h=e(this).resizable("instance"),l=h.options,u=h.element,d=l.containment,c=d instanceof e?d.get(0):/parent/.test(d)?u.parent().get(0):d;c&&(h.containerElement=e(c),/document/.test(d)||d===document?(h.containerOffset={left:0,top:0},h.containerPosition={left:0,top:0},h.parentData={element:e(document),left:0,top:0,width:e(document).width(),height:e(document).height()||document.body.parentNode.scrollHeight}):(t=e(c),i=[],e(["Top","Right","Left","Bottom"]).each(function(e,s){i[e]=h._num(t.css("padding"+s))}),h.containerOffset=t.offset(),h.containerPosition=t.position(),h.containerSize={height:t.innerHeight()-i[3],width:t.innerWidth()-i[1]},s=h.containerOffset,n=h.containerSize.height,a=h.containerSize.width,o=h._hasScroll(c,"left")?c.scrollWidth:a,r=h._hasScroll(c)?c.scrollHeight:n,h.parentData={element:c,left:s.left,top:s.top,width:o,height:r}))},resize:function(t){var i,s,n,a,o=e(this).resizable("instance"),r=o.options,h=o.containerOffset,l=o.position,u=o._aspectRatio||t.shiftKey,d={top:0,left:0},c=o.containerElement,p=!0;c[0]!==document&&/static/.test(c.css("position"))&&(d=h),l.left<(o._helper?h.left:0)&&(o.size.width=o.size.width+(o._helper?o.position.left-h.left:o.position.left-d.left),u&&(o.size.height=o.size.width/o.aspectRatio,p=!1),o.position.left=r.helper?h.left:0),l.top<(o._helper?h.top:0)&&(o.size.height=o.size.height+(o._helper?o.position.top-h.top:o.position.top),u&&(o.size.width=o.size.height*o.aspectRatio,p=!1),o.position.top=o._helper?h.top:0),n=o.containerElement.get(0)===o.element.parent().get(0),a=/relative|absolute/.test(o.containerElement.css("position")),n&&a?(o.offset.left=o.parentData.left+o.position.left,o.offset.top=o.parentData.top+o.position.top):(o.offset.left=o.element.offset().left,o.offset.top=o.element.offset().top),i=Math.abs(o.sizeDiff.width+(o._helper?o.offset.left-d.left:o.offset.left-h.left)),s=Math.abs(o.sizeDiff.height+(o._helper?o.offset.top-d.top:o.offset.top-h.top)),i+o.size.width>=o.parentData.width&&(o.size.width=o.parentData.width-i,u&&(o.size.height=o.size.width/o.aspectRatio,p=!1)),s+o.size.height>=o.parentData.height&&(o.size.height=o.parentData.height-s,u&&(o.size.width=o.size.height*o.aspectRatio,p=!1)),p||(o.position.left=o.prevPosition.left,o.position.top=o.prevPosition.top,o.size.width=o.prevSize.width,o.size.height=o.prevSize.height)},stop:function(){var t=e(this).resizable("instance"),i=t.options,s=t.containerOffset,n=t.containerPosition,a=t.containerElement,o=e(t.helper),r=o.offset(),h=o.outerWidth()-t.sizeDiff.width,l=o.outerHeight()-t.sizeDiff.height;t._helper&&!i.animate&&/relative/.test(a.css("position"))&&e(this).css({left:r.left-n.left-s.left,width:h,height:l}),t._helper&&!i.animate&&/static/.test(a.css("position"))&&e(this).css({left:r.left-n.left-s.left,width:h,height:l})}}),e.ui.plugin.add("resizable","alsoResize",{start:function(){var t=e(this).resizable("instance"),i=t.options;e(i.alsoResize).each(function(){var t=e(this);t.data("ui-resizable-alsoresize",{width:parseInt(t.width(),10),height:parseInt(t.height(),10),left:parseInt(t.css("left"),10),top:parseInt(t.css("top"),10)})})},resize:function(t,i){var s=e(this).resizable("instance"),n=s.options,a=s.originalSize,o=s.originalPosition,r={height:s.size.height-a.height||0,width:s.size.width-a.width||0,top:s.position.top-o.top||0,left:s.position.left-o.left||0};e(n.alsoResize).each(function(){var t=e(this),s=e(this).data("ui-resizable-alsoresize"),n={},a=t.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];e.each(a,function(e,t){var i=(s[t]||0)+(r[t]||0);i&&i>=0&&(n[t]=i||null)}),t.css(n)})},stop:function(){e(this).removeData("resizable-alsoresize")}}),e.ui.plugin.add("resizable","ghost",{start:function(){var t=e(this).resizable("instance"),i=t.options,s=t.size;t.ghost=t.originalElement.clone(),t.ghost.css({opacity:.25,display:"block",position:"relative",height:s.height,width:s.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass("string"==typeof i.ghost?i.ghost:""),t.ghost.appendTo(t.helper)},resize:function(){var t=e(this).resizable("instance");t.ghost&&t.ghost.css({position:"relative",height:t.size.height,width:t.size.width})},stop:function(){var t=e(this).resizable("instance");t.ghost&&t.helper&&t.helper.get(0).removeChild(t.ghost.get(0))}}),e.ui.plugin.add("resizable","grid",{resize:function(){var t,i=e(this).resizable("instance"),s=i.options,n=i.size,a=i.originalSize,o=i.originalPosition,r=i.axis,h="number"==typeof s.grid?[s.grid,s.grid]:s.grid,l=h[0]||1,u=h[1]||1,d=Math.round((n.width-a.width)/l)*l,c=Math.round((n.height-a.height)/u)*u,p=a.width+d,f=a.height+c,m=s.maxWidth&&p>s.maxWidth,g=s.maxHeight&&f>s.maxHeight,v=s.minWidth&&s.minWidth>p,y=s.minHeight&&s.minHeight>f;s.grid=h,v&&(p+=l),y&&(f+=u),m&&(p-=l),g&&(f-=u),/^(se|s|e)$/.test(r)?(i.size.width=p,i.size.height=f):/^(ne)$/.test(r)?(i.size.width=p,i.size.height=f,i.position.top=o.top-c):/^(sw)$/.test(r)?(i.size.width=p,i.size.height=f,i.position.left=o.left-d):((0>=f-u||0>=p-l)&&(t=i._getPaddingPlusBorderDimensions(this)),f-u>0?(i.size.height=f,i.position.top=o.top-c):(f=u-t.height,i.size.height=f,i.position.top=o.top+a.height-f),p-l>0?(i.size.width=p,i.position.left=o.left-d):(p=l-t.width,i.size.width=p,i.position.left=o.left+a.width-p))}}),e.ui.resizable,e.widget("ui.dialog",{version:"1.11.4",options:{appendTo:"body",autoOpen:!0,buttons:[],closeOnEscape:!0,closeText:"Close",dialogClass:"",draggable:!0,hide:null,height:"auto",maxHeight:null,maxWidth:null,minHeight:150,minWidth:150,modal:!1,position:{my:"center",at:"center",of:window,collision:"fit",using:function(t){var i=e(this).css(t).offset().top;0>i&&e(this).css("top",t.top-i)}},resizable:!0,show:null,title:null,width:300,beforeClose:null,close:null,drag:null,dragStart:null,dragStop:null,focus:null,open:null,resize:null,resizeStart:null,resizeStop:null},sizeRelatedOptions:{buttons:!0,height:!0,maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0,width:!0},resizableRelatedOptions:{maxHeight:!0,maxWidth:!0,minHeight:!0,minWidth:!0},_create:function(){this.originalCss={display:this.element[0].style.display,width:this.element[0].style.width,minHeight:this.element[0].style.minHeight,maxHeight:this.element[0].style.maxHeight,height:this.element[0].style.height},this.originalPosition={parent:this.element.parent(),index:this.element.parent().children().index(this.element)},this.originalTitle=this.element.attr("title"),this.options.title=this.options.title||this.originalTitle,this._createWrapper(),this.element.show().removeAttr("title").addClass("ui-dialog-content ui-widget-content").appendTo(this.uiDialog),this._createTitlebar(),this._createButtonPane(),this.options.draggable&&e.fn.draggable&&this._makeDraggable(),this.options.resizable&&e.fn.resizable&&this._makeResizable(),this._isOpen=!1,this._trackFocus()},_init:function(){this.options.autoOpen&&this.open()},_appendTo:function(){var t=this.options.appendTo;return t&&(t.jquery||t.nodeType)?e(t):this.document.find(t||"body").eq(0)},_destroy:function(){var e,t=this.originalPosition;this._untrackInstance(),this._destroyOverlay(),this.element.removeUniqueId().removeClass("ui-dialog-content ui-widget-content").css(this.originalCss).detach(),this.uiDialog.stop(!0,!0).remove(),this.originalTitle&&this.element.attr("title",this.originalTitle),e=t.parent.children().eq(t.index),e.length&&e[0]!==this.element[0]?e.before(this.element):t.parent.append(this.element)},widget:function(){return this.uiDialog},disable:e.noop,enable:e.noop,close:function(t){var i,s=this;if(this._isOpen&&this._trigger("beforeClose",t)!==!1){if(this._isOpen=!1,this._focusedElement=null,this._destroyOverlay(),this._untrackInstance(),!this.opener.filter(":focusable").focus().length)try{i=this.document[0].activeElement,i&&"body"!==i.nodeName.toLowerCase()&&e(i).blur()}catch(n){}this._hide(this.uiDialog,this.options.hide,function(){s._trigger("close",t)})}},isOpen:function(){return this._isOpen},moveToTop:function(){this._moveToTop()},_moveToTop:function(t,i){var s=!1,n=this.uiDialog.siblings(".ui-front:visible").map(function(){return+e(this).css("z-index")}).get(),a=Math.max.apply(null,n);return a>=+this.uiDialog.css("z-index")&&(this.uiDialog.css("z-index",a+1),s=!0),s&&!i&&this._trigger("focus",t),s},open:function(){var t=this;return this._isOpen?(this._moveToTop()&&this._focusTabbable(),void 0):(this._isOpen=!0,this.opener=e(this.document[0].activeElement),this._size(),this._position(),this._createOverlay(),this._moveToTop(null,!0),this.overlay&&this.overlay.css("z-index",this.uiDialog.css("z-index")-1),this._show(this.uiDialog,this.options.show,function(){t._focusTabbable(),t._trigger("focus")}),this._makeFocusTarget(),this._trigger("open"),void 0)},_focusTabbable:function(){var e=this._focusedElement;e||(e=this.element.find("[autofocus]")),e.length||(e=this.element.find(":tabbable")),e.length||(e=this.uiDialogButtonPane.find(":tabbable")),e.length||(e=this.uiDialogTitlebarClose.filter(":tabbable")),e.length||(e=this.uiDialog),e.eq(0).focus()},_keepFocus:function(t){function i(){var t=this.document[0].activeElement,i=this.uiDialog[0]===t||e.contains(this.uiDialog[0],t);i||this._focusTabbable()}t.preventDefault(),i.call(this),this._delay(i)},_createWrapper:function(){this.uiDialog=e("
    ").addClass("ui-dialog ui-widget ui-widget-content ui-corner-all ui-front "+this.options.dialogClass).hide().attr({tabIndex:-1,role:"dialog"}).appendTo(this._appendTo()),this._on(this.uiDialog,{keydown:function(t){if(this.options.closeOnEscape&&!t.isDefaultPrevented()&&t.keyCode&&t.keyCode===e.ui.keyCode.ESCAPE)return t.preventDefault(),this.close(t),void 0; +if(t.keyCode===e.ui.keyCode.TAB&&!t.isDefaultPrevented()){var i=this.uiDialog.find(":tabbable"),s=i.filter(":first"),n=i.filter(":last");t.target!==n[0]&&t.target!==this.uiDialog[0]||t.shiftKey?t.target!==s[0]&&t.target!==this.uiDialog[0]||!t.shiftKey||(this._delay(function(){n.focus()}),t.preventDefault()):(this._delay(function(){s.focus()}),t.preventDefault())}},mousedown:function(e){this._moveToTop(e)&&this._focusTabbable()}}),this.element.find("[aria-describedby]").length||this.uiDialog.attr({"aria-describedby":this.element.uniqueId().attr("id")})},_createTitlebar:function(){var t;this.uiDialogTitlebar=e("
    ").addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix").prependTo(this.uiDialog),this._on(this.uiDialogTitlebar,{mousedown:function(t){e(t.target).closest(".ui-dialog-titlebar-close")||this.uiDialog.focus()}}),this.uiDialogTitlebarClose=e("").button({label:this.options.closeText,icons:{primary:"ui-icon-closethick"},text:!1}).addClass("ui-dialog-titlebar-close").appendTo(this.uiDialogTitlebar),this._on(this.uiDialogTitlebarClose,{click:function(e){e.preventDefault(),this.close(e)}}),t=e("").uniqueId().addClass("ui-dialog-title").prependTo(this.uiDialogTitlebar),this._title(t),this.uiDialog.attr({"aria-labelledby":t.attr("id")})},_title:function(e){this.options.title||e.html(" "),e.text(this.options.title)},_createButtonPane:function(){this.uiDialogButtonPane=e("
    ").addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"),this.uiButtonSet=e("
    ").addClass("ui-dialog-buttonset").appendTo(this.uiDialogButtonPane),this._createButtons()},_createButtons:function(){var t=this,i=this.options.buttons;return this.uiDialogButtonPane.remove(),this.uiButtonSet.empty(),e.isEmptyObject(i)||e.isArray(i)&&!i.length?(this.uiDialog.removeClass("ui-dialog-buttons"),void 0):(e.each(i,function(i,s){var n,a;s=e.isFunction(s)?{click:s,text:i}:s,s=e.extend({type:"button"},s),n=s.click,s.click=function(){n.apply(t.element[0],arguments)},a={icons:s.icons,text:s.showText},delete s.icons,delete s.showText,e("",s).button(a).appendTo(t.uiButtonSet)}),this.uiDialog.addClass("ui-dialog-buttons"),this.uiDialogButtonPane.appendTo(this.uiDialog),void 0)},_makeDraggable:function(){function t(e){return{position:e.position,offset:e.offset}}var i=this,s=this.options;this.uiDialog.draggable({cancel:".ui-dialog-content, .ui-dialog-titlebar-close",handle:".ui-dialog-titlebar",containment:"document",start:function(s,n){e(this).addClass("ui-dialog-dragging"),i._blockFrames(),i._trigger("dragStart",s,t(n))},drag:function(e,s){i._trigger("drag",e,t(s))},stop:function(n,a){var o=a.offset.left-i.document.scrollLeft(),r=a.offset.top-i.document.scrollTop();s.position={my:"left top",at:"left"+(o>=0?"+":"")+o+" "+"top"+(r>=0?"+":"")+r,of:i.window},e(this).removeClass("ui-dialog-dragging"),i._unblockFrames(),i._trigger("dragStop",n,t(a))}})},_makeResizable:function(){function t(e){return{originalPosition:e.originalPosition,originalSize:e.originalSize,position:e.position,size:e.size}}var i=this,s=this.options,n=s.resizable,a=this.uiDialog.css("position"),o="string"==typeof n?n:"n,e,s,w,se,sw,ne,nw";this.uiDialog.resizable({cancel:".ui-dialog-content",containment:"document",alsoResize:this.element,maxWidth:s.maxWidth,maxHeight:s.maxHeight,minWidth:s.minWidth,minHeight:this._minHeight(),handles:o,start:function(s,n){e(this).addClass("ui-dialog-resizing"),i._blockFrames(),i._trigger("resizeStart",s,t(n))},resize:function(e,s){i._trigger("resize",e,t(s))},stop:function(n,a){var o=i.uiDialog.offset(),r=o.left-i.document.scrollLeft(),h=o.top-i.document.scrollTop();s.height=i.uiDialog.height(),s.width=i.uiDialog.width(),s.position={my:"left top",at:"left"+(r>=0?"+":"")+r+" "+"top"+(h>=0?"+":"")+h,of:i.window},e(this).removeClass("ui-dialog-resizing"),i._unblockFrames(),i._trigger("resizeStop",n,t(a))}}).css("position",a)},_trackFocus:function(){this._on(this.widget(),{focusin:function(t){this._makeFocusTarget(),this._focusedElement=e(t.target)}})},_makeFocusTarget:function(){this._untrackInstance(),this._trackingInstances().unshift(this)},_untrackInstance:function(){var t=this._trackingInstances(),i=e.inArray(this,t);-1!==i&&t.splice(i,1)},_trackingInstances:function(){var e=this.document.data("ui-dialog-instances");return e||(e=[],this.document.data("ui-dialog-instances",e)),e},_minHeight:function(){var e=this.options;return"auto"===e.height?e.minHeight:Math.min(e.minHeight,e.height)},_position:function(){var e=this.uiDialog.is(":visible");e||this.uiDialog.show(),this.uiDialog.position(this.options.position),e||this.uiDialog.hide()},_setOptions:function(t){var i=this,s=!1,n={};e.each(t,function(e,t){i._setOption(e,t),e in i.sizeRelatedOptions&&(s=!0),e in i.resizableRelatedOptions&&(n[e]=t)}),s&&(this._size(),this._position()),this.uiDialog.is(":data(ui-resizable)")&&this.uiDialog.resizable("option",n)},_setOption:function(e,t){var i,s,n=this.uiDialog;"dialogClass"===e&&n.removeClass(this.options.dialogClass).addClass(t),"disabled"!==e&&(this._super(e,t),"appendTo"===e&&this.uiDialog.appendTo(this._appendTo()),"buttons"===e&&this._createButtons(),"closeText"===e&&this.uiDialogTitlebarClose.button({label:""+t}),"draggable"===e&&(i=n.is(":data(ui-draggable)"),i&&!t&&n.draggable("destroy"),!i&&t&&this._makeDraggable()),"position"===e&&this._position(),"resizable"===e&&(s=n.is(":data(ui-resizable)"),s&&!t&&n.resizable("destroy"),s&&"string"==typeof t&&n.resizable("option","handles",t),s||t===!1||this._makeResizable()),"title"===e&&this._title(this.uiDialogTitlebar.find(".ui-dialog-title")))},_size:function(){var e,t,i,s=this.options;this.element.show().css({width:"auto",minHeight:0,maxHeight:"none",height:0}),s.minWidth>s.width&&(s.width=s.minWidth),e=this.uiDialog.css({height:"auto",width:s.width}).outerHeight(),t=Math.max(0,s.minHeight-e),i="number"==typeof s.maxHeight?Math.max(0,s.maxHeight-e):"none","auto"===s.height?this.element.css({minHeight:t,maxHeight:i,height:"auto"}):this.element.height(Math.max(0,s.height-e)),this.uiDialog.is(":data(ui-resizable)")&&this.uiDialog.resizable("option","minHeight",this._minHeight())},_blockFrames:function(){this.iframeBlocks=this.document.find("iframe").map(function(){var t=e(this);return e("
    ").css({position:"absolute",width:t.outerWidth(),height:t.outerHeight()}).appendTo(t.parent()).offset(t.offset())[0]})},_unblockFrames:function(){this.iframeBlocks&&(this.iframeBlocks.remove(),delete this.iframeBlocks)},_allowInteraction:function(t){return e(t.target).closest(".ui-dialog").length?!0:!!e(t.target).closest(".ui-datepicker").length},_createOverlay:function(){if(this.options.modal){var t=!0;this._delay(function(){t=!1}),this.document.data("ui-dialog-overlays")||this._on(this.document,{focusin:function(e){t||this._allowInteraction(e)||(e.preventDefault(),this._trackingInstances()[0]._focusTabbable())}}),this.overlay=e("
    ").addClass("ui-widget-overlay ui-front").appendTo(this._appendTo()),this._on(this.overlay,{mousedown:"_keepFocus"}),this.document.data("ui-dialog-overlays",(this.document.data("ui-dialog-overlays")||0)+1)}},_destroyOverlay:function(){if(this.options.modal&&this.overlay){var e=this.document.data("ui-dialog-overlays")-1;e?this.document.data("ui-dialog-overlays",e):this.document.unbind("focusin").removeData("ui-dialog-overlays"),this.overlay.remove(),this.overlay=null}}}),e.widget("ui.droppable",{version:"1.11.4",widgetEventPrefix:"drop",options:{accept:"*",activeClass:!1,addClasses:!0,greedy:!1,hoverClass:!1,scope:"default",tolerance:"intersect",activate:null,deactivate:null,drop:null,out:null,over:null},_create:function(){var t,i=this.options,s=i.accept;this.isover=!1,this.isout=!0,this.accept=e.isFunction(s)?s:function(e){return e.is(s)},this.proportions=function(){return arguments.length?(t=arguments[0],void 0):t?t:t={width:this.element[0].offsetWidth,height:this.element[0].offsetHeight}},this._addToManager(i.scope),i.addClasses&&this.element.addClass("ui-droppable")},_addToManager:function(t){e.ui.ddmanager.droppables[t]=e.ui.ddmanager.droppables[t]||[],e.ui.ddmanager.droppables[t].push(this)},_splice:function(e){for(var t=0;e.length>t;t++)e[t]===this&&e.splice(t,1)},_destroy:function(){var t=e.ui.ddmanager.droppables[this.options.scope];this._splice(t),this.element.removeClass("ui-droppable ui-droppable-disabled")},_setOption:function(t,i){if("accept"===t)this.accept=e.isFunction(i)?i:function(e){return e.is(i)};else if("scope"===t){var s=e.ui.ddmanager.droppables[this.options.scope];this._splice(s),this._addToManager(i)}this._super(t,i)},_activate:function(t){var i=e.ui.ddmanager.current;this.options.activeClass&&this.element.addClass(this.options.activeClass),i&&this._trigger("activate",t,this.ui(i))},_deactivate:function(t){var i=e.ui.ddmanager.current;this.options.activeClass&&this.element.removeClass(this.options.activeClass),i&&this._trigger("deactivate",t,this.ui(i))},_over:function(t){var i=e.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this.options.hoverClass&&this.element.addClass(this.options.hoverClass),this._trigger("over",t,this.ui(i)))},_out:function(t){var i=e.ui.ddmanager.current;i&&(i.currentItem||i.element)[0]!==this.element[0]&&this.accept.call(this.element[0],i.currentItem||i.element)&&(this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("out",t,this.ui(i)))},_drop:function(t,i){var s=i||e.ui.ddmanager.current,n=!1;return s&&(s.currentItem||s.element)[0]!==this.element[0]?(this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function(){var i=e(this).droppable("instance");return i.options.greedy&&!i.options.disabled&&i.options.scope===s.options.scope&&i.accept.call(i.element[0],s.currentItem||s.element)&&e.ui.intersect(s,e.extend(i,{offset:i.element.offset()}),i.options.tolerance,t)?(n=!0,!1):void 0}),n?!1:this.accept.call(this.element[0],s.currentItem||s.element)?(this.options.activeClass&&this.element.removeClass(this.options.activeClass),this.options.hoverClass&&this.element.removeClass(this.options.hoverClass),this._trigger("drop",t,this.ui(s)),this.element):!1):!1},ui:function(e){return{draggable:e.currentItem||e.element,helper:e.helper,position:e.position,offset:e.positionAbs}}}),e.ui.intersect=function(){function e(e,t,i){return e>=t&&t+i>e}return function(t,i,s,n){if(!i.offset)return!1;var a=(t.positionAbs||t.position.absolute).left+t.margins.left,o=(t.positionAbs||t.position.absolute).top+t.margins.top,r=a+t.helperProportions.width,h=o+t.helperProportions.height,l=i.offset.left,u=i.offset.top,d=l+i.proportions().width,c=u+i.proportions().height;switch(s){case"fit":return a>=l&&d>=r&&o>=u&&c>=h;case"intersect":return a+t.helperProportions.width/2>l&&d>r-t.helperProportions.width/2&&o+t.helperProportions.height/2>u&&c>h-t.helperProportions.height/2;case"pointer":return e(n.pageY,u,i.proportions().height)&&e(n.pageX,l,i.proportions().width);case"touch":return(o>=u&&c>=o||h>=u&&c>=h||u>o&&h>c)&&(a>=l&&d>=a||r>=l&&d>=r||l>a&&r>d);default:return!1}}}(),e.ui.ddmanager={current:null,droppables:{"default":[]},prepareOffsets:function(t,i){var s,n,a=e.ui.ddmanager.droppables[t.options.scope]||[],o=i?i.type:null,r=(t.currentItem||t.element).find(":data(ui-droppable)").addBack();e:for(s=0;a.length>s;s++)if(!(a[s].options.disabled||t&&!a[s].accept.call(a[s].element[0],t.currentItem||t.element))){for(n=0;r.length>n;n++)if(r[n]===a[s].element[0]){a[s].proportions().height=0;continue e}a[s].visible="none"!==a[s].element.css("display"),a[s].visible&&("mousedown"===o&&a[s]._activate.call(a[s],i),a[s].offset=a[s].element.offset(),a[s].proportions({width:a[s].element[0].offsetWidth,height:a[s].element[0].offsetHeight}))}},drop:function(t,i){var s=!1;return e.each((e.ui.ddmanager.droppables[t.options.scope]||[]).slice(),function(){this.options&&(!this.options.disabled&&this.visible&&e.ui.intersect(t,this,this.options.tolerance,i)&&(s=this._drop.call(this,i)||s),!this.options.disabled&&this.visible&&this.accept.call(this.element[0],t.currentItem||t.element)&&(this.isout=!0,this.isover=!1,this._deactivate.call(this,i)))}),s},dragStart:function(t,i){t.element.parentsUntil("body").bind("scroll.droppable",function(){t.options.refreshPositions||e.ui.ddmanager.prepareOffsets(t,i)})},drag:function(t,i){t.options.refreshPositions&&e.ui.ddmanager.prepareOffsets(t,i),e.each(e.ui.ddmanager.droppables[t.options.scope]||[],function(){if(!this.options.disabled&&!this.greedyChild&&this.visible){var s,n,a,o=e.ui.intersect(t,this,this.options.tolerance,i),r=!o&&this.isover?"isout":o&&!this.isover?"isover":null;r&&(this.options.greedy&&(n=this.options.scope,a=this.element.parents(":data(ui-droppable)").filter(function(){return e(this).droppable("instance").options.scope===n}),a.length&&(s=e(a[0]).droppable("instance"),s.greedyChild="isover"===r)),s&&"isover"===r&&(s.isover=!1,s.isout=!0,s._out.call(s,i)),this[r]=!0,this["isout"===r?"isover":"isout"]=!1,this["isover"===r?"_over":"_out"].call(this,i),s&&"isout"===r&&(s.isout=!1,s.isover=!0,s._over.call(s,i)))}})},dragStop:function(t,i){t.element.parentsUntil("body").unbind("scroll.droppable"),t.options.refreshPositions||e.ui.ddmanager.prepareOffsets(t,i)}},e.ui.droppable;var y="ui-effects-",b=e;e.effects={effect:{}},function(e,t){function i(e,t,i){var s=d[t.type]||{};return null==e?i||!t.def?null:t.def:(e=s.floor?~~e:parseFloat(e),isNaN(e)?t.def:s.mod?(e+s.mod)%s.mod:0>e?0:e>s.max?s.max:e)}function s(i){var s=l(),n=s._rgba=[];return i=i.toLowerCase(),f(h,function(e,a){var o,r=a.re.exec(i),h=r&&a.parse(r),l=a.space||"rgba";return h?(o=s[l](h),s[u[l].cache]=o[u[l].cache],n=s._rgba=o._rgba,!1):t}),n.length?("0,0,0,0"===n.join()&&e.extend(n,a.transparent),s):a[i]}function n(e,t,i){return i=(i+1)%1,1>6*i?e+6*(t-e)*i:1>2*i?t:2>3*i?e+6*(t-e)*(2/3-i):e}var a,o="backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",r=/^([\-+])=\s*(\d+\.?\d*)/,h=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(e){return[e[1],e[2],e[3],e[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(e){return[2.55*e[1],2.55*e[2],2.55*e[3],e[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(e){return[parseInt(e[1],16),parseInt(e[2],16),parseInt(e[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(e){return[parseInt(e[1]+e[1],16),parseInt(e[2]+e[2],16),parseInt(e[3]+e[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(e){return[e[1],e[2]/100,e[3]/100,e[4]]}}],l=e.Color=function(t,i,s,n){return new e.Color.fn.parse(t,i,s,n)},u={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},d={"byte":{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},c=l.support={},p=e("

    ")[0],f=e.each;p.style.cssText="background-color:rgba(1,1,1,.5)",c.rgba=p.style.backgroundColor.indexOf("rgba")>-1,f(u,function(e,t){t.cache="_"+e,t.props.alpha={idx:3,type:"percent",def:1}}),l.fn=e.extend(l.prototype,{parse:function(n,o,r,h){if(n===t)return this._rgba=[null,null,null,null],this;(n.jquery||n.nodeType)&&(n=e(n).css(o),o=t);var d=this,c=e.type(n),p=this._rgba=[];return o!==t&&(n=[n,o,r,h],c="array"),"string"===c?this.parse(s(n)||a._default):"array"===c?(f(u.rgba.props,function(e,t){p[t.idx]=i(n[t.idx],t)}),this):"object"===c?(n instanceof l?f(u,function(e,t){n[t.cache]&&(d[t.cache]=n[t.cache].slice())}):f(u,function(t,s){var a=s.cache;f(s.props,function(e,t){if(!d[a]&&s.to){if("alpha"===e||null==n[e])return;d[a]=s.to(d._rgba)}d[a][t.idx]=i(n[e],t,!0)}),d[a]&&0>e.inArray(null,d[a].slice(0,3))&&(d[a][3]=1,s.from&&(d._rgba=s.from(d[a])))}),this):t},is:function(e){var i=l(e),s=!0,n=this;return f(u,function(e,a){var o,r=i[a.cache];return r&&(o=n[a.cache]||a.to&&a.to(n._rgba)||[],f(a.props,function(e,i){return null!=r[i.idx]?s=r[i.idx]===o[i.idx]:t})),s}),s},_space:function(){var e=[],t=this;return f(u,function(i,s){t[s.cache]&&e.push(i)}),e.pop()},transition:function(e,t){var s=l(e),n=s._space(),a=u[n],o=0===this.alpha()?l("transparent"):this,r=o[a.cache]||a.to(o._rgba),h=r.slice();return s=s[a.cache],f(a.props,function(e,n){var a=n.idx,o=r[a],l=s[a],u=d[n.type]||{};null!==l&&(null===o?h[a]=l:(u.mod&&(l-o>u.mod/2?o+=u.mod:o-l>u.mod/2&&(o-=u.mod)),h[a]=i((l-o)*t+o,n)))}),this[n](h)},blend:function(t){if(1===this._rgba[3])return this;var i=this._rgba.slice(),s=i.pop(),n=l(t)._rgba;return l(e.map(i,function(e,t){return(1-s)*n[t]+s*e}))},toRgbaString:function(){var t="rgba(",i=e.map(this._rgba,function(e,t){return null==e?t>2?1:0:e});return 1===i[3]&&(i.pop(),t="rgb("),t+i.join()+")"},toHslaString:function(){var t="hsla(",i=e.map(this.hsla(),function(e,t){return null==e&&(e=t>2?1:0),t&&3>t&&(e=Math.round(100*e)+"%"),e});return 1===i[3]&&(i.pop(),t="hsl("),t+i.join()+")"},toHexString:function(t){var i=this._rgba.slice(),s=i.pop();return t&&i.push(~~(255*s)),"#"+e.map(i,function(e){return e=(e||0).toString(16),1===e.length?"0"+e:e}).join("")},toString:function(){return 0===this._rgba[3]?"transparent":this.toRgbaString()}}),l.fn.parse.prototype=l.fn,u.hsla.to=function(e){if(null==e[0]||null==e[1]||null==e[2])return[null,null,null,e[3]];var t,i,s=e[0]/255,n=e[1]/255,a=e[2]/255,o=e[3],r=Math.max(s,n,a),h=Math.min(s,n,a),l=r-h,u=r+h,d=.5*u;return t=h===r?0:s===r?60*(n-a)/l+360:n===r?60*(a-s)/l+120:60*(s-n)/l+240,i=0===l?0:.5>=d?l/u:l/(2-u),[Math.round(t)%360,i,d,null==o?1:o]},u.hsla.from=function(e){if(null==e[0]||null==e[1]||null==e[2])return[null,null,null,e[3]];var t=e[0]/360,i=e[1],s=e[2],a=e[3],o=.5>=s?s*(1+i):s+i-s*i,r=2*s-o;return[Math.round(255*n(r,o,t+1/3)),Math.round(255*n(r,o,t)),Math.round(255*n(r,o,t-1/3)),a]},f(u,function(s,n){var a=n.props,o=n.cache,h=n.to,u=n.from;l.fn[s]=function(s){if(h&&!this[o]&&(this[o]=h(this._rgba)),s===t)return this[o].slice();var n,r=e.type(s),d="array"===r||"object"===r?s:arguments,c=this[o].slice();return f(a,function(e,t){var s=d["object"===r?e:t.idx];null==s&&(s=c[t.idx]),c[t.idx]=i(s,t)}),u?(n=l(u(c)),n[o]=c,n):l(c)},f(a,function(t,i){l.fn[t]||(l.fn[t]=function(n){var a,o=e.type(n),h="alpha"===t?this._hsla?"hsla":"rgba":s,l=this[h](),u=l[i.idx];return"undefined"===o?u:("function"===o&&(n=n.call(this,u),o=e.type(n)),null==n&&i.empty?this:("string"===o&&(a=r.exec(n),a&&(n=u+parseFloat(a[2])*("+"===a[1]?1:-1))),l[i.idx]=n,this[h](l)))})})}),l.hook=function(t){var i=t.split(" ");f(i,function(t,i){e.cssHooks[i]={set:function(t,n){var a,o,r="";if("transparent"!==n&&("string"!==e.type(n)||(a=s(n)))){if(n=l(a||n),!c.rgba&&1!==n._rgba[3]){for(o="backgroundColor"===i?t.parentNode:t;(""===r||"transparent"===r)&&o&&o.style;)try{r=e.css(o,"backgroundColor"),o=o.parentNode}catch(h){}n=n.blend(r&&"transparent"!==r?r:"_default")}n=n.toRgbaString()}try{t.style[i]=n}catch(h){}}},e.fx.step[i]=function(t){t.colorInit||(t.start=l(t.elem,i),t.end=l(t.end),t.colorInit=!0),e.cssHooks[i].set(t.elem,t.start.transition(t.end,t.pos))}})},l.hook(o),e.cssHooks.borderColor={expand:function(e){var t={};return f(["Top","Right","Bottom","Left"],function(i,s){t["border"+s+"Color"]=e}),t}},a=e.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(b),function(){function t(t){var i,s,n=t.ownerDocument.defaultView?t.ownerDocument.defaultView.getComputedStyle(t,null):t.currentStyle,a={};if(n&&n.length&&n[0]&&n[n[0]])for(s=n.length;s--;)i=n[s],"string"==typeof n[i]&&(a[e.camelCase(i)]=n[i]);else for(i in n)"string"==typeof n[i]&&(a[i]=n[i]);return a}function i(t,i){var s,a,o={};for(s in i)a=i[s],t[s]!==a&&(n[s]||(e.fx.step[s]||!isNaN(parseFloat(a)))&&(o[s]=a));return o}var s=["add","remove","toggle"],n={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};e.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(t,i){e.fx.step[i]=function(e){("none"!==e.end&&!e.setAttr||1===e.pos&&!e.setAttr)&&(b.style(e.elem,i,e.end),e.setAttr=!0)}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}),e.effects.animateClass=function(n,a,o,r){var h=e.speed(a,o,r);return this.queue(function(){var a,o=e(this),r=o.attr("class")||"",l=h.children?o.find("*").addBack():o;l=l.map(function(){var i=e(this);return{el:i,start:t(this)}}),a=function(){e.each(s,function(e,t){n[t]&&o[t+"Class"](n[t])})},a(),l=l.map(function(){return this.end=t(this.el[0]),this.diff=i(this.start,this.end),this}),o.attr("class",r),l=l.map(function(){var t=this,i=e.Deferred(),s=e.extend({},h,{queue:!1,complete:function(){i.resolve(t)}});return this.el.animate(this.diff,s),i.promise()}),e.when.apply(e,l.get()).done(function(){a(),e.each(arguments,function(){var t=this.el;e.each(this.diff,function(e){t.css(e,"")})}),h.complete.call(o[0])})})},e.fn.extend({addClass:function(t){return function(i,s,n,a){return s?e.effects.animateClass.call(this,{add:i},s,n,a):t.apply(this,arguments)}}(e.fn.addClass),removeClass:function(t){return function(i,s,n,a){return arguments.length>1?e.effects.animateClass.call(this,{remove:i},s,n,a):t.apply(this,arguments)}}(e.fn.removeClass),toggleClass:function(t){return function(i,s,n,a,o){return"boolean"==typeof s||void 0===s?n?e.effects.animateClass.call(this,s?{add:i}:{remove:i},n,a,o):t.apply(this,arguments):e.effects.animateClass.call(this,{toggle:i},s,n,a)}}(e.fn.toggleClass),switchClass:function(t,i,s,n,a){return e.effects.animateClass.call(this,{add:i,remove:t},s,n,a)}})}(),function(){function t(t,i,s,n){return e.isPlainObject(t)&&(i=t,t=t.effect),t={effect:t},null==i&&(i={}),e.isFunction(i)&&(n=i,s=null,i={}),("number"==typeof i||e.fx.speeds[i])&&(n=s,s=i,i={}),e.isFunction(s)&&(n=s,s=null),i&&e.extend(t,i),s=s||i.duration,t.duration=e.fx.off?0:"number"==typeof s?s:s in e.fx.speeds?e.fx.speeds[s]:e.fx.speeds._default,t.complete=n||i.complete,t}function i(t){return!t||"number"==typeof t||e.fx.speeds[t]?!0:"string"!=typeof t||e.effects.effect[t]?e.isFunction(t)?!0:"object"!=typeof t||t.effect?!1:!0:!0}e.extend(e.effects,{version:"1.11.4",save:function(e,t){for(var i=0;t.length>i;i++)null!==t[i]&&e.data(y+t[i],e[0].style[t[i]])},restore:function(e,t){var i,s;for(s=0;t.length>s;s++)null!==t[s]&&(i=e.data(y+t[s]),void 0===i&&(i=""),e.css(t[s],i))},setMode:function(e,t){return"toggle"===t&&(t=e.is(":hidden")?"show":"hide"),t},getBaseline:function(e,t){var i,s;switch(e[0]){case"top":i=0;break;case"middle":i=.5;break;case"bottom":i=1;break;default:i=e[0]/t.height}switch(e[1]){case"left":s=0;break;case"center":s=.5;break;case"right":s=1;break;default:s=e[1]/t.width}return{x:s,y:i}},createWrapper:function(t){if(t.parent().is(".ui-effects-wrapper"))return t.parent();var i={width:t.outerWidth(!0),height:t.outerHeight(!0),"float":t.css("float")},s=e("

    ").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),n={width:t.width(),height:t.height()},a=document.activeElement;try{a.id}catch(o){a=document.body}return t.wrap(s),(t[0]===a||e.contains(t[0],a))&&e(a).focus(),s=t.parent(),"static"===t.css("position")?(s.css({position:"relative"}),t.css({position:"relative"})):(e.extend(i,{position:t.css("position"),zIndex:t.css("z-index")}),e.each(["top","left","bottom","right"],function(e,s){i[s]=t.css(s),isNaN(parseInt(i[s],10))&&(i[s]="auto")}),t.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),t.css(n),s.css(i).show()},removeWrapper:function(t){var i=document.activeElement;return t.parent().is(".ui-effects-wrapper")&&(t.parent().replaceWith(t),(t[0]===i||e.contains(t[0],i))&&e(i).focus()),t},setTransition:function(t,i,s,n){return n=n||{},e.each(i,function(e,i){var a=t.cssUnit(i);a[0]>0&&(n[i]=a[0]*s+a[1])}),n}}),e.fn.extend({effect:function(){function i(t){function i(){e.isFunction(a)&&a.call(n[0]),e.isFunction(t)&&t()}var n=e(this),a=s.complete,r=s.mode;(n.is(":hidden")?"hide"===r:"show"===r)?(n[r](),i()):o.call(n[0],s,i)}var s=t.apply(this,arguments),n=s.mode,a=s.queue,o=e.effects.effect[s.effect];return e.fx.off||!o?n?this[n](s.duration,s.complete):this.each(function(){s.complete&&s.complete.call(this)}):a===!1?this.each(i):this.queue(a||"fx",i)},show:function(e){return function(s){if(i(s))return e.apply(this,arguments);var n=t.apply(this,arguments);return n.mode="show",this.effect.call(this,n)}}(e.fn.show),hide:function(e){return function(s){if(i(s))return e.apply(this,arguments);var n=t.apply(this,arguments);return n.mode="hide",this.effect.call(this,n)}}(e.fn.hide),toggle:function(e){return function(s){if(i(s)||"boolean"==typeof s)return e.apply(this,arguments);var n=t.apply(this,arguments);return n.mode="toggle",this.effect.call(this,n)}}(e.fn.toggle),cssUnit:function(t){var i=this.css(t),s=[];return e.each(["em","px","%","pt"],function(e,t){i.indexOf(t)>0&&(s=[parseFloat(i),t])}),s}})}(),function(){var t={};e.each(["Quad","Cubic","Quart","Quint","Expo"],function(e,i){t[i]=function(t){return Math.pow(t,e+2)}}),e.extend(t,{Sine:function(e){return 1-Math.cos(e*Math.PI/2)},Circ:function(e){return 1-Math.sqrt(1-e*e)},Elastic:function(e){return 0===e||1===e?e:-Math.pow(2,8*(e-1))*Math.sin((80*(e-1)-7.5)*Math.PI/15)},Back:function(e){return e*e*(3*e-2)},Bounce:function(e){for(var t,i=4;((t=Math.pow(2,--i))-1)/11>e;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*t-2)/22-e,2)}}),e.each(t,function(t,i){e.easing["easeIn"+t]=i,e.easing["easeOut"+t]=function(e){return 1-i(1-e)},e.easing["easeInOut"+t]=function(e){return.5>e?i(2*e)/2:1-i(-2*e+2)/2}})}(),e.effects,e.effects.effect.blind=function(t,i){var s,n,a,o=e(this),r=/up|down|vertical/,h=/up|left|vertical|horizontal/,l=["position","top","bottom","left","right","height","width"],u=e.effects.setMode(o,t.mode||"hide"),d=t.direction||"up",c=r.test(d),p=c?"height":"width",f=c?"top":"left",m=h.test(d),g={},v="show"===u;o.parent().is(".ui-effects-wrapper")?e.effects.save(o.parent(),l):e.effects.save(o,l),o.show(),s=e.effects.createWrapper(o).css({overflow:"hidden"}),n=s[p](),a=parseFloat(s.css(f))||0,g[p]=v?n:0,m||(o.css(c?"bottom":"right",0).css(c?"top":"left","auto").css({position:"absolute"}),g[f]=v?a:n+a),v&&(s.css(p,0),m||s.css(f,a+n)),s.animate(g,{duration:t.duration,easing:t.easing,queue:!1,complete:function(){"hide"===u&&o.hide(),e.effects.restore(o,l),e.effects.removeWrapper(o),i()}})},e.effects.effect.bounce=function(t,i){var s,n,a,o=e(this),r=["position","top","bottom","left","right","height","width"],h=e.effects.setMode(o,t.mode||"effect"),l="hide"===h,u="show"===h,d=t.direction||"up",c=t.distance,p=t.times||5,f=2*p+(u||l?1:0),m=t.duration/f,g=t.easing,v="up"===d||"down"===d?"top":"left",y="up"===d||"left"===d,b=o.queue(),_=b.length;for((u||l)&&r.push("opacity"),e.effects.save(o,r),o.show(),e.effects.createWrapper(o),c||(c=o["top"===v?"outerHeight":"outerWidth"]()/3),u&&(a={opacity:1},a[v]=0,o.css("opacity",0).css(v,y?2*-c:2*c).animate(a,m,g)),l&&(c/=Math.pow(2,p-1)),a={},a[v]=0,s=0;p>s;s++)n={},n[v]=(y?"-=":"+=")+c,o.animate(n,m,g).animate(a,m,g),c=l?2*c:c/2;l&&(n={opacity:0},n[v]=(y?"-=":"+=")+c,o.animate(n,m,g)),o.queue(function(){l&&o.hide(),e.effects.restore(o,r),e.effects.removeWrapper(o),i()}),_>1&&b.splice.apply(b,[1,0].concat(b.splice(_,f+1))),o.dequeue()},e.effects.effect.clip=function(t,i){var s,n,a,o=e(this),r=["position","top","bottom","left","right","height","width"],h=e.effects.setMode(o,t.mode||"hide"),l="show"===h,u=t.direction||"vertical",d="vertical"===u,c=d?"height":"width",p=d?"top":"left",f={};e.effects.save(o,r),o.show(),s=e.effects.createWrapper(o).css({overflow:"hidden"}),n="IMG"===o[0].tagName?s:o,a=n[c](),l&&(n.css(c,0),n.css(p,a/2)),f[c]=l?a:0,f[p]=l?0:a/2,n.animate(f,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){l||o.hide(),e.effects.restore(o,r),e.effects.removeWrapper(o),i()}})},e.effects.effect.drop=function(t,i){var s,n=e(this),a=["position","top","bottom","left","right","opacity","height","width"],o=e.effects.setMode(n,t.mode||"hide"),r="show"===o,h=t.direction||"left",l="up"===h||"down"===h?"top":"left",u="up"===h||"left"===h?"pos":"neg",d={opacity:r?1:0};e.effects.save(n,a),n.show(),e.effects.createWrapper(n),s=t.distance||n["top"===l?"outerHeight":"outerWidth"](!0)/2,r&&n.css("opacity",0).css(l,"pos"===u?-s:s),d[l]=(r?"pos"===u?"+=":"-=":"pos"===u?"-=":"+=")+s,n.animate(d,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){"hide"===o&&n.hide(),e.effects.restore(n,a),e.effects.removeWrapper(n),i()}})},e.effects.effect.explode=function(t,i){function s(){b.push(this),b.length===d*c&&n()}function n(){p.css({visibility:"visible"}),e(b).remove(),m||p.hide(),i()}var a,o,r,h,l,u,d=t.pieces?Math.round(Math.sqrt(t.pieces)):3,c=d,p=e(this),f=e.effects.setMode(p,t.mode||"hide"),m="show"===f,g=p.show().css("visibility","hidden").offset(),v=Math.ceil(p.outerWidth()/c),y=Math.ceil(p.outerHeight()/d),b=[];for(a=0;d>a;a++)for(h=g.top+a*y,u=a-(d-1)/2,o=0;c>o;o++)r=g.left+o*v,l=o-(c-1)/2,p.clone().appendTo("body").wrap("
    ").css({position:"absolute",visibility:"visible",left:-o*v,top:-a*y}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:v,height:y,left:r+(m?l*v:0),top:h+(m?u*y:0),opacity:m?0:1}).animate({left:r+(m?0:l*v),top:h+(m?0:u*y),opacity:m?1:0},t.duration||500,t.easing,s)},e.effects.effect.fade=function(t,i){var s=e(this),n=e.effects.setMode(s,t.mode||"toggle");s.animate({opacity:n},{queue:!1,duration:t.duration,easing:t.easing,complete:i})},e.effects.effect.fold=function(t,i){var s,n,a=e(this),o=["position","top","bottom","left","right","height","width"],r=e.effects.setMode(a,t.mode||"hide"),h="show"===r,l="hide"===r,u=t.size||15,d=/([0-9]+)%/.exec(u),c=!!t.horizFirst,p=h!==c,f=p?["width","height"]:["height","width"],m=t.duration/2,g={},v={};e.effects.save(a,o),a.show(),s=e.effects.createWrapper(a).css({overflow:"hidden"}),n=p?[s.width(),s.height()]:[s.height(),s.width()],d&&(u=parseInt(d[1],10)/100*n[l?0:1]),h&&s.css(c?{height:0,width:u}:{height:u,width:0}),g[f[0]]=h?n[0]:u,v[f[1]]=h?n[1]:0,s.animate(g,m,t.easing).animate(v,m,t.easing,function(){l&&a.hide(),e.effects.restore(a,o),e.effects.removeWrapper(a),i()})},e.effects.effect.highlight=function(t,i){var s=e(this),n=["backgroundImage","backgroundColor","opacity"],a=e.effects.setMode(s,t.mode||"show"),o={backgroundColor:s.css("backgroundColor")};"hide"===a&&(o.opacity=0),e.effects.save(s,n),s.show().css({backgroundImage:"none",backgroundColor:t.color||"#ffff99"}).animate(o,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){"hide"===a&&s.hide(),e.effects.restore(s,n),i()}})},e.effects.effect.size=function(t,i){var s,n,a,o=e(this),r=["position","top","bottom","left","right","width","height","overflow","opacity"],h=["position","top","bottom","left","right","overflow","opacity"],l=["width","height","overflow"],u=["fontSize"],d=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],c=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],p=e.effects.setMode(o,t.mode||"effect"),f=t.restore||"effect"!==p,m=t.scale||"both",g=t.origin||["middle","center"],v=o.css("position"),y=f?r:h,b={height:0,width:0,outerHeight:0,outerWidth:0};"show"===p&&o.show(),s={height:o.height(),width:o.width(),outerHeight:o.outerHeight(),outerWidth:o.outerWidth()},"toggle"===t.mode&&"show"===p?(o.from=t.to||b,o.to=t.from||s):(o.from=t.from||("show"===p?b:s),o.to=t.to||("hide"===p?b:s)),a={from:{y:o.from.height/s.height,x:o.from.width/s.width},to:{y:o.to.height/s.height,x:o.to.width/s.width}},("box"===m||"both"===m)&&(a.from.y!==a.to.y&&(y=y.concat(d),o.from=e.effects.setTransition(o,d,a.from.y,o.from),o.to=e.effects.setTransition(o,d,a.to.y,o.to)),a.from.x!==a.to.x&&(y=y.concat(c),o.from=e.effects.setTransition(o,c,a.from.x,o.from),o.to=e.effects.setTransition(o,c,a.to.x,o.to))),("content"===m||"both"===m)&&a.from.y!==a.to.y&&(y=y.concat(u).concat(l),o.from=e.effects.setTransition(o,u,a.from.y,o.from),o.to=e.effects.setTransition(o,u,a.to.y,o.to)),e.effects.save(o,y),o.show(),e.effects.createWrapper(o),o.css("overflow","hidden").css(o.from),g&&(n=e.effects.getBaseline(g,s),o.from.top=(s.outerHeight-o.outerHeight())*n.y,o.from.left=(s.outerWidth-o.outerWidth())*n.x,o.to.top=(s.outerHeight-o.to.outerHeight)*n.y,o.to.left=(s.outerWidth-o.to.outerWidth)*n.x),o.css(o.from),("content"===m||"both"===m)&&(d=d.concat(["marginTop","marginBottom"]).concat(u),c=c.concat(["marginLeft","marginRight"]),l=r.concat(d).concat(c),o.find("*[width]").each(function(){var i=e(this),s={height:i.height(),width:i.width(),outerHeight:i.outerHeight(),outerWidth:i.outerWidth()}; +f&&e.effects.save(i,l),i.from={height:s.height*a.from.y,width:s.width*a.from.x,outerHeight:s.outerHeight*a.from.y,outerWidth:s.outerWidth*a.from.x},i.to={height:s.height*a.to.y,width:s.width*a.to.x,outerHeight:s.height*a.to.y,outerWidth:s.width*a.to.x},a.from.y!==a.to.y&&(i.from=e.effects.setTransition(i,d,a.from.y,i.from),i.to=e.effects.setTransition(i,d,a.to.y,i.to)),a.from.x!==a.to.x&&(i.from=e.effects.setTransition(i,c,a.from.x,i.from),i.to=e.effects.setTransition(i,c,a.to.x,i.to)),i.css(i.from),i.animate(i.to,t.duration,t.easing,function(){f&&e.effects.restore(i,l)})})),o.animate(o.to,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){0===o.to.opacity&&o.css("opacity",o.from.opacity),"hide"===p&&o.hide(),e.effects.restore(o,y),f||("static"===v?o.css({position:"relative",top:o.to.top,left:o.to.left}):e.each(["top","left"],function(e,t){o.css(t,function(t,i){var s=parseInt(i,10),n=e?o.to.left:o.to.top;return"auto"===i?n+"px":s+n+"px"})})),e.effects.removeWrapper(o),i()}})},e.effects.effect.scale=function(t,i){var s=e(this),n=e.extend(!0,{},t),a=e.effects.setMode(s,t.mode||"effect"),o=parseInt(t.percent,10)||(0===parseInt(t.percent,10)?0:"hide"===a?0:100),r=t.direction||"both",h=t.origin,l={height:s.height(),width:s.width(),outerHeight:s.outerHeight(),outerWidth:s.outerWidth()},u={y:"horizontal"!==r?o/100:1,x:"vertical"!==r?o/100:1};n.effect="size",n.queue=!1,n.complete=i,"effect"!==a&&(n.origin=h||["middle","center"],n.restore=!0),n.from=t.from||("show"===a?{height:0,width:0,outerHeight:0,outerWidth:0}:l),n.to={height:l.height*u.y,width:l.width*u.x,outerHeight:l.outerHeight*u.y,outerWidth:l.outerWidth*u.x},n.fade&&("show"===a&&(n.from.opacity=0,n.to.opacity=1),"hide"===a&&(n.from.opacity=1,n.to.opacity=0)),s.effect(n)},e.effects.effect.puff=function(t,i){var s=e(this),n=e.effects.setMode(s,t.mode||"hide"),a="hide"===n,o=parseInt(t.percent,10)||150,r=o/100,h={height:s.height(),width:s.width(),outerHeight:s.outerHeight(),outerWidth:s.outerWidth()};e.extend(t,{effect:"scale",queue:!1,fade:!0,mode:n,complete:i,percent:a?o:100,from:a?h:{height:h.height*r,width:h.width*r,outerHeight:h.outerHeight*r,outerWidth:h.outerWidth*r}}),s.effect(t)},e.effects.effect.pulsate=function(t,i){var s,n=e(this),a=e.effects.setMode(n,t.mode||"show"),o="show"===a,r="hide"===a,h=o||"hide"===a,l=2*(t.times||5)+(h?1:0),u=t.duration/l,d=0,c=n.queue(),p=c.length;for((o||!n.is(":visible"))&&(n.css("opacity",0).show(),d=1),s=1;l>s;s++)n.animate({opacity:d},u,t.easing),d=1-d;n.animate({opacity:d},u,t.easing),n.queue(function(){r&&n.hide(),i()}),p>1&&c.splice.apply(c,[1,0].concat(c.splice(p,l+1))),n.dequeue()},e.effects.effect.shake=function(t,i){var s,n=e(this),a=["position","top","bottom","left","right","height","width"],o=e.effects.setMode(n,t.mode||"effect"),r=t.direction||"left",h=t.distance||20,l=t.times||3,u=2*l+1,d=Math.round(t.duration/u),c="up"===r||"down"===r?"top":"left",p="up"===r||"left"===r,f={},m={},g={},v=n.queue(),y=v.length;for(e.effects.save(n,a),n.show(),e.effects.createWrapper(n),f[c]=(p?"-=":"+=")+h,m[c]=(p?"+=":"-=")+2*h,g[c]=(p?"-=":"+=")+2*h,n.animate(f,d,t.easing),s=1;l>s;s++)n.animate(m,d,t.easing).animate(g,d,t.easing);n.animate(m,d,t.easing).animate(f,d/2,t.easing).queue(function(){"hide"===o&&n.hide(),e.effects.restore(n,a),e.effects.removeWrapper(n),i()}),y>1&&v.splice.apply(v,[1,0].concat(v.splice(y,u+1))),n.dequeue()},e.effects.effect.slide=function(t,i){var s,n=e(this),a=["position","top","bottom","left","right","width","height"],o=e.effects.setMode(n,t.mode||"show"),r="show"===o,h=t.direction||"left",l="up"===h||"down"===h?"top":"left",u="up"===h||"left"===h,d={};e.effects.save(n,a),n.show(),s=t.distance||n["top"===l?"outerHeight":"outerWidth"](!0),e.effects.createWrapper(n).css({overflow:"hidden"}),r&&n.css(l,u?isNaN(s)?"-"+s:-s:s),d[l]=(r?u?"+=":"-=":u?"-=":"+=")+s,n.animate(d,{queue:!1,duration:t.duration,easing:t.easing,complete:function(){"hide"===o&&n.hide(),e.effects.restore(n,a),e.effects.removeWrapper(n),i()}})},e.effects.effect.transfer=function(t,i){var s=e(this),n=e(t.to),a="fixed"===n.css("position"),o=e("body"),r=a?o.scrollTop():0,h=a?o.scrollLeft():0,l=n.offset(),u={top:l.top-r,left:l.left-h,height:n.innerHeight(),width:n.innerWidth()},d=s.offset(),c=e("
    ").appendTo(document.body).addClass(t.className).css({top:d.top-r,left:d.left-h,height:s.innerHeight(),width:s.innerWidth(),position:a?"fixed":"absolute"}).animate(u,t.duration,t.easing,function(){c.remove(),i()})},e.widget("ui.progressbar",{version:"1.11.4",options:{max:100,value:0,change:null,complete:null},min:0,_create:function(){this.oldValue=this.options.value=this._constrainedValue(),this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this.min}),this.valueDiv=e("
    ").appendTo(this.element),this._refreshValue()},_destroy:function(){this.element.removeClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"),this.valueDiv.remove()},value:function(e){return void 0===e?this.options.value:(this.options.value=this._constrainedValue(e),this._refreshValue(),void 0)},_constrainedValue:function(e){return void 0===e&&(e=this.options.value),this.indeterminate=e===!1,"number"!=typeof e&&(e=0),this.indeterminate?!1:Math.min(this.options.max,Math.max(this.min,e))},_setOptions:function(e){var t=e.value;delete e.value,this._super(e),this.options.value=this._constrainedValue(t),this._refreshValue()},_setOption:function(e,t){"max"===e&&(t=Math.max(this.min,t)),"disabled"===e&&this.element.toggleClass("ui-state-disabled",!!t).attr("aria-disabled",t),this._super(e,t)},_percentage:function(){return this.indeterminate?100:100*(this.options.value-this.min)/(this.options.max-this.min)},_refreshValue:function(){var t=this.options.value,i=this._percentage();this.valueDiv.toggle(this.indeterminate||t>this.min).toggleClass("ui-corner-right",t===this.options.max).width(i.toFixed(0)+"%"),this.element.toggleClass("ui-progressbar-indeterminate",this.indeterminate),this.indeterminate?(this.element.removeAttr("aria-valuenow"),this.overlayDiv||(this.overlayDiv=e("
    ").appendTo(this.valueDiv))):(this.element.attr({"aria-valuemax":this.options.max,"aria-valuenow":t}),this.overlayDiv&&(this.overlayDiv.remove(),this.overlayDiv=null)),this.oldValue!==t&&(this.oldValue=t,this._trigger("change")),t===this.options.max&&this._trigger("complete")}}),e.widget("ui.selectable",e.ui.mouse,{version:"1.11.4",options:{appendTo:"body",autoRefresh:!0,distance:0,filter:"*",tolerance:"touch",selected:null,selecting:null,start:null,stop:null,unselected:null,unselecting:null},_create:function(){var t,i=this;this.element.addClass("ui-selectable"),this.dragged=!1,this.refresh=function(){t=e(i.options.filter,i.element[0]),t.addClass("ui-selectee"),t.each(function(){var t=e(this),i=t.offset();e.data(this,"selectable-item",{element:this,$element:t,left:i.left,top:i.top,right:i.left+t.outerWidth(),bottom:i.top+t.outerHeight(),startselected:!1,selected:t.hasClass("ui-selected"),selecting:t.hasClass("ui-selecting"),unselecting:t.hasClass("ui-unselecting")})})},this.refresh(),this.selectees=t.addClass("ui-selectee"),this._mouseInit(),this.helper=e("
    ")},_destroy:function(){this.selectees.removeClass("ui-selectee").removeData("selectable-item"),this.element.removeClass("ui-selectable ui-selectable-disabled"),this._mouseDestroy()},_mouseStart:function(t){var i=this,s=this.options;this.opos=[t.pageX,t.pageY],this.options.disabled||(this.selectees=e(s.filter,this.element[0]),this._trigger("start",t),e(s.appendTo).append(this.helper),this.helper.css({left:t.pageX,top:t.pageY,width:0,height:0}),s.autoRefresh&&this.refresh(),this.selectees.filter(".ui-selected").each(function(){var s=e.data(this,"selectable-item");s.startselected=!0,t.metaKey||t.ctrlKey||(s.$element.removeClass("ui-selected"),s.selected=!1,s.$element.addClass("ui-unselecting"),s.unselecting=!0,i._trigger("unselecting",t,{unselecting:s.element}))}),e(t.target).parents().addBack().each(function(){var s,n=e.data(this,"selectable-item");return n?(s=!t.metaKey&&!t.ctrlKey||!n.$element.hasClass("ui-selected"),n.$element.removeClass(s?"ui-unselecting":"ui-selected").addClass(s?"ui-selecting":"ui-unselecting"),n.unselecting=!s,n.selecting=s,n.selected=s,s?i._trigger("selecting",t,{selecting:n.element}):i._trigger("unselecting",t,{unselecting:n.element}),!1):void 0}))},_mouseDrag:function(t){if(this.dragged=!0,!this.options.disabled){var i,s=this,n=this.options,a=this.opos[0],o=this.opos[1],r=t.pageX,h=t.pageY;return a>r&&(i=r,r=a,a=i),o>h&&(i=h,h=o,o=i),this.helper.css({left:a,top:o,width:r-a,height:h-o}),this.selectees.each(function(){var i=e.data(this,"selectable-item"),l=!1;i&&i.element!==s.element[0]&&("touch"===n.tolerance?l=!(i.left>r||a>i.right||i.top>h||o>i.bottom):"fit"===n.tolerance&&(l=i.left>a&&r>i.right&&i.top>o&&h>i.bottom),l?(i.selected&&(i.$element.removeClass("ui-selected"),i.selected=!1),i.unselecting&&(i.$element.removeClass("ui-unselecting"),i.unselecting=!1),i.selecting||(i.$element.addClass("ui-selecting"),i.selecting=!0,s._trigger("selecting",t,{selecting:i.element}))):(i.selecting&&((t.metaKey||t.ctrlKey)&&i.startselected?(i.$element.removeClass("ui-selecting"),i.selecting=!1,i.$element.addClass("ui-selected"),i.selected=!0):(i.$element.removeClass("ui-selecting"),i.selecting=!1,i.startselected&&(i.$element.addClass("ui-unselecting"),i.unselecting=!0),s._trigger("unselecting",t,{unselecting:i.element}))),i.selected&&(t.metaKey||t.ctrlKey||i.startselected||(i.$element.removeClass("ui-selected"),i.selected=!1,i.$element.addClass("ui-unselecting"),i.unselecting=!0,s._trigger("unselecting",t,{unselecting:i.element})))))}),!1}},_mouseStop:function(t){var i=this;return this.dragged=!1,e(".ui-unselecting",this.element[0]).each(function(){var s=e.data(this,"selectable-item");s.$element.removeClass("ui-unselecting"),s.unselecting=!1,s.startselected=!1,i._trigger("unselected",t,{unselected:s.element})}),e(".ui-selecting",this.element[0]).each(function(){var s=e.data(this,"selectable-item");s.$element.removeClass("ui-selecting").addClass("ui-selected"),s.selecting=!1,s.selected=!0,s.startselected=!0,i._trigger("selected",t,{selected:s.element})}),this._trigger("stop",t),this.helper.remove(),!1}}),e.widget("ui.selectmenu",{version:"1.11.4",defaultElement:"",widgetEventPrefix:"spin",options:{culture:null,icons:{down:"ui-icon-triangle-1-s",up:"ui-icon-triangle-1-n"},incremental:!0,max:null,min:null,numberFormat:null,page:10,step:1,change:null,spin:null,start:null,stop:null},_create:function(){this._setOption("max",this.options.max),this._setOption("min",this.options.min),this._setOption("step",this.options.step),""!==this.value()&&this._value(this.element.val(),!0),this._draw(),this._on(this._events),this._refresh(),this._on(this.window,{beforeunload:function(){this.element.removeAttr("autocomplete")}})},_getCreateOptions:function(){var t={},i=this.element;return e.each(["min","max","step"],function(e,s){var n=i.attr(s);void 0!==n&&n.length&&(t[s]=n)}),t},_events:{keydown:function(e){this._start(e)&&this._keydown(e)&&e.preventDefault()},keyup:"_stop",focus:function(){this.previous=this.element.val()},blur:function(e){return this.cancelBlur?(delete this.cancelBlur,void 0):(this._stop(),this._refresh(),this.previous!==this.element.val()&&this._trigger("change",e),void 0)},mousewheel:function(e,t){if(t){if(!this.spinning&&!this._start(e))return!1;this._spin((t>0?1:-1)*this.options.step,e),clearTimeout(this.mousewheelTimer),this.mousewheelTimer=this._delay(function(){this.spinning&&this._stop(e)},100),e.preventDefault()}},"mousedown .ui-spinner-button":function(t){function i(){var e=this.element[0]===this.document[0].activeElement;e||(this.element.focus(),this.previous=s,this._delay(function(){this.previous=s}))}var s;s=this.element[0]===this.document[0].activeElement?this.previous:this.element.val(),t.preventDefault(),i.call(this),this.cancelBlur=!0,this._delay(function(){delete this.cancelBlur,i.call(this)}),this._start(t)!==!1&&this._repeat(null,e(t.currentTarget).hasClass("ui-spinner-up")?1:-1,t)},"mouseup .ui-spinner-button":"_stop","mouseenter .ui-spinner-button":function(t){return e(t.currentTarget).hasClass("ui-state-active")?this._start(t)===!1?!1:(this._repeat(null,e(t.currentTarget).hasClass("ui-spinner-up")?1:-1,t),void 0):void 0},"mouseleave .ui-spinner-button":"_stop"},_draw:function(){var e=this.uiSpinner=this.element.addClass("ui-spinner-input").attr("autocomplete","off").wrap(this._uiSpinnerHtml()).parent().append(this._buttonHtml());this.element.attr("role","spinbutton"),this.buttons=e.find(".ui-spinner-button").attr("tabIndex",-1).button().removeClass("ui-corner-all"),this.buttons.height()>Math.ceil(.5*e.height())&&e.height()>0&&e.height(e.height()),this.options.disabled&&this.disable()},_keydown:function(t){var i=this.options,s=e.ui.keyCode;switch(t.keyCode){case s.UP:return this._repeat(null,1,t),!0;case s.DOWN:return this._repeat(null,-1,t),!0;case s.PAGE_UP:return this._repeat(null,i.page,t),!0;case s.PAGE_DOWN:return this._repeat(null,-i.page,t),!0}return!1},_uiSpinnerHtml:function(){return""},_buttonHtml:function(){return""+""+""+""+""},_start:function(e){return this.spinning||this._trigger("start",e)!==!1?(this.counter||(this.counter=1),this.spinning=!0,!0):!1},_repeat:function(e,t,i){e=e||500,clearTimeout(this.timer),this.timer=this._delay(function(){this._repeat(40,t,i)},e),this._spin(t*this.options.step,i)},_spin:function(e,t){var i=this.value()||0;this.counter||(this.counter=1),i=this._adjustValue(i+e*this._increment(this.counter)),this.spinning&&this._trigger("spin",t,{value:i})===!1||(this._value(i),this.counter++)},_increment:function(t){var i=this.options.incremental;return i?e.isFunction(i)?i(t):Math.floor(t*t*t/5e4-t*t/500+17*t/200+1):1},_precision:function(){var e=this._precisionOf(this.options.step);return null!==this.options.min&&(e=Math.max(e,this._precisionOf(this.options.min))),e},_precisionOf:function(e){var t=""+e,i=t.indexOf(".");return-1===i?0:t.length-i-1},_adjustValue:function(e){var t,i,s=this.options;return t=null!==s.min?s.min:0,i=e-t,i=Math.round(i/s.step)*s.step,e=t+i,e=parseFloat(e.toFixed(this._precision())),null!==s.max&&e>s.max?s.max:null!==s.min&&s.min>e?s.min:e},_stop:function(e){this.spinning&&(clearTimeout(this.timer),clearTimeout(this.mousewheelTimer),this.counter=0,this.spinning=!1,this._trigger("stop",e))},_setOption:function(e,t){if("culture"===e||"numberFormat"===e){var i=this._parse(this.element.val());return this.options[e]=t,this.element.val(this._format(i)),void 0}("max"===e||"min"===e||"step"===e)&&"string"==typeof t&&(t=this._parse(t)),"icons"===e&&(this.buttons.first().find(".ui-icon").removeClass(this.options.icons.up).addClass(t.up),this.buttons.last().find(".ui-icon").removeClass(this.options.icons.down).addClass(t.down)),this._super(e,t),"disabled"===e&&(this.widget().toggleClass("ui-state-disabled",!!t),this.element.prop("disabled",!!t),this.buttons.button(t?"disable":"enable"))},_setOptions:h(function(e){this._super(e)}),_parse:function(e){return"string"==typeof e&&""!==e&&(e=window.Globalize&&this.options.numberFormat?Globalize.parseFloat(e,10,this.options.culture):+e),""===e||isNaN(e)?null:e},_format:function(e){return""===e?"":window.Globalize&&this.options.numberFormat?Globalize.format(e,this.options.numberFormat,this.options.culture):e},_refresh:function(){this.element.attr({"aria-valuemin":this.options.min,"aria-valuemax":this.options.max,"aria-valuenow":this._parse(this.element.val())})},isValid:function(){var e=this.value();return null===e?!1:e===this._adjustValue(e)},_value:function(e,t){var i;""!==e&&(i=this._parse(e),null!==i&&(t||(i=this._adjustValue(i)),e=this._format(i))),this.element.val(e),this._refresh()},_destroy:function(){this.element.removeClass("ui-spinner-input").prop("disabled",!1).removeAttr("autocomplete").removeAttr("role").removeAttr("aria-valuemin").removeAttr("aria-valuemax").removeAttr("aria-valuenow"),this.uiSpinner.replaceWith(this.element)},stepUp:h(function(e){this._stepUp(e)}),_stepUp:function(e){this._start()&&(this._spin((e||1)*this.options.step),this._stop())},stepDown:h(function(e){this._stepDown(e)}),_stepDown:function(e){this._start()&&(this._spin((e||1)*-this.options.step),this._stop())},pageUp:h(function(e){this._stepUp((e||1)*this.options.page)}),pageDown:h(function(e){this._stepDown((e||1)*this.options.page)}),value:function(e){return arguments.length?(h(this._value).call(this,e),void 0):this._parse(this.element.val())},widget:function(){return this.uiSpinner}}),e.widget("ui.tabs",{version:"1.11.4",delay:300,options:{active:null,collapsible:!1,event:"click",heightStyle:"content",hide:null,show:null,activate:null,beforeActivate:null,beforeLoad:null,load:null},_isLocal:function(){var e=/#.*$/;return function(t){var i,s;t=t.cloneNode(!1),i=t.href.replace(e,""),s=location.href.replace(e,"");try{i=decodeURIComponent(i)}catch(n){}try{s=decodeURIComponent(s)}catch(n){}return t.hash.length>1&&i===s}}(),_create:function(){var t=this,i=this.options;this.running=!1,this.element.addClass("ui-tabs ui-widget ui-widget-content ui-corner-all").toggleClass("ui-tabs-collapsible",i.collapsible),this._processTabs(),i.active=this._initialActive(),e.isArray(i.disabled)&&(i.disabled=e.unique(i.disabled.concat(e.map(this.tabs.filter(".ui-state-disabled"),function(e){return t.tabs.index(e)}))).sort()),this.active=this.options.active!==!1&&this.anchors.length?this._findActive(i.active):e(),this._refresh(),this.active.length&&this.load(i.active)},_initialActive:function(){var t=this.options.active,i=this.options.collapsible,s=location.hash.substring(1);return null===t&&(s&&this.tabs.each(function(i,n){return e(n).attr("aria-controls")===s?(t=i,!1):void 0}),null===t&&(t=this.tabs.index(this.tabs.filter(".ui-tabs-active"))),(null===t||-1===t)&&(t=this.tabs.length?0:!1)),t!==!1&&(t=this.tabs.index(this.tabs.eq(t)),-1===t&&(t=i?!1:0)),!i&&t===!1&&this.anchors.length&&(t=0),t},_getCreateEventData:function(){return{tab:this.active,panel:this.active.length?this._getPanelForTab(this.active):e()}},_tabKeydown:function(t){var i=e(this.document[0].activeElement).closest("li"),s=this.tabs.index(i),n=!0;if(!this._handlePageNav(t)){switch(t.keyCode){case e.ui.keyCode.RIGHT:case e.ui.keyCode.DOWN:s++;break;case e.ui.keyCode.UP:case e.ui.keyCode.LEFT:n=!1,s--;break;case e.ui.keyCode.END:s=this.anchors.length-1;break;case e.ui.keyCode.HOME:s=0;break;case e.ui.keyCode.SPACE:return t.preventDefault(),clearTimeout(this.activating),this._activate(s),void 0;case e.ui.keyCode.ENTER:return t.preventDefault(),clearTimeout(this.activating),this._activate(s===this.options.active?!1:s),void 0;default:return}t.preventDefault(),clearTimeout(this.activating),s=this._focusNextTab(s,n),t.ctrlKey||t.metaKey||(i.attr("aria-selected","false"),this.tabs.eq(s).attr("aria-selected","true"),this.activating=this._delay(function(){this.option("active",s)},this.delay))}},_panelKeydown:function(t){this._handlePageNav(t)||t.ctrlKey&&t.keyCode===e.ui.keyCode.UP&&(t.preventDefault(),this.active.focus())},_handlePageNav:function(t){return t.altKey&&t.keyCode===e.ui.keyCode.PAGE_UP?(this._activate(this._focusNextTab(this.options.active-1,!1)),!0):t.altKey&&t.keyCode===e.ui.keyCode.PAGE_DOWN?(this._activate(this._focusNextTab(this.options.active+1,!0)),!0):void 0},_findNextTab:function(t,i){function s(){return t>n&&(t=0),0>t&&(t=n),t}for(var n=this.tabs.length-1;-1!==e.inArray(s(),this.options.disabled);)t=i?t+1:t-1;return t},_focusNextTab:function(e,t){return e=this._findNextTab(e,t),this.tabs.eq(e).focus(),e},_setOption:function(e,t){return"active"===e?(this._activate(t),void 0):"disabled"===e?(this._setupDisabled(t),void 0):(this._super(e,t),"collapsible"===e&&(this.element.toggleClass("ui-tabs-collapsible",t),t||this.options.active!==!1||this._activate(0)),"event"===e&&this._setupEvents(t),"heightStyle"===e&&this._setupHeightStyle(t),void 0)},_sanitizeSelector:function(e){return e?e.replace(/[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g,"\\$&"):""},refresh:function(){var t=this.options,i=this.tablist.children(":has(a[href])");t.disabled=e.map(i.filter(".ui-state-disabled"),function(e){return i.index(e)}),this._processTabs(),t.active!==!1&&this.anchors.length?this.active.length&&!e.contains(this.tablist[0],this.active[0])?this.tabs.length===t.disabled.length?(t.active=!1,this.active=e()):this._activate(this._findNextTab(Math.max(0,t.active-1),!1)):t.active=this.tabs.index(this.active):(t.active=!1,this.active=e()),this._refresh()},_refresh:function(){this._setupDisabled(this.options.disabled),this._setupEvents(this.options.event),this._setupHeightStyle(this.options.heightStyle),this.tabs.not(this.active).attr({"aria-selected":"false","aria-expanded":"false",tabIndex:-1}),this.panels.not(this._getPanelForTab(this.active)).hide().attr({"aria-hidden":"true"}),this.active.length?(this.active.addClass("ui-tabs-active ui-state-active").attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0}),this._getPanelForTab(this.active).show().attr({"aria-hidden":"false"})):this.tabs.eq(0).attr("tabIndex",0)},_processTabs:function(){var t=this,i=this.tabs,s=this.anchors,n=this.panels; +this.tablist=this._getList().addClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").attr("role","tablist").delegate("> li","mousedown"+this.eventNamespace,function(t){e(this).is(".ui-state-disabled")&&t.preventDefault()}).delegate(".ui-tabs-anchor","focus"+this.eventNamespace,function(){e(this).closest("li").is(".ui-state-disabled")&&this.blur()}),this.tabs=this.tablist.find("> li:has(a[href])").addClass("ui-state-default ui-corner-top").attr({role:"tab",tabIndex:-1}),this.anchors=this.tabs.map(function(){return e("a",this)[0]}).addClass("ui-tabs-anchor").attr({role:"presentation",tabIndex:-1}),this.panels=e(),this.anchors.each(function(i,s){var n,a,o,r=e(s).uniqueId().attr("id"),h=e(s).closest("li"),l=h.attr("aria-controls");t._isLocal(s)?(n=s.hash,o=n.substring(1),a=t.element.find(t._sanitizeSelector(n))):(o=h.attr("aria-controls")||e({}).uniqueId()[0].id,n="#"+o,a=t.element.find(n),a.length||(a=t._createPanel(o),a.insertAfter(t.panels[i-1]||t.tablist)),a.attr("aria-live","polite")),a.length&&(t.panels=t.panels.add(a)),l&&h.data("ui-tabs-aria-controls",l),h.attr({"aria-controls":o,"aria-labelledby":r}),a.attr("aria-labelledby",r)}),this.panels.addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").attr("role","tabpanel"),i&&(this._off(i.not(this.tabs)),this._off(s.not(this.anchors)),this._off(n.not(this.panels)))},_getList:function(){return this.tablist||this.element.find("ol,ul").eq(0)},_createPanel:function(t){return e("
    ").attr("id",t).addClass("ui-tabs-panel ui-widget-content ui-corner-bottom").data("ui-tabs-destroy",!0)},_setupDisabled:function(t){e.isArray(t)&&(t.length?t.length===this.anchors.length&&(t=!0):t=!1);for(var i,s=0;i=this.tabs[s];s++)t===!0||-1!==e.inArray(s,t)?e(i).addClass("ui-state-disabled").attr("aria-disabled","true"):e(i).removeClass("ui-state-disabled").removeAttr("aria-disabled");this.options.disabled=t},_setupEvents:function(t){var i={};t&&e.each(t.split(" "),function(e,t){i[t]="_eventHandler"}),this._off(this.anchors.add(this.tabs).add(this.panels)),this._on(!0,this.anchors,{click:function(e){e.preventDefault()}}),this._on(this.anchors,i),this._on(this.tabs,{keydown:"_tabKeydown"}),this._on(this.panels,{keydown:"_panelKeydown"}),this._focusable(this.tabs),this._hoverable(this.tabs)},_setupHeightStyle:function(t){var i,s=this.element.parent();"fill"===t?(i=s.height(),i-=this.element.outerHeight()-this.element.height(),this.element.siblings(":visible").each(function(){var t=e(this),s=t.css("position");"absolute"!==s&&"fixed"!==s&&(i-=t.outerHeight(!0))}),this.element.children().not(this.panels).each(function(){i-=e(this).outerHeight(!0)}),this.panels.each(function(){e(this).height(Math.max(0,i-e(this).innerHeight()+e(this).height()))}).css("overflow","auto")):"auto"===t&&(i=0,this.panels.each(function(){i=Math.max(i,e(this).height("").height())}).height(i))},_eventHandler:function(t){var i=this.options,s=this.active,n=e(t.currentTarget),a=n.closest("li"),o=a[0]===s[0],r=o&&i.collapsible,h=r?e():this._getPanelForTab(a),l=s.length?this._getPanelForTab(s):e(),u={oldTab:s,oldPanel:l,newTab:r?e():a,newPanel:h};t.preventDefault(),a.hasClass("ui-state-disabled")||a.hasClass("ui-tabs-loading")||this.running||o&&!i.collapsible||this._trigger("beforeActivate",t,u)===!1||(i.active=r?!1:this.tabs.index(a),this.active=o?e():a,this.xhr&&this.xhr.abort(),l.length||h.length||e.error("jQuery UI Tabs: Mismatching fragment identifier."),h.length&&this.load(this.tabs.index(a),t),this._toggle(t,u))},_toggle:function(t,i){function s(){a.running=!1,a._trigger("activate",t,i)}function n(){i.newTab.closest("li").addClass("ui-tabs-active ui-state-active"),o.length&&a.options.show?a._show(o,a.options.show,s):(o.show(),s())}var a=this,o=i.newPanel,r=i.oldPanel;this.running=!0,r.length&&this.options.hide?this._hide(r,this.options.hide,function(){i.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),n()}):(i.oldTab.closest("li").removeClass("ui-tabs-active ui-state-active"),r.hide(),n()),r.attr("aria-hidden","true"),i.oldTab.attr({"aria-selected":"false","aria-expanded":"false"}),o.length&&r.length?i.oldTab.attr("tabIndex",-1):o.length&&this.tabs.filter(function(){return 0===e(this).attr("tabIndex")}).attr("tabIndex",-1),o.attr("aria-hidden","false"),i.newTab.attr({"aria-selected":"true","aria-expanded":"true",tabIndex:0})},_activate:function(t){var i,s=this._findActive(t);s[0]!==this.active[0]&&(s.length||(s=this.active),i=s.find(".ui-tabs-anchor")[0],this._eventHandler({target:i,currentTarget:i,preventDefault:e.noop}))},_findActive:function(t){return t===!1?e():this.tabs.eq(t)},_getIndex:function(e){return"string"==typeof e&&(e=this.anchors.index(this.anchors.filter("[href$='"+e+"']"))),e},_destroy:function(){this.xhr&&this.xhr.abort(),this.element.removeClass("ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible"),this.tablist.removeClass("ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all").removeAttr("role"),this.anchors.removeClass("ui-tabs-anchor").removeAttr("role").removeAttr("tabIndex").removeUniqueId(),this.tablist.unbind(this.eventNamespace),this.tabs.add(this.panels).each(function(){e.data(this,"ui-tabs-destroy")?e(this).remove():e(this).removeClass("ui-state-default ui-state-active ui-state-disabled ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel").removeAttr("tabIndex").removeAttr("aria-live").removeAttr("aria-busy").removeAttr("aria-selected").removeAttr("aria-labelledby").removeAttr("aria-hidden").removeAttr("aria-expanded").removeAttr("role")}),this.tabs.each(function(){var t=e(this),i=t.data("ui-tabs-aria-controls");i?t.attr("aria-controls",i).removeData("ui-tabs-aria-controls"):t.removeAttr("aria-controls")}),this.panels.show(),"content"!==this.options.heightStyle&&this.panels.css("height","")},enable:function(t){var i=this.options.disabled;i!==!1&&(void 0===t?i=!1:(t=this._getIndex(t),i=e.isArray(i)?e.map(i,function(e){return e!==t?e:null}):e.map(this.tabs,function(e,i){return i!==t?i:null})),this._setupDisabled(i))},disable:function(t){var i=this.options.disabled;if(i!==!0){if(void 0===t)i=!0;else{if(t=this._getIndex(t),-1!==e.inArray(t,i))return;i=e.isArray(i)?e.merge([t],i).sort():[t]}this._setupDisabled(i)}},load:function(t,i){t=this._getIndex(t);var s=this,n=this.tabs.eq(t),a=n.find(".ui-tabs-anchor"),o=this._getPanelForTab(n),r={tab:n,panel:o},h=function(e,t){"abort"===t&&s.panels.stop(!1,!0),n.removeClass("ui-tabs-loading"),o.removeAttr("aria-busy"),e===s.xhr&&delete s.xhr};this._isLocal(a[0])||(this.xhr=e.ajax(this._ajaxSettings(a,i,r)),this.xhr&&"canceled"!==this.xhr.statusText&&(n.addClass("ui-tabs-loading"),o.attr("aria-busy","true"),this.xhr.done(function(e,t,n){setTimeout(function(){o.html(e),s._trigger("load",i,r),h(n,t)},1)}).fail(function(e,t){setTimeout(function(){h(e,t)},1)})))},_ajaxSettings:function(t,i,s){var n=this;return{url:t.attr("href"),beforeSend:function(t,a){return n._trigger("beforeLoad",i,e.extend({jqXHR:t,ajaxSettings:a},s))}}},_getPanelForTab:function(t){var i=e(t).attr("aria-controls");return this.element.find(this._sanitizeSelector("#"+i))}}),e.widget("ui.tooltip",{version:"1.11.4",options:{content:function(){var t=e(this).attr("title")||"";return e("").text(t).html()},hide:!0,items:"[title]:not([disabled])",position:{my:"left top+15",at:"left bottom",collision:"flipfit flip"},show:!0,tooltipClass:null,track:!1,close:null,open:null},_addDescribedBy:function(t,i){var s=(t.attr("aria-describedby")||"").split(/\s+/);s.push(i),t.data("ui-tooltip-id",i).attr("aria-describedby",e.trim(s.join(" ")))},_removeDescribedBy:function(t){var i=t.data("ui-tooltip-id"),s=(t.attr("aria-describedby")||"").split(/\s+/),n=e.inArray(i,s);-1!==n&&s.splice(n,1),t.removeData("ui-tooltip-id"),s=e.trim(s.join(" ")),s?t.attr("aria-describedby",s):t.removeAttr("aria-describedby")},_create:function(){this._on({mouseover:"open",focusin:"open"}),this.tooltips={},this.parents={},this.options.disabled&&this._disable(),this.liveRegion=e("
    ").attr({role:"log","aria-live":"assertive","aria-relevant":"additions"}).addClass("ui-helper-hidden-accessible").appendTo(this.document[0].body)},_setOption:function(t,i){var s=this;return"disabled"===t?(this[i?"_disable":"_enable"](),this.options[t]=i,void 0):(this._super(t,i),"content"===t&&e.each(this.tooltips,function(e,t){s._updateContent(t.element)}),void 0)},_disable:function(){var t=this;e.each(this.tooltips,function(i,s){var n=e.Event("blur");n.target=n.currentTarget=s.element[0],t.close(n,!0)}),this.element.find(this.options.items).addBack().each(function(){var t=e(this);t.is("[title]")&&t.data("ui-tooltip-title",t.attr("title")).removeAttr("title")})},_enable:function(){this.element.find(this.options.items).addBack().each(function(){var t=e(this);t.data("ui-tooltip-title")&&t.attr("title",t.data("ui-tooltip-title"))})},open:function(t){var i=this,s=e(t?t.target:this.element).closest(this.options.items);s.length&&!s.data("ui-tooltip-id")&&(s.attr("title")&&s.data("ui-tooltip-title",s.attr("title")),s.data("ui-tooltip-open",!0),t&&"mouseover"===t.type&&s.parents().each(function(){var t,s=e(this);s.data("ui-tooltip-open")&&(t=e.Event("blur"),t.target=t.currentTarget=this,i.close(t,!0)),s.attr("title")&&(s.uniqueId(),i.parents[this.id]={element:this,title:s.attr("title")},s.attr("title",""))}),this._registerCloseHandlers(t,s),this._updateContent(s,t))},_updateContent:function(e,t){var i,s=this.options.content,n=this,a=t?t.type:null;return"string"==typeof s?this._open(t,e,s):(i=s.call(e[0],function(i){n._delay(function(){e.data("ui-tooltip-open")&&(t&&(t.type=a),this._open(t,e,i))})}),i&&this._open(t,e,i),void 0)},_open:function(t,i,s){function n(e){l.of=e,o.is(":hidden")||o.position(l)}var a,o,r,h,l=e.extend({},this.options.position);if(s){if(a=this._find(i))return a.tooltip.find(".ui-tooltip-content").html(s),void 0;i.is("[title]")&&(t&&"mouseover"===t.type?i.attr("title",""):i.removeAttr("title")),a=this._tooltip(i),o=a.tooltip,this._addDescribedBy(i,o.attr("id")),o.find(".ui-tooltip-content").html(s),this.liveRegion.children().hide(),s.clone?(h=s.clone(),h.removeAttr("id").find("[id]").removeAttr("id")):h=s,e("
    ").html(h).appendTo(this.liveRegion),this.options.track&&t&&/^mouse/.test(t.type)?(this._on(this.document,{mousemove:n}),n(t)):o.position(e.extend({of:i},this.options.position)),o.hide(),this._show(o,this.options.show),this.options.show&&this.options.show.delay&&(r=this.delayedShow=setInterval(function(){o.is(":visible")&&(n(l.of),clearInterval(r))},e.fx.interval)),this._trigger("open",t,{tooltip:o})}},_registerCloseHandlers:function(t,i){var s={keyup:function(t){if(t.keyCode===e.ui.keyCode.ESCAPE){var s=e.Event(t);s.currentTarget=i[0],this.close(s,!0)}}};i[0]!==this.element[0]&&(s.remove=function(){this._removeTooltip(this._find(i).tooltip)}),t&&"mouseover"!==t.type||(s.mouseleave="close"),t&&"focusin"!==t.type||(s.focusout="close"),this._on(!0,i,s)},close:function(t){var i,s=this,n=e(t?t.currentTarget:this.element),a=this._find(n);return a?(i=a.tooltip,a.closing||(clearInterval(this.delayedShow),n.data("ui-tooltip-title")&&!n.attr("title")&&n.attr("title",n.data("ui-tooltip-title")),this._removeDescribedBy(n),a.hiding=!0,i.stop(!0),this._hide(i,this.options.hide,function(){s._removeTooltip(e(this))}),n.removeData("ui-tooltip-open"),this._off(n,"mouseleave focusout keyup"),n[0]!==this.element[0]&&this._off(n,"remove"),this._off(this.document,"mousemove"),t&&"mouseleave"===t.type&&e.each(this.parents,function(t,i){e(i.element).attr("title",i.title),delete s.parents[t]}),a.closing=!0,this._trigger("close",t,{tooltip:i}),a.hiding||(a.closing=!1)),void 0):(n.removeData("ui-tooltip-open"),void 0)},_tooltip:function(t){var i=e("
    ").attr("role","tooltip").addClass("ui-tooltip ui-widget ui-corner-all ui-widget-content "+(this.options.tooltipClass||"")),s=i.uniqueId().attr("id");return e("
    ").addClass("ui-tooltip-content").appendTo(i),i.appendTo(this.document[0].body),this.tooltips[s]={element:t,tooltip:i}},_find:function(e){var t=e.data("ui-tooltip-id");return t?this.tooltips[t]:null},_removeTooltip:function(e){e.remove(),delete this.tooltips[e.attr("id")]},_destroy:function(){var t=this;e.each(this.tooltips,function(i,s){var n=e.Event("blur"),a=s.element;n.target=n.currentTarget=a[0],t.close(n,!0),e("#"+i).remove(),a.data("ui-tooltip-title")&&(a.attr("title")||a.attr("title",a.data("ui-tooltip-title")),a.removeData("ui-tooltip-title"))}),this.liveRegion.remove()}})}); \ No newline at end of file diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/public/jquery.js b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/public/jquery.js new file mode 100644 index 00000000..e8364758 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/public/jquery.js @@ -0,0 +1,5 @@ +/*! jQuery v1.12.4 | (c) jQuery Foundation | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="1.12.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(n.isPlainObject(c)||(b=n.isArray(c)))?(b?(b=!1,f=a&&n.isArray(a)?a:[]):f=a&&n.isPlainObject(a)?a:{},g[d]=n.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray||function(a){return"array"===n.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;try{if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(!l.ownFirst)for(b in a)return k.call(a,b);for(b in a);return void 0===b||k.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(b){b&&n.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(h)return h.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(f=a[b],b=a,a=f),n.isFunction(a)?(c=e.call(arguments,2),d=function(){return a.apply(b||this,c.concat(e.call(arguments)))},d.guid=a.guid=a.guid||n.guid++,d):void 0},now:function(){return+new Date},support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return n.inArray(a,b)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;e>b;b++)if(n.contains(d[b],this))return!0}));for(b=0;e>b;b++)n.find(a,d[b],c);return c=this.pushStack(e>1?n.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}if(f=d.getElementById(e[2]),f&&f.parentNode){if(f.id!==e[2])return A.find(a);this.length=1,this[0]=f}return this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b,c=n(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(n.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?n.inArray(this[0],n(a)):n.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return n.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||(e=n.uniqueSort(e)),D.test(a)&&(e=e.reverse())),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=!0,c||j.disable(),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.addEventListener?(d.removeEventListener("DOMContentLoaded",K),a.removeEventListener("load",K)):(d.detachEvent("onreadystatechange",K),a.detachEvent("onload",K))}function K(){(d.addEventListener||"load"===a.event.type||"complete"===d.readyState)&&(J(),n.ready())}n.ready.promise=function(b){if(!I)if(I=n.Deferred(),"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll)a.setTimeout(n.ready);else if(d.addEventListener)d.addEventListener("DOMContentLoaded",K),a.addEventListener("load",K);else{d.attachEvent("onreadystatechange",K),a.attachEvent("onload",K);var c=!1;try{c=null==a.frameElement&&d.documentElement}catch(e){}c&&c.doScroll&&!function f(){if(!n.isReady){try{c.doScroll("left")}catch(b){return a.setTimeout(f,50)}J(),n.ready()}}()}return I.promise(b)},n.ready.promise();var L;for(L in n(l))break;l.ownFirst="0"===L,l.inlineBlockNeedsLayout=!1,n(function(){var a,b,c,e;c=d.getElementsByTagName("body")[0],c&&c.style&&(b=d.createElement("div"),e=d.createElement("div"),e.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(e).appendChild(b),"undefined"!=typeof b.style.zoom&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",l.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(e))}),function(){var a=d.createElement("div");l.deleteExpando=!0;try{delete a.test}catch(b){l.deleteExpando=!1}a=null}();var M=function(a){var b=n.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b},N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(O,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}n.data(a,b,c)}else c=void 0; +}return c}function Q(a){var b;for(b in a)if(("data"!==b||!n.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function R(a,b,d,e){if(M(a)){var f,g,h=n.expando,i=a.nodeType,j=i?n.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||n.guid++:h),j[k]||(j[k]=i?{}:{toJSON:n.noop}),"object"!=typeof b&&"function"!=typeof b||(e?j[k]=n.extend(j[k],b):j[k].data=n.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[n.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[n.camelCase(b)])):f=g,f}}function S(a,b,c){if(M(a)){var d,e,f=a.nodeType,g=f?n.cache:a,h=f?a[n.expando]:n.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){n.isArray(b)?b=b.concat(n.map(b,n.camelCase)):b in d?b=[b]:(b=n.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!Q(d):!n.isEmptyObject(d))return}(c||(delete g[h].data,Q(g[h])))&&(f?n.cleanData([a],!0):l.deleteExpando||g!=g.window?delete g[h]:g[h]=void 0)}}}n.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?n.cache[a[n.expando]]:a[n.expando],!!a&&!Q(a)},data:function(a,b,c){return R(a,b,c)},removeData:function(a,b){return S(a,b)},_data:function(a,b,c){return R(a,b,c,!0)},_removeData:function(a,b){return S(a,b,!0)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=n.data(f),1===f.nodeType&&!n._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));n._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){n.data(this,a)}):arguments.length>1?this.each(function(){n.data(this,a,b)}):f?P(f,a,n.data(f,a)):void 0},removeData:function(a){return this.each(function(){n.removeData(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=n._data(a,b),c&&(!d||n.isArray(c)?d=n._data(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return n._data(a,c)||n._data(a,c,{empty:n.Callbacks("once memory").add(function(){n._removeData(a,b+"queue"),n._removeData(a,c)})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},Z=/^(?:checkbox|radio)$/i,$=/<([\w:-]+)/,_=/^$|\/(?:java|ecma)script/i,aa=/^\s+/,ba="abbr|article|aside|audio|bdi|canvas|data|datalist|details|dialog|figcaption|figure|footer|header|hgroup|main|mark|meter|nav|output|picture|progress|section|summary|template|time|video";function ca(a){var b=ba.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}!function(){var a=d.createElement("div"),b=d.createDocumentFragment(),c=d.createElement("input");a.innerHTML="
    a",l.leadingWhitespace=3===a.firstChild.nodeType,l.tbody=!a.getElementsByTagName("tbody").length,l.htmlSerialize=!!a.getElementsByTagName("link").length,l.html5Clone="<:nav>"!==d.createElement("nav").cloneNode(!0).outerHTML,c.type="checkbox",c.checked=!0,b.appendChild(c),l.appendChecked=c.checked,a.innerHTML="",l.noCloneChecked=!!a.cloneNode(!0).lastChild.defaultValue,b.appendChild(a),c=d.createElement("input"),c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),a.appendChild(c),l.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,l.noCloneEvent=!!a.addEventListener,a[n.expando]=1,l.attributes=!a.getAttribute(n.expando)}();var da={option:[1,""],legend:[1,"
    ","
    "],area:[1,"",""],param:[1,"",""],thead:[1,"","
    "],tr:[2,"","
    "],col:[2,"","
    "],td:[3,"","
    "],_default:l.htmlSerialize?[0,"",""]:[1,"X
    ","
    "]};da.optgroup=da.option,da.tbody=da.tfoot=da.colgroup=da.caption=da.thead,da.th=da.td;function ea(a,b){var c,d,e=0,f="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||n.nodeName(d,b)?f.push(d):n.merge(f,ea(d,b));return void 0===b||b&&n.nodeName(a,b)?n.merge([a],f):f}function fa(a,b){for(var c,d=0;null!=(c=a[d]);d++)n._data(c,"globalEval",!b||n._data(b[d],"globalEval"))}var ga=/<|&#?\w+;/,ha=/r;r++)if(g=a[r],g||0===g)if("object"===n.type(g))n.merge(q,g.nodeType?[g]:g);else if(ga.test(g)){i=i||p.appendChild(b.createElement("div")),j=($.exec(g)||["",""])[1].toLowerCase(),m=da[j]||da._default,i.innerHTML=m[1]+n.htmlPrefilter(g)+m[2],f=m[0];while(f--)i=i.lastChild;if(!l.leadingWhitespace&&aa.test(g)&&q.push(b.createTextNode(aa.exec(g)[0])),!l.tbody){g="table"!==j||ha.test(g)?""!==m[1]||ha.test(g)?0:i:i.firstChild,f=g&&g.childNodes.length;while(f--)n.nodeName(k=g.childNodes[f],"tbody")&&!k.childNodes.length&&g.removeChild(k)}n.merge(q,i.childNodes),i.textContent="";while(i.firstChild)i.removeChild(i.firstChild);i=p.lastChild}else q.push(b.createTextNode(g));i&&p.removeChild(i),l.appendChecked||n.grep(ea(q,"input"),ia),r=0;while(g=q[r++])if(d&&n.inArray(g,d)>-1)e&&e.push(g);else if(h=n.contains(g.ownerDocument,g),i=ea(p.appendChild(g),"script"),h&&fa(i),c){f=0;while(g=i[f++])_.test(g.type||"")&&c.push(g)}return i=null,p}!function(){var b,c,e=d.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(l[b]=c in a)||(e.setAttribute(c,"t"),l[b]=e.attributes[c].expando===!1);e=null}();var ka=/^(?:input|select|textarea)$/i,la=/^key/,ma=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,na=/^(?:focusinfocus|focusoutblur)$/,oa=/^([^.]*)(?:\.(.+)|)/;function pa(){return!0}function qa(){return!1}function ra(){try{return d.activeElement}catch(a){}}function sa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)sa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=qa;else if(!e)return a;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=n.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return"undefined"==typeof n||a&&n.event.triggered===a.type?void 0:n.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(G)||[""],h=b.length;while(h--)f=oa.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=n.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=n.event.special[o]||{},l=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},i),(m=g[o])||(m=g[o]=[],m.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,l):m.push(l),n.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n.hasData(a)&&n._data(a);if(r&&(k=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=oa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=m.length;while(f--)g=m[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(m.splice(f,1),g.selector&&m.delegateCount--,l.remove&&l.remove.call(a,g));i&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(k)&&(delete r.handle,n._removeData(a,"events"))}},trigger:function(b,c,e,f){var g,h,i,j,l,m,o,p=[e||d],q=k.call(b,"type")?b.type:b,r=k.call(b,"namespace")?b.namespace.split("."):[];if(i=m=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!na.test(q+n.event.triggered)&&(q.indexOf(".")>-1&&(r=q.split("."),q=r.shift(),r.sort()),h=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=r.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:n.makeArray(c,[b]),l=n.event.special[q]||{},f||!l.trigger||l.trigger.apply(e,c)!==!1)){if(!f&&!l.noBubble&&!n.isWindow(e)){for(j=l.delegateType||q,na.test(j+q)||(i=i.parentNode);i;i=i.parentNode)p.push(i),m=i;m===(e.ownerDocument||d)&&p.push(m.defaultView||m.parentWindow||a)}o=0;while((i=p[o++])&&!b.isPropagationStopped())b.type=o>1?j:l.bindType||q,g=(n._data(i,"events")||{})[b.type]&&n._data(i,"handle"),g&&g.apply(i,c),g=h&&i[h],g&&g.apply&&M(i)&&(b.result=g.apply(i,c),b.result===!1&&b.preventDefault());if(b.type=q,!f&&!b.isDefaultPrevented()&&(!l._default||l._default.apply(p.pop(),c)===!1)&&M(e)&&h&&e[q]&&!n.isWindow(e)){m=e[h],m&&(e[h]=null),n.event.triggered=q;try{e[q]()}catch(s){}n.event.triggered=void 0,m&&(e[h]=m)}return b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(n._data(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())a.rnamespace&&!a.rnamespace.test(g.namespace)||(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]","i"),va=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,wa=/\s*$/g,Aa=ca(d),Ba=Aa.appendChild(d.createElement("div"));function Ca(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function Da(a){return a.type=(null!==n.find.attr(a,"type"))+"/"+a.type,a}function Ea(a){var b=ya.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Fa(a,b){if(1===b.nodeType&&n.hasData(a)){var c,d,e,f=n._data(a),g=n._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)n.event.add(b,c,h[c][d])}g.data&&(g.data=n.extend({},g.data))}}function Ga(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!l.noCloneEvent&&b[n.expando]){e=n._data(b);for(d in e.events)n.removeEvent(b,d,e.handle);b.removeAttribute(n.expando)}"script"===c&&b.text!==a.text?(Da(b).text=a.text,Ea(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),l.html5Clone&&a.innerHTML&&!n.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&Z.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}}function Ha(a,b,c,d){b=f.apply([],b);var e,g,h,i,j,k,m=0,o=a.length,p=o-1,q=b[0],r=n.isFunction(q);if(r||o>1&&"string"==typeof q&&!l.checkClone&&xa.test(q))return a.each(function(e){var f=a.eq(e);r&&(b[0]=q.call(this,e,f.html())),Ha(f,b,c,d)});if(o&&(k=ja(b,a[0].ownerDocument,!1,a,d),e=k.firstChild,1===k.childNodes.length&&(k=e),e||d)){for(i=n.map(ea(k,"script"),Da),h=i.length;o>m;m++)g=k,m!==p&&(g=n.clone(g,!0,!0),h&&n.merge(i,ea(g,"script"))),c.call(a[m],g,m);if(h)for(j=i[i.length-1].ownerDocument,n.map(i,Ea),m=0;h>m;m++)g=i[m],_.test(g.type||"")&&!n._data(g,"globalEval")&&n.contains(j,g)&&(g.src?n._evalUrl&&n._evalUrl(g.src):n.globalEval((g.text||g.textContent||g.innerHTML||"").replace(za,"")));k=e=null}return a}function Ia(a,b,c){for(var d,e=b?n.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||n.cleanData(ea(d)),d.parentNode&&(c&&n.contains(d.ownerDocument,d)&&fa(ea(d,"script")),d.parentNode.removeChild(d));return a}n.extend({htmlPrefilter:function(a){return a.replace(va,"<$1>")},clone:function(a,b,c){var d,e,f,g,h,i=n.contains(a.ownerDocument,a);if(l.html5Clone||n.isXMLDoc(a)||!ua.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(Ba.innerHTML=a.outerHTML,Ba.removeChild(f=Ba.firstChild)),!(l.noCloneEvent&&l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(d=ea(f),h=ea(a),g=0;null!=(e=h[g]);++g)d[g]&&Ga(e,d[g]);if(b)if(c)for(h=h||ea(a),d=d||ea(f),g=0;null!=(e=h[g]);g++)Fa(e,d[g]);else Fa(a,f);return d=ea(f,"script"),d.length>0&&fa(d,!i&&ea(a,"script")),d=h=e=null,f},cleanData:function(a,b){for(var d,e,f,g,h=0,i=n.expando,j=n.cache,k=l.attributes,m=n.event.special;null!=(d=a[h]);h++)if((b||M(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)m[e]?n.event.remove(d,e):n.removeEvent(d,e,g.handle);j[f]&&(delete j[f],k||"undefined"==typeof d.removeAttribute?d[i]=void 0:d.removeAttribute(i),c.push(f))}}}),n.fn.extend({domManip:Ha,detach:function(a){return Ia(this,a,!0)},remove:function(a){return Ia(this,a)},text:function(a){return Y(this,function(a){return void 0===a?n.text(this):this.empty().append((this[0]&&this[0].ownerDocument||d).createTextNode(a))},null,a,arguments.length)},append:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.appendChild(a)}})},prepend:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&n.cleanData(ea(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&n.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return Y(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(ta,""):void 0;if("string"==typeof a&&!wa.test(a)&&(l.htmlSerialize||!ua.test(a))&&(l.leadingWhitespace||!aa.test(a))&&!da[($.exec(a)||["",""])[1].toLowerCase()]){a=n.htmlPrefilter(a);try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(ea(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return Ha(this,arguments,function(b){var c=this.parentNode;n.inArray(this,a)<0&&(n.cleanData(ea(this)),c&&c.replaceChild(b,this))},a)}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=0,e=[],f=n(a),h=f.length-1;h>=d;d++)c=d===h?this:this.clone(!0),n(f[d])[b](c),g.apply(e,c.get());return this.pushStack(e)}});var Ja,Ka={HTML:"block",BODY:"block"};function La(a,b){var c=n(b.createElement(a)).appendTo(b.body),d=n.css(c[0],"display");return c.detach(),d}function Ma(a){var b=d,c=Ka[a];return c||(c=La(a,b),"none"!==c&&c||(Ja=(Ja||n(" + + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/buttons.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/buttons.erb new file mode 100644 index 00000000..3276fb67 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/buttons.erb @@ -0,0 +1,5 @@ + +

    Buttons

    + + + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/fieldsets.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/fieldsets.erb new file mode 100644 index 00000000..e4b6cd23 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/fieldsets.erb @@ -0,0 +1,30 @@ + + +
    + Agent + +

    + + +

    + +

    + +

    +
    + + +
    +
    + Villain + +

    + + +

    + +

    + +

    +
    + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/form.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/form.erb new file mode 100644 index 00000000..03369d5b --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/form.erb @@ -0,0 +1,670 @@ + +

    Form

    + +
    + +

    + + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + + +

    + +

    + +

    + +

    + + +

    + +

    + + +

    + +

    + + +

    + +

    + + +

    + +

    + +

    + +

    + + +

    + +

    + + +

    + +

    + + + +

    + +

    + + +

    + +

    + + +

    + +

    + +

    + +

    + + +

    + +

    + + +

    + +

    + + +

    + +

    + + +

    + +

    + + +

    + +

    +
    + +

    + +

    +
    + +

    + +

    + + + + + + +

    + +

    + + + + + + +

    + +

    + +

    + +

    + + + + + + +

    + +

    + + + + + + + + +

    + +
    + +
    +
    + + + + + + +

    + +

    + + + + +

    + +

    + + + + +

    + +

    + + +

    + +

    + + +

    + + +

    + + +

    + + +

    + + +

    + + +

    + + +

    + + +

    + + +

    + +

    + First address + + + + + + + + +

    + +

    + Second address + + + + + + + + +

    + +
    + +
    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +
    + +
    + +
    + Disabled Child + + +
    + +
    + + Nested Disabled + + + + Another WLegend + + +
    + + Disabled? + + + +
    +
    + +

    + +

    + +

    + + + + + + + + + +

    + +

    + + + +

    + +

    + + +

    + +

    + + +

    + + + + + + +
    + + + + + + + +
    + + + + + + + + + + + + +
    +

    + + +

    + +

    + +

    + + +
    +

    + + +

    + +

    + + +

    + + +
    +

    + +

    + +

    + + +

    + + +
    +

    + + +

    + +

    + + +

    + +

    + +

    + + +
    +

    + + +

    + +

    + + +

    + +

    + + +

    + +

    + +

    + + +
    +

    + +

    + + +

    + +

    +

    + +

    + + +
    +

    + + +

    +

    + + +

    +

    + + +

    +

    + + +

    +

    Emergency Number

    +

    + + +

    +

    + + +

    + +

    + +

    + + +
    +

    + + + + +

    + + +
    +

    + +

    + + + + +
    +

    + +

    + + +
    +

    + +

    + + + + + + + + +

    + + +

    + +

    + +

    diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/frame_child.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/frame_child.erb new file mode 100644 index 00000000..0583f1ab --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/frame_child.erb @@ -0,0 +1,18 @@ + + + + This is the child frame title + + + + Close Window Now + Close Window Soon + + + + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/frame_one.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/frame_one.erb new file mode 100644 index 00000000..58e52edf --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/frame_one.erb @@ -0,0 +1,10 @@ + + + + This is the title of frame one + + +
    This is the text of divInFrameOne
    +
    Some other text
    + + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/frame_parent.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/frame_parent.erb new file mode 100644 index 00000000..151665ec --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/frame_parent.erb @@ -0,0 +1,9 @@ + + + + This is the parent frame title + + + + + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/frame_two.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/frame_two.erb new file mode 100644 index 00000000..c1e6d623 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/frame_two.erb @@ -0,0 +1,9 @@ + + + + This is the title of frame two + + +
    This is the text of divInFrameTwo
    + + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/header_links.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/header_links.erb new file mode 100644 index 00000000..ef4b4128 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/header_links.erb @@ -0,0 +1,8 @@ + +

    + Link +

    + +
    +

    + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/host_links.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/host_links.erb new file mode 100644 index 00000000..3c139d6e --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/host_links.erb @@ -0,0 +1,13 @@ + +

    + Relative Host + Absolute Host +

    + +
    +

    + + +
    +

    + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/initial_alert.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/initial_alert.erb new file mode 100644 index 00000000..c0294d79 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/initial_alert.erb @@ -0,0 +1,10 @@ + + +
    + Initial alert page +
    + + + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/obscured.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/obscured.erb new file mode 100644 index 00000000..5f8e020a --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/obscured.erb @@ -0,0 +1,47 @@ + + + + Obscured + + + +
    + +
    +
    + + +
    +
    +
    + + + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/offset.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/offset.erb new file mode 100644 index 00000000..7db2dbfa --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/offset.erb @@ -0,0 +1,32 @@ + + + + Offset + + + + + +
    +
    +
    + + \ No newline at end of file diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/path.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/path.erb new file mode 100644 index 00000000..8c43921e --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/path.erb @@ -0,0 +1,13 @@ + + + + + Text node + + + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/popup_one.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/popup_one.erb new file mode 100644 index 00000000..3f6e62db --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/popup_one.erb @@ -0,0 +1,9 @@ + + + + Title of the first popup + + +
    This is the text of divInPopupOne
    + + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/popup_two.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/popup_two.erb new file mode 100644 index 00000000..ef6f8bf0 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/popup_two.erb @@ -0,0 +1,9 @@ + + + + Title of popup two + + +
    This is the text of divInPopupTwo
    + + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/postback.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/postback.erb new file mode 100644 index 00000000..dcd7e88a --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/postback.erb @@ -0,0 +1,14 @@ + +

    Postback

    + +
    +

    + +

    + + +
    +

    + +

    + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/react.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/react.erb new file mode 100644 index 00000000..78e00d18 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/react.erb @@ -0,0 +1,45 @@ + + + + + + + +
    + + + \ No newline at end of file diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/scroll.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/scroll.erb new file mode 100644 index 00000000..d95c9318 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/scroll.erb @@ -0,0 +1,20 @@ + + + + + +
    scroll
    +
    +
    +
    inner
    +
    +
    + + + \ No newline at end of file diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/spatial.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/spatial.erb new file mode 100644 index 00000000..7d352055 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/spatial.erb @@ -0,0 +1,31 @@ + + + + spatial + + + + +
    1
    +
    2
    +
    3
    +
    4
    +
    5
    +
    6
    +
    7
    +
    8
    +
    9
    + + + + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/tables.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/tables.erb new file mode 100644 index 00000000..e26e20be --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/tables.erb @@ -0,0 +1,130 @@ + +
    +
    + + + + + + + + + + +
    Agent
    + + + +
    + +
    + + +
    + + + + + + + + + + + +
    Girl
    + + + +
    + +
    +
    + +
    + + + + + + + + + + + +
    Villain
    + + + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Horizontal Headers
    First NameLast NameCity
    ThomasWalpoleOceanside
    DaniloWilkinsonJohnsonville
    VernKonopelskiEverette
    RatkeLawrenceEast Sorayashire
    PalmerSawaynWest Trinidad
    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Vertical Headers
    First NameThomasDaniloVernRatkePalmer
    Last NameWalpoleWilkinsonKonopelskiLawrenceSawayn
    CityOceansideJohnsonvilleEveretteEast SorayashireWest Trinidad
    + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_animation.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_animation.erb new file mode 100644 index 00000000..c64ae040 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_animation.erb @@ -0,0 +1,74 @@ + + + + + with_animation + + + + + + + + + transition me away + + animate me away + + + + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_base_tag.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_base_tag.erb new file mode 100644 index 00000000..77cfdcd8 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_base_tag.erb @@ -0,0 +1,11 @@ + + + + + + with_external_source + + +

    FooBar

    + +This page is used for testing number options of has_text? + +

    count1

    +
    +

    2 count

    +

    Count

    +
    diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_dragula.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_dragula.erb new file mode 100644 index 00000000..d788e4b9 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_dragula.erb @@ -0,0 +1,22 @@ + + + + with_dragula + + + + +
    +
    Item 1
    +
    Item 2
    +
    Item 3
    +
    Item 4
    +
    Item 5
    +
    + + + + + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_fixed_header_footer.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_fixed_header_footer.erb new file mode 100644 index 00000000..826748c8 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_fixed_header_footer.erb @@ -0,0 +1,17 @@ + + + + + +
    My headers
    +
    +
    A tall block
    + Go to root +
    +
    My footer
    + \ No newline at end of file diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_hover.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_hover.erb new file mode 100644 index 00000000..ddb11e62 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_hover.erb @@ -0,0 +1,24 @@ + + + + + with_hover + + + + + Other hover page +
    + Some text here so the wrapper has size +
    Here I am
    +
    +
    +
    + Some text here so the wrapper has size +
    Here I am
    +
    + + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_hover1.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_hover1.erb new file mode 100644 index 00000000..19df3489 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_hover1.erb @@ -0,0 +1,10 @@ + + + + + + + + Go back + + \ No newline at end of file diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_html.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_html.erb new file mode 100644 index 00000000..adb1c69c --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_html.erb @@ -0,0 +1,208 @@ + + + +
    <%= referrer %>
    +

    This is a test

    + +

    +

    +

    Header Class Test One

    +

    Header Class Test Two

    +

    Header Class Test Three

    +

    Header Class Test Four

    +

    Header Class RandomTest Five

    + +42 +Other span + +

    + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod + tempor incididunt ut labore + et dolore magna aliqua. Ut enim ad minim veniam, + quis nostrud exercitation ullamco laboris nisi + ut aliquip ex ea commodo consequat. + awesome image +

    + +

    + Duis aute irure dolor in reprehenderit in voluptate velit esse cillum + dolore eu fugiat Redirect pariatur. Excepteur sint occaecat cupidatat non proident, + sunt in culpa qui officia + text with   + whitespace + id est laborum. +

    + +

    + + + + + + BackToMyself + A link came first + A link + A link with data-method + A link with capitalized data-method + No Href + Blank Href + Blank Anchor + Blank JS Anchor + Normal Anchor + Anchor on different page + Anchor on same page + Relative + Protocol + + very fine image + fine image + Disabled link + + Naked Query String + <% if params[:query_string] %> + Query String sent + <% end %> +

    + + + + + + +
    + Some of this text is hidden! +
    + +
    + Some of this text is not hidden and some is hidden +
    + + + + + + + +
    + Number 42 +
    + +
      +
    • John
    • +
    • Paul
    • +
    • George
    • +
    • Ringo
    • +
    + +
    +
    singular
    +
    multiple one
    +
    multiple two
    +
    almost singular but not quite
    +
    almost singular
    +
    + + + + + + +
    + text here +
    + +
    + Ancestor +
    + Ancestor +
    + Ancestor +
    Child
    +
    +
    + ASibling +
    +
    + +
    + +
    +
    +
    Pre Sibling
    +
    Mid Sibling
    +
    Post Sibling
    +
    +
    +
    Pre Sibling
    +
    Post Sibling
    +
    +
    + +
    needs escaping
    + +
    + Some text
    More text
    +
    And more text
    + Even more    text + + on multiple lines +
    + + + +
    +                    +
    + +
    + +
    Something
    +
    +
      +
    • Random
    • +
    • Things
    • +
    +
    + +
    + +
    Summary
    +
    +
    + Contents +
    +
    + + + +Download Me +Download Other diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_html5_svg.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_html5_svg.erb new file mode 100644 index 00000000..e931f861 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_html5_svg.erb @@ -0,0 +1,20 @@ + + + + Namespace + + +
    + + + + + + + + + +
    + + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_html_entities.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_html_entities.erb new file mode 100644 index 00000000..52f35144 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_html_entities.erb @@ -0,0 +1,2 @@ + +Encoding with — html entities » diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_js.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_js.erb new file mode 100644 index 00000000..0e52b280 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_js.erb @@ -0,0 +1,160 @@ + + + + + with_js + + + + + + +

    FooBar

    + +

    This is text

    + This link is non-HTML5 draggable +
    +

    This is a draggable element.

    +
    +
    +

    It should be dropped here.

    +
    +
    +

    It should be dropped here.

    +
    +
    +

    It should be dropped here.

    +
    +
    +

    This is an HTML5 draggable element.

    +
    +

    This is an HTML5 draggable link

    +
    +

    It should be dropped here.

    +
    + +

    Click me

    +

    Slowly

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    +

    Editable content
    +
    +
    + Some content +
    Content
    +
    +

    + +

    + +

    + +

    + +

    + +

    + Reload! + this won't change +

    waiting to be reloaded
    +

    + +

    + Fetch new list! +

      +
    • Item 1
    • +
    • Item 2
    • +
    +

    + +

    + Change title +

    + +

    + Change size +

    + +

    Click me

    + +

    + Open alert + Alert page change +

    + +

    + Open delayed alert + Open slow alert +

    + +

    + Open confirm +

    + +

    + Open check twice +

    + +

    + Open prompt +

    + +

    + Open defaulted prompt +

    + +

    + +

    +

    + +

    +

    + Change page + Non-escaped query options + Escaped query options +

    + +

    + +

    +

    + +

    + + +

    + +

    + +

    + +
    +

    This is a draggable element.

    +
    +
    +

    This is an HTML5 draggable element.

    +
    + + + + + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_jstree.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_jstree.erb new file mode 100644 index 00000000..31a57dd8 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_jstree.erb @@ -0,0 +1,26 @@ + + + + + with_jstree + + +
    +
      +
    • Child node A
    • +
    • Child node B
    • +
    • Child node C
    • +
    +
    + + + + + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_namespace.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_namespace.erb new file mode 100644 index 00000000..f4d4c421 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_namespace.erb @@ -0,0 +1,20 @@ + + + Namespace + + +
    + + + + + + + + + +
    + + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_scope.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_scope.erb new file mode 100644 index 00000000..638e62dc --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_scope.erb @@ -0,0 +1,42 @@ + +

    This page is used for testing various scopes

    + +

    + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor + incididunt ut labore et dolore magna aliqua. Ut enim ad minim venia. + Go + +

    + +
    +
      +
    • With Simple HTML: Go +
      +

      + + +

      +

      +
      +
    • +
    • Bar: Go +
      +

      + + +

      +

      + + +

      +

      +
      +
    • +
    +
    + +
    +
      +
    • With Simple HTML: Go +
    +
    diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_scope_other.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_scope_other.erb new file mode 100644 index 00000000..9abe4d3a --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_scope_other.erb @@ -0,0 +1,6 @@ + +

    This page is used for testing various scopes

    + +

    + Different text same wrapper id +

    diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_simple_html.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_simple_html.erb new file mode 100644 index 00000000..538d95bf --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_simple_html.erb @@ -0,0 +1,2 @@ + +Bar diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_slow_unload.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_slow_unload.erb new file mode 100644 index 00000000..49d2b07e --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_slow_unload.erb @@ -0,0 +1,17 @@ + + + + + +
    This delays unload by 2 seconds
    + + \ No newline at end of file diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_sortable_js.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_sortable_js.erb new file mode 100644 index 00000000..97192ff8 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_sortable_js.erb @@ -0,0 +1,21 @@ + + + + with_sortable_js + + + + +
    +
    Item 1
    +
    Item 2
    +
    Item 3
    +
    Item 4
    +
    Item 5
    +
    + + + + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_title.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_title.erb new file mode 100644 index 00000000..283df4e4 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_title.erb @@ -0,0 +1,5 @@ + +Test Title + + abcdefg + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_unload_alert.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_unload_alert.erb new file mode 100644 index 00000000..4bcb4cd0 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_unload_alert.erb @@ -0,0 +1,14 @@ + + + + + +
    This triggers an alert on unload
    + + Go away + + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_windows.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_windows.erb new file mode 100644 index 00000000..e098d99c --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/with_windows.erb @@ -0,0 +1,54 @@ + + + + With Windows + + + + + + + + + + + + + + + + +
    + My scoped content +
    + + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/within_frames.erb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/within_frames.erb new file mode 100644 index 00000000..fc211cd2 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/spec/views/within_frames.erb @@ -0,0 +1,15 @@ + + + + With Frames + + +
    + This is the text for divInMainWindow + +
    + + + + + diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/version.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/version.rb new file mode 100644 index 00000000..961e2329 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module Capybara + VERSION = '3.29.0' +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/window.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/window.rb new file mode 100644 index 00000000..22ac451f --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/lib/capybara/window.rb @@ -0,0 +1,146 @@ +# frozen_string_literal: true + +module Capybara + ## + # The {Window} class represents a browser window. + # + # You can get an instance of the class by calling either of: + # + # * {Capybara::Session#windows} + # * {Capybara::Session#current_window} + # * {Capybara::Session#window_opened_by} + # * {Capybara::Session#switch_to_window} + # + # Note that some drivers (e.g. Selenium) support getting size of/resizing/closing only + # current window. So if you invoke such method for: + # + # * window that is current, Capybara will make 2 Selenium method invocations + # (get handle of current window + get size/resize/close). + # * window that is not current, Capybara will make 4 Selenium method invocations + # (get handle of current window + switch to given handle + get size/resize/close + switch to original handle) + # + class Window + # @return [String] a string that uniquely identifies window within session + attr_reader :handle + + # @return [Capybara::Session] session that this window belongs to + attr_reader :session + + # @api private + def initialize(session, handle) + @session = session + @driver = session.driver + @handle = handle + end + + ## + # @return [Boolean] whether the window is not closed + def exists? + @driver.window_handles.include?(@handle) + end + + ## + # @return [Boolean] whether the window is closed + def closed? + !exists? + end + + ## + # @return [Boolean] whether this window is the window in which commands are being executed + def current? + @driver.current_window_handle == @handle + rescue @driver.no_such_window_error + false + end + + ## + # Close window. + # + # If this method was called for window that is current, then after calling this method + # future invocations of other Capybara methods should raise + # {Capybara::Driver::Base#no_such_window_error session.driver.no_such_window_error} until another window will be switched to. + # + # @!macro about_current + # If this method was called for window that is not current, then after calling this method + # current window should remain the same as it was before calling this method. + # + def close + @driver.close_window(handle) + end + + ## + # Get window size. + # + # @macro about_current + # @return [Array<(Integer, Integer)>] an array with width and height + # + def size + @driver.window_size(handle) + end + + ## + # Resize window. + # + # @macro about_current + # @param width [String] the new window width in pixels + # @param height [String] the new window height in pixels + # + def resize_to(width, height) + wait_for_stable_size { @driver.resize_window_to(handle, width, height) } + end + + ## + # Maximize window. + # + # If a particular driver (e.g. headless driver) doesn't have concept of maximizing it + # may not support this method. + # + # @macro about_current + # + def maximize + wait_for_stable_size { @driver.maximize_window(handle) } + end + + ## + # Fullscreen window. + # + # If a particular driver doesn't have concept of fullscreen it may not support this method. + # + # @macro about_current + # + def fullscreen + @driver.fullscreen_window(handle) + end + + def eql?(other) + other.is_a?(self.class) && @session == other.session && @handle == other.handle + end + alias_method :==, :eql? + + def hash + @session.hash ^ @handle.hash + end + + def inspect + "#" + end + + private + + def wait_for_stable_size(seconds = session.config.default_max_wait_time) + res = yield if block_given? + timer = Capybara::Helpers.timer(expire_in: seconds) + loop do + prev_size = size + sleep 0.025 + return res if prev_size == size + break if timer.expired? + end + raise Capybara::WindowError, "Window size not stable within #{seconds} seconds." + end + + def raise_unless_current(what) + raise Capybara::WindowError, "#{what} not current window is not possible." unless current? + end + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/basic_node_spec.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/basic_node_spec.rb new file mode 100644 index 00000000..5424c96f --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/basic_node_spec.rb @@ -0,0 +1,154 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Capybara do + describe '.string' do + let :string do + described_class.string <<-STRING + + + simple_node + + + not document title +
    +
    +

    Totally awesome

    +

    Yes it is

    +
    + +
    + + +
    + + + + + +
    +
    +
    +
    + + + STRING + end + + it 'allows using matchers' do + expect(string).to have_css('#page') + expect(string).not_to have_css('#does-not-exist') + end + + it 'allows using custom matchers' do + described_class.add_selector :lifeform do + xpath { |name| ".//option[contains(.,'#{name}')]" } + end + expect(string).to have_selector(:id, 'page') + expect(string).not_to have_selector(:id, 'does-not-exist') + expect(string).to have_selector(:lifeform, 'Monkey') + expect(string).not_to have_selector(:lifeform, 'Gorilla') + end + + it 'allows custom matcher using css' do + described_class.add_selector :section do + css { |css_class| "section .#{css_class}" } + end + expect(string).to have_selector(:section, 'subsection') + expect(string).not_to have_selector(:section, 'section_8') + end + + it 'allows using matchers with text option' do + expect(string).to have_css('h1', text: 'Totally awesome') + expect(string).not_to have_css('h1', text: 'Not so awesome') + end + + it 'allows finding only visible nodes' do + expect(string.all(:css, '#secret', visible: true)).to be_empty + expect(string.all(:css, '#secret', visible: false).size).to eq(1) + end + + it 'allows finding elements and extracting text from them' do + expect(string.find('//h1').text).to eq('Totally awesome') + end + + it 'allows finding elements and extracting attributes from them' do + expect(string.find('//h1')[:data]).to eq('fantastic') + end + + it 'allows finding elements and extracting the tag name from them' do + expect(string.find('//h1').tag_name).to eq('h1') + end + + it 'allows finding elements and extracting the path' do + expect(string.find('//h1').path).to eq('/html/body/div/div[1]/h1') + end + + it 'allows finding elements and extracting the value' do + expect(string.find('//div/input').value).to eq('bar') + expect(string.find('//select').value).to eq('Capybara') + end + + it 'allows finding elements and checking if they are visible' do + expect(string.find('//h1')).to be_visible + expect(string.find(:css, '#secret', visible: false)).not_to be_visible + end + + it 'allows finding elements and checking if they are disabled' do + expect(string.find('//form/input[@name="bleh"]')).to be_disabled + expect(string.find('//form/input[@name="meh"]')).not_to be_disabled + end + + it 'drops illegal fragments when using gumbo' do + skip 'libxml is less strict than Gumbo' unless Nokogiri.respond_to?(:HTML5) + expect(described_class.string('1')).not_to have_css('td') + end + + it 'can disable use of gumbo' do + skip "Test doesn't make sense unlesss nokogumbo is loaded" unless Nokogiri.respond_to?(:HTML5) + described_class.allow_gumbo = false + expect(described_class.string('1')).to have_css('td') + end + + describe '#title' do + it 'returns the page title' do + expect(string.title).to eq('simple_node') + end + end + + describe '#has_title?' do + it 'returns whether the page has the given title' do + expect(string.has_title?('simple_node')).to be_truthy + expect(string.has_title?('monkey')).to be_falsey + end + + it 'allows regexp matches' do + expect(string.has_title?(/s[a-z]+_node/)).to be_truthy + expect(string.has_title?(/monkey/)).to be_falsey + end + end + + describe '#has_no_title?' do + it 'returns whether the page does not have the given title' do + expect(string.has_no_title?('simple_node')).to be_falsey + expect(string.has_no_title?('monkey')).to be_truthy + end + + it 'allows regexp matches' do + expect(string.has_no_title?(/s[a-z]+_node/)).to be_falsey + expect(string.has_no_title?(/monkey/)).to be_truthy + end + end + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/capybara_spec.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/capybara_spec.rb new file mode 100644 index 00000000..e0f32dbe --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/capybara_spec.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Capybara do + describe 'default_max_wait_time' do + before { @previous_default_time = described_class.default_max_wait_time } + + after { described_class.default_max_wait_time = @previous_default_time } # rubocop:disable RSpec/InstanceVariable + + it 'should be changeable' do + expect(described_class.default_max_wait_time).not_to eq(5) + described_class.default_max_wait_time = 5 + expect(described_class.default_max_wait_time).to eq(5) + end + end + + describe '.register_driver' do + it 'should add a new driver' do + described_class.register_driver :schmoo do |app| + Capybara::RackTest::Driver.new(app) + end + session = Capybara::Session.new(:schmoo, TestApp) + session.visit('/') + expect(session.body).to include('Hello world!') + end + end + + describe '.register_server' do + it 'should add a new server' do + described_class.register_server :blob do |_app, _port, _host| + # do nothing + end + + expect(described_class.servers).to have_key(:blob) + end + end + + describe '.server' do + after do + described_class.server = :default + end + + it 'should default to a proc that calls run_default_server' do + mock_app = Object.new + allow(described_class).to receive(:run_default_server).and_return(true) + described_class.server.call(mock_app, 8000) + expect(described_class).to have_received(:run_default_server).with(mock_app, 8000) + end + + it 'should return a custom server proc' do + server = ->(_app, _port) {} + described_class.register_server :custom, &server + described_class.server = :custom + expect(described_class.server).to eq(server) + end + + it 'should have :webrick registered' do + expect(described_class.servers[:webrick]).not_to be_nil + end + + it 'should have :puma registered' do + expect(described_class.servers[:puma]).not_to be_nil + end + end + + describe 'server=' do + after do + described_class.server = :default + end + + it 'accepts a proc' do + server = ->(_app, _port) {} + described_class.server = server + expect(described_class.server).to eq server + end + end + + describe 'app_host' do + after do + described_class.app_host = nil + end + + it 'should warn if not a valid URL' do + expect { described_class.app_host = 'www.example.com' }.to raise_error(ArgumentError, /Capybara\.app_host should be set to a url/) + end + + it 'should not warn if a valid URL' do + expect { described_class.app_host = 'http://www.example.com' }.not_to raise_error + end + + it 'should not warn if nil' do + expect { described_class.app_host = nil }.not_to raise_error + end + end + + describe 'default_host' do + around do |test| + old_default = described_class.default_host + test.run + described_class.default_host = old_default + end + + it 'should raise if not a valid URL' do + expect { described_class.default_host = 'www.example.com' }.to raise_error(ArgumentError, /Capybara\.default_host should be set to a url/) + end + + it 'should not warn if a valid URL' do + expect { described_class.default_host = 'http://www.example.com' }.not_to raise_error + end + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/css_builder_spec.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/css_builder_spec.rb new file mode 100644 index 00000000..e00f8556 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/css_builder_spec.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +require 'spec_helper' + +# rubocop:disable RSpec/InstanceVariable +RSpec.describe Capybara::Selector::CSSBuilder do + let :builder do + ::Capybara::Selector::CSSBuilder.new(@css) + end + + context 'add_attribute_conditions' do + it 'adds a single string condition to a single selector' do + @css = 'div' + selector = builder.add_attribute_conditions(random: 'abc') + expect(selector).to eq %(div[random='abc']) + end + + it 'adds multiple string conditions to a single selector' do + @css = 'div' + selector = builder.add_attribute_conditions(random: 'abc', other: 'def') + expect(selector).to eq %(div[random='abc'][other='def']) + end + + it 'adds a single string condition to a multiple selector' do + @css = 'div, ul' + selector = builder.add_attribute_conditions(random: 'abc') + expect(selector).to eq %(div[random='abc'], ul[random='abc']) + end + + it 'adds multiple string conditions to a multiple selector' do + @css = 'div, ul' + selector = builder.add_attribute_conditions(random: 'abc', other: 'def') + expect(selector).to eq %(div[random='abc'][other='def'], ul[random='abc'][other='def']) + end + + it 'adds simple regexp conditions to a single selector' do + @css = 'div' + selector = builder.add_attribute_conditions(random: /abc/, other: /def/) + expect(selector).to eq %(div[random*='abc'][other*='def']) + end + + it 'adds wildcard regexp conditions to a single selector' do + @css = 'div' + selector = builder.add_attribute_conditions(random: /abc.*def/, other: /def.*ghi/) + expect(selector).to eq %(div[random*='abc'][random*='def'][other*='def'][other*='ghi']) + end + + it 'adds alternated regexp conditions to a single selector' do + @css = 'div' + selector = builder.add_attribute_conditions(random: /abc|def/, other: /def|ghi/) + expect(selector).to eq %(div[random*='abc'][other*='def'], div[random*='abc'][other*='ghi'], div[random*='def'][other*='def'], div[random*='def'][other*='ghi']) + end + + it 'adds alternated regexp conditions to a multiple selector' do + @css = 'div,ul' + selector = builder.add_attribute_conditions(other: /def.*ghi|jkl/) + expect(selector).to eq %(div[other*='def'][other*='ghi'], div[other*='jkl'], ul[other*='def'][other*='ghi'], ul[other*='jkl']) + end + + it "returns original selector when regexp can't be substringed" do + @css = 'div' + selector = builder.add_attribute_conditions(other: /.+/) + expect(selector).to eq 'div' + end + + context ':class' do + it 'handles string with CSS .' do + @css = 'a' + selector = builder.add_attribute_conditions(class: 'my_class') + expect(selector).to eq 'a.my_class' + end + + it 'handles negated string with CSS .' do + @css = 'a' + selector = builder.add_attribute_conditions(class: '!my_class') + expect(selector).to eq 'a:not(.my_class)' + end + + it 'handles array of string with CSS .' do + @css = 'a' + selector = builder.add_attribute_conditions(class: %w[my_class my_other_class]) + expect(selector).to eq 'a.my_class.my_other_class' + end + + it 'handles array of string with CSS . when negated included' do + @css = 'a' + selector = builder.add_attribute_conditions(class: %w[my_class !my_other_class]) + expect(selector).to eq 'a.my_class:not(.my_other_class)' + end + end + + context ':id' do + it 'handles string with CSS #' do + @css = 'ul' + selector = builder.add_attribute_conditions(id: 'my_id') + expect(selector).to eq 'ul#my_id' + end + end + end +end +# rubocop:enable RSpec/InstanceVariable diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/css_splitter_spec.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/css_splitter_spec.rb new file mode 100644 index 00000000..9d04e46e --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/css_splitter_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Capybara::Selector::CSS::Splitter do + let :splitter do + ::Capybara::Selector::CSS::Splitter.new + end + + context 'split not needed' do + it 'normal CSS selector' do + css = 'div[id="abc"]' + expect(splitter.split(css)).to eq [css] + end + + it 'comma in strings' do + css = 'div[id="a,bc"]' + expect(splitter.split(css)).to eq [css] + end + + it 'comma in pseudo-selector' do + css = 'div.class1:not(.class1, .class2)' + expect(splitter.split(css)).to eq [css] + end + end + + context 'split needed' do + it 'root level comma' do + css = 'div.class1, span, p.class2' + expect(splitter.split(css)).to eq ['div.class1', 'span', 'p.class2'] + end + + it 'root level comma when quotes and pseudo selectors' do + css = 'div.class1[id="abc\\"def,ghi"]:not(.class3, .class4), span[id=\'a"c\\\'de\'], section, #abc\\,def' + expect(splitter.split(css)).to eq ['div.class1[id="abc\\"def,ghi"]:not(.class3, .class4)', 'span[id=\'a"c\\\'de\']', 'section', '#abc\\,def'] + end + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/dsl_spec.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/dsl_spec.rb new file mode 100644 index 00000000..2f9a7458 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/dsl_spec.rb @@ -0,0 +1,276 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'capybara/dsl' + +class TestClass + include Capybara::DSL +end + +Capybara::SpecHelper.run_specs TestClass.new, 'DSL', capybara_skip: %i[ + js modals screenshot frames windows send_keys server hover about_scheme psc download css driver scroll spatial +] do |example| + case example.metadata[:full_description] + when /has_css\? should support case insensitive :class and :id options/ + pending "Nokogiri doesn't support case insensitive CSS attribute matchers" + when /#click_button should follow permanent redirects that maintain method/ + pending "Rack < 2 doesn't support 308" if Gem.loaded_specs['rack'].version < Gem::Version.new('2.0.0') + end +end + +RSpec.describe Capybara::DSL do + after do + Capybara.session_name = nil + Capybara.default_driver = nil + Capybara.javascript_driver = nil + Capybara.use_default_driver + Capybara.app = TestApp + end + + describe '#default_driver' do + it 'should default to rack_test' do + expect(Capybara.default_driver).to eq(:rack_test) + end + + it 'should be changeable' do + Capybara.default_driver = :culerity + expect(Capybara.default_driver).to eq(:culerity) + end + end + + describe '#current_driver' do + it 'should default to the default driver' do + expect(Capybara.current_driver).to eq(:rack_test) + Capybara.default_driver = :culerity + expect(Capybara.current_driver).to eq(:culerity) + end + + it 'should be changeable' do + Capybara.current_driver = :culerity + expect(Capybara.current_driver).to eq(:culerity) + end + end + + describe '#javascript_driver' do + it 'should default to selenium' do + expect(Capybara.javascript_driver).to eq(:selenium) + end + + it 'should be changeable' do + Capybara.javascript_driver = :culerity + expect(Capybara.javascript_driver).to eq(:culerity) + end + end + + describe '#use_default_driver' do + it 'should restore the default driver' do + Capybara.current_driver = :culerity + Capybara.use_default_driver + expect(Capybara.current_driver).to eq(:rack_test) + end + end + + describe '#using_driver' do + before do + expect(Capybara.current_driver).not_to eq(:selenium) # rubocop:disable RSpec/ExpectInHook + end + + it 'should set the driver using Capybara.current_driver=' do + driver = nil + Capybara.using_driver(:selenium) { driver = Capybara.current_driver } + expect(driver).to eq(:selenium) + end + + it 'should return the driver to default if it has not been changed' do + Capybara.using_driver(:selenium) do + expect(Capybara.current_driver).to eq(:selenium) + end + expect(Capybara.current_driver).to eq(Capybara.default_driver) + end + + it 'should reset the driver even if an exception occurs' do + driver_before_block = Capybara.current_driver + begin + Capybara.using_driver(:selenium) { raise 'ohnoes!' } + rescue Exception # rubocop:disable Lint/RescueException,Lint/HandleExceptions + end + expect(Capybara.current_driver).to eq(driver_before_block) + end + + it 'should return the driver to what it was previously' do + Capybara.current_driver = :selenium + Capybara.using_driver(:culerity) do + Capybara.using_driver(:rack_test) do + expect(Capybara.current_driver).to eq(:rack_test) + end + expect(Capybara.current_driver).to eq(:culerity) + end + expect(Capybara.current_driver).to eq(:selenium) + end + + it 'should yield the passed block' do + called = false + Capybara.using_driver(:selenium) { called = true } + expect(called).to eq(true) + end + end + + # rubocop:disable RSpec/InstanceVariable + describe '#using_wait_time' do + before { @previous_wait_time = Capybara.default_max_wait_time } + + after { Capybara.default_max_wait_time = @previous_wait_time } + + it 'should switch the wait time and switch it back' do + in_block = nil + Capybara.using_wait_time 6 do + in_block = Capybara.default_max_wait_time + end + expect(in_block).to eq(6) + expect(Capybara.default_max_wait_time).to eq(@previous_wait_time) + end + + it 'should ensure wait time is reset' do + expect do + Capybara.using_wait_time 6 do + raise 'hell' + end + end.to raise_error(RuntimeError, 'hell') + expect(Capybara.default_max_wait_time).to eq(@previous_wait_time) + end + end + # rubocop:enable RSpec/InstanceVariable + + describe '#app' do + it 'should be changeable' do + Capybara.app = 'foobar' + expect(Capybara.app).to eq('foobar') + end + end + + describe '#current_session' do + it 'should choose a session object of the current driver type' do + expect(Capybara.current_session).to be_a(Capybara::Session) + end + + it 'should use #app as the application' do + Capybara.app = proc {} + expect(Capybara.current_session.app).to eq(Capybara.app) + end + + it 'should change with the current driver' do + expect(Capybara.current_session.mode).to eq(:rack_test) + Capybara.current_driver = :selenium + expect(Capybara.current_session.mode).to eq(:selenium) + end + + it 'should be persistent even across driver changes' do + object_id = Capybara.current_session.object_id + expect(Capybara.current_session.object_id).to eq(object_id) + Capybara.current_driver = :selenium + expect(Capybara.current_session.mode).to eq(:selenium) + expect(Capybara.current_session.object_id).not_to eq(object_id) + + Capybara.current_driver = :rack_test + expect(Capybara.current_session.object_id).to eq(object_id) + end + + it 'should change when changing application' do + object_id = Capybara.current_session.object_id + expect(Capybara.current_session.object_id).to eq(object_id) + Capybara.app = proc {} + expect(Capybara.current_session.object_id).not_to eq(object_id) + expect(Capybara.current_session.app).to eq(Capybara.app) + end + + it 'should change when the session name changes' do + object_id = Capybara.current_session.object_id + Capybara.session_name = :administrator + expect(Capybara.session_name).to eq(:administrator) + expect(Capybara.current_session.object_id).not_to eq(object_id) + Capybara.session_name = :default + expect(Capybara.session_name).to eq(:default) + expect(Capybara.current_session.object_id).to eq(object_id) + end + end + + describe '#using_session' do + it 'should change the session name for the duration of the block' do + expect(Capybara.session_name).to eq(:default) + Capybara.using_session(:administrator) do + expect(Capybara.session_name).to eq(:administrator) + end + expect(Capybara.session_name).to eq(:default) + end + + it 'should reset the session to the default, even if an exception occurs' do + begin + Capybara.using_session(:raise) do + raise + end + rescue Exception # rubocop:disable Lint/RescueException,Lint/HandleExceptions + end + expect(Capybara.session_name).to eq(:default) + end + + it 'should yield the passed block' do + called = false + Capybara.using_session(:administrator) { called = true } + expect(called).to eq(true) + end + + it 'should be nestable' do + Capybara.using_session(:outer) do + expect(Capybara.session_name).to eq(:outer) + Capybara.using_session(:inner) do + expect(Capybara.session_name).to eq(:inner) + end + expect(Capybara.session_name).to eq(:outer) + end + expect(Capybara.session_name).to eq(:default) + end + + it 'should allow a session object' do + original_session = Capybara.current_session + new_session = Capybara::Session.new(:rack_test, proc {}) + Capybara.using_session(new_session) do + expect(Capybara.current_session).to eq(new_session) + end + expect(Capybara.current_session).to eq(original_session) + end + end + + describe '#session_name' do + it 'should default to :default' do + expect(Capybara.session_name).to eq(:default) + end + end + + describe 'the DSL' do + let(:session) { Class.new { include Capybara::DSL }.new } + + it 'should be possible to include it in another class' do + session.visit('/with_html') + session.click_link('ullamco') + expect(session.body).to include('Another World') + end + + it "should provide a 'page' shortcut for more expressive tests" do + session.page.visit('/with_html') + session.page.click_link('ullamco') + expect(session.page.body).to include('Another World') + end + + it "should provide an 'using_session' shortcut" do + allow(Capybara).to receive(:using_session) + session.using_session(:name) + expect(Capybara).to have_received(:using_session).with(:name) + end + + it "should provide a 'using_wait_time' shortcut" do + allow(Capybara).to receive(:using_wait_time) + session.using_wait_time(6) + expect(Capybara).to have_received(:using_wait_time).with(6) + end + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/filter_set_spec.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/filter_set_spec.rb new file mode 100644 index 00000000..7bab137d --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/filter_set_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Capybara::Selector::FilterSet do + after do + described_class.remove(:test) + end + + it 'allows node filters' do + fs = described_class.add(:test) do + node_filter(:node_test, :boolean) { |_node, _value| true } + expression_filter(:expression_test, :boolean) { |_expr, _value| true } + end + + expect(fs.node_filters.keys).to include(:node_test) + expect(fs.node_filters.keys).not_to include(:expression_test) + end + + it 'allows expression filters' do + fs = described_class.add(:test) do + node_filter(:node_test, :boolean) { |_node, _value| true } + expression_filter(:expression_test, :boolean) { |_expr, _value| true } + end + + expect(fs.expression_filters.keys).to include(:expression_test) + expect(fs.expression_filters.keys).not_to include(:node_test) + end + + it 'allows node filter and expression filter with the same name' do + fs = described_class.add(:test) do + node_filter(:test, :boolean) { |_node, _value| true } + expression_filter(:test, :boolean) { |_expr, _value| true } + end + + expect(fs.expression_filters[:test]).not_to eq fs.node_filters[:test] + end + + it 'allows `filter` as an alias of `node_filter`' do + fs = described_class.add(:test) do + filter(:node_test, :boolean) { |_node, _value| true } + end + + expect(fs.node_filters.keys).to include(:node_test) + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/fixtures/capybara.csv b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/fixtures/capybara.csv new file mode 100644 index 00000000..d1331b32 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/fixtures/capybara.csv @@ -0,0 +1 @@ +test, mime-type, file diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/fixtures/certificate.pem b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/fixtures/certificate.pem new file mode 100644 index 00000000..79f5e372 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/fixtures/certificate.pem @@ -0,0 +1,25 @@ +-----BEGIN CERTIFICATE----- +MIIEITCCAwmgAwIBAgIJALROkwd1gZHQMA0GCSqGSIb3DQEBBQUAMGgxCzAJBgNV +BAYTAlVTMQswCQYDVQQIEwJDQTERMA8GA1UEChMIQ2FweWJhcmExFjAUBgNVBAMT +DWNhcHliYXJhLnRlc3QxITAfBgkqhkiG9w0BCQEWEnR3YWxwb2xlQGdtYWlsLmNv +bTAeFw0xODA1MDEyMDI5NDVaFw0yODA0MjgyMDI5NDVaMGgxCzAJBgNVBAYTAlVT +MQswCQYDVQQIEwJDQTERMA8GA1UEChMIQ2FweWJhcmExFjAUBgNVBAMTDWNhcHli +YXJhLnRlc3QxITAfBgkqhkiG9w0BCQEWEnR3YWxwb2xlQGdtYWlsLmNvbTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL7icqMv9uApxRXlcIQ3hSEfmULk +7CLT1aUAjEmTiqy8TkFqOeSuA3elnbVBhOW+emrb1uUyje20LvEOHbqYYw90ezlV +jqNWUapawqjxb+q7KVNpA5uDCtOHIC/1Z1kxQ+yZP/8St4SvnLGUMsUhu0h+ywJa +iGZ2xy8wdfjABUiUImRESdNkT7Xs0wxQGY0/FZ4K5Sec4kroOuvdxKKhyf5Js5xS +NRv2tXSMWlKHbYEvYzsVtFgv/4GjqN4wVvbfJmsS8thcyrSYSMN7eE0R6dcbJaLM +P/GTiw669zPJP164K84QoabLClgwyG+7mqjm7jutq9qXipwyrGsf/WR5fkUCAwEA +AaOBzTCByjAdBgNVHQ4EFgQU4ut9c0oB2OGMlN/nvn0Y7twBzJowgZoGA1UdIwSB +kjCBj4AU4ut9c0oB2OGMlN/nvn0Y7twBzJqhbKRqMGgxCzAJBgNVBAYTAlVTMQsw +CQYDVQQIEwJDQTERMA8GA1UEChMIQ2FweWJhcmExFjAUBgNVBAMTDWNhcHliYXJh +LnRlc3QxITAfBgkqhkiG9w0BCQEWEnR3YWxwb2xlQGdtYWlsLmNvbYIJALROkwd1 +gZHQMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAANtqdDrdehBt20s +IVOsVzOp+tJI8pn6G62WJcZWGsvLLfx6Y6eJSP24r5NW4v39cqCgE76J2znzKxeo +VIShAJ1SLodR0GwzFGsl3TVKVoce6gBWZNXgkZyJTNqKGWeme8CH1EG3TqYlcybt +EXWLQ6wKndD+uHJIfqUy7HfaUv+TghJGLkv8QYqcAWILQko4xqZ1NULDQ2uhjxei +oaQlK9teHlVaH87dONesAg3f5rh9j6nvzGiC/T8ycdEhn/DxJMxRS2+mVZqKkk7U +60+e9Foc6qTW8UaMShIFYMspoRnr9baBxzxh9V9LZznwqVTf3vuEIck2l2j2KL/R +SOBenCM= +-----END CERTIFICATE----- diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/fixtures/key.pem b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/fixtures/key.pem new file mode 100644 index 00000000..534fcb73 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/fixtures/key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAvuJyoy/24CnFFeVwhDeFIR+ZQuTsItPVpQCMSZOKrLxOQWo5 +5K4Dd6WdtUGE5b56atvW5TKN7bQu8Q4duphjD3R7OVWOo1ZRqlrCqPFv6rspU2kD +m4MK04cgL/VnWTFD7Jk//xK3hK+csZQyxSG7SH7LAlqIZnbHLzB1+MAFSJQiZERJ +02RPtezTDFAZjT8VngrlJ5ziSug6693EoqHJ/kmznFI1G/a1dIxaUodtgS9jOxW0 +WC//gaOo3jBW9t8maxLy2FzKtJhIw3t4TRHp1xslosw/8ZOLDrr3M8k/XrgrzhCh +pssKWDDIb7uaqObuO62r2peKnDKsax/9ZHl+RQIDAQABAoIBABhZepYmgC+IJIPu +iLPVAT6AcWR/H0AyFYa+0yZvk7kFLFZb3pa1O+v/TGbavMEx0xvef0Mtd71ixrop +OtGarshB65Ycu91KHZDFkx9J7STcSyFAvB0SUkc5bXmwrEZMaoW75tX65T4fyLU+ +WlubOfC9e9gJBG1NqYrze5kHpaTkR8pBaSxECns5y2HLaMvs1t1kK7snqtGHFRAb +deAUl0ONENaO6PF797yM3bsIEgnljdKvaVP3kYq/eAzH4jyw+qG/xq0bZ+nalJzM +IL7xRSR/Yak7WZ3QUh99t3dng/z3De4yvhBLq/mc2mIVvt7aERPo/aDG4SrmQ9Jw +f1GUT+ECgYEA+i96w2zPHkq0djuuhNaa/R0vybD9hVCdB7enQ8skBzQAQyhp9X9W +by38VAbFRVIMrzdfbvwSFrE3Nd9BvyLWl16G+MVu+hq0jhM9p44a62vXvfgQmfFh +iXyGkzCTufeLEzOFnP8S+ezacodK1hbQTjllUndn6NUhXcbfozTPfx8CgYEAw1Il +xqfU7NkVX6UP/FvTx9aBKqwrD8YOPn9nj26Q45zVbmHJZZIvywlsMDCiJ6JJawEa +GSkiZTDpypEvS/2UtLEY0zfbSbQGkBTnagHIFAuVh0AD1EP+kCcnrxA5Vjr8v0WE +B0HBfMgoZxa8rmHMpPhrlCCo1ectRgu+dgzEKhsCgYAQ17lwBpc69tSHUSVClCAD +AkABWAT5QKARsO91xOs8AOgznTjk6hmrinD+RyZosEliUlv+YMHm/S82VT1b3MCN +mDOF8+SwubOGDQ2NhieRycTQaS7U7kcetl9o8VBAqMWYGVPZaeKhKKzcIPeMyiRj +38FOd/Nq3U5NveG4XwnJCQKBgGrzmnfTAsbGf+uliMFYzviIPqZNLC8w9i/Gt8BU +fMYF5ODSbuNNTxpQiItCtigZtzX+nnnUil76j6o6IbnsmvbuWneeCFetWkKfD7B+ +VT6UsUYkCXS73rK0nghATAUpu6hIumj22qonN+hrDNo390UGOnIcCBdIxQOr/pjJ +mMitAoGAB3tFzlzZHpCE38IqdHL7CKIML9cYJSOm07nFIEUAKN2bUHGsvMH/tMS1 +I3hyeHxMmYCrOLn7xLqiOVgRjbYS9JiQVdDojqeSvvnLCtY9/lHWi50pqbPfwyfK +Og6QoFyxjBEsoSilc6dai58VTO6ufhoJXbXX9PcIl7le4dVPnzE= +-----END RSA PRIVATE KEY----- diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/fixtures/selenium_driver_rspec_failure.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/fixtures/selenium_driver_rspec_failure.rb new file mode 100644 index 00000000..df36b3a8 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/fixtures/selenium_driver_rspec_failure.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'selenium-webdriver' + +RSpec.describe Capybara::Selenium::Driver do + it 'should exit with a non-zero exit status' do + options = { browser: (ENV['SELENIUM_BROWSER'] || :firefox).to_sym } + browser = described_class.new(TestApp, options).browser + expect(browser).to be_truthy + expect(true).to eq(false) # rubocop:disable RSpec/ExpectActual + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/fixtures/selenium_driver_rspec_success.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/fixtures/selenium_driver_rspec_success.rb new file mode 100644 index 00000000..275d93ee --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/fixtures/selenium_driver_rspec_success.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'selenium-webdriver' + +RSpec.describe Capybara::Selenium::Driver do + it 'should exit with a zero exit status' do + options = { browser: (ENV['SELENIUM_BROWSER'] || :firefox).to_sym } + browser = described_class.new(TestApp, options).browser + expect(browser).to be_truthy + expect(true).to eq(true) # rubocop:disable RSpec/ExpectActual + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/minitest_spec.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/minitest_spec.rb new file mode 100644 index 00000000..b1c2b1e7 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/minitest_spec.rb @@ -0,0 +1,163 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'capybara/minitest' + +class MinitestTest < Minitest::Test + include Capybara::DSL + include Capybara::Minitest::Assertions + + def setup + visit('/form') + end + + def teardown + Capybara.reset_sessions! + end + + def test_assert_text + assert_text('Form') + assert_no_text('Not on the page') + refute_text('Also Not on the page') + end + + def test_assert_title + visit('/with_title') + assert_title('Test Title') + assert_no_title('Not the title') + refute_title('Not the title') + end + + def test_assert_current_path + assert_current_path('/form') + assert_no_current_path('/not_form') + refute_current_path('/not_form') + end + + def test_assert_xpath + assert_xpath('.//select[@id="form_title"]') + assert_xpath('.//select', count: 1) { |el| el[:id] == 'form_title' } + assert_no_xpath('.//select[@id="not_form_title"]') + assert_no_xpath('.//select') { |el| el[:id] == 'not_form_title' } + refute_xpath('.//select[@id="not_form_title"]') + end + + def test_assert_css + assert_css('select#form_title') + assert_no_css('select#not_form_title') + end + + def test_assert_selector + assert_selector(:css, 'select#form_title') + assert_selector(:xpath, './/select[@id="form_title"]') + assert_no_selector(:css, 'select#not_form_title') + assert_no_selector(:xpath, './/select[@id="not_form_title"]') + refute_selector(:css, 'select#not_form_title') + end + + def test_assert_link + visit('/with_html') + assert_link('A link') + assert_link(count: 1) { |el| el.text == 'A link' } + assert_no_link('Not on page') + end + + def test_assert_button + assert_button('fresh_btn') + assert_button(count: 1) { |el| el[:id] == 'fresh_btn' } + assert_no_button('not_btn') + end + + def test_assert_field + assert_field('customer_email') + assert_no_field('not_on_the_form') + end + + def test_assert_select + assert_select('form_title') + assert_no_select('not_form_title') + end + + def test_assert_checked_field + assert_checked_field('form_pets_dog') + assert_no_checked_field('form_pets_cat') + refute_checked_field('form_pets_snake') + end + + def test_assert_unchecked_field + assert_unchecked_field('form_pets_cat') + assert_no_unchecked_field('form_pets_dog') + refute_unchecked_field('form_pets_snake') + end + + def test_assert_table + visit('/tables') + assert_table('agent_table') + assert_no_table('not_on_form') + refute_table('not_on_form') + end + + def test_assert_all_of_selectors + assert_all_of_selectors(:css, 'select#form_other_title', 'input#form_last_name') + end + + def test_assert_none_of_selectors + assert_none_of_selectors(:css, 'input#not_on_page', 'input#also_not_on_page') + end + + def test_assert_any_of_selectors + assert_any_of_selectors(:css, 'input#not_on_page', 'select#form_other_title') + end + + def test_assert_matches_selector + assert_matches_selector(find(:field, 'customer_email'), :field, 'customer_email') + assert_not_matches_selector(find(:select, 'form_title'), :field, 'customer_email') + refute_matches_selector(find(:select, 'form_title'), :field, 'customer_email') + end + + def test_assert_matches_css + assert_matches_css(find(:select, 'form_title'), 'select#form_title') + refute_matches_css(find(:select, 'form_title'), 'select#form_other_title') + end + + def test_assert_matches_xpath + assert_matches_xpath(find(:select, 'form_title'), './/select[@id="form_title"]') + refute_matches_xpath(find(:select, 'form_title'), './/select[@id="form_other_title"]') + end + + def test_assert_matches_style + skip "Rack test doesn't support style" if Capybara.current_driver == :rack_test + visit('/with_html') + assert_matches_style(find(:css, '#second'), display: 'inline') + end + + def test_assert_ancestor + option = find(:option, 'Finnish') + assert_ancestor(option, :css, '#form_locale') + end + + def test_assert_sibling + option = find(:css, '#form_title').find(:option, 'Mrs') + assert_sibling(option, :option, 'Mr') + end +end + +RSpec.describe 'capybara/minitest' do + before do + Capybara.current_driver = :rack_test + Capybara.app = TestApp + end + + after do + Capybara.use_default_driver + end + + it 'should support minitest' do + output = StringIO.new + reporter = Minitest::SummaryReporter.new(output) + reporter.start + MinitestTest.run reporter, {} + reporter.report + expect(output.string).to include('22 runs, 52 assertions, 0 failures, 0 errors, 1 skips') + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/minitest_spec_spec.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/minitest_spec_spec.rb new file mode 100644 index 00000000..e0850fcd --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/minitest_spec_spec.rb @@ -0,0 +1,162 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'capybara/minitest' +require 'capybara/minitest/spec' + +class MinitestSpecTest < Minitest::Spec + include ::Capybara::DSL + include ::Capybara::Minitest::Assertions + + before do + visit('/form') + end + + after do + Capybara.reset_sessions! + end + + it 'supports text expectations' do + page.must_have_text('Form', minimum: 1) + page.wont_have_text('Not a form') + form = find(:css, 'form', text: 'Title') + form.must_have_text('Customer Email') + form.wont_have_text('Some other email') + end + + it 'supports current_path expectations' do + page.must_have_current_path('/form') + page.wont_have_current_path('/form2') + end + + it 'supports title expectations' do + visit('/with_title') + page.must_have_title('Test Title') + page.wont_have_title('Not the title') + end + + it 'supports xpath expectations' do + page.must_have_xpath('.//input[@id="customer_email"]') + page.wont_have_xpath('.//select[@id="not_form_title"]') + page.wont_have_xpath('.//input[@id="customer_email"]') { |el| el[:id] == 'not_customer_email' } + select = find(:select, 'form_title') + select.must_have_xpath('.//option[@class="title"]') + select.must_have_xpath('.//option', count: 1) { |option| option[:class] != 'title' && !option.disabled? } + select.wont_have_xpath('.//input[@id="customer_email"]') + end + + it 'support css expectations' do + page.must_have_css('input#customer_email') + page.wont_have_css('select#not_form_title') + el = find(:select, 'form_title') + el.must_have_css('option.title') + el.wont_have_css('input#customer_email') + end + + it 'supports link expectations' do + visit('/with_html') + page.must_have_link('A link') + page.wont_have_link('Not on page') + end + + it 'supports button expectations' do + page.must_have_button('fresh_btn') + page.wont_have_button('not_btn') + end + + it 'supports field expectations' do + page.must_have_field('customer_email') + page.wont_have_field('not_on_the_form') + end + + it 'supports select expectations' do + page.must_have_select('form_title') + page.wont_have_select('not_form_title') + end + + it 'supports checked_field expectations' do + page.must_have_checked_field('form_pets_dog') + page.wont_have_checked_field('form_pets_cat') + end + + it 'supports unchecked_field expectations' do + page.must_have_unchecked_field('form_pets_cat') + page.wont_have_unchecked_field('form_pets_dog') + end + + it 'supports table expectations' do + visit('/tables') + page.must_have_table('agent_table') + page.wont_have_table('not_on_form') + end + + it 'supports all_of_selectors expectations' do + page.must_have_all_of_selectors(:css, 'select#form_other_title', 'input#form_last_name') + end + + it 'supports none_of_selectors expectations' do + page.must_have_none_of_selectors(:css, 'input#not_on_page', 'input#also_not_on_page') + end + + it 'supports any_of_selectors expectations' do + page.must_have_any_of_selectors(:css, 'select#form_other_title', 'input#not_on_page') + end + + it 'supports match_selector expectations' do + find(:field, 'customer_email').must_match_selector(:field, 'customer_email') + find(:select, 'form_title').wont_match_selector(:field, 'customer_email') + end + + it 'supports match_css expectations' do + find(:select, 'form_title').must_match_css('select#form_title') + find(:select, 'form_title').wont_match_css('select#form_other_title') + end + + it 'supports match_xpath expectations' do + find(:select, 'form_title').must_match_xpath('.//select[@id="form_title"]') + find(:select, 'form_title').wont_match_xpath('.//select[@id="not_on_page"]') + end + + it 'handles failures' do + page.must_have_select('non_existing_form_title') + end + + it 'supports style expectations' do + skip "Rack test doesn't support style" if Capybara.current_driver == :rack_test + visit('/with_html') + find(:css, '#second').must_have_style('display' => 'inline') # deprecated + find(:css, '#second').must_match_style('display' => 'inline') + end + + it 'supports ancestor expectations' do + option = find(:option, 'Finnish') + option.must_have_ancestor(:css, '#form_locale') + end + + it 'supports sibling expectations' do + option = find(:css, '#form_title').find(:option, 'Mrs') + option.must_have_sibling(:option, 'Mr') + end +end + +RSpec.describe 'capybara/minitest/spec' do + before do + Capybara.current_driver = :rack_test + Capybara.app = TestApp + end + + after do + Capybara.use_default_driver + end + + it 'should support minitest spec' do + output = StringIO.new + reporter = Minitest::SummaryReporter.new(output) + reporter.start + MinitestSpecTest.run reporter, {} + reporter.report + expect(output.string).to include('22 runs, 44 assertions, 1 failures, 0 errors, 1 skips') + # Make sure error messages are displayed + expect(output.string).to match(/expected to find select box "non_existing_form_title" .*but there were no matches/) + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/per_session_config_spec.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/per_session_config_spec.rb new file mode 100644 index 00000000..849da968 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/per_session_config_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'capybara/dsl' + +RSpec.describe Capybara::SessionConfig do + describe 'threadsafe' do + it 'defaults to global session options' do + Capybara.threadsafe = true + session = Capybara::Session.new(:rack_test, TestApp) + %i[default_host app_host always_include_port run_server + default_selector default_max_wait_time ignore_hidden_elements + automatic_reload match exact raise_server_errors visible_text_only + automatic_label_click enable_aria_label save_path + asset_host].each do |m| + expect(session.config.public_send(m)).to eq Capybara.public_send(m) + end + end + + it "doesn't change global session when changed" do + Capybara.threadsafe = true + host = 'http://my.example.com' + session = Capybara::Session.new(:rack_test, TestApp) do |config| + config.default_host = host + config.automatic_label_click = !config.automatic_label_click + config.server_errors << ArgumentError + end + expect(Capybara.default_host).not_to eq host + expect(session.config.default_host).to eq host + expect(Capybara.automatic_label_click).not_to eq session.config.automatic_label_click + expect(Capybara.server_errors).not_to eq session.config.server_errors + end + + it "doesn't allow session configuration block when false" do + Capybara.threadsafe = false + expect do + Capybara::Session.new(:rack_test, TestApp) { |config| } + end.to raise_error 'A configuration block is only accepted when Capybara.threadsafe == true' + end + + it "doesn't allow session config when false" do + Capybara.threadsafe = false + session = Capybara::Session.new(:rack_test, TestApp) + expect { session.config.default_selector = :title }.to raise_error(/Per session settings are only supported when Capybara.threadsafe == true/) + expect do + session.configure do |config| + config.exact = true + end + end.to raise_error(/Session configuration is only supported when Capybara.threadsafe == true/) + end + + it 'uses the config from the session' do + Capybara.threadsafe = true + session = Capybara::Session.new(:rack_test, TestApp) do |config| + config.default_selector = :link + end + session.visit('/with_html') + expect(session.find('foo').tag_name).to eq 'a' + end + + it "won't change threadsafe once a session is created" do + Capybara.threadsafe = true + Capybara.threadsafe = false + Capybara::Session.new(:rack_test, TestApp) + expect { Capybara.threadsafe = true }.to raise_error(/Threadsafe setting cannot be changed once a session is created/) + end + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/rack_test_spec.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/rack_test_spec.rb new file mode 100644 index 00000000..2725f491 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/rack_test_spec.rb @@ -0,0 +1,257 @@ +# frozen_string_literal: true + +require 'spec_helper' +nokogumbo_required = begin + require 'nokogumbo' + true + rescue LoadError + false + end + +module TestSessions + RackTest = Capybara::Session.new(:rack_test, TestApp) +end + +skipped_tests = %i[ + js + modals + screenshot + frames + windows + send_keys + server + hover + about_scheme + download + css + scroll + spatial +] +Capybara::SpecHelper.run_specs TestSessions::RackTest, 'RackTest', capybara_skip: skipped_tests do |example| + case example.metadata[:full_description] + when /has_css\? should support case insensitive :class and :id options/ + skip "Nokogiri doesn't support case insensitive CSS attribute matchers" + when /#click_button should follow permanent redirects that maintain method/ + skip "Rack < 2 doesn't support 308" if Gem.loaded_specs['rack'].version < Gem::Version.new('2.0.0') + end +end + +RSpec.describe Capybara::Session do # rubocop:disable RSpec/MultipleDescribes + context 'with rack test driver' do + let(:session) { TestSessions::RackTest } + + describe '#driver' do + it 'should be a rack test driver' do + expect(session.driver).to be_an_instance_of(Capybara::RackTest::Driver) + end + end + + describe '#mode' do + it 'should remember the mode' do + expect(session.mode).to eq(:rack_test) + end + end + + describe '#click_link' do + after do + session.driver.options[:respect_data_method] = false + end + + it 'should use data-method if option is true' do + session.driver.options[:respect_data_method] = true + session.visit '/with_html' + session.click_link 'A link with data-method' + expect(session.html).to include('The requested object was deleted') + end + + it 'should not use data-method if option is false' do + session.driver.options[:respect_data_method] = false + session.visit '/with_html' + session.click_link 'A link with data-method' + expect(session.html).to include('Not deleted') + end + + it "should use data-method if available even if it's capitalized" do + session.driver.options[:respect_data_method] = true + session.visit '/with_html' + session.click_link 'A link with capitalized data-method' + expect(session.html).to include('The requested object was deleted') + end + end + + describe '#fill_in' do + it 'should warn that :fill_options are not supported' do + session.visit '/with_html' + + expect { session.fill_in 'test_field', with: 'not_monkey', fill_options: { random: true } }.to \ + output(/^Options passed to Node#set but the RackTest driver doesn't support any - ignoring/).to_stderr + expect(session).to have_field('test_field', with: 'not_monkey') + end + end + + describe '#attach_file' do + context 'with multipart form' do + it 'should submit an empty form-data section if no file is submitted' do + session.visit('/form') + session.click_button('Upload Empty') + expect(session.html).to include('Successfully ignored empty file field.') + end + end + + it 'should not submit an obsolete mime type' do + test_jpg_file_path = File.expand_path('fixtures/capybara.csv', File.dirname(__FILE__)) + session.visit('/form') + session.attach_file 'form_document', test_jpg_file_path + session.click_button('Upload Single') + expect(session).to have_content('Content-type: text/csv') + end + end + + describe '#click' do + context 'on a label' do + it 'should toggle the associated checkbox' do + session.visit('/form') + expect(session).to have_unchecked_field('form_pets_cat') + session.find(:label, 'Cat').click + expect(session).to have_checked_field('form_pets_cat') + session.find(:label, 'Cat').click + expect(session).to have_unchecked_field('form_pets_cat') + session.find(:label, 'McLaren').click + expect(session).to have_checked_field('form_cars_mclaren', visible: :hidden) + end + + it 'should toggle the associated radio' do + session.visit('/form') + expect(session).to have_unchecked_field('gender_male') + session.find(:label, 'Male').click + expect(session).to have_checked_field('gender_male') + session.find(:label, 'Female').click + expect(session).to have_unchecked_field('gender_male') + end + end + end + + describe '#text' do + it 'should return original text content for textareas' do + session.visit('/with_html') + session.find_field('normal', type: 'textarea', with: 'banana').set('hello') + normal = session.find(:css, '#normal') + expect(normal.value).to eq 'hello' + expect(normal.text).to eq 'banana' + end + end + + describe '#style' do + it 'should raise an error' do + session.visit('/with_html') + el = session.find(:css, '#first') + expect { el.style('display') }.to raise_error NotImplementedError, /not process CSS/ + end + end + end +end + +RSpec.describe Capybara::RackTest::Driver do + let(:driver) { TestSessions::RackTest.driver } + + describe ':headers option' do + it 'should always set headers' do + driver = described_class.new(TestApp, headers: { 'HTTP_FOO' => 'foobar' }) + driver.visit('/get_header') + expect(driver.html).to include('foobar') + end + + it 'should keep headers on link clicks' do + driver = described_class.new(TestApp, headers: { 'HTTP_FOO' => 'foobar' }) + driver.visit('/header_links') + driver.find_xpath('.//a').first.click + expect(driver.html).to include('foobar') + end + + it 'should keep headers on form submit' do + driver = described_class.new(TestApp, headers: { 'HTTP_FOO' => 'foobar' }) + driver.visit('/header_links') + driver.find_xpath('.//input').first.click + expect(driver.html).to include('foobar') + end + + it 'should keep headers on redirects' do + driver = described_class.new(TestApp, headers: { 'HTTP_FOO' => 'foobar' }) + driver.visit('/get_header_via_redirect') + expect(driver.html).to include('foobar') + end + end + + describe ':follow_redirects option' do + it 'defaults to following redirects' do + driver = described_class.new(TestApp) + + driver.visit('/redirect') + expect(driver.response.header['Location']).to be_nil + expect(driver.current_url).to match %r{/landed$} + end + + it 'is possible to not follow redirects' do + driver = described_class.new(TestApp, follow_redirects: false) + + driver.visit('/redirect') + expect(driver.response.header['Location']).to match %r{/redirect_again$} + expect(driver.current_url).to match %r{/redirect$} + end + end + + describe ':redirect_limit option' do + context 'with default redirect limit' do + let(:driver) { described_class.new(TestApp) } + + it 'should follow 5 redirects' do + driver.visit('/redirect/5/times') + expect(driver.html).to include('redirection complete') + end + + it 'should not follow more than 6 redirects' do + expect do + driver.visit('/redirect/6/times') + end.to raise_error(Capybara::InfiniteRedirectError) + end + end + + context 'with 21 redirect limit' do + let(:driver) { described_class.new(TestApp, redirect_limit: 21) } + + it 'should follow 21 redirects' do + driver.visit('/redirect/21/times') + expect(driver.html).to include('redirection complete') + end + + it 'should not follow more than 21 redirects' do + expect do + driver.visit('/redirect/22/times') + end.to raise_error(Capybara::InfiniteRedirectError) + end + end + end +end + +RSpec.describe 'Capybara::String' do + it 'should use gumbo' do + skip 'Only valid if gumbo is included' unless nokogumbo_required + allow(Nokogiri).to receive(:HTML5).and_call_original + Capybara.string('
    ') + expect(Nokogiri).to have_received(:HTML5) + end +end + +module CSSHandlerIncludeTester + def dont_extend_css_handler + raise 'should never be called' + end +end + +RSpec.describe Capybara::RackTest::CSSHandlers do + include CSSHandlerIncludeTester + + it 'should not be extended by global includes' do + expect(described_class.new).not_to respond_to(:dont_extend_css_handler) + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/regexp_dissassembler_spec.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/regexp_dissassembler_spec.rb new file mode 100644 index 00000000..ef9d8170 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/regexp_dissassembler_spec.rb @@ -0,0 +1,246 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Capybara::Selector::RegexpDisassembler, :aggregate_failures do + it 'handles strings' do + verify_strings( + /abcdef/ => %w[abcdef], + /abc def/ => ['abc def'] + ) + end + + it 'handles escaped characters' do + verify_strings( + /abc\\def/ => %w[abc\def], + /abc\.def/ => %w[abc.def], + /\nabc/ => ["\nabc"], + %r{abc/} => %w[abc/], + /ab\++cd/ => %w[ab+ cd] + ) + end + + it 'handles wildcards' do + verify_strings( + /abc.*def/ => %w[abc def], + /.*def/ => %w[def], + /abc./ => %w[abc], + /abc.*/ => %w[abc], + /abc.def/ => %w[abc def], + /abc.def.ghi/ => %w[abc def ghi], + /abc.abcd.abcde/ => %w[abcde], + /.*/ => [] + ) + end + + it 'ignores optional characters for substrings' do + { + /abc*def/ => %w[ab def], + /abc*/ => %w[ab], + /c*/ => [], + /abc?def/ => %w[ab def], + /abc?/ => %w[ab], + /abc?def?/ => %w[ab de], + /abc?def?g/ => %w[ab de g], + /d?/ => [] + }.each do |regexp, expected| + expect(described_class.new(regexp).substrings).to eq expected + end + end + + it 'handles optional characters for #alternated_substrings' do + verify_alternated_strings( + /abc*def/ => [%w[ab def]], + /abc*/ => [%w[ab]], + /c*/ => [], + /abc?def/ => [%w[abdef], %w[abcdef]], + /abc?/ => [%w[ab]], + /abc?def?/ => [%w[abde], %w[abcde]], + /abc?def?g/ => [%w[abdeg], %w[abdefg], %w[abcdeg], %w[abcdefg]], + /d?/ => [] + ) + end + + it 'handles character classes' do + verify_strings( + /abc[a-z]/ => %w[abc], + /abc[a-z]def[0-9]g/ => %w[abc def g], + /[0-9]abc/ => %w[abc], + /[0-9]+/ => [], + /abc[0-9&&[^7]]/ => %w[abc] + ) + end + + it 'handles posix bracket expressions' do + verify_strings( + /abc[[:alpha:]]/ => %w[abc], + /[[:digit:]]abc/ => %w[abc], + /abc[[:print:]]def/ => %w[abc def] + ) + end + + it 'handles repitition' do + verify_strings( + /abc{3}/ => %w[abccc], + /abc{3}d/ => %w[abcccd], + /abc{0}/ => %w[ab], + /abc{,2}/ => %w[ab], + /abc{2,}/ => %w[abcc], + /def{1,5}/ => %w[def], + /abc+def/ => %w[abc def], + /ab(cde){,4}/ => %w[ab], + /(ab){,2}cd/ => %w[cd], + /(abc){2,3}/ => %w[abcabc], + /(abc){3}/ => %w[abcabcabc], + /ab{2,3}cd/ => %w[abb cd], + /(ab){2,3}cd/ => %w[abab cd] + ) + end + + it 'handles non-greedy repetition' do + verify_strings( + /abc.*?/ => %w[abc], + /abc+?/ => %w[abc], + /abc*?cde/ => %w[ab cde], + /(abc)+?def/ => %w[abc def], + /ab(cde)*?fg/ => %w[ab fg] + ) + end + + it 'ignores alternation for #substrings' do + { + /abc|def/ => [], + /ab(?:c|d)/ => %w[ab], + /ab(c|d|e)fg/ => %w[ab fg], + /ab?(c|d)fg/ => %w[a fg], + /ab(c|d)ef/ => %w[ab ef], + /ab(cd?|ef)g/ => %w[ab g], + /ab(cd|ef*)g/ => %w[ab g], + /ab|cd*/ => [], + /cd(?:ef|gh)|xyz/ => [], + /(cd(?:ef|gh)|xyz)/ => [], + /cd(ef|gh)+/ => %w[cd], + /cd(ef|gh)?/ => %w[cd], + /cd(ef|gh)?ij/ => %w[cd ij], + /cd(ef|gh)+ij/ => %w[cd ij], + /cd(ef|gh){2}ij/ => %w[cd ij], + /(cd(ef|g*))/ => %w[cd], + /ab(cd){0,2}ef/ => %w[ab ef], + /ab(cd){0,1}ef/ => %w[ab ef], + /ab(cd|cd)ef/ => %w[ab ef], + /ab(cd|cd)?ef/ => %w[ab ef], + /ab\\?cd/ => %w[ab cd] + }.each do |regexp, expected| + expect(described_class.new(regexp).substrings).to eq expected + end + end + + it 'handles alternation for #alternated_substrings' do + verify_alternated_strings( + /abc|def/ => [%w[abc], %w[def]], + /ab(?:c|d)/ => [%w[abc], %w[abd]], + /ab(c|d|e)fg/ => [%w[abcfg], %w[abdfg], %w[abefg]], + /ab?(c|d)fg/ => [%w[acfg], %w[adfg], %w[abcfg], %w[abdfg]], + /ab(c|d)ef/ => [%w[abcef], %w[abdef]], + /ab(cd?|ef)g/ => [%w[abcg], %w[abcdg], %w[abefg]], + /ab(cd|ef*)g/ => [%w[abcdg], %w[abe g]], + /ab|cd*/ => [%w[ab], %w[c]], + /cd(?:ef|gh)|xyz/ => [%w[cdef], %w[cdgh], %w[xyz]], + /(cd(?:ef|gh)|xyz)/ => [%w[cdef], %w[cdgh], %w[xyz]], + /cd(ef|gh)+/ => [%w[cdef], %w[cdgh]], + /cd(ef|gh)?/ => [%w[cd]], + /cd(ef|gh)?ij/ => [%w[cdij], %w[cdefij], %w[cdghij]], + /cd(ef|gh)+ij/ => [%w[cdef ij], %w[cdgh ij]], + /cd(ef|gh){2}ij/ => [%w[cdefefij], %w[cdefghij], %w[cdghefij], %w[cdghghij]], + /(cd(ef|g*))/ => [%w[cd]], + /a|b*/ => [], + /ab(?:c|d?)/ => [%w[ab]], + /ab(c|d)|a*/ => [], + /(abc)?(d|e)/ => [%w[d], %w[e]], + /(abc*de)?(d|e)/ => [%w[d], %w[e]], + /(abc*de)?(d|e?)/ => [], + /(abc)?(d|e?)/ => [], + /ab(cd){0,2}ef/ => [%w[ab ef]], + /ab(cd){0,1}ef/ => [%w[abef], %w[abcdef]], + /ab(cd|cd)ef/ => [%w[abcdef]], + /ab(cd|cd)?ef/ => [%w[abef], %w[abcdef]], + /ab\\?cd/ => [%w[abcd], %w[ab\cd]] + ) + end + + it 'handles grouping' do + verify_strings( + /(abc)/ => %w[abc], + /(abc)?/ => [], + /ab(cde)/ => %w[abcde], + /(abc)de/ => %w[abcde], + /ab(cde)fg/ => %w[abcdefg], + /ab(?cd)ef/ => %w[abcdef], + /gh(?>ij)kl/ => %w[ghijkl], + /m(n.*p)q/ => %w[mn pq], + /(?:ab(cd)*){2,3}/ => %w[ab], + /(ab(cd){3})?/ => [], + /(ab(cd)+){2}/ => %w[abcd] + ) + end + + it 'handles meta characters' do + verify_strings( + /abc\d/ => %w[abc], + /abc\wdef/ => %w[abc def], + /\habc/ => %w[abc] + ) + end + + it 'handles character properties' do + verify_strings( + /ab\p{Alpha}cd/ => %w[ab cd], + /ab\p{Blank}/ => %w[ab], + /\p{Digit}cd/ => %w[cd] + ) + end + + it 'handles backreferences' do + verify_strings( + /a(?abc).\k.+/ => %w[aabc] + ) + end + + it 'handles subexpressions' do + verify_strings( + /\A(?a\g*b)+\z/ => %w[a b] + ) + end + + it 'ignores negative lookaheads' do + verify_strings( + /^(?!.*\bContributing Editor\b).*$/ => %w[], + /abc(?!.*def).*/ => %w[abc], + /(?!.*def)abc/ => %w[abc], + /abc(?!.*def.*).*ghi/ => %w[abc ghi], + /abc(?!.*bcd)def/ => %w[abcdef] + ) + end + + it 'handles anchors' do + verify_strings( + /^abc/ => %w[abc], + /def$/ => %w[def], + /^abc$/ => %w[abc] + ) + end + + def verify_strings(hsh) + hsh.each do |regexp, expected| + expect(Capybara::Selector::RegexpDisassembler.new(regexp).substrings).to eq expected + end + verify_alternated_strings(hsh, wrap: true) + end + + def verify_alternated_strings(hsh, wrap: false) + hsh.each do |regexp, expected| + expected = [expected] if wrap && (expected != []) + expect(Capybara::Selector::RegexpDisassembler.new(regexp).alternated_substrings).to eq expected + end + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/result_spec.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/result_spec.rb new file mode 100644 index 00000000..15b6f598 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/result_spec.rb @@ -0,0 +1,185 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Capybara::Result do + let :string do + Capybara.string <<-STRING +
      +
    • Alpha
    • +
    • Beta
    • +
    • Gamma
    • +
    • Delta
    • +
    + STRING + end + + let :result do + string.all '//li', minimum: 0 # pass minimum: 0 so lazy evaluation doesn't get triggered yet + end + + it 'has a length' do + expect(result.length).to eq(4) + end + + it 'has a first element' do + result.first.text == 'Alpha' + end + + it 'has a last element' do + result.last.text == 'Delta' + end + + it 'can supports values_at method' do + expect(result.values_at(0, 2).map(&:text)).to eq(%w[Alpha Gamma]) + end + + it 'can return an element by its index' do + expect(result.at(1).text).to eq('Beta') + expect(result[2].text).to eq('Gamma') + end + + it 'can be mapped' do + expect(result.map(&:text)).to eq(%w[Alpha Beta Gamma Delta]) + end + + it 'can be selected' do + expect(result.select do |element| + element.text.include? 't' + end.length).to eq(2) + end + + it 'can be reduced' do + expect(result.reduce('') do |memo, element| + memo + element.text[0] + end).to eq('ABGD') + end + + it 'can be sampled' do + expect(result).to include(result.sample) + end + + it 'can be indexed' do + expect(result.index do |el| + el.text == 'Gamma' + end).to eq(2) + end + + it 'supports all modes of []' do + expect(result[1].text).to eq 'Beta' + expect(result[0, 2].map(&:text)).to eq %w[Alpha Beta] + expect(result[1..3].map(&:text)).to eq %w[Beta Gamma Delta] + expect(result[-1].text).to eq 'Delta' + expect(result[-2, 3].map(&:text)).to eq %w[Gamma Delta] + expect(result[1..7].map(&:text)).to eq %w[Beta Gamma Delta] + expect(result[1...3].map(&:text)).to eq %w[Beta Gamma] + expect(result[2..-1].map(&:text)).to eq %w[Gamma Delta] + expect(result[2...-1].map(&:text)).to eq %w[Gamma] + eval <<~TEST, binding, __FILE__, __LINE__ + 1 if RUBY_VERSION.to_f > 2.5 + expect(result[2..].map(&:text)).to eq %w[Gamma Delta] + TEST + end + + it 'works with filter blocks' do + result = string.all('//li') { |node| node.text == 'Alpha' } + expect(result.size).to eq 1 + end + + # Not a great test but it indirectly tests what is needed + it 'should evaluate filters lazily for idx' do + skip 'JRuby has an issue with lazy enumerator evaluation' if jruby_lazy_enumerator_workaround? + # Not processed until accessed + expect(result.instance_variable_get('@result_cache').size).to be 0 + + # Only one retrieved when needed + result.first + expect(result.instance_variable_get('@result_cache').size).to be 1 + + # works for indexed access + result[0] + expect(result.instance_variable_get('@result_cache').size).to be 1 + + result[2] + expect(result.instance_variable_get('@result_cache').size).to be 3 + + # All cached when converted to array + result.to_a + expect(result.instance_variable_get('@result_cache').size).to eq 4 + end + + it 'should evaluate filters lazily for range' do + skip 'JRuby has an issue with lazy enumerator evaluation' if jruby_lazy_enumerator_workaround? + result[0..1] + expect(result.instance_variable_get('@result_cache').size).to be 2 + + expect(result[0..7].size).to eq 4 + expect(result.instance_variable_get('@result_cache').size).to be 4 + end + + it 'should evaluate filters lazily for idx and length' do + skip 'JRuby has an issue with lazy enumerator evaluation' if jruby_lazy_enumerator_workaround? + result[1, 2] + expect(result.instance_variable_get('@result_cache').size).to be 3 + + expect(result[2, 5].size).to eq 2 + expect(result.instance_variable_get('@result_cache').size).to be 4 + end + + it 'should only need to evaluate one result for any?' do + skip 'JRuby has an issue with lazy enumerator evaluation' if jruby_lazy_enumerator_workaround? + result.any? + expect(result.instance_variable_get('@result_cache').size).to be 1 + end + + it 'should evaluate all elements when #to_a called' do + # All cached when converted to array + result.to_a + expect(result.instance_variable_get('@result_cache').size).to eq 4 + end + + context '#each' do + it 'lazily evaluates' do + skip 'JRuby has an issue with lazy enumerator evaluation' if jruby_lazy_enumerator_workaround? + results = [] + result.each do |el| + results << el + expect(result.instance_variable_get('@result_cache').size).to eq results.size + end + + expect(results.size).to eq 4 + end + + context 'without a block' do + it 'returns an iterator' do + expect(result.each).to be_a(Enumerator) + end + + it 'lazily evaluates' do + skip 'JRuby has an issue with lazy enumerator evaluation' if jruby_lazy_enumerator_workaround? + result.each.with_index do |_el, idx| + expect(result.instance_variable_get('@result_cache').size).to eq(idx + 1) # 0 indexing + end + end + end + end + + context 'lazy select' do + it 'is compatible' do + # This test will let us know when JRuby fixes lazy select so we can re-enable it in Result + pending 'JRuby < 9.2.8.0 has an issue with lazy enumberator evaluation' if jruby_lazy_enumerator_workaround? + eval_count = 0 + enum = %w[Text1 Text2 Text3].lazy.select do + eval_count += 1 + true + end + expect(eval_count).to eq 0 + enum.next + sleep 1 + expect(eval_count).to eq 1 + end + end + + def jruby_lazy_enumerator_workaround? + (RUBY_PLATFORM == 'java') && (Gem::Version.new(JRUBY_VERSION) < Gem::Version.new('9.2.8.0')) + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/rspec/features_spec.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/rspec/features_spec.rb new file mode 100644 index 00000000..ca03277c --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/rspec/features_spec.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'capybara/rspec' + +# rubocop:disable RSpec/InstanceVariable +RSpec.configuration.before(:each, file_path: './spec/rspec/features_spec.rb') do + @in_filtered_hook = true +end + +feature "Capybara's feature DSL" do + background do + @in_background = true + end + + scenario 'includes Capybara' do + visit('/') + expect(page).to have_content('Hello world!') + end + + scenario 'preserves description' do |ex| + expect(ex.metadata[:full_description]) + .to eq("Capybara's feature DSL preserves description") + end + + scenario 'allows driver switching', driver: :selenium do + expect(Capybara.current_driver).to eq(:selenium) + end + + scenario 'runs background' do + expect(@in_background).to be_truthy + end + + scenario 'runs hooks filtered by file path' do + expect(@in_filtered_hook).to be_truthy + end + + scenario "doesn't pollute the Object namespace" do + expect(Object.new).not_to respond_to(:feature) + end + + feature 'nested features' do + scenario 'work as expected' do + visit '/' + expect(page).to have_content 'Hello world!' + end + + scenario 'are marked in the metadata as capybara_feature' do |ex| + expect(ex.metadata[:capybara_feature]).to be_truthy + end + + scenario 'have a type of :feature' do |ex| + expect(ex.metadata[:type]).to eq :feature + end + end +end +# rubocop:enable RSpec/InstanceVariable + +feature 'given and given! aliases to let and let!' do + given(:value) { :available } + given!(:value_in_background) { :available } + + background do + expect(value_in_background).to be(:available) + end + + scenario 'given and given! work as intended' do + expect(value).to be(:available) + expect(value_in_background).to be(:available) + end +end + +feature "Capybara's feature DSL with driver", driver: :culerity do + scenario 'switches driver' do + expect(Capybara.current_driver).to eq(:culerity) + end +end + +# rubocop:disable RSpec/RepeatedExample +xfeature 'if xfeature aliases to pending then' do + scenario "this should be 'temporarily disabled with xfeature'" do + # dummy + end + scenario "this also should be 'temporarily disabled with xfeature'" do + # dummy + end +end + +ffeature 'if ffeature aliases focused tag then' do # rubocop:disable RSpec/Focus + scenario 'scenario inside this feature has metatag focus tag' do |example| + expect(example.metadata[:focus]).to eq true + end + + scenario 'other scenarios also has metatag focus tag ' do |example| + expect(example.metadata[:focus]).to eq true + end +end +# rubocop:enable RSpec/RepeatedExample diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/rspec/scenarios_spec.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/rspec/scenarios_spec.rb new file mode 100644 index 00000000..84c642f5 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/rspec/scenarios_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'capybara/rspec' + +RSpec.configuration.before(:each, file_path: './spec/rspec/scenarios_spec.rb') do + @in_filtered_hook = true +end + +feature 'if fscenario aliases focused tag then' do + fscenario 'scenario should have focused meta tag' do |example| # rubocop:disable RSpec/Focus + expect(example.metadata[:focus]).to eq true + end +end + +feature 'if xscenario aliases to pending then' do + xscenario "this test should be 'temporarily disabled with xscenario'" do + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/rspec/shared_spec_matchers.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/rspec/shared_spec_matchers.rb new file mode 100644 index 00000000..072785aa --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/rspec/shared_spec_matchers.rb @@ -0,0 +1,947 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'capybara/dsl' +require 'capybara/rspec/matchers' +require 'benchmark' + +# rubocop:disable RSpec/ExpectActual + +RSpec.shared_examples Capybara::RSpecMatchers do |session, _mode| + include Capybara::DSL + include Capybara::RSpecMatchers + + describe 'have_css matcher' do + it 'gives proper description' do + expect(have_css('h1').description).to eq('have visible css "h1"') + end + + context 'on a string' do + context 'with should' do + it 'passes if has_css? returns true' do + expect('

    Text

    ').to have_css('h1') + end + + it 'fails if has_css? returns false' do + expect do + expect('

    Text

    ').to have_css('h2') + end.to raise_error(/expected to find css "h2" but there were no matches/) + end + + it 'passes if matched node count equals expected count' do + expect('

    Text

    ').to have_css('h1', count: 1) + end + + it 'fails if matched node count does not equal expected count' do + expect do + expect('

    Text

    ').to have_css('h1', count: 2) + end.to raise_error('expected to find visible css "h1" 2 times, found 1 match: "Text"') + end + + it 'fails if matched node count is less than expected minimum count' do + expect do + expect('

    Text

    ').to have_css('p', minimum: 1) + end.to raise_error('expected to find css "p" at least 1 time but there were no matches') + end + + it 'fails if matched node count is more than expected maximum count' do + expect do + expect('

    Text

    Text

    Text

    ').to have_css('h1', maximum: 2) + end.to raise_error('expected to find visible css "h1" at most 2 times, found 3 matches: "Text", "Text", "Text"') + end + + it 'fails if matched node count does not belong to expected range' do + expect do + expect('

    Text

    ').to have_css('h1', between: 2..3) + end.to raise_error('expected to find visible css "h1" between 2 and 3 times, found 1 match: "Text"') + end + end + + context 'with should_not' do + it 'passes if has_no_css? returns true' do + expect('

    Text

    ').not_to have_css('h2') + end + + it 'fails if has_no_css? returns false' do + expect do + expect('

    Text

    ').not_to have_css('h1') + end.to raise_error(/expected not to find visible css "h1"/) + end + + it 'passes if matched node count does not equal expected count' do + expect('

    Text

    ').not_to have_css('h1', count: 2) + end + + it 'fails if matched node count equals expected count' do + expect do + expect('

    Text

    ').not_to have_css('h1', count: 1) + end.to raise_error(/expected not to find visible css "h1"/) + end + end + + it 'supports compounding' do + expect('

    Text

    Text

    ').to have_css('h1').and have_css('h2') + expect('

    Text

    Text

    ').to have_css('h3').or have_css('h1') + expect('

    Text

    Text

    ').to have_no_css('h4').and have_css('h2') + expect('

    Text

    Text

    ').to have_no_css('h2').or have_css('h1') + end + end + + context 'on a page or node' do + before do + visit('/with_html') + end + + context 'with should' do + it 'passes if has_css? returns true' do + expect(page).to have_css('h1') + end + + it 'fails if has_css? returns false' do + expect do + expect(page).to have_css('h1#doesnotexist') + end.to raise_error(/expected to find css "h1#doesnotexist" but there were no matches/) + end + end + + context 'with should_not' do + it 'passes if has_no_css? returns true' do + expect(page).not_to have_css('h1#doesnotexist') + end + + it 'fails if has_no_css? returns false' do + expect do + expect(page).not_to have_css('h1') + end.to raise_error(/expected not to find visible css "h1"/) + end + end + end + end + + describe 'have_xpath matcher' do + it 'gives proper description' do + expect(have_xpath('//h1').description).to eq("have visible xpath \"\/\/h1\"") + end + + context 'on a string' do + context 'with should' do + it 'passes if has_xpath? returns true' do + expect('

    Text

    ').to have_xpath('//h1') + end + + it 'fails if has_xpath? returns false' do + expect do + expect('

    Text

    ').to have_xpath('//h2') + end.to raise_error(%r{expected to find xpath "//h2" but there were no matches}) + end + end + + context 'with should_not' do + it 'passes if has_no_xpath? returns true' do + expect('

    Text

    ').not_to have_xpath('//h2') + end + + it 'fails if has_no_xpath? returns false' do + expect do + expect('

    Text

    ').not_to have_xpath('//h1') + end.to raise_error(%r{expected not to find visible xpath "//h1"}) + end + end + + it 'supports compounding' do + expect('

    Text

    Text

    ').to have_xpath('//h1').and have_xpath('//h2') + expect('

    Text

    Text

    ').to have_xpath('//h3').or have_xpath('//h1') + expect('

    Text

    Text

    ').to have_no_xpath('//h4').and have_xpath('//h1') + expect('

    Text

    Text

    ').to have_no_xpath('//h4').or have_xpath('//h4') + end + end + + context 'on a page or node' do + before do + visit('/with_html') + end + + context 'with should' do + it 'passes if has_xpath? returns true' do + expect(page).to have_xpath('//h1') + end + + it 'fails if has_xpath? returns false' do + expect do + expect(page).to have_xpath("//h1[@id='doesnotexist']") + end.to raise_error(%r{expected to find xpath "//h1\[@id='doesnotexist'\]" but there were no matches}) + end + end + + context 'with should_not' do + it 'passes if has_no_xpath? returns true' do + expect(page).not_to have_xpath('//h1[@id="doesnotexist"]') + end + + it 'fails if has_no_xpath? returns false' do + expect do + expect(page).not_to have_xpath('//h1') + end.to raise_error(%r{expected not to find visible xpath "//h1"}) + end + end + end + end + + describe 'have_selector matcher' do + it 'gives proper description' do + matcher = have_selector('//h1') + expect('

    Text

    ').to matcher + expect(matcher.description).to eq('have visible xpath "//h1"') + end + + context 'on a string' do + context 'with should' do + it 'passes if has_selector? returns true' do + expect('

    Text

    ').to have_selector('//h1') + end + + it 'fails if has_selector? returns false' do + expect do + expect('

    Text

    ').to have_selector('//h2') + end.to raise_error(%r{expected to find xpath "//h2" but there were no matches}) + end + end + + context 'with should_not' do + it 'passes if has_no_selector? returns true' do + expect('

    Text

    ').not_to have_selector(:css, 'h2') + end + + it 'fails if has_no_selector? returns false' do + expect do + expect('

    Text

    ').not_to have_selector(:css, 'h1') + end.to raise_error(/expected not to find visible css "h1"/) + end + end + end + + context 'on a page or node' do + before do + visit('/with_html') + end + + context 'with should' do + it 'passes if has_selector? returns true' do + expect(page).to have_selector('//h1', text: 'test') + end + + it 'fails if has_selector? returns false' do + expect do + expect(page).to have_selector("//h1[@id='doesnotexist']") + end.to raise_error(%r{expected to find xpath "//h1\[@id='doesnotexist'\]" but there were no matches}) + end + + it 'includes text in error message' do + expect do + expect(page).to have_selector('//h1', text: 'wrong text') + end.to raise_error(%r{expected to find visible xpath "//h1" with text "wrong text" but there were no matches}) + end + end + + context 'with should_not' do + it 'passes if has_no_css? returns true' do + expect(page).not_to have_selector(:css, 'h1#doesnotexist') + end + + it 'fails if has_no_selector? returns false' do + expect do + expect(page).not_to have_selector(:css, 'h1', text: 'test') + end.to raise_error(/expected not to find visible css "h1" with text "test"/) + end + end + end + + it 'supports compounding' do + expect('

    Text

    Text

    ').to have_selector('//h1').and have_selector('//h2') + expect('

    Text

    Text

    ').to have_selector('//h3').or have_selector('//h1') + expect('

    Text

    Text

    ').to have_no_selector('//h3').and have_selector('//h1') + end + end + + describe 'have_content matcher' do + it 'gives proper description' do + expect(have_content('Text').description).to eq('text "Text"') + end + + context 'on a string' do + context 'with should' do + it 'passes if has_content? returns true' do + expect('

    Text

    ').to have_content('Text') + end + + it 'passes if has_content? returns true using regexp' do + expect('

    Text

    ').to have_content(/ext/) + end + + it 'fails if has_content? returns false' do + expect do + expect('

    Text

    ').to have_content('No such Text') + end.to raise_error(/expected to find text "No such Text" in "Text"/) + end + end + + context 'with should_not' do + it 'passes if has_no_content? returns true' do + expect('

    Text

    ').not_to have_content('No such Text') + end + + it 'passes because escapes any characters that would have special meaning in a regexp' do + expect('

    Text

    ').not_to have_content('.') + end + + it 'fails if has_no_content? returns false' do + expect do + expect('

    Text

    ').not_to have_content('Text') + end.to raise_error(/expected not to find text "Text" in "Text"/) + end + end + end + + context 'on a page or node' do + before do + visit('/with_html') + end + + context 'with should' do + it 'passes if has_content? returns true' do + expect(page).to have_content('This is a test') + end + + it 'passes if has_content? returns true using regexp' do + expect(page).to have_content(/test/) + end + + it 'fails if has_content? returns false' do + expect do + expect(page).to have_content('No such Text') + end.to raise_error(/expected to find text "No such Text" in "(.*)This is a test(.*)"/) + end + + context 'with default selector CSS' do + before { Capybara.default_selector = :css } + + after { Capybara.default_selector = :xpath } + + it 'fails if has_content? returns false' do + expect do + expect(page).to have_content('No such Text') + end.to raise_error(/expected to find text "No such Text" in "(.*)This is a test(.*)"/) + end + end + end + + context 'with should_not' do + it 'passes if has_no_content? returns true' do + expect(page).not_to have_content('No such Text') + end + + it 'fails if has_no_content? returns false' do + expect do + expect(page).not_to have_content('This is a test') + end.to raise_error(/expected not to find text "This is a test"/) + end + end + end + + it 'supports compounding' do + expect('

    Text

    And

    ').to have_content('Text').and have_content('And') + expect('

    Text

    Or

    ').to have_content('XYZ').or have_content('Or') + expect('

    Text

    Or

    ').to have_no_content('XYZ').and have_content('Or') + end + end + + describe 'have_text matcher' do + it 'gives proper description' do + expect(have_text('Text').description).to eq('text "Text"') + end + + context 'on a string' do + context 'with should' do + it 'passes if text contains given string' do + expect('

    Text

    ').to have_text('Text') + end + + it 'passes if text matches given regexp' do + expect('

    Text

    ').to have_text(/ext/) + end + + it "fails if text doesn't contain given string" do + expect do + expect('

    Text

    ').to have_text('No such Text') + end.to raise_error(/expected to find text "No such Text" in "Text"/) + end + + it "fails if text doesn't match given regexp" do + expect do + expect('

    Text

    ').to have_text(/No such Text/) + end.to raise_error('expected to find text matching /No such Text/ in "Text"') + end + + it 'casts Integer to string' do + expect do + expect('

    Text

    ').to have_text(3) + end.to raise_error(/expected to find text "3" in "Text"/) + end + + it 'fails if matched text count does not equal to expected count' do + expect do + expect('

    Text

    ').to have_text('Text', count: 2) + end.to raise_error('expected to find text "Text" 2 times but found 1 time in "Text"') + end + + it 'fails if matched text count is less than expected minimum count' do + expect do + expect('

    Text

    ').to have_text('Lorem', minimum: 1) + end.to raise_error('expected to find text "Lorem" at least 1 time but found 0 times in "Text"') + end + + it 'fails if matched text count is more than expected maximum count' do + expect do + expect('

    Text TextText

    ').to have_text('Text', maximum: 2) + end.to raise_error('expected to find text "Text" at most 2 times but found 3 times in "Text TextText"') + end + + it 'fails if matched text count does not belong to expected range' do + expect do + expect('

    Text

    ').to have_text('Text', between: 2..3) + end.to raise_error('expected to find text "Text" between 2 and 3 times but found 1 time in "Text"') + end + end + + context 'with should_not' do + it "passes if text doesn't contain a string" do + expect('

    Text

    ').not_to have_text('No such Text') + end + + it 'passes because escapes any characters that would have special meaning in a regexp' do + expect('

    Text

    ').not_to have_text('.') + end + + it 'fails if text contains a string' do + expect do + expect('

    Text

    ').not_to have_text('Text') + end.to raise_error(/expected not to find text "Text" in "Text"/) + end + end + end + + context 'on a page or node' do + before do + visit('/with_html') + end + + context 'with should' do + it 'passes if has_text? returns true' do + expect(page).to have_text('This is a test') + end + + it 'passes if has_text? returns true using regexp' do + expect(page).to have_text(/test/) + end + + it 'can check for all text' do + expect(page).to have_text(:all, 'Some of this text is hidden!') + end + + it 'can check for visible text' do + expect(page).to have_text(:visible, 'Some of this text is') + expect(page).not_to have_text(:visible, 'Some of this text is hidden!') + end + + it 'fails if has_text? returns false' do + expect do + expect(page).to have_text('No such Text') + end.to raise_error(/expected to find text "No such Text" in "(.*)This is a test(.*)"/) + end + + context 'with default selector CSS' do + before { Capybara.default_selector = :css } + + after { Capybara.default_selector = :xpath } + + it 'fails if has_text? returns false' do + expect do + expect(page).to have_text('No such Text') + end.to raise_error(/expected to find text "No such Text" in "(.*)This is a test(.*)"/) + end + end + end + + context 'with should_not' do + it 'passes if has_no_text? returns true' do + expect(page).not_to have_text('No such Text') + end + + it 'fails if has_no_text? returns false' do + expect do + expect(page).not_to have_text('This is a test') + end.to raise_error(/expected not to find text "This is a test"/) + end + end + end + + it 'supports compounding' do + expect('

    Text

    And

    ').to have_text('Text').and have_text('And') + expect('

    Text

    Or

    ').to have_text('Not here').or have_text('Or') + end + end + + describe 'have_link matcher' do + let(:html) { 'Just a linkAnother link' } + + it 'gives proper description' do + expect(have_link('Just a link').description).to eq('have visible link "Just a link"') + end + + it 'passes if there is such a link' do + expect(html).to have_link('Just a link') + end + + it 'fails if there is no such link' do + expect do + expect(html).to have_link('No such Link') + end.to raise_error(/expected to find link "No such Link"/) + end + + it 'supports compounding' do + expect(html).to have_link('Just a link').and have_link('Another link') + expect(html).to have_link('Not a link').or have_link('Another link') + expect(html).to have_no_link('Not a link').and have_link('Another link') + end + end + + describe 'have_title matcher' do + it 'gives proper description' do + expect(have_title('Just a title').description).to eq('have title "Just a title"') + end + + context 'on a string' do + let(:html) { 'Just a title' } + + it 'passes if there is such a title' do + expect(html).to have_title('Just a title') + end + + it 'fails if there is no such title' do + expect do + expect(html).to have_title('No such title') + end.to raise_error('expected "Just a title" to include "No such title"') + end + + it "fails if title doesn't match regexp" do + expect do + expect(html).to have_title(/[[:upper:]]+[[:lower:]]+l{2}o/) + end.to raise_error('expected "Just a title" to match /[[:upper:]]+[[:lower:]]+l{2}o/') + end + end + + context 'on a page or node' do + it 'passes if there is such a title' do + visit('/with_js') + expect(page).to have_title('with_js') + end + + it 'fails if there is no such title' do + visit('/with_js') + expect do + expect(page).to have_title('No such title') + end.to raise_error('expected "with_js" to include "No such title"') + end + + context 'with wait' do + before do + session.visit('/with_js') + end + + it 'waits if wait time is more than timeout' do + session.click_link('Change title') + using_wait_time 0 do + expect(session).to have_title('changed title', wait: 2) + end + end + + it "doesn't wait if wait time is less than timeout" do + session.click_link('Change title') + using_wait_time 3 do + expect(session).not_to have_title('changed title', wait: 0) + end + end + end + end + + it 'supports compounding' do + expect('I compound').to have_title('I dont compound').or have_title('I compound') + end + end + + describe 'have_current_path matcher' do + it 'gives proper description' do + expect(have_current_path('http://www.example.com').description).to eq('have current path "http://www.example.com"') + end + + context 'on a page or node' do + it 'passes if there is such a current path' do + visit('/with_js') + expect(page).to have_current_path('/with_js') + end + + it 'fails if there is no such current_path' do + visit('/with_js') + expect do + expect(page).to have_current_path('/not_with_js') + end.to raise_error('expected "/with_js" to equal "/not_with_js"') + end + + context 'with wait' do + before do + session.visit('/with_js') + end + + it 'waits if wait time is more than timeout' do + session.click_link('Change page') + using_wait_time 0 do + expect(session).to have_current_path('/with_html', wait: 2) + end + end + + it "doesn't wait if wait time is less than timeout" do + session.click_link('Change page') + using_wait_time 0 do + expect(session).not_to have_current_path('/with_html') + end + end + end + end + + it 'supports compounding' do + visit('/with_html') + expect(page).to have_current_path('/not_with_html').or have_current_path('/with_html') + end + end + + describe 'have_button matcher' do + let(:html) { '' } + + it 'gives proper description with no options' do + expect(have_button('A button').description).to eq('have visible button "A button" that is not disabled') + end + + it 'gives proper description with disabled :any option' do + expect(have_button('A button', disabled: :all).description).to eq('have visible button "A button"') + end + + it 'passes if there is such a button' do + expect(html).to have_button('A button') + end + + it 'fails if there is no such button' do + expect do + expect(html).to have_button('No such Button') + end.to raise_error(/expected to find button "No such Button"/) + end + + it 'supports compounding' do + expect(html).to have_button('Not this button').or have_button('A button') + end + end + + describe 'have_field matcher' do + let(:html) { '

    ' } + + it 'gives proper description' do + expect(have_field('Text field').description).to eq('have visible field "Text field" that is not disabled') + end + + it 'gives proper description for a given value' do + expect(have_field('Text field', with: 'some value').description).to eq('have visible field "Text field" that is not disabled with value "some value"') + end + + it 'passes if there is such a field' do + expect(html).to have_field('Text field') + end + + it 'passes if there is such a field with value' do + expect(html).to have_field('Text field', with: 'some value') + end + + it 'fails if there is no such field' do + expect do + expect(html).to have_field('No such Field') + end.to raise_error(/expected to find field "No such Field"/) + end + + it 'fails if there is such field but with false value' do + expect do + expect(html).to have_field('Text field', with: 'false value') + end.to raise_error(/expected to find visible field "Text field"/) + end + + it 'treats a given value as a string' do + foo = Class.new do + def to_s + 'some value' + end + end + expect(html).to have_field('Text field', with: foo.new) + end + + it 'supports compounding' do + expect(html).to have_field('Not this one').or have_field('Text field') + end + end + + describe 'have_checked_field matcher' do + let(:html) do + ' + ' + end + + it 'gives proper description' do + expect(have_checked_field('it is checked').description).to eq('have visible field "it is checked" that is not disabled that is checked') + end + + context 'with should' do + it 'passes if there is such a field and it is checked' do + expect(html).to have_checked_field('it is checked') + end + + it 'fails if there is such a field but it is not checked' do + expect do + expect(html).to have_checked_field('unchecked field') + end.to raise_error(/expected to find visible field "unchecked field"/) + end + + it 'fails if there is no such field' do + expect do + expect(html).to have_checked_field('no such field') + end.to raise_error(/expected to find field "no such field"/) + end + end + + context 'with should not' do + it 'fails if there is such a field and it is checked' do + expect do + expect(html).not_to have_checked_field('it is checked') + end.to raise_error(/expected not to find visible field "it is checked"/) + end + + it 'passes if there is such a field but it is not checked' do + expect(html).not_to have_checked_field('unchecked field') + end + + it 'passes if there is no such field' do + expect(html).not_to have_checked_field('no such field') + end + end + + it 'supports compounding' do + expect(html).to have_checked_field('not this one').or have_checked_field('it is checked') + end + end + + describe 'have_unchecked_field matcher' do + let(:html) do + ' + ' + end + + it 'gives proper description' do + expect(have_unchecked_field('unchecked field').description).to eq('have visible field "unchecked field" that is not disabled that is not checked') + end + + context 'with should' do + it 'passes if there is such a field and it is not checked' do + expect(html).to have_unchecked_field('unchecked field') + end + + it 'fails if there is such a field but it is checked' do + expect do + expect(html).to have_unchecked_field('it is checked') + end.to raise_error(/expected to find visible field "it is checked"/) + end + + it 'fails if there is no such field' do + expect do + expect(html).to have_unchecked_field('no such field') + end.to raise_error(/expected to find field "no such field"/) + end + end + + context 'with should not' do + it 'fails if there is such a field and it is not checked' do + expect do + expect(html).not_to have_unchecked_field('unchecked field') + end.to raise_error(/expected not to find visible field "unchecked field"/) + end + + it 'passes if there is such a field but it is checked' do + expect(html).not_to have_unchecked_field('it is checked') + end + + it 'passes if there is no such field' do + expect(html).not_to have_unchecked_field('no such field') + end + end + + it 'supports compounding' do + expect(html).to have_unchecked_field('it is checked').or have_unchecked_field('unchecked field') + end + end + + describe 'have_select matcher' do + let(:html) { '' } + + it 'gives proper description' do + expect(have_select('Select Box').description).to eq('have visible select box "Select Box" that is not disabled') + end + + it 'gives proper description for a given selected value' do + expect(have_select('Select Box', selected: 'some value').description).to eq('have visible select box "Select Box" that is not disabled with "some value" selected') + end + + it 'passes if there is such a select' do + expect(html).to have_select('Select Box') + end + + it 'fails if there is no such select' do + expect do + expect(html).to have_select('No such Select box') + end.to raise_error(/expected to find select box "No such Select box"/) + end + + it 'supports compounding' do + expect(html).to have_select('Not this one').or have_select('Select Box') + end + end + + describe 'have_table matcher' do + let(:html) { '
    Lovely table
    ' } + + it 'gives proper description' do + expect(have_table('Lovely table').description).to eq('have visible table "Lovely table"') + expect(have_table('Lovely table', caption: 'my caption').description).to eq('have visible table "Lovely table" with caption "my caption"') + end + + it 'gives proper description when :visible option passed' do + expect(have_table('Lovely table', visible: true).description).to eq('have visible table "Lovely table"') + expect(have_table('Lovely table', visible: :hidden).description).to eq('have non-visible table "Lovely table"') + expect(have_table('Lovely table', visible: :all).description).to eq('have table "Lovely table"') + expect(have_table('Lovely table', visible: false).description).to eq('have table "Lovely table"') + end + + it 'passes if there is such a table' do + expect(html).to have_table('Lovely table') + end + + it 'fails if there is no such table' do + expect do + expect(html).to have_table('No such Table') + end.to raise_error(/expected to find table "No such Table"/) + end + + it 'supports compounding' do + expect(html).to have_table('nope').or have_table('Lovely table') + end + end + + context 'compounding timing' do + let(:session) { session } + let(:el) { session.find(:css, '#reload-me') } + + before do + session.visit('/with_js') + end + + context '#and' do + it "should run 'concurrently'" do + Capybara.using_wait_time(2) do + matcher = have_text('this is not there').and have_text('neither is this') + expect(Benchmark.realtime do + expect do + expect(el).to matcher + end.to raise_error RSpec::Expectations::ExpectationNotMetError + end).to be_between(2, 3) + end + end + + it "should run 'concurrently' and retry" do + session.click_link('reload-link') + session.using_wait_time(2) do + expect(Benchmark.realtime do + expect do + expect(el).to have_text('waiting to be reloaded').and(have_text('has been reloaded')) + end.to raise_error RSpec::Expectations::ExpectationNotMetError, /expected to find text "waiting to be reloaded" in "has been reloaded"/ + end).to be_between(2, 3) + end + end + + it 'should ignore :wait options' do + session.using_wait_time(2) do + matcher = have_text('this is not there', wait: 5).and have_text('neither is this', wait: 6) + expect(Benchmark.realtime do + expect do + expect(el).to matcher + end.to raise_error RSpec::Expectations::ExpectationNotMetError + end).to be_between(2, 3) + end + end + + it 'should work on the session' do + session.using_wait_time(2) do + session.click_link('reload-link') + expect(session).to have_selector(:css, 'h1', text: 'FooBar').and have_text('has been reloaded') + end + end + end + + context '#and_then' do + it 'should run sequentially' do + session.click_link('reload-link') + expect(el).to have_text('waiting to be reloaded').and_then have_text('has been reloaded') + end + end + + context '#or' do + it "should run 'concurrently'" do + session.using_wait_time(3) do + expect(Benchmark.realtime do + expect(el).to have_text('has been reloaded').or have_text('waiting to be reloaded') + end).to be < 1 + end + end + + it 'should retry' do + session.using_wait_time(3) do + expect(Benchmark.realtime do + expect do + expect(el).to have_text('has been reloaded').or have_text('random stuff') + end.to raise_error RSpec::Expectations::ExpectationNotMetError + end).to be > 3 + end + end + + it 'should ignore :wait options' do + session.using_wait_time(2) do + expect(Benchmark.realtime do + expect do + expect(el).to have_text('this is not there', wait: 10).or have_text('neither is this', wait: 15) + end.to raise_error RSpec::Expectations::ExpectationNotMetError + end).to be_between(2, 3) + end + end + + it 'should work on the session' do + session.using_wait_time(2) do + session.click_link('reload-link') + expect(session).to have_selector(:css, 'h1', text: 'Not on the page').or have_text('has been reloaded') + end + end + end + end +end +# rubocop:enable RSpec/ExpectActual diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/rspec/views_spec.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/rspec/views_spec.rb new file mode 100644 index 00000000..cd19c008 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/rspec/views_spec.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'capybara/rspec', type: :view do + it 'allows matchers to be used on strings' do + html = %(

    Test header

    ) + expect(html).to have_css('h1', text: 'Test header') + end + + it "doesn't include RSpecMatcherProxies" do + expect(self.class.ancestors).not_to include(Capybara::RSpecMatcherProxies) + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/rspec_matchers_spec.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/rspec_matchers_spec.rb new file mode 100644 index 00000000..4166d2e7 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/rspec_matchers_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Capybara RSpec Matchers', type: :feature do + context 'after called on session' do + it 'HaveSelector should allow getting a description of the matcher' do + visit('/with_html') + matcher = have_selector(:css, 'h2.head', minimum: 3) + expect(page).to matcher + expect { matcher.description }.not_to raise_error + end + + it 'HaveText should allow getting a description' do + visit('/with_html') + matcher = have_text('Lorem') + expect(page).to matcher + expect { matcher.description }.not_to raise_error + end + + it 'should produce the same error for .to have_no_xxx and .not_to have_xxx' do + visit('/with_html') + not_to_msg = error_msg_for { expect(page).not_to have_selector(:css, '#referrer') } + have_no_msg = error_msg_for { expect(page).to have_no_selector(:css, '#referrer') } + expect(not_to_msg).to eq have_no_msg + + not_to_msg = error_msg_for { expect(page).not_to have_text('This is a test') } + have_no_msg = error_msg_for { expect(page).to have_no_text('This is a test') } + expect(not_to_msg).to eq have_no_msg + end + end + + context 'after called on element' do + it 'HaveSelector should allow getting a description' do + visit('/with_html') + el = find(:css, '#first') + matcher = have_selector(:css, 'a#foo') + expect(el).to matcher + expect { matcher.description }.not_to raise_error + end + + it 'MatchSelector should allow getting a description' do + visit('/with_html') + el = find(:css, '#first') + matcher = match_selector(:css, '#first') + expect(el).to matcher + expect { matcher.description }.not_to raise_error + end + + it 'HaveText should allow getting a description' do + visit('/with_html') + el = find(:css, '#first') + matcher = have_text('Lorem') + expect(el).to matcher + expect { matcher.description }.not_to raise_error + end + end + + def error_msg_for(&block) + expect(&block).to(raise_error { |e| return e.message }) + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/rspec_spec.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/rspec_spec.rb new file mode 100644 index 00000000..d13e3374 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/rspec_spec.rb @@ -0,0 +1,145 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'capybara/rspec' do + context 'Feature', type: :feature do + it 'should include Capybara in rspec' do + visit('/foo') + expect(page.body).to include('Another World') + end + + it 'should include RSpec matcher proxies' do + expect(self.class.ancestors).to include Capybara::RSpecMatcherProxies + end + + context 'resetting session' do + it 'sets a cookie in one example...' do + visit('/set_cookie') + expect(page.body).to include('Cookie set to test_cookie') + end + + it '...then it is not available in the next' do + visit('/get_cookie') + expect(page.body).not_to include('test_cookie') + end + end + + context 'setting the current driver' do + it 'sets the current driver in one example...' do + Capybara.current_driver = :selenium + end + + it '...then it has returned to the default in the next example' do + expect(Capybara.current_driver).to eq(:rack_test) + end + end + + it 'switches to the javascript driver when giving it as metadata', js: true do + expect(Capybara.current_driver).to eq(Capybara.javascript_driver) + end + + it 'switches to the given driver when giving it as metadata', driver: :culerity do + expect(Capybara.current_driver).to eq(:culerity) + end + + context '#all' do + it 'allows access to the Capybara finder' do + visit('/with_html') + found = all(:css, 'h2') { |element| element[:class] == 'head' } + expect(found.size).to eq(5) + end + + it 'allows access to the RSpec matcher' do + visit('/with_html') + strings = %w[test1 test2] + expect(strings).to all(be_a(String)) + end + end + + context '#within' do + it 'allows access to the Capybara scoper' do + visit('/with_html') + expect do + within(:css, '#does_not_exist') { click_link 'Go to simple' } + end.to raise_error(Capybara::ElementNotFound) + end + + it 'allows access to the RSpec matcher' do + visit('/with_html') + # This reads terribly, but must call #within + expect(find(:css, 'span.number').text.to_i).to within(1).of(41) + end + end + end + + context 'Type: Other', type: :other do + context 'when RSpec::Matchers is included after Capybara::DSL' do + let(:test_class_instance) do + Class.new do + include Capybara::DSL + include RSpec::Matchers + end.new + end + + context '#all' do + it 'allows access to the Capybara finder' do + test_class_instance.visit('/with_html') + expect(test_class_instance.all(:css, 'h2.head').size).to eq(5) + end + + it 'allows access to the RSpec matcher' do + test_class_instance.visit('/with_html') + strings = %w[test1 test2] + expect(strings).to test_class_instance.all(be_a(String)) + end + end + + context '#within' do + it 'allows access to the Capybara scoper' do + test_class_instance.visit('/with_html') + expect do + test_class_instance.within(:css, '#does_not_exist') { test_class_instance.click_link 'Go to simple' } + end.to raise_error(Capybara::ElementNotFound) + end + + it 'allows access to the RSpec matcher' do + test_class_instance.visit('/with_html') + # This reads terribly, but must call #within + expect(test_class_instance.find(:css, 'span.number').text.to_i).to test_class_instance.within(1).of(41) + end + end + + context 'when `match_when_negated` is not defined in a matcher' do + before do + RSpec::Matchers.define :only_match_matcher do |expected| + match do |actual| + !(actual ^ expected) + end + end + end + + it 'can be called with `not_to`' do + # This test is for a bug in jruby where `super` isn't defined correctly - https://github.com/jruby/jruby/issues/4678 + # Reported in https://github.com/teamcapybara/capybara/issues/2115 + test_class_instance.instance_eval do + expect do + expect(true).not_to only_match_matcher(false) # rubocop:disable RSpec/ExpectActual + end.not_to raise_error + end + end + end + end + + it 'should not include Capybara' do + expect { visit('/') }.to raise_error(NoMethodError) + end + end +end + +feature 'Feature DSL' do + scenario 'is pulled in' do + visit('/foo') + expect(page.body).to include('Another World') + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/sauce_spec_chrome.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/sauce_spec_chrome.rb new file mode 100644 index 00000000..85e8bc11 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/sauce_spec_chrome.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'selenium-webdriver' + +require 'sauce_whisk' +# require 'shared_selenium_session' +# require 'shared_selenium_node' +# require 'rspec/shared_spec_matchers' + +Capybara.register_driver :sauce_chrome do |app| + options = { + selenium_version: '3.141.59', + platform: 'macOS 10.12', + browser_name: 'chrome', + version: '65.0', + name: 'Capybara test', + build: ENV['TRAVIS_REPO_SLUG'] || "Ruby-RSpec-Selenium: Local-#{Time.now.to_i}", + username: ENV['SAUCE_USERNAME'], + access_key: ENV['SAUCE_ACCESS_KEY'] + } + + options.delete(:browser_name) + + capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(options) + url = 'https://ondemand.saucelabs.com:443/wd/hub' + + Capybara::Selenium::Driver.new(app, + browser: :remote, url: url, + desired_capabilities: capabilities, + options: Selenium::WebDriver::Chrome::Options.new(args: [''])) +end + +CHROME_REMOTE_DRIVER = :sauce_chrome + +module TestSessions + Chrome = Capybara::Session.new(CHROME_REMOTE_DRIVER, TestApp) +end + +skipped_tests = %i[response_headers status_code trigger download] + +Capybara::SpecHelper.run_specs TestSessions::Chrome, CHROME_REMOTE_DRIVER.to_s, capybara_skip: skipped_tests do |example| +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/selector_spec.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/selector_spec.rb new file mode 100644 index 00000000..49846f17 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/selector_spec.rb @@ -0,0 +1,513 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Capybara do + describe 'Selectors' do + let :string do + described_class.string <<-STRING + + + selectors + + +
    +
    +

    Totally awesome

    +

    Yes it is

    +
    +

    Some Content

    +

    +
    +
    +
    +
    + Something +
    + + + + + + + + + link +
    + + + +
    + + + + + + + +
    ABC
    DEF
    + + + + + + + + + + + + +
    AD
    BE
    CF
    + + + STRING + end + + before do + described_class.add_selector :custom_selector do + css { |css_class| "div.#{css_class}" } + node_filter(:not_empty, boolean: true, default: true, skip_if: :all) { |node, value| value ^ (node.text == '') } + end + + described_class.add_selector :custom_css_selector do + css(:name, :other_name) do |selector, name: nil, **| + selector ||= '' + selector += "[name='#{name}']" if name + selector + end + + expression_filter(:placeholder) do |expr, val| + expr + "[placeholder='#{val}']" + end + + expression_filter(:value) do |expr, val| + expr + "[value='#{val}']" + end + + expression_filter(:title) do |expr, val| + expr + "[title='#{val}']" + end + end + + described_class.add_selector :custom_xpath_selector do + xpath(:valid1, :valid2) { |selector| selector } + match { |value| value == 'match_me' } + end + end + + it 'supports `filter` as an alias for `node_filter`' do + expect do + described_class.add_selector :filter_alias_selector do + css { |_unused| 'div' } + filter(:something) { |_node, _value| true } + end + end.not_to raise_error + end + + describe 'adding a selector' do + it 'can set default visiblity' do + described_class.add_selector :hidden_field do + visible :hidden + css { |_sel| 'input[type="hidden"]' } + end + + expect(string).to have_no_css('input[type="hidden"]') + expect(string).to have_selector(:hidden_field) + end + end + + describe 'modify_selector' do + it 'allows modifying a selector' do + el = string.find(:custom_selector, 'aa') + expect(el.tag_name).to eq 'div' + described_class.modify_selector :custom_selector do + css { |css_class| "h1.#{css_class}" } + end + el = string.find(:custom_selector, 'aa') + expect(el.tag_name).to eq 'h1' + end + + it "doesn't change existing filters" do + described_class.modify_selector :custom_selector do + css { |css_class| "p.#{css_class}" } + end + expect(string).to have_selector(:custom_selector, 'bb', count: 1) + expect(string).to have_selector(:custom_selector, 'bb', not_empty: false, count: 1) + expect(string).to have_selector(:custom_selector, 'bb', not_empty: :all, count: 2) + end + end + + describe '::[]' do + it 'can find a selector' do + expect(Capybara::Selector[:field]).not_to be_nil + end + + it 'raises if no selector found' do + expect { Capybara::Selector[:no_exist] }.to raise_error(ArgumentError, /Unknown selector type/) + end + end + + describe '::for' do + it 'finds selector that matches the locator' do + expect(Capybara::Selector.for('match_me').name).to eq :custom_xpath_selector + end + + it 'returns nil if no match' do + expect(Capybara::Selector.for('nothing')).to be nil + end + end + + describe 'xpath' do + it 'uses filter names passed in' do + described_class.add_selector :test do + xpath(:something, :other) { |_locator| XPath.descendant } + end + selector = Capybara::Selector.new :test, config: nil, format: nil + + expect(selector.expression_filters.keys).to include(:something, :other) + end + + it 'gets filter names from block if none passed to xpath method' do + described_class.add_selector :test do + xpath { |_locator, valid3:, valid4: nil| "#{valid3} #{valid4}" } + end + selector = Capybara::Selector.new :test, config: nil, format: nil + + expect(selector.expression_filters.keys).to include(:valid3, :valid4) + end + + it 'ignores block parameters if names passed in' do + described_class.add_selector :test do + xpath(:valid1) { |_locator, valid3:, valid4: nil| "#{valid3} #{valid4}" } + end + selector = Capybara::Selector.new :test, config: nil, format: nil + + expect(selector.expression_filters.keys).to include(:valid1) + expect(selector.expression_filters.keys).not_to include(:valid3, :valid4) + end + end + + describe 'css' do + it "supports filters specified in 'css' definition" do + expect(string).to have_selector(:custom_css_selector, 'input', name: 'form[my_text_input]') + expect(string).to have_no_selector(:custom_css_selector, 'input', name: 'form[not_my_text_input]') + end + + it 'supports explicitly defined expression filters' do + expect(string).to have_selector(:custom_css_selector, placeholder: 'my text') + expect(string).to have_no_selector(:custom_css_selector, placeholder: 'not my text') + expect(string).to have_selector(:custom_css_selector, value: 'click me', title: 'submit button') + end + + it 'uses filter names passed in' do + described_class.add_selector :test do + css(:name, :other_name) { |_locator| '' } + end + selector = Capybara::Selector.new :test, config: nil, format: nil + + expect(selector.expression_filters.keys).to include(:name, :other_name) + end + + it 'gets filter names from block if none passed to css method' do + described_class.add_selector :test do + css { |_locator, valid3:, valid4: nil| "#{valid3} #{valid4}" } + end + selector = Capybara::Selector.new :test, config: nil, format: nil + + expect(selector.expression_filters.keys).to include(:valid3, :valid4) + end + + it 'ignores block parameters if names passed in' do + described_class.add_selector :test do + css(:valid1) { |_locator, valid3:, valid4: nil| "#{valid3} #{valid4}" } + end + selector = Capybara::Selector.new :test, config: nil, format: nil + + expect(selector.expression_filters.keys).to include(:valid1) + expect(selector.expression_filters.keys).not_to include(:valid3, :valid4) + end + end + + describe 'builtin selectors' do + context 'when locator is nil' do + it 'devolves to just finding element types' do + selectors = { + field: ".//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]", + fieldset: './/fieldset', + link: './/a[./@href]', + link_or_button: ".//a[./@href] | .//input[./@type = 'submit' or ./@type = 'reset' or ./@type = 'image' or ./@type = 'button'] | .//button", + fillable_field: ".//*[self::input | self::textarea][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'radio' or ./@type = 'checkbox' or ./@type = 'hidden' or ./@type = 'file')]", + radio_button: ".//input[./@type = 'radio']", + checkbox: ".//input[./@type = 'checkbox']", + select: './/select', + option: './/option', + file_field: ".//input[./@type = 'file']", + table: './/table' + } + selectors.each do |selector, xpath| + results = string.all(selector, nil).to_a.map(&:native) + expect(results.size).to be > 0 + expect(results).to eq string.all(:xpath, xpath).to_a.map(&:native) + end + end + end + + context 'with :id option' do + it 'works with compound css selectors' do + expect(string.all(:custom_css_selector, 'div, h1', id: 'page').size).to eq 1 + expect(string.all(:custom_css_selector, 'h1, div', id: 'page').size).to eq 1 + end + + it "works with 'special' characters" do + expect(string.find(:custom_css_selector, 'div', id: '#special')[:id]).to eq '#special' + expect(string.find(:custom_css_selector, 'input', id: '2checkbox')[:id]).to eq '2checkbox' + end + + it 'accepts XPath expression for xpath based selectors' do + expect(string.find(:custom_xpath_selector, './/div', id: XPath.contains('peci'))[:id]).to eq '#special' + expect(string.find(:custom_xpath_selector, './/input', id: XPath.ends_with('box'))[:id]).to eq '2checkbox' + end + + it 'errors XPath expression for CSS based selectors' do + expect { string.find(:custom_css_selector, 'div', id: XPath.contains('peci')) } + .to raise_error(ArgumentError, /not supported/) + end + + it 'accepts Regexp for xpath based selectors' do + expect(string.find(:custom_xpath_selector, './/div', id: /peci/)[:id]).to eq '#special' + expect(string.find(:custom_xpath_selector, './/div', id: /pEcI/i)[:id]).to eq '#special' + end + + it 'accepts Regexp for css based selectors' do + expect(string.find(:custom_css_selector, 'div', id: /sp.*al/)[:id]).to eq '#special' + end + end + + context 'with :class option' do + it 'works with compound css selectors' do + expect(string.all(:custom_css_selector, 'div, h1', class: 'aa').size).to eq 2 + expect(string.all(:custom_css_selector, 'h1, div', class: 'aa').size).to eq 2 + end + + it 'handles negated classes' do + expect(string.all(:custom_css_selector, 'div, p', class: ['bb', '!cc']).size).to eq 2 + expect(string.all(:custom_css_selector, 'div, p', class: ['!cc', '!dd', 'bb']).size).to eq 1 + expect(string.all(:custom_xpath_selector, XPath.descendant(:div, :p), class: ['bb', '!cc']).size).to eq 2 + expect(string.all(:custom_xpath_selector, XPath.descendant(:div, :p), class: ['!cc', '!dd', 'bb']).size).to eq 1 + end + + it 'handles classes starting with ! by requiring negated negated first' do + expect(string.all(:custom_css_selector, 'div, p', class: ['!!!mine']).size).to eq 1 + expect(string.all(:custom_xpath_selector, XPath.descendant(:div, :p), class: ['!!!mine']).size).to eq 1 + end + + it "works with 'special' characters" do + expect(string.find(:custom_css_selector, 'input', class: '.special')[:id]).to eq 'file' + expect(string.find(:custom_css_selector, 'input', class: '2checkbox')[:id]).to eq '2checkbox' + end + + it 'accepts XPath expression for xpath based selectors' do + expect(string.find(:custom_xpath_selector, './/div', class: XPath.contains('dom wor'))[:id]).to eq 'random_words' + expect(string.find(:custom_xpath_selector, './/div', class: XPath.ends_with('words'))[:id]).to eq 'random_words' + end + + it 'errors XPath expression for CSS based selectors' do + expect { string.find(:custom_css_selector, 'div', class: XPath.contains('random')) } + .to raise_error(ArgumentError, /not supported/) + end + + it 'accepts Regexp for XPath based selectors' do + expect(string.find(:custom_xpath_selector, './/div', class: /dom wor/)[:id]).to eq 'random_words' + expect(string.find(:custom_xpath_selector, './/div', class: /dOm WoR/i)[:id]).to eq 'random_words' + end + + it 'accepts Regexp for CSS base selectors' do + expect(string.find(:custom_css_selector, 'div', class: /random/)[:id]).to eq 'random_words' + end + end + + context 'with :style option' do + it 'accepts string for CSS based selectors' do + expect(string.find(:custom_css_selector, 'input', style: 'line-height: 30px;')[:title]).to eq 'Other button 1' + end + + it 'accepts Regexp for CSS base selectors' do + expect(string.find(:custom_css_selector, 'input', style: /30px/)[:title]).to eq 'Other button 1' + end + end + + # :css, :xpath, :id, :field, :fieldset, :link, :button, :link_or_button, :fillable_field, :radio_button, :checkbox, :select, + # :option, :file_field, :label, :table, :frame + + describe ':css selector' do + it 'finds by CSS locator' do + expect(string.find(:css, 'input#my_text_input')[:name]).to eq 'form[my_text_input]' + end + end + + describe ':xpath selector' do + it 'finds by XPath locator' do + expect(string.find(:xpath, './/input[@id="my_text_input"]')[:name]).to eq 'form[my_text_input]' + end + end + + describe ':id selector' do + it 'finds by locator' do + expect(string.find(:id, 'my_text_input')[:name]).to eq 'form[my_text_input]' + expect(string.find(:id, /my_text_input/)[:name]).to eq 'form[my_text_input]' + expect(string.find(:id, /_text_/)[:name]).to eq 'form[my_text_input]' + expect(string.find(:id, /i[nmo]/)[:name]).to eq 'form[my_text_input]' + end + end + + describe ':field selector' do + it 'finds by locator' do + expect(string.find(:field, 'My Text Input')[:id]).to eq 'my_text_input' + expect(string.find(:field, 'my_text_input')[:id]).to eq 'my_text_input' + expect(string.find(:field, 'form[my_text_input]')[:id]).to eq 'my_text_input' + end + + it 'finds by id string' do + expect(string.find(:field, id: 'my_text_input')[:name]).to eq 'form[my_text_input]' + end + + it 'finds by id regexp' do + expect(string.find(:field, id: /my_text_inp/)[:name]).to eq 'form[my_text_input]' + end + + it 'finds by name' do + expect(string.find(:field, name: 'form[my_text_input]')[:id]).to eq 'my_text_input' + end + + it 'finds by placeholder' do + expect(string.find(:field, placeholder: 'my text')[:id]).to eq 'my_text_input' + end + + it 'finds by type' do + expect(string.find(:field, type: 'file')[:id]).to eq 'file' + expect(string.find(:field, type: 'select')[:id]).to eq 'select' + end + end + + describe ':option selector' do + it 'finds disabled options' do + expect(string.find(:option, disabled: true).value).to eq 'b' + end + + it 'finds selected options' do + expect(string.find(:option, selected: true).value).to eq 'c' + end + + it 'finds not selected and not disabled options' do + expect(string.find(:option, disabled: false, selected: false).value).to eq 'a' + end + end + + describe ':button selector' do + it 'finds by value' do + expect(string.find(:button, 'click me').value).to eq 'click me' + end + + it 'finds by title' do + expect(string.find(:button, 'submit button').value).to eq 'click me' + end + + it 'includes non-matching parameters in failure message' do + expect { string.find(:button, 'click me', title: 'click me') }.to raise_error(/with title click me/) + end + end + + describe ':element selector' do + it 'finds by any attributes' do + expect(string.find(:element, 'input', type: 'submit').value).to eq 'click me' + end + + it 'supports regexp matching' do + expect(string.find(:element, 'input', type: /sub/).value).to eq 'click me' + expect(string.find(:element, 'input', title: /sub\w.*button/).value).to eq 'click me' + expect(string.find(:element, 'input', title: /sub.* b.*ton/).value).to eq 'click me' + expect(string.find(:element, 'input', title: /sub.*mit.*/).value).to eq 'click me' + expect(string.find(:element, 'input', title: /^submit button$/).value).to eq 'click me' + expect(string.find(:element, 'input', title: /^(?:submit|other) button$/).value).to eq 'click me' + expect(string.find(:element, 'input', title: /SuB.*mIt/i).value).to eq 'click me' + expect(string.find(:element, 'input', title: /^Su.*Bm.*It/i).value).to eq 'click me' + expect(string.find(:element, 'input', title: /^Ot.*he.*r b.*\d/i).value).to eq "don't click me" + end + + it 'still works with system keys' do + expect { string.all(:element, 'input', type: 'submit', count: 1) }.not_to raise_error + end + + it 'works without element type' do + expect(string.find(:element, type: 'submit').value).to eq 'click me' + end + + it 'validates attribute presence when true' do + expect(string.find(:element, name: true)[:id]).to eq 'my_text_input' + end + + it 'validates attribute absence when false' do + expect(string.find(:element, 'option', disabled: false, selected: false).value).to eq 'a' + end + + it 'includes wildcarded keys in description' do + expect { string.find(:element, 'input', not_there: 'bad', presence: true, absence: false, count: 1) } + .to(raise_error do |e| + expect(e).to be_a(Capybara::ElementNotFound) + expect(e.message).to include 'not_there => bad' + expect(e.message).to include 'with presence attribute' + expect(e.message).to include 'without absence attribute' + expect(e.message).not_to include 'count 1' + end) + end + + it 'accepts XPath::Expression' do + expect(string.find(:element, 'input', type: XPath.starts_with('subm')).value).to eq 'click me' + expect(string.find(:element, 'input', type: XPath.ends_with('ext'))[:type]).to eq 'text' + expect(string.find(:element, 'input', type: XPath.contains('ckb'))[:type]).to eq 'checkbox' + expect(string.find(:element, 'input', title: XPath.contains_word('submit'))[:type]).to eq 'submit' + expect(string.find(:element, 'input', title: XPath.contains_word('button 1'))[:type]).to eq 'button' + end + end + + describe ':link_or_button selector' do + around(:all) do |example| + described_class.modify_selector(:link_or_button) do + expression_filter(:random) { |xpath, _| xpath } # do nothing filter + end + example.run + Capybara::Selector[:link_or_button].expression_filters.delete(:random) + end + + it 'should not find links when disabled == true' do + expect(string.all(:link_or_button, disabled: true).size).to eq 0 + end + + context 'when modified' do + it 'should still work' do + filter = Capybara::Selector[:link_or_button].expression_filters[:random] + allow(filter).to receive(:apply_filter).and_call_original + + expect(string.find(:link_or_button, 'click me', random: 'blah').value).to eq 'click me' + expect(filter).to have_received(:apply_filter).with(anything, :random, 'blah', anything) + end + end + end + + describe ':table selector' do + it 'finds by rows' do + expect(string.find(:table, with_rows: [%w[D E F]])[:id]).to eq 'rows' + end + + it 'finds by columns' do + expect(string.find(:table, with_cols: [%w[A B C]])[:id]).to eq 'cols' + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/selenium_spec_chrome.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/selenium_spec_chrome.rb new file mode 100644 index 00000000..1dc32ba3 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/selenium_spec_chrome.rb @@ -0,0 +1,183 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'selenium-webdriver' +require 'shared_selenium_session' +require 'shared_selenium_node' +require 'rspec/shared_spec_matchers' + +CHROME_DRIVER = :selenium_chrome + +Selenium::WebDriver::Chrome.path = '/usr/bin/google-chrome-beta' if ENV['CI'] && ENV['CHROME_BETA'] + +browser_options = ::Selenium::WebDriver::Chrome::Options.new +browser_options.headless! if ENV['HEADLESS'] +browser_options.add_option(:w3c, ENV['W3C'] != 'false') +# Chromedriver 77 requires setting this for headless mode on linux +# browser_options.add_preference('download.default_directory', Capybara.save_path) +browser_options.add_preference(:download, default_directory: Capybara.save_path) + +Capybara.register_driver :selenium_chrome do |app| + Capybara::Selenium::Driver.new(app, browser: :chrome, options: browser_options, timeout: 30).tap do |driver| + # Set download dir for Chrome < 77 + driver.browser.download_path = Capybara.save_path + end +end + +Capybara.register_driver :selenium_chrome_not_clear_storage do |app| + chrome_options = { + browser: :chrome, + options: browser_options + } + Capybara::Selenium::Driver.new(app, chrome_options.merge(clear_local_storage: false, clear_session_storage: false)) +end + +Capybara.register_driver :selenium_chrome_not_clear_session_storage do |app| + chrome_options = { + browser: :chrome, + options: browser_options + } + Capybara::Selenium::Driver.new(app, chrome_options.merge(clear_session_storage: false)) +end + +Capybara.register_driver :selenium_chrome_not_clear_local_storage do |app| + chrome_options = { + browser: :chrome, + options: browser_options + } + Capybara::Selenium::Driver.new(app, chrome_options.merge(clear_local_storage: false)) +end + +Capybara.register_driver :selenium_driver_subclass_with_chrome do |app| + subclass = Class.new(Capybara::Selenium::Driver) + subclass.new(app, browser: :chrome, options: browser_options, timeout: 30) +end + +module TestSessions + Chrome = Capybara::Session.new(CHROME_DRIVER, TestApp) +end + +skipped_tests = %i[response_headers status_code trigger] + +Capybara::SpecHelper.log_selenium_driver_version(Selenium::WebDriver::Chrome) if ENV['CI'] + +Capybara::SpecHelper.run_specs TestSessions::Chrome, CHROME_DRIVER.to_s, capybara_skip: skipped_tests do |example| + case example.metadata[:full_description] + when /#click_link can download a file$/ + skip 'Need to figure out testing of file downloading on windows platform' if Gem.win_platform? + when /Capybara::Session selenium_chrome Capybara::Window#maximize/ + pending "Chrome headless doesn't support maximize" if ENV['HEADLESS'] + when /details non-summary descendants should be non-visible/ + pending 'Chromedriver built-in is_displayed is currently broken' if ENV['W3C'] == 'false' + end +end + +RSpec.describe 'Capybara::Session with chrome' do + include Capybara::SpecHelper + ['Capybara::Session', 'Capybara::Node', Capybara::RSpecMatchers].each do |examples| + include_examples examples, TestSessions::Chrome, CHROME_DRIVER + end + + context 'storage' do + describe '#reset!' do + it 'clears storage by default' do + session = TestSessions::Chrome + session.visit('/with_js') + session.find(:css, '#set-storage').click + session.reset! + session.visit('/with_js') + expect(session.evaluate_script('Object.keys(localStorage)')).to be_empty + expect(session.evaluate_script('Object.keys(sessionStorage)')).to be_empty + end + + it 'does not clear storage when false' do + session = Capybara::Session.new(:selenium_chrome_not_clear_storage, TestApp) + session.visit('/with_js') + session.find(:css, '#set-storage').click + session.reset! + session.visit('/with_js') + expect(session.evaluate_script('Object.keys(localStorage)')).not_to be_empty + expect(session.evaluate_script('Object.keys(sessionStorage)')).not_to be_empty + end + + it 'can not clear session storage' do + session = Capybara::Session.new(:selenium_chrome_not_clear_session_storage, TestApp) + session.visit('/with_js') + session.find(:css, '#set-storage').click + session.reset! + session.visit('/with_js') + expect(session.evaluate_script('Object.keys(localStorage)')).to be_empty + expect(session.evaluate_script('Object.keys(sessionStorage)')).not_to be_empty + end + + it 'can not clear local storage' do + session = Capybara::Session.new(:selenium_chrome_not_clear_local_storage, TestApp) + session.visit('/with_js') + session.find(:css, '#set-storage').click + session.reset! + session.visit('/with_js') + expect(session.evaluate_script('Object.keys(localStorage)')).not_to be_empty + expect(session.evaluate_script('Object.keys(sessionStorage)')).to be_empty + end + end + end + + context 'timeout' do + it 'sets the http client read timeout' do + expect(TestSessions::Chrome.driver.browser.send(:bridge).http.read_timeout).to eq 30 + end + end + + describe 'filling in Chrome-specific date and time fields with keystrokes' do + let(:datetime) { Time.new(1983, 6, 19, 6, 30) } + let(:session) { TestSessions::Chrome } + + before do + session.visit('/form') + end + + it 'should fill in a date input with a String' do + session.fill_in('form_date', with: '06/19/1983') + session.click_button('awesome') + expect(Date.parse(extract_results(session)['date'])).to eq datetime.to_date + end + + it 'should fill in a time input with a String' do + session.fill_in('form_time', with: '06:30A') + session.click_button('awesome') + results = extract_results(session)['time'] + expect(Time.parse(results).strftime('%r')).to eq datetime.strftime('%r') + end + + it 'should fill in a datetime input with a String' do + session.fill_in('form_datetime', with: "06/19/1983\t06:30A") + session.click_button('awesome') + expect(Time.parse(extract_results(session)['datetime'])).to eq datetime + end + end + + describe 'using subclass of selenium driver' do + it 'works' do + session = Capybara::Session.new(:selenium_driver_subclass_with_chrome, TestApp) + session.visit('/form') + expect(session).to have_current_path('/form') + end + end + + describe 'log access' do + before { skip 'Only makes sense in W3C mode' if ENV['W3C'] == 'false' } + + it 'does not error getting log types' do + skip if Gem::Version.new(session.driver.browser.capabilities['chrome']['chromedriverVersion'].split[0]) < Gem::Version.new('75.0.3770.90') + expect do + session.driver.browser.manage.logs.available_types + end.not_to raise_error + end + + it 'does not error when getting logs' do + expect do + session.driver.browser.manage.logs.get(:browser) + end.not_to raise_error + end + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/selenium_spec_chrome_remote.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/selenium_spec_chrome_remote.rb new file mode 100644 index 00000000..afcaf53b --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/selenium_spec_chrome_remote.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'selenium-webdriver' +require 'shared_selenium_session' +require 'shared_selenium_node' +require 'rspec/shared_spec_matchers' + +def selenium_host + ENV.fetch('SELENIUM_HOST', '0.0.0.0') +end + +def selenium_port + ENV.fetch('SELENIUM_PORT', 4444) +end + +def ensure_selenium_running! + timer = Capybara::Helpers.timer(expire_in: 20) + begin + TCPSocket.open(selenium_host, selenium_port) + rescue StandardError + if timer.expired? + raise 'Selenium is not running. ' \ + "You can run a selenium server easily with: \n" \ + ' $ docker-compose up -d selenium_chrome' + else + puts 'Waiting for Selenium docker instance...' + sleep 1 + retry + end + end +end + +def selenium_gte?(version) + defined?(Selenium::WebDriver::VERSION) && (Selenium::WebDriver::VERSION.to_f >= version) +end + +Capybara.register_driver :selenium_chrome_remote do |app| + ensure_selenium_running! + + url = "http://#{selenium_host}:#{selenium_port}/wd/hub" + browser_options = ::Selenium::WebDriver::Chrome::Options.new + + Capybara::Selenium::Driver.new app, + browser: :remote, + desired_capabilities: :chrome, + options: browser_options, + url: url +end + +CHROME_REMOTE_DRIVER = :selenium_chrome_remote + +module TestSessions + Chrome = Capybara::Session.new(CHROME_REMOTE_DRIVER, TestApp) +end + +skipped_tests = %i[response_headers status_code trigger download] + +Capybara::SpecHelper.run_specs TestSessions::Chrome, CHROME_REMOTE_DRIVER.to_s, capybara_skip: skipped_tests do |example| + case example.metadata[:full_description] + when 'Capybara::Session selenium_chrome_remote #attach_file with multipart form should not break when using HTML5 multiple file input uploading multiple files', + 'Capybara::Session selenium_chrome_remote #attach_file with multipart form should fire change once for each set of files uploaded', + 'Capybara::Session selenium_chrome_remote #attach_file with multipart form should fire change once when uploading multiple files from empty' + pending "Selenium with Remote Chrome doesn't support multiple file upload" unless selenium_gte?(3.14) + end +end + +RSpec.describe 'Capybara::Session with remote Chrome' do + include Capybara::SpecHelper + ['Capybara::Session', 'Capybara::Node', Capybara::RSpecMatchers].each do |examples| + include_examples examples, TestSessions::Chrome, CHROME_REMOTE_DRIVER + end + + it 'is considered to be chrome' do + expect(session.driver.browser.browser).to eq :chrome + end + + describe 'log access' do + before { skip 'Only makes sense in W3C mode' if ENV['W3C'] == 'false' } + + it 'does not error when getting log types' do + skip if Gem::Version.new(session.driver.browser.capabilities['chrome']['chromedriverVersion'].split[0]) < Gem::Version.new('75.0.3770.90') + expect do + session.driver.browser.manage.logs.available_types + end.not_to raise_error + end + + it 'does not error when getting logs' do + expect do + session.driver.browser.manage.logs.get(:browser) + end.not_to raise_error + end + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/selenium_spec_edge.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/selenium_spec_edge.rb new file mode 100644 index 00000000..49c307e3 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/selenium_spec_edge.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'selenium-webdriver' +require 'shared_selenium_session' +require 'shared_selenium_node' +require 'rspec/shared_spec_matchers' + +# unless ENV['CI'] +# Selenium::WebDriver::Edge::Service.driver_path = '/usr/local/bin/msedgedriver' +# end + +if ::Selenium::WebDriver::Platform.mac? + Selenium::WebDriver::EdgeChrome.path = '/Applications/Microsoft Edge Dev.app/Contents/MacOS/Microsoft Edge Dev' +end + +Capybara.register_driver :selenium_edge do |app| + # ::Selenium::WebDriver.logger.level = "debug" + # If we don't create an options object the path set above won't be used + browser_options = ::Selenium::WebDriver::EdgeChrome::Options.new + Capybara::Selenium::Driver.new(app, browser: :edge_chrome, options: browser_options).tap do |driver| + driver.browser + driver.download_path = Capybara.save_path + end +end + +module TestSessions + SeleniumEdge = Capybara::Session.new(:selenium_edge, TestApp) +end + +skipped_tests = %i[response_headers status_code trigger] + +Capybara::SpecHelper.log_selenium_driver_version(Selenium::WebDriver::EdgeChrome) if ENV['CI'] + +Capybara::SpecHelper.run_specs TestSessions::SeleniumEdge, 'selenium', capybara_skip: skipped_tests do |example| + case example.metadata[:full_description] + when 'Capybara::Session selenium #attach_file with a block can upload by clicking the file input' + pending "EdgeChrome doesn't allow clicking on file inputs" + end +end + +RSpec.describe 'Capybara::Session with Edge', capybara_skip: skipped_tests do + include Capybara::SpecHelper + ['Capybara::Session', 'Capybara::Node', Capybara::RSpecMatchers].each do |examples| + include_examples examples, TestSessions::SeleniumEdge, :selenium_edge + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/selenium_spec_firefox.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/selenium_spec_firefox.rb new file mode 100644 index 00000000..fa9d9bf2 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/selenium_spec_firefox.rb @@ -0,0 +1,203 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'selenium-webdriver' +require 'shared_selenium_session' +require 'shared_selenium_node' +require 'rspec/shared_spec_matchers' + +browser_options = ::Selenium::WebDriver::Firefox::Options.new +browser_options.headless! if ENV['HEADLESS'] +# browser_options.add_option("log", {"level": "trace"}) + +browser_options.profile = Selenium::WebDriver::Firefox::Profile.new.tap do |profile| + profile['browser.download.dir'] = Capybara.save_path + profile['browser.download.folderList'] = 2 + profile['browser.helperApps.neverAsk.saveToDisk'] = 'text/csv' +end + +Capybara.register_driver :selenium_firefox do |app| + # ::Selenium::WebDriver.logger.level = "debug" + Capybara::Selenium::Driver.new( + app, + browser: :firefox, + options: browser_options, + timeout: 31 + # Get a trace level log from geckodriver + # :driver_opts => { args: ['-vv'] } + ) +end + +Capybara.register_driver :selenium_firefox_not_clear_storage do |app| + Capybara::Selenium::Driver.new( + app, + browser: :firefox, + clear_local_storage: false, + clear_session_storage: false, + options: browser_options + ) +end + +module TestSessions + SeleniumFirefox = Capybara::Session.new(:selenium_firefox, TestApp) +end + +skipped_tests = %i[response_headers status_code trigger] + +Capybara::SpecHelper.log_selenium_driver_version(Selenium::WebDriver::Firefox) if ENV['CI'] + +Capybara::SpecHelper.run_specs TestSessions::SeleniumFirefox, 'selenium', capybara_skip: skipped_tests do |example| + case example.metadata[:full_description] + when 'Capybara::Session selenium node #click should allow multiple modifiers' + pending "Firefox doesn't generate an event for shift+control+click" if firefox_gte?(62, @session) && !Gem.win_platform? + when /^Capybara::Session selenium node #double_click/ + pending "selenium-webdriver/geckodriver doesn't generate double click event" if firefox_lt?(59, @session) + when 'Capybara::Session selenium #accept_prompt should accept the prompt with a blank response when there is a default' + pending "Geckodriver doesn't set a blank response in FF < 63 - https://bugzilla.mozilla.org/show_bug.cgi?id=1486485" if firefox_lt?(63, @session) + when 'Capybara::Session selenium #attach_file with multipart form should fire change once when uploading multiple files from empty' + pending "FF < 62 doesn't support setting all files at once" if firefox_lt?(62, @session) + when 'Capybara::Session selenium #accept_confirm should work with nested modals' + skip 'Broken in 63 <= FF < 69 - https://bugzilla.mozilla.org/show_bug.cgi?id=1487358' if firefox_gte?(63, @session) && firefox_lt?(69, @session) + when 'Capybara::Session selenium #click_link can download a file' + skip 'Need to figure out testing of file downloading on windows platform' if Gem.win_platform? + when 'Capybara::Session selenium #reset_session! removes ALL cookies' + pending "Geckodriver doesn't provide a way to remove cookies outside the current domain" + when /drag_to.*HTML5/ + pending "Firefox < 62 doesn't support a DataTransfer constuctor" if firefox_lt?(62.0, @session) + end +end + +RSpec.describe 'Capybara::Session with firefox' do # rubocop:disable RSpec/MultipleDescribes + include Capybara::SpecHelper + ['Capybara::Session', 'Capybara::Node', Capybara::RSpecMatchers].each do |examples| + include_examples examples, TestSessions::SeleniumFirefox, :selenium_firefox + end + + describe 'filling in Firefox-specific date and time fields with keystrokes' do + let(:datetime) { Time.new(1983, 6, 19, 6, 30) } + let(:session) { TestSessions::SeleniumFirefox } + + before do + session.visit('/form') + end + + it 'should fill in a date input with a String' do + session.fill_in('form_date', with: datetime.to_date.iso8601) + session.click_button('awesome') + expect(Date.parse(extract_results(session)['date'])).to eq datetime.to_date + end + + it 'should fill in a time input with a String' do + session.fill_in('form_time', with: datetime.to_time.strftime('%T')) + session.click_button('awesome') + results = extract_results(session)['time'] + expect(Time.parse(results).strftime('%r')).to eq datetime.strftime('%r') + end + + it 'should fill in a datetime input with a String' do + # FF doesn't currently support datetime-local so this is really just a text input + session.fill_in('form_datetime', with: datetime.iso8601) + session.click_button('awesome') + expect(Time.parse(extract_results(session)['datetime'])).to eq datetime + end + end +end + +RSpec.describe Capybara::Selenium::Driver do + let(:driver) { described_class.new(TestApp, browser: :firefox, options: browser_options) } + + describe '#quit' do + it 'should reset browser when quit' do + expect(driver.browser).to be_truthy + driver.quit + # access instance variable directly so we don't create a new browser instance + expect(driver.instance_variable_get(:@browser)).to be_nil + end + + context 'with errors' do + let!(:original_browser) { driver.browser } + + after do + # Ensure browser is actually quit so we don't leave hanging processe + RSpec::Mocks.space.proxy_for(original_browser).reset + original_browser.quit + end + + it 'warns UnknownError returned during quit because the browser is probably already gone' do + allow(driver).to receive(:warn) + allow(driver.browser).to( + receive(:quit) + .and_raise(Selenium::WebDriver::Error::UnknownError, 'random message') + ) + + expect { driver.quit }.not_to raise_error + expect(driver.instance_variable_get(:@browser)).to be_nil + expect(driver).to have_received(:warn).with(/random message/) + end + + it 'ignores silenced UnknownError returned during quit because the browser is almost definitely already gone' do + allow(driver).to receive(:warn) + allow(driver.browser).to( + receive(:quit) + .and_raise(Selenium::WebDriver::Error::UnknownError, 'Error communicating with the remote browser') + ) + + expect { driver.quit }.not_to raise_error + expect(driver.instance_variable_get(:@browser)).to be_nil + expect(driver).not_to have_received(:warn) + end + end + end + + context 'storage' do + describe '#reset!' do + it 'clears storage by default' do + session = TestSessions::SeleniumFirefox + session.visit('/with_js') + session.find(:css, '#set-storage').click + session.reset! + session.visit('/with_js') + expect(session.driver.browser.local_storage.keys).to be_empty + expect(session.driver.browser.session_storage.keys).to be_empty + end + + it 'does not clear storage when false' do + session = Capybara::Session.new(:selenium_firefox_not_clear_storage, TestApp) + session.visit('/with_js') + session.find(:css, '#set-storage').click + session.reset! + session.visit('/with_js') + expect(session.driver.browser.local_storage.keys).not_to be_empty + expect(session.driver.browser.session_storage.keys).not_to be_empty + end + end + end + + context 'timeout' do + it 'sets the http client read timeout' do + expect(TestSessions::SeleniumFirefox.driver.browser.send(:bridge).http.read_timeout).to eq 31 + end + end +end + +RSpec.describe Capybara::Selenium::Node do + context '#click' do + it 'warns when attempting on a table row' do + session = TestSessions::SeleniumFirefox + session.visit('/tables') + tr = session.find(:css, '#agent_table tr:first-child') + allow(tr.base).to receive(:warn) + tr.click + expect(tr.base).to have_received(:warn).with(/Clicking the first cell in the row instead/) + end + + it 'should allow multiple modifiers', requires: [:js] do + session = TestSessions::SeleniumFirefox + session.visit('with_js') + # Firefox v62+ doesn't generate an event for control+shift+click + session.find(:css, '#click-test').click(:alt, :ctrl, :meta) + # it also triggers a contextmenu event when control is held so don't check click type + expect(session).to have_link('Has been alt control meta') + end + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/selenium_spec_firefox_remote.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/selenium_spec_firefox_remote.rb new file mode 100644 index 00000000..c470cbe4 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/selenium_spec_firefox_remote.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'selenium-webdriver' +require 'shared_selenium_session' +require 'shared_selenium_node' +require 'rspec/shared_spec_matchers' + +def selenium_host + ENV.fetch('SELENIUM_HOST', '0.0.0.0') +end + +def selenium_port + ENV.fetch('SELENIUM_PORT', 4445) +end + +def ensure_selenium_running! + timer = Capybara::Helpers.timer(expire_in: 20) + begin + TCPSocket.open(selenium_host, selenium_port) + rescue StandardError + if timer.expired? + raise 'Selenium is not running. ' \ + "You can run a selenium server easily with: \n" \ + ' $ docker-compose up -d selenium_firefox' + else + puts 'Waiting for Selenium docker instance...' + sleep 1 + retry + end + end +end + +Capybara.register_driver :selenium_firefox_remote do |app| + ensure_selenium_running! + + url = "http://#{selenium_host}:#{selenium_port}/wd/hub" + browser_options = ::Selenium::WebDriver::Firefox::Options.new + + Capybara::Selenium::Driver.new app, + browser: :remote, + desired_capabilities: :firefox, + options: browser_options, + url: url +end + +FIREFOX_REMOTE_DRIVER = :selenium_firefox_remote + +module TestSessions + RemoteFirefox = Capybara::Session.new(FIREFOX_REMOTE_DRIVER, TestApp) +end + +skipped_tests = %i[response_headers status_code trigger download] + +Capybara::SpecHelper.run_specs TestSessions::RemoteFirefox, FIREFOX_REMOTE_DRIVER.to_s, capybara_skip: skipped_tests do |example| + case example.metadata[:full_description] + when 'Capybara::Session selenium_firefox_remote node #click should allow multiple modifiers' + skip "Firefox doesn't generate an event for shift+control+click" if firefox_gte?(62, @session) + when 'Capybara::Session selenium_firefox_remote #accept_prompt should accept the prompt with a blank response when there is a default' + pending "Geckodriver doesn't set a blank response in FF < 63 - https://bugzilla.mozilla.org/show_bug.cgi?id=1486485" if firefox_lt?(63, @session) + when 'Capybara::Session selenium_firefox_remote #attach_file with multipart form should fire change once when uploading multiple files from empty' + pending "FF < 62 doesn't support setting all files at once" if firefox_lt?(62, @session) + when 'Capybara::Session selenium_firefox_remote #reset_session! removes ALL cookies' + pending "Geckodriver doesn't provide a way to remove cookies outside the current domain" + when /#accept_confirm should work with nested modals$/ + # skip because this is timing based and hence flaky when set to pending + skip 'Broken in FF 63 - https://bugzilla.mozilla.org/show_bug.cgi?id=1487358' if firefox_gte?(63, @session) + end +end + +RSpec.describe 'Capybara::Session with remote firefox' do + include Capybara::SpecHelper + ['Capybara::Session', 'Capybara::Node', Capybara::RSpecMatchers].each do |examples| + include_examples examples, TestSessions::RemoteFirefox, FIREFOX_REMOTE_DRIVER + end + + it 'is considered to be firefox' do + expect(session.driver.browser.browser).to eq :firefox + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/selenium_spec_ie.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/selenium_spec_ie.rb new file mode 100644 index 00000000..4a73d92f --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/selenium_spec_ie.rb @@ -0,0 +1,150 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'selenium-webdriver' +require 'shared_selenium_session' +require 'shared_selenium_node' +require 'rspec/shared_spec_matchers' + +# if ENV['CI'] +# if ::Selenium::WebDriver::Service.respond_to? :driver_path= +# ::Selenium::WebDriver::IE::Service +# else +# ::Selenium::WebDriver::IE +# end.driver_path = 'C:\Tools\WebDriver\IEDriverServer.exe' +# end + +def selenium_host + ENV.fetch('SELENIUM_HOST', '192.168.56.102') +end + +def selenium_port + ENV.fetch('SELENIUM_PORT', 4444) +end + +def server_host + ENV.fetch('SERVER_HOST', '10.24.4.135') +end + +Capybara.register_driver :selenium_ie do |app| + # ::Selenium::WebDriver.logger.level = "debug" + options = ::Selenium::WebDriver::IE::Options.new + # options.require_window_focus = true + # options.add_option("log", {"level": "trace"}) + + if ENV['REMOTE'] + Capybara.server_host = server_host + + url = "http://#{selenium_host}:#{selenium_port}/wd/hub" + Capybara::Selenium::Driver.new(app, + browser: :remote, + options: options, + url: url) + else + Capybara::Selenium::Driver.new( + app, + browser: :ie, + options: options + ) + end +end + +module TestSessions + SeleniumIE = Capybara::Session.new(:selenium_ie, TestApp) +end + +TestSessions::SeleniumIE.current_window.resize_to(800, 500) + +skipped_tests = %i[response_headers status_code trigger modals hover form_attribute windows] + +Capybara::SpecHelper.log_selenium_driver_version(Selenium::WebDriver::IE) if ENV['CI'] + +TestSessions::SeleniumIE.current_window.resize_to(1600, 1200) + +Capybara::SpecHelper.run_specs TestSessions::SeleniumIE, 'selenium', capybara_skip: skipped_tests do |example| + case example.metadata[:full_description] + when /#refresh it reposts$/ + skip 'IE insists on prompting without providing a way to suppress' + when /#click_link can download a file$/ + skip 'Not sure how to configure IE for automatic downloading' + when /#fill_in with Date / + pending "IE 11 doesn't support date input types" + when /#click_link_or_button with :disabled option happily clicks on links which incorrectly have the disabled attribute$/ + skip 'IE 11 obeys non-standard disabled attribute on anchor tag' + when /#right_click should allow modifiers$/ + skip "Windows can't :meta click because :meta triggers start menu" + when /#click should allow modifiers$/ + pending "Doesn't work with IE for some unknown reason$" + when /#double_click should allow modifiers$/ + pending "Doesn't work with IE for some unknown reason$" + when /#click should allow multiple modifiers$/ + skip "Windows can't :meta click because :meta triggers start menu" + when /#double_click should allow multiple modifiers$/ + skip "Windows can't :alt double click due to being properties shortcut" + when /#has_css\? should support case insensitive :class and :id options$/ + pending "IE doesn't support case insensitive CSS selectors" + when /#reset_session! removes ALL cookies$/ + pending "IE driver doesn't provide a way to remove ALL cookies" + when /#click_button should send button in document order$/ + pending "IE 11 doesn't support the 'form' attribute" + when /#click_button should follow permanent redirects that maintain method$/ + pending "Window 7 and 8.1 don't support 308 http status code" + when /#scroll_to can scroll an element to the center of the viewport$/, + /#scroll_to can scroll an element to the center of the scrolling element$/ + pending " IE doesn't support ScrollToOptions" + when /#attach_file with multipart form should fire change once for each set of files uploaded$/, + /#attach_file with multipart form should fire change once when uploading multiple files from empty$/, + /#attach_file with multipart form should not break when using HTML5 multiple file input uploading multiple files$/ + pending "IE requires all files be uploaded from same directory. Selenium doesn't provide that." if ENV['REMOTE'] + when %r{#attach_file with multipart form should send content type image/jpeg when uploading an image$} + pending 'IE gets text/plain type for some reason' + # when /#click should not retry clicking when wait is disabled$/ + # Fixed in IEDriverServer 3.141.0.5 + # pending "IE driver doesn't error when clicking on covered elements, it just clicks the wrong element" + when /#click should go to the same page if href is blank$/ + pending 'IE treats blank href as a parent request (against HTML spec)' + when /#attach_file with a block/ + skip 'Hangs IE testing for unknown reason' + when /drag_to.*HTML5/ + pending "IE doesn't support a DataTransfer constuctor" + when /template elements should not be visible/ + skip "IE doesn't support template elements" + when /Element#drop/ + pending "IE doesn't support DataTransfer constructor" + end +end + +RSpec.describe 'Capybara::Session with Internet Explorer', capybara_skip: skipped_tests do # rubocop:disable RSpec/MultipleDescribes + include Capybara::SpecHelper + ['Capybara::Session', 'Capybara::Node', Capybara::RSpecMatchers].each do |examples| + include_examples examples, TestSessions::SeleniumIE, :selenium_ie + end +end + +RSpec.describe Capybara::Selenium::Node do + it '#right_click should allow modifiers' do + # pending "Actions API doesn't appear to work for this" + session = TestSessions::SeleniumIE + session.visit('/with_js') + el = session.find(:css, '#click-test') + el.right_click(:control) + expect(session).to have_link('Has been control right clicked') + end + + it '#click should allow multiple modifiers' do + # pending "Actions API doesn't appear to work for this" + session = TestSessions::SeleniumIE + session.visit('with_js') + # IE triggers system behavior with :meta so can't use those here + session.find(:css, '#click-test').click(:ctrl, :shift, :alt) + expect(session).to have_link('Has been alt control shift clicked') + end + + it '#double_click should allow modifiers' do + # pending "Actions API doesn't appear to work for this" + session = TestSessions::SeleniumIE + session.visit('/with_js') + session.find(:css, '#click-test').double_click(:shift) + expect(session).to have_link('Has been shift double clicked') + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/selenium_spec_safari.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/selenium_spec_safari.rb new file mode 100644 index 00000000..1273a04b --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/selenium_spec_safari.rb @@ -0,0 +1,148 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'selenium-webdriver' +require 'shared_selenium_session' +require 'shared_selenium_node' +require 'rspec/shared_spec_matchers' + +SAFARI_DRIVER = :selenium_safari + +if ::Selenium::WebDriver::Service.respond_to? :driver_path= + ::Selenium::WebDriver::Safari::Service +else + ::Selenium::WebDriver::Safari +end.driver_path = '/Applications/Safari Technology Preview.app/Contents/MacOS/safaridriver' + +browser_options = ::Selenium::WebDriver::Safari::Options.new +# browser_options.headless! if ENV['HEADLESS'] +# browser_options.add_option(:w3c, !!ENV['W3C']) + +Capybara.register_driver :selenium_safari do |app| + Capybara::Selenium::Driver.new(app, browser: :safari, options: browser_options, timeout: 30).tap do |driver| + # driver.browser.download_path = Capybara.save_path + end +end + +Capybara.register_driver :selenium_safari_not_clear_storage do |app| + safari_options = { + browser: :safari, + options: browser_options + } + Capybara::Selenium::Driver.new(app, safari_options.merge(clear_local_storage: false, clear_session_storage: false)) +end + +module TestSessions + Safari = Capybara::Session.new(SAFARI_DRIVER, TestApp) +end + +skipped_tests = %i[response_headers status_code trigger windows drag] + +Capybara::SpecHelper.log_selenium_driver_version(Selenium::WebDriver::Safari) if ENV['CI'] + +Capybara::SpecHelper.run_specs TestSessions::Safari, SAFARI_DRIVER.to_s, capybara_skip: skipped_tests do |example| + case example.metadata[:full_description] + when /click_link can download a file/ + skip "safaridriver doesn't provide a way to set the download directory" + when /Capybara::Session selenium_safari Capybara::Window#maximize/ + pending "Safari headless doesn't support maximize" if ENV['HEADLESS'] + when /Capybara::Session selenium_safari #visit without a server/, + /Capybara::Session selenium_safari #visit with Capybara.app_host set should override server/, + /Capybara::Session selenium_safari #reset_session! When reuse_server == false raises any standard errors caught inside the server during a second session/ + skip "Safari webdriver doesn't support multiple sessions" + when /Capybara::Session selenium_safari #click_link with alternative text given to a contained image/, + 'Capybara::Session selenium_safari #click_link_or_button with enable_aria_label should click on link' + pending 'safaridriver thinks these links are non-interactable for some unknown reason' + when /Capybara::Session selenium_safari #attach_file with a block can upload by clicking the file input/ + skip "safaridriver doesn't allow clicking on file inputs" + when /Capybara::Session selenium_safari #within_frame works if the frame is closed/, + /Capybara::Session selenium_safari #switch_to_frame works if the frame is closed/ + skip 'Safari has a race condition when clicking an element that causes the frame to close. It will sometimes raise a NoSuchFrameError' + when /Capybara::Session selenium_safari #reset_session! removes ALL cookies/ + skip 'Safari webdriver can only remove cookies for the current domain' + when /Capybara::Session selenium_safari #refresh it reposts/ + skip "Safari opens an alert that can't be closed" + when 'Capybara::Session selenium_safari node #double_click should allow to adjust the offset', + 'Capybara::Session selenium_safari node #double_click should double click an element' + pending "safardriver doesn't generate a double click event" + when 'Capybara::Session selenium_safari node #click should allow multiple modifiers', + /Capybara::Session selenium_safari node #(click|right_click|double_click) should allow modifiers/ + pending "safaridriver doesn't take key state into account when clicking" + when 'Capybara::Session selenium_safari #fill_in on a pre-populated textfield with a reformatting onchange should trigger change when clearing field' + pending "safardriver clear doesn't generate change event" + when 'Capybara::Session selenium_safari #go_back should fetch a response from the driver from the previous page', + 'Capybara::Session selenium_safari #go_forward should fetch a response from the driver from the previous page' + skip 'safaridriver loses the ability to find elements in the document after `go_back`' + when /drag_to.*HTML5/ + pending "Safari doesn't support" + end +end + +RSpec.describe 'Capybara::Session with safari' do + include Capybara::SpecHelper + ['Capybara::Session', 'Capybara::Node', Capybara::RSpecMatchers].each do |examples| + include_examples examples, TestSessions::Safari, SAFARI_DRIVER + end + + context 'storage' do + describe '#reset!' do + it 'clears storage by default' do + session = TestSessions::Safari + session.visit('/with_js') + session.find(:css, '#set-storage').click + session.reset! + session.visit('/with_js') + expect(session.evaluate_script('Object.keys(localStorage)')).to be_empty + expect(session.evaluate_script('Object.keys(sessionStorage)')).to be_empty + end + + it 'does not clear storage when false' do + skip "Safari webdriver doesn't support multiple sessions" + session = Capybara::Session.new(:selenium_safari_not_clear_storage, TestApp) + session.visit('/with_js') + session.find(:css, '#set-storage').click + session.reset! + session.visit('/with_js') + expect(session.evaluate_script('Object.keys(localStorage)')).not_to be_empty + expect(session.evaluate_script('Object.keys(sessionStorage)')).not_to be_empty + end + end + end + + context 'timeout' do + it 'sets the http client read timeout' do + expect(TestSessions::Safari.driver.browser.send(:bridge).http.read_timeout).to eq 30 + end + end + + describe 'filling in Safari-specific date and time fields with keystrokes' do + let(:datetime) { Time.new(1983, 6, 19, 6, 30) } + let(:session) { TestSessions::Safari } + + before do + session.visit('/form') + end + + it 'should fill in a date input with a String' do + pending "Safari doesn't support date inputs" + session.fill_in('form_date', with: '06/19/1983') + session.click_button('awesome') + expect(Date.parse(extract_results(session)['date'])).to eq datetime.to_date + end + + it 'should fill in a time input with a String' do + # Safari doesn't support time inputs - so this is just a text input + session.fill_in('form_time', with: '06:30A') + session.click_button('awesome') + results = extract_results(session)['time'] + expect(Time.parse(results).strftime('%r')).to eq datetime.strftime('%r') + end + + it 'should fill in a datetime input with a String' do + pending "Safari doesn't support datetime inputs" + session.fill_in('form_datetime', with: "06/19/1983\t06:30A") + session.click_button('awesome') + expect(Time.parse(extract_results(session)['datetime'])).to eq datetime + end + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/server_spec.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/server_spec.rb new file mode 100644 index 00000000..9bb5bb49 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/server_spec.rb @@ -0,0 +1,250 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Capybara::Server do + it 'should spool up a rack server' do + app = proc { |_env| [200, {}, ['Hello Server!']] } + server = described_class.new(app).boot + + res = Net::HTTP.start(server.host, server.port) { |http| http.get('/') } + + expect(res.body).to include('Hello Server') + end + + it 'should do nothing when no server given' do + expect do + described_class.new(nil).boot + end.not_to raise_error + end + + it 'should bind to the specified host' do + # TODO: travis with jruby in container mode has an issue with this test + skip 'This platform has an issue with this test' if (ENV['TRAVIS'] && (RUBY_ENGINE == 'jruby')) || Gem.win_platform? + + begin + app = proc { |_env| [200, {}, ['Hello Server!']] } + + Capybara.server_host = '127.0.0.1' + server = described_class.new(app).boot + res = Net::HTTP.get(URI("http://127.0.0.1:#{server.port}")) + expect(res).to eq('Hello Server!') + + Capybara.server_host = '0.0.0.0' + server = described_class.new(app).boot + res = Net::HTTP.get(URI("http://127.0.0.1:#{server.port}")) + expect(res).to eq('Hello Server!') + ensure + Capybara.server_host = nil + end + end + + it 'should use specified port' do + Capybara.server_port = 22789 + + app = proc { |_env| [200, {}, ['Hello Server!']] } + server = described_class.new(app).boot + + res = Net::HTTP.start(server.host, 22789) { |http| http.get('/') } + expect(res.body).to include('Hello Server') + + Capybara.server_port = nil + end + + it 'should use given port' do + app = proc { |_env| [200, {}, ['Hello Server!']] } + server = described_class.new(app, port: 22790).boot + + res = Net::HTTP.start(server.host, 22790) { |http| http.get('/') } + expect(res.body).to include('Hello Server') + + Capybara.server_port = nil + end + + it 'should find an available port' do + responses = ['Hello Server!', 'Hello Second Server!'] + apps = responses.map do |response| + proc { |_env| [200, {}, [response]] } + end + servers = apps.map { |app| described_class.new(app).boot } + + servers.each_with_index do |server, idx| + result = Net::HTTP.start(server.host, server.port) { |http| http.get('/') } + expect(result.body).to include(responses[idx]) + end + end + + it 'should return its #base_url' do + app = proc { |_env| [200, {}, ['Hello Server!']] } + server = described_class.new(app).boot + uri = ::Addressable::URI.parse(server.base_url) + expect(uri.to_hash).to include(scheme: 'http', host: server.host, port: server.port) + end + + it 'should support SSL' do + begin + key = File.join(Dir.pwd, 'spec', 'fixtures', 'key.pem') + cert = File.join(Dir.pwd, 'spec', 'fixtures', 'certificate.pem') + Capybara.server = :puma, { Host: "ssl://#{Capybara.server_host}?key=#{key}&cert=#{cert}" } + app = proc { |_env| [200, {}, ['Hello SSL Server!']] } + server = described_class.new(app).boot + + expect do + Net::HTTP.start(server.host, server.port, max_retries: 0) { |http| http.get('/__identify__') } + end.to(raise_error do |e| + expect(e.is_a?(EOFError) || e.is_a?(Net::ReadTimeout)).to be true + end) + + res = Net::HTTP.start(server.host, server.port, use_ssl: true, verify_mode: OpenSSL::SSL::VERIFY_NONE) do |https| + https.get('/') + end + + expect(res.body).to include('Hello SSL Server!') + uri = ::Addressable::URI.parse(server.base_url) + expect(uri.to_hash).to include(scheme: 'https', host: server.host, port: server.port) + ensure + Capybara.server = :default + end + end + + context 'When Capybara.reuse_server is true' do + let!(:old_reuse_server) { Capybara.reuse_server } + + before do + Capybara.reuse_server = true + end + + after do + Capybara.reuse_server = old_reuse_server + end + + it 'should use the existing server if it already running' do + app = proc { |_env| [200, {}, ['Hello Server!']] } + + servers = Array.new(2) { described_class.new(app).boot } + + servers.each do |server| + res = Net::HTTP.start(server.host, server.port) { |http| http.get('/') } + expect(res.body).to include('Hello Server') + end + + expect(servers[0].port).to eq(servers[1].port) + end + + it 'detects and waits for all reused server sessions pending requests' do + done = 0 + + app = proc do |env| + request = Rack::Request.new(env) + sleep request.params['wait_time'].to_f + done += 1 + [200, {}, ['Hello Server!']] + end + + server1 = described_class.new(app).boot + server2 = described_class.new(app).boot + + expect do + start_request(server1, 1.0) + start_request(server2, 3.0) + server1.wait_for_pending_requests + end.to change { done }.from(0).to(2) + expect(server2.send(:pending_requests?)).to eq(false) + end + end + + context 'When Capybara.reuse_server is false' do + before do + @old_reuse_server = Capybara.reuse_server + Capybara.reuse_server = false + end + + after do + Capybara.reuse_server = @old_reuse_server # rubocop:disable RSpec/InstanceVariable + end + + it 'should not reuse an already running server' do + app = proc { |_env| [200, {}, ['Hello Server!']] } + + servers = Array.new(2) { described_class.new(app).boot } + + servers.each do |server| + res = Net::HTTP.start(server.host, server.port) { |http| http.get('/') } + expect(res.body).to include('Hello Server') + end + + expect(servers[0].port).not_to eq(servers[1].port) + end + + it 'detects and waits for only one sessions pending requests' do + done = 0 + + app = proc do |env| + request = Rack::Request.new(env) + sleep request.params['wait_time'].to_f + done += 1 + [200, {}, ['Hello Server!']] + end + + server1 = described_class.new(app).boot + server2 = described_class.new(app).boot + + expect do + start_request(server1, 1.0) + start_request(server2, 3.0) + server1.wait_for_pending_requests + end.to change { done }.from(0).to(1) + expect(server2.send(:pending_requests?)).to eq(true) + expect do + server2.wait_for_pending_requests + end.to change { done }.from(1).to(2) + end + end + + it 'should raise server errors when the server errors before the timeout' do + begin + Capybara.register_server :kaboom do + sleep 0.1 + raise 'kaboom' + end + Capybara.server = :kaboom + + expect do + described_class.new(proc { |e| }).boot + end.to raise_error(RuntimeError, 'kaboom') + ensure + Capybara.server = :default + end + end + + it 'is not #responsive? when Net::HTTP raises a SystemCallError' do + app = -> { [200, {}, ['Hello, world']] } + server = described_class.new(app) + allow(Net::HTTP).to receive(:start).and_raise(SystemCallError.allocate) + expect(server.responsive?).to eq false + end + + [EOFError, Net::ReadTimeout].each do |err| + it "should attempt an HTTPS connection if HTTP connection returns #{err}" do + app = -> { [200, {}, ['Hello, world']] } + ordered_errors = [Errno::ECONNREFUSED, err] + allow(Net::HTTP).to receive(:start).with(anything, anything, hash_excluding(:use_ssl)) do + raise ordered_errors.shift + end + response = Net::HTTPSuccess.allocate + allow(response).to receive(:body).and_return app.object_id.to_s + allow(Net::HTTP).to receive(:start).with(anything, anything, hash_including(use_ssl: true)).and_return(response).once + described_class.new(app).boot + expect(Net::HTTP).to have_received(:start).exactly(3).times + end + end + + def start_request(server, wait_time) + # Start request, but don't wait for it to finish + socket = TCPSocket.new(server.host, server.port) + socket.write "GET /?wait_time=#{wait_time} HTTP/1.0\r\n\r\n" + sleep 0.1 + socket.close + sleep 0.1 + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/session_spec.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/session_spec.rb new file mode 100644 index 00000000..9367f683 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/session_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Capybara::Session do + context '#new' do + it 'should raise an error if passed non-existent driver' do + expect do + described_class.new(:quox, TestApp).driver + end.to raise_error(Capybara::DriverNotFoundError) + end + + it 'verifies a passed app is a rack app' do + expect do + described_class.new(:unknown, random: 'hash') + end.to raise_error TypeError, 'The second parameter to Session::new should be a rack app if passed.' + end + end + + context 'current_driver' do + around do |example| + orig_driver = Capybara.current_driver + example.run + Capybara.current_driver = orig_driver + end + + it 'is global when threadsafe false' do + Capybara.threadsafe = false + Capybara.current_driver = :selenium + thread = Thread.new do + Capybara.current_driver = :random + end + thread.join + expect(Capybara.current_driver).to eq :random + end + + it 'is thread specific threadsafe true' do + Capybara.threadsafe = true + Capybara.current_driver = :selenium + thread = Thread.new do + Capybara.current_driver = :random + end + thread.join + expect(Capybara.current_driver).to eq :selenium + end + end + + context 'session_name' do + around do |example| + orig_name = Capybara.session_name + example.run + Capybara.session_name = orig_name + end + + it 'is global when threadsafe false' do + Capybara.threadsafe = false + Capybara.session_name = 'sess1' + thread = Thread.new do + Capybara.session_name = 'sess2' + end + thread.join + expect(Capybara.session_name).to eq 'sess2' + end + + it 'is thread specific when threadsafe true' do + Capybara.threadsafe = true + Capybara.session_name = 'sess1' + thread = Thread.new do + Capybara.session_name = 'sess2' + end + thread.join + expect(Capybara.session_name).to eq 'sess1' + end + end + + context 'quit' do + it 'will reset the driver' do + session = described_class.new(:rack_test, TestApp) + driver = session.driver + session.quit + expect(session.driver).not_to eql driver + end + + it 'resets the document' do + session = described_class.new(:rack_test, TestApp) + document = session.document + session.quit + expect(session.document.base).not_to eql document.base + end + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/shared_selenium_node.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/shared_selenium_node.rb new file mode 100644 index 00000000..62ffba38 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/shared_selenium_node.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'selenium-webdriver' + +RSpec.shared_examples 'Capybara::Node' do |session, _mode| + let(:session) { session } + + context '#content_editable?' do + it 'returns true when the element is content editable' do + session.visit('/with_js') + expect(session.find(:css, '#existing_content_editable').base.content_editable?).to be true + expect(session.find(:css, '#existing_content_editable_child').base.content_editable?).to be true + end + + it 'returns false when the element is not content editable' do + session.visit('/with_js') + expect(session.find(:css, '#drag').base.content_editable?).to be false + end + end + + context '#send_keys' do + it 'should process space' do + session.visit('/form') + session.find(:css, '#address1_city').send_keys('ocean', [:shift, :space, 'side']) + expect(session.find(:css, '#address1_city').value).to eq 'ocean SIDE' + end + end + + context '#visible?' do + let(:bridge) do + session.driver.browser.send(:bridge) + end + + around do |example| + native_displayed = session.driver.options[:native_displayed] + example.run + session.driver.options[:native_displayed] = native_displayed + end + + before do + allow(bridge).to receive(:execute_atom).and_call_original + end + + it 'will use native displayed if told to' do + pending "Chromedriver < 76.0.3809.25 doesn't support native displayed in W3C mode" if chrome_lt?(76, session) && (ENV['W3C'] != 'false') + + session.driver.options[:native_displayed] = true + session.visit('/form') + session.find(:css, '#address1_city', visible: true) + + expect(bridge).not_to have_received(:execute_atom) + end + + it "won't use native displayed if told not to" do + skip 'Non-W3C uses native' if chrome?(session) && (ENV['W3C'] == 'false') + + session.driver.options[:native_displayed] = false + session.visit('/form') + session.find(:css, '#address1_city', visible: true) + + expect(bridge).to have_received(:execute_atom).with(:isDisplayed, any_args) + end + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/shared_selenium_session.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/shared_selenium_session.rb new file mode 100644 index 00000000..40ed7642 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/shared_selenium_session.rb @@ -0,0 +1,466 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'selenium-webdriver' + +RSpec.shared_examples 'Capybara::Session' do |session, mode| + let(:session) { session } + + context 'with selenium driver' do + describe '#driver' do + it 'should be a selenium driver' do + expect(session.driver).to be_an_instance_of(Capybara::Selenium::Driver) + end + end + + describe '#mode' do + it 'should remember the mode' do + expect(session.mode).to eq(mode) + end + end + + describe '#reset!' do + it 'freshly reset session should not be touched' do + session.instance_variable_set(:@touched, true) + session.reset! + expect(session.instance_variable_get(:@touched)).to eq false + end + end + + describe 'exit codes' do + let(:env) { { 'SELENIUM_BROWSER' => session.driver.options[:browser].to_s } } + let!(:orig_dir) { Dir.getwd } + + before do + Dir.chdir(File.join(File.dirname(__FILE__), '..')) + end + + after do + Dir.chdir(orig_dir) + end + + it 'should have return code 1 when running selenium_driver_rspec_failure.rb' do + skip 'only setup for local non-headless' if headless_or_remote? + skip 'Not setup for edge' if edge?(session) + + system(env, 'rspec spec/fixtures/selenium_driver_rspec_failure.rb', out: File::NULL, err: File::NULL) + expect($CHILD_STATUS.exitstatus).to eq(1) + end + + it 'should have return code 0 when running selenium_driver_rspec_success.rb' do + skip 'only setup for local non-headless' if headless_or_remote? + skip 'Not setup for edge' if edge?(session) + + system(env, 'rspec spec/fixtures/selenium_driver_rspec_success.rb', out: File::NULL, err: File::NULL) + expect($CHILD_STATUS.exitstatus).to eq(0) + end + end + + describe '#accept_alert', requires: [:modals] do + it 'supports a blockless mode' do + session.visit('/with_js') + session.click_link('Open alert') + session.accept_alert + expect { session.driver.browser.switch_to.alert }.to raise_error(session.driver.send(:modal_error)) + end + + it 'can be called before visiting' do + session.accept_alert 'Initial alert' do + session.visit('/initial_alert') + end + expect(session).to have_text('Initial alert page') + end + end + + context '#fill_in_with empty string and no options' do + it 'should trigger change when clearing a field' do + pending "safaridriver doesn't trigger change for clear" if safari?(session) + session.visit('/with_js') + session.fill_in('with_change_event', with: '') + # click outside the field to trigger the change event + session.find(:css, 'body').click + expect(session).to have_selector(:css, '.change_event_triggered', match: :one) + end + end + + context '#fill_in with { :clear => :backspace } fill_option', requires: [:js] do + before do + # Firefox has an issue with change events if the main window doesn't think it's focused + session.execute_script('window.focus()') + end + + it 'should fill in a field, replacing an existing value' do + session.visit('/form') + session.fill_in('form_first_name', + with: 'Harry', + fill_options: { clear: :backspace }) + expect(session.find(:fillable_field, 'form_first_name').value).to eq('Harry') + end + + it 'should fill in a field, replacing an existing value, even with caret position' do + session.visit('/form') + session.find(:css, '#form_first_name').execute_script <<-JS + this.focus(); + this.setSelectionRange(0, 0); + JS + + session.fill_in('form_first_name', + with: 'Harry', + fill_options: { clear: :backspace }) + expect(session.find(:fillable_field, 'form_first_name').value).to eq('Harry') + end + + it 'should fill in if the option is set via global option' do + Capybara.default_set_options = { clear: :backspace } + session.visit('/form') + session.fill_in('form_first_name', with: 'Thomas') + expect(session.find(:fillable_field, 'form_first_name').value).to eq('Thomas') + end + + it 'should only trigger onchange once' do + session.visit('/with_js') + sleep 2 if safari?(session) # Safari needs a delay (to load event handlers maybe ???) + session.fill_in('with_change_event', + with: 'some value', + fill_options: { clear: :backspace }) + # click outside the field to trigger the change event + session.find(:css, '#with_focus_event').click + expect(session.find(:css, '.change_event_triggered', match: :one, wait: 5)).to have_text 'some value' + end + + it 'should trigger change when clearing field' do + session.visit('/with_js') + session.fill_in('with_change_event', + with: '', + fill_options: { clear: :backspace }) + # click outside the field to trigger the change event + session.find(:css, '#with_focus_event').click + expect(session).to have_selector(:css, '.change_event_triggered', match: :one, wait: 5) + end + + it 'should trigger input event field_value.length times' do + session.visit('/with_js') + session.fill_in('with_change_event', + with: '', + fill_options: { clear: :backspace }) + # click outside the field to trigger the change event + # session.find(:css, 'body').click + session.find(:css, 'h1', text: 'FooBar').click + expect(session).to have_xpath('//p[@class="input_event_triggered"]', count: 13) + end + end + + context '#fill_in with { clear: :none } fill_options' do + it 'should append to content in a field' do + pending 'Safari overwrites by default - need to figure out a workaround' if safari?(session) + + session.visit('/form') + session.fill_in('form_first_name', + with: 'Harry', + fill_options: { clear: :none }) + expect(session.find(:fillable_field, 'form_first_name').value).to eq('JohnHarry') + end + end + + context '#fill_in with Date' do + before do + session.visit('/form') + session.find(:css, '#form_date').execute_script <<-JS + window.capybara_formDateFiredEvents = []; + var fd = this; + ['focus', 'input', 'change'].forEach(function(eventType) { + fd.addEventListener(eventType, function() { window.capybara_formDateFiredEvents.push(eventType); }); + }); + JS + # work around weird FF issue where it would create an extra focus issue in some cases + session.find(:css, 'h1', text: 'Form').click + # session.find(:css, 'body').click + end + + it 'should generate standard events on changing value' do + pending "IE 11 doesn't support date input type" if ie?(session) + pending "Safari doesn't support date input type" if safari?(session) + session.fill_in('form_date', with: Date.today) + expect(session.evaluate_script('window.capybara_formDateFiredEvents')).to eq %w[focus input change] + end + + it 'should not generate input and change events if the value is not changed' do + pending "IE 11 doesn't support date input type" if ie?(session) + pending "Safari doesn't support date input type" if safari?(session) + session.fill_in('form_date', with: Date.today) + session.fill_in('form_date', with: Date.today) + # Chrome adds an extra focus for some reason - ok for now + expect(session.evaluate_script('window.capybara_formDateFiredEvents')).to eq(%w[focus input change]) + end + end + + context '#fill_in with { clear: Array } fill_options' do + it 'should pass the array through to the element' do + # this is mainly for use with [[:control, 'a'], :backspace] - however since that is platform dependant I'm testing with something less useful + session.visit('/form') + session.fill_in('form_first_name', + with: 'Harry', + fill_options: { clear: [[:shift, 'abc'], :backspace] }) + expect(session.find(:fillable_field, 'form_first_name').value).to eq('JohnABHarry') + end + end + + describe '#path' do + it 'returns xpath' do + # this is here because it is testing for an XPath that is specific to the algorithm used in the selenium driver + session.visit('/path') + element = session.find(:link, 'Second Link') + expect(element.path).to eq('/HTML/BODY[1]/DIV[2]/A[1]') + end + + it 'handles namespaces in xhtml' do + pending "IE 11 doesn't handle all XPath querys (namespace-uri, etc)" if ie?(session) + session.visit '/with_namespace' + rect = session.find(:css, 'div svg rect:first-of-type') + expect(rect.path).to eq("/HTML/BODY[1]/DIV[1]/*[local-name()='svg' and namespace-uri()='http://www.w3.org/2000/svg'][1]/*[local-name()='rect' and namespace-uri()='http://www.w3.org/2000/svg'][1]") + expect(session.find(:xpath, rect.path)).to eq rect + end + + it 'handles default namespaces in html5' do + pending "IE 11 doesn't handle all XPath querys (namespace-uri, etc)" if ie?(session) + session.visit '/with_html5_svg' + rect = session.find(:css, 'div svg rect:first-of-type') + expect(rect.path).to eq("/HTML/BODY[1]/DIV[1]/*[local-name()='svg' and namespace-uri()='http://www.w3.org/2000/svg'][1]/*[local-name()='rect' and namespace-uri()='http://www.w3.org/2000/svg'][1]") + expect(session.find(:xpath, rect.path)).to eq rect + end + + it 'handles case sensitive element names' do + pending "IE 11 doesn't handle all XPath querys (namespace-uri, etc)" if ie?(session) + session.visit '/with_namespace' + els = session.all(:css, 'div *', visible: :all) + expect { els.map(&:path) }.not_to raise_error + lg = session.find(:css, 'div linearGradient', visible: :all) + expect(session.find(:xpath, lg.path, visible: :all)).to eq lg + end + end + + describe 'all with disappearing elements' do + it 'ignores stale elements in results' do + session.visit('/path') + elements = session.all(:link) { |_node| raise Selenium::WebDriver::Error::StaleElementReferenceError } + expect(elements.size).to eq 0 + end + end + + describe '#evaluate_script' do + it 'can return an element' do + session.visit('/form') + element = session.evaluate_script("document.getElementById('form_title')") + expect(element).to eq session.find(:id, 'form_title') + end + + it 'can return arrays of nested elements' do + session.visit('/form') + elements = session.evaluate_script('document.querySelectorAll("#form_city option")') + expect(elements).to all(be_instance_of Capybara::Node::Element) + expect(elements).to eq session.find(:css, '#form_city').all(:css, 'option').to_a + end + + it 'can return hashes with elements' do + session.visit('/form') + result = session.evaluate_script("{ a: document.getElementById('form_title'), b: {c: document.querySelectorAll('#form_city option')}}") + expect(result).to eq( + 'a' => session.find(:id, 'form_title'), + 'b' => { + 'c' => session.find(:css, '#form_city').all(:css, 'option').to_a + } + ) + end + + describe '#evaluate_async_script' do + it 'will timeout if the script takes too long' do + skip 'safaridriver returns the wrong error type' if safari?(session) + session.visit('/with_js') + expect do + session.using_wait_time(1) do + session.evaluate_async_script('var cb = arguments[0]; setTimeout(function(){ cb(null) }, 3000)') + end + end.to raise_error Selenium::WebDriver::Error::ScriptTimeoutError + end + end + end + + describe 'Element#inspect' do + it 'outputs obsolete elements' do + session.visit('/form') + el = session.find(:button, 'Click me!').click + expect(session).to have_no_button('Click me!') + allow(el).to receive(:synchronize) + expect(el.inspect).to eq 'Obsolete #' + expect(el).not_to have_received(:synchronize) + end + end + + describe 'Element#click' do + it 'should handle fixed headers/footers' do + session.visit('/with_fixed_header_footer') + # session.click_link('Go to root') + session.find(:link, 'Go to root').click + expect(session).to have_current_path('/') + end + end + + describe 'Capybara#Node#attach_file' do + it 'can attach a directory' do + pending "Geckodriver doesn't support uploading a directory" if firefox?(session) + pending "Selenium remote doesn't support transferring a directory" if remote?(session) + pending "Headless Chrome doesn't support directory upload - https://bugs.chromium.org/p/chromedriver/issues/detail?id=2521&q=directory%20upload&colspec=ID%20Status%20Pri%20Owner%20Summary" if chrome?(session) && ENV['HEADLESS'] + pending "IE doesn't support uploading a directory" if ie?(session) + pending 'Chrome/chromedriver 73 breaks this' if chrome?(session) && chrome_gte?(73, session) && chrome_lt?(75, session) + pending "Safari doesn't support uploading a directory" if safari?(session) + # pending "Edge/msedgedriver doesn't support directory upload" if edge?(session) && edge_gte?(75, session) + + session.visit('/form') + test_file_dir = File.expand_path('./fixtures', File.dirname(__FILE__)) + session.attach_file('Directory Upload', test_file_dir) + session.click_button('Upload Multiple') + expect(session.body).to include('5 | ') # number of files + end + + it 'can attach a relative file' do + pending 'Geckdoriver on windows requires alternate file separator which path expansion replaces' if Gem.win_platform? && firefox?(session) + + session.visit('/form') + session.attach_file('Single Document', 'spec/fixtures/capybara.csv') + session.click_button('Upload Single') + expect(session.body).to include('Content-type: text/csv') + end + end + + context 'Windows' do + it "can't close the primary window" do + expect do + session.current_window.close + end.to raise_error(ArgumentError, 'Not allowed to close the primary window') + end + end + + # rubocop:disable RSpec/InstanceVariable + describe 'Capybara#disable_animation' do + context 'when set to `true`' do + before(:context) do # rubocop:disable RSpec/BeforeAfterAll + skip "Safari doesn't support multiple sessions" if safari?(session) + # NOTE: Although Capybara.SpecHelper.reset! sets Capybara.disable_animation to false, + # it doesn't affect any of these tests because the settings are applied per-session + Capybara.disable_animation = true + @animation_session = Capybara::Session.new(session.mode, TestApp.new) + end + + it 'should disable CSS transitions' do + @animation_session.visit('with_animation') + @animation_session.click_link('transition me away') + expect(@animation_session).to have_no_link('transition me away', wait: 0.5) + end + + it 'should disable CSS animations (set to 0s)' do + @animation_session.visit('with_animation') + @animation_session.click_link('animate me away') + expect(@animation_session).to have_no_link('animate me away', wait: 0.5) + end + + it 'should disable CSS animations on pseudo elements (set to 0s)' do + @animation_session.visit('with_animation') + @animation_session.find_link('animate me away').right_click + expect(@animation_session).to have_content('Animation Ended', wait: 0.1) + end + end + + context 'if we pass in css that matches elements' do + before(:context) do # rubocop:disable RSpec/BeforeAfterAll + skip "safaridriver doesn't support multiple sessions" if safari?(session) + # NOTE: Although Capybara.SpecHelper.reset! sets Capybara.disable_animation to false, + # it doesn't affect any of these tests because the settings are applied per-session + Capybara.disable_animation = '#with_animation a' + @animation_session_with_matching_css = Capybara::Session.new(session.mode, TestApp.new) + end + + it 'should disable CSS transitions' do + @animation_session_with_matching_css.visit('with_animation') + @animation_session_with_matching_css.click_link('transition me away') + expect(@animation_session_with_matching_css).to have_no_link('transition me away', wait: 0.5) + end + + it 'should disable CSS animations' do + @animation_session_with_matching_css.visit('with_animation') + @animation_session_with_matching_css.click_link('animate me away') + expect(@animation_session_with_matching_css).to have_no_link('animate me away', wait: 0.5) + end + end + + context 'if we pass in css that does not match elements' do + before(:context) do # rubocop:disable RSpec/BeforeAfterAll + skip "Safari doesn't support multiple sessions" if safari?(session) + # NOTE: Although Capybara.SpecHelper.reset! sets Capybara.disable_animation to false, + # it doesn't affect any of these tests because the settings are applied per-session + Capybara.disable_animation = '.this-class-matches-nothing' + @animation_session_without_matching_css = Capybara::Session.new(session.mode, TestApp.new) + end + + it 'should not disable CSS transitions' do + @animation_session_without_matching_css.visit('with_animation') + @animation_session_without_matching_css.click_link('transition me away') + sleep 0.5 # Wait long enough for click to have been processed + expect(@animation_session_without_matching_css).to have_link('transition me away', wait: false) + expect(@animation_session_without_matching_css).to have_no_link('transition me away', wait: 5) + end + + it 'should not disable CSS animations' do + @animation_session_without_matching_css.visit('with_animation') + @animation_session_without_matching_css.click_link('animate me away') + sleep 0.5 # Wait long enough for click to have been processed + expect(@animation_session_without_matching_css).to have_link('animate me away', wait: false) + expect(@animation_session_without_matching_css).to have_no_link('animate me away', wait: 5) + end + end + end + # rubocop:enable RSpec/InstanceVariable + + describe ':element selector' do + it 'can find html5 svg elements' do + session.visit('with_html5_svg') + expect(session).to have_selector(:element, :svg) + expect(session).to have_selector(:element, :rect, visible: true) + expect(session).to have_selector(:element, :circle) + expect(session).to have_selector(:element, :linearGradient, visible: :all) + end + + it 'can query attributes with strange characters' do + session.visit('/form') + expect(session).to have_selector(:element, "{custom}": true) + expect(session).to have_selector(:element, "{custom}": 'abcdef') + end + end + + describe 'with react' do + context 'controlled components' do + it 'can set and clear a text field' do + skip "This test doesn't support older browsers" if ie?(session) + # session.visit 'https://reactjs.org/docs/forms.html' + # session.all(:css, 'h2#controlled-components ~ p a', text: 'Try it on CodePen')[0].click + # copied into local view + session.visit 'react' + # Not necessary when accessed locally + # session.within_frame(:css, 'iframe.result-iframe:not([src=""])', wait: 10) do + session.fill_in('Name:', with: 'abc') + session.accept_prompt 'A name was submitted: abc' do + session.click_button('Submit') + end + session.fill_in('Name:', with: '') + session.accept_prompt(/A name was submitted: $/) do + session.click_button('Submit') + end + # end + end + end + end + end + + def headless_or_remote? + !ENV['HEADLESS'].nil? || session.driver.options[:browser] == :remote + end +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/spec_helper.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/spec_helper.rb new file mode 100644 index 00000000..c8cb11fd --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +require 'rspec/expectations' +require 'webdrivers' if ENV['CI'] || ENV['WEBDRIVERS'] +require 'selenium_statistics' +if ENV['TRAVIS'] + require 'coveralls' + Coveralls.wear! do + add_filter '/lib/capybara/driver/' + add_filter '/lib/capybara/registrations/' + end +end +require 'capybara/spec/spec_helper' + +module Capybara + module SpecHelper + def firefox?(session) + browser_name(session) == :firefox && + ((defined?(::Selenium::WebDriver::VERSION) && (::Selenium::WebDriver::VERSION.to_f >= 4)) || + session.driver.browser.capabilities.is_a?(::Selenium::WebDriver::Remote::W3C::Capabilities)) + end + + def firefox_lt?(version, session) + firefox?(session) && (session.driver.browser.capabilities[:browser_version].to_f < version) + end + + def firefox_gte?(version, session) + firefox?(session) && (session.driver.browser.capabilities[:browser_version].to_f >= version) + end + + def chrome?(session) + browser_name(session) == :chrome + end + + def chrome_version(session) + (session.driver.browser.capabilities[:browser_version] || + session.driver.browser.capabilities[:version]).to_f + end + + def chrome_lt?(version, session) + chrome?(session) && (chrome_version(session) < version) + end + + def chrome_gte?(version, session) + chrome?(session) && (chrome_version(session) >= version) + end + + def edge?(session) + browser_name(session).match?(/^edge/) + end + + def legacy_edge?(session) + browser_name(session) == :edge + end + + def edge_lt?(version, session) + edge?(session) && (chrome_version(session) < version) + end + + def edge_gte?(version, session) + edge?(session) && (chrome_version(session) >= version) + end + + def ie?(session) + %i[internet_explorer ie].include?(browser_name(session)) + end + + def safari?(session) + %i[safari Safari Safari_Technology_Preview].include?(browser_name(session)) + end + + def browser_name(session) + session.driver.browser.browser if session.respond_to?(:driver) + end + + def remote?(session) + session.driver.browser.is_a? ::Selenium::WebDriver::Remote::Driver + end + + def self.log_selenium_driver_version(mod) + mod = mod::Service if ::Selenium::WebDriver::Service.respond_to? :driver_path + path = mod.driver_path + path = path.call if path.respond_to? :call + $stdout.puts `#{path.gsub(' ', '\ ')} --version` + end + end +end + +RSpec.configure do |config| + Capybara::SpecHelper.configure(config) + config.expect_with :rspec do |expectations| + expectations.syntax = :expect + end + config.mock_with :rspec do |mocks| + mocks.verify_partial_doubles = true + end + config.filter_run_including focus_: true unless ENV['CI'] + config.run_all_when_everything_filtered = true + config.after(:suite) { SeleniumStatistics.print_results } +end diff --git a/path/ruby/2.6.0/gems/capybara-3.29.0/spec/xpath_builder_spec.rb b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/xpath_builder_spec.rb new file mode 100644 index 00000000..d621c7c0 --- /dev/null +++ b/path/ruby/2.6.0/gems/capybara-3.29.0/spec/xpath_builder_spec.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require 'spec_helper' + +# rubocop:disable RSpec/InstanceVariable +RSpec.describe Capybara::Selector::XPathBuilder do + let :builder do + ::Capybara::Selector::XPathBuilder.new(@xpath) + end + + context 'add_attribute_conditions' do + it 'adds a single string condition to a single selector' do + @xpath = './/div' + selector = builder.add_attribute_conditions(random: 'abc') + expect(selector).to eq %((.//div)[(./@random = 'abc')]) + end + + it 'adds multiple string conditions to a single selector' do + @xpath = './/div' + selector = builder.add_attribute_conditions(random: 'abc', other: 'def') + expect(selector).to eq %(((.//div)[(./@random = 'abc')])[(./@other = 'def')]) + end + + it 'adds a single string condition to a multiple selector' do + @xpath = XPath.descendant(:div, :ul) + selector = builder.add_attribute_conditions(random: 'abc') + expect(selector.to_s).to eq @xpath[XPath.attr(:random) == 'abc'].to_s + end + + it 'adds multiple string conditions to a multiple selector' do + @xpath = XPath.descendant(:div, :ul) + selector = builder.add_attribute_conditions(random: 'abc', other: 'def') + expect(selector.to_s).to eq %(.//*[self::div | self::ul][(./@random = 'abc')][(./@other = 'def')]) + end + + it 'adds simple regexp conditions to a single selector' do + @xpath = XPath.descendant(:div) + selector = builder.add_attribute_conditions(random: /abc/, other: /def/) + expect(selector.to_s).to eq %(.//div[./@random[contains(., 'abc')]][./@other[contains(., 'def')]]) + end + + it 'adds wildcard regexp conditions to a single selector' do + @xpath = './/div' + selector = builder.add_attribute_conditions(random: /abc.*def/, other: /def.*ghi/) + expect(selector).to eq %(((.//div)[./@random[(contains(., 'abc') and contains(., 'def'))]])[./@other[(contains(., 'def') and contains(., 'ghi'))]]) + end + + it 'adds alternated regexp conditions to a single selector' do + @xpath = XPath.descendant(:div) + selector = builder.add_attribute_conditions(random: /abc|def/, other: /def|ghi/) + expect(selector.to_s).to eq %(.//div[./@random[(contains(., 'abc') or contains(., 'def'))]][./@other[(contains(., 'def') or contains(., 'ghi'))]]) + end + + it 'adds alternated regexp conditions to a multiple selector' do + @xpath = XPath.descendant(:div, :ul) + selector = builder.add_attribute_conditions(other: /def.*ghi|jkl/) + expect(selector.to_s).to eq %(.//*[self::div | self::ul][./@other[((contains(., 'def') and contains(., 'ghi')) or contains(., 'jkl'))]]) + end + + it "returns original selector when regexp can't be substringed" do + @xpath = './/div' + selector = builder.add_attribute_conditions(other: /.+/) + expect(selector).to eq '(.//div)[./@other]' + end + + context ':class' do + it 'handles string' do + @xpath = './/a' + selector = builder.add_attribute_conditions(class: 'my_class') + expect(selector).to eq %((.//a)[contains(concat(' ', normalize-space(./@class), ' '), ' my_class ')]) + end + + it 'handles negated strings' do + @xpath = XPath.descendant(:a) + selector = builder.add_attribute_conditions(class: '!my_class') + expect(selector.to_s).to eq @xpath[!XPath.attr(:class).contains_word('my_class')].to_s + end + + it 'handles array of strings' do + @xpath = './/a' + selector = builder.add_attribute_conditions(class: %w[my_class my_other_class]) + expect(selector).to eq %((.//a)[(contains(concat(' ', normalize-space(./@class), ' '), ' my_class ') and contains(concat(' ', normalize-space(./@class), ' '), ' my_other_class '))]) + end + + it 'handles array of string when negated included' do + @xpath = XPath.descendant(:a) + selector = builder.add_attribute_conditions(class: %w[my_class !my_other_class]) + expect(selector.to_s).to eq @xpath[XPath.attr(:class).contains_word('my_class') & !XPath.attr(:class).contains_word('my_other_class')].to_s + end + end + end +end +# rubocop:enable RSpec/InstanceVariable diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/.document b/path/ruby/2.6.0/gems/childprocess-3.0.0/.document new file mode 100644 index 00000000..ed6ef11a --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/.document @@ -0,0 +1,6 @@ +README.rdoc +lib/**/*.rb +bin/* +features/**/*.feature +- +LICENSE diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/.gitignore b/path/ruby/2.6.0/gems/childprocess-3.0.0/.gitignore new file mode 100644 index 00000000..36ae330d --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/.gitignore @@ -0,0 +1,28 @@ +## MAC OS +.DS_Store + +## TEXTMATE +*.tmproj +tmtags + +## EMACS +*~ +\#* +.\#* + +## VIM +*.swp + +## RubyMine +.idea/* + +## PROJECT::GENERAL +coverage +rdoc +pkg +.rbx +Gemfile.lock +.ruby-version +.bundle + +## PROJECT::SPECIFIC diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/.rspec b/path/ruby/2.6.0/gems/childprocess-3.0.0/.rspec new file mode 100644 index 00000000..4e1e0d2f --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/.rspec @@ -0,0 +1 @@ +--color diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/.travis.yml b/path/ruby/2.6.0/gems/childprocess-3.0.0/.travis.yml new file mode 100644 index 00000000..28f4cc31 --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/.travis.yml @@ -0,0 +1,40 @@ +os: + - linux + - osx + +rvm: + - rbx-3 + - 2.3 + - 2.4 + - 2.5 + - 2.6 + - ruby-head + +sudo: false + +cache: bundler + +before_install: + - "echo 'gem: --no-document' > ~/.gemrc" + # RubyGems update is supported for Ruby 2.3 and later + - ruby -e "system('gem update --system') if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.3')" + - gem install bundler --version '~> 1.17' + +before_script: + - 'export JAVA_OPTS="${JAVA_OPTS_FOR_SPECS}"' + +env: + global: + matrix: + - CHILDPROCESS_POSIX_SPAWN=true CHILDPROCESS_UNSET=should-be-unset + - CHILDPROCESS_POSIX_SPAWN=false CHILDPROCESS_UNSET=should-be-unset + +matrix: + allow_failures: + - rvm: rbx-3 + - rvm: ruby-head + - env: "CHILDPROCESS_POSIX_SPAWN=true" + include: + - rvm: jruby-9.2.5.0 + jdk: openjdk11 + env: "JAVA_OPTS_FOR_SPECS='--add-opens java.base/java.io=org.jruby.dist --add-opens java.base/sun.nio.ch=org.jruby.dist'" diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/CHANGELOG.md b/path/ruby/2.6.0/gems/childprocess-3.0.0/CHANGELOG.md new file mode 100644 index 00000000..38b381b8 --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/CHANGELOG.md @@ -0,0 +1,73 @@ +### Version 3.0.0 / 2019-09-20 + +* [#156](https://github.com/enkessler/childprocess/pull/156)Remove unused `rubyforge_project` from gemspec +* [#160](https://github.com/enkessler/childprocess/pull/160): Remove extension to conditionally install `ffi` gem on Windows platforms +* [#160](https://github.com/enkessler/childprocess/pull/160): Remove runtime dependency on `rake` gem + +### Version 2.0.0 / 2019-07-11 + +* [#148](https://github.com/enkessler/childprocess/pull/148): Drop support for Ruby 2.0, 2.1, and 2.2 +* [#149](https://github.com/enkessler/childprocess/pull/149): Fix Unix fork reopen to be compatible with Ruby 2.6 +* [#152](https://github.com/enkessler/childprocess/pull/152)/[#154](https://github.com/enkessler/childprocess/pull/154): Fix hangs and permission errors introduced in Ruby 2.6 for leader processes of process groups + +### Version 1.0.1 / 2019-02-03 + +* [#143](https://github.com/enkessler/childprocess/pull/144): Fix installs by adding `rake` gem as runtime dependency +* [#147](https://github.com/enkessler/childprocess/pull/147): Relax `rake` gem constraint from `< 12` to `< 13` + +### Version 1.0.0 / 2019-01-28 + +* [#134](https://github.com/enkessler/childprocess/pull/134): Add support for non-ASCII characters on Windows +* [#132](https://github.com/enkessler/childprocess/pull/132): Install `ffi` gem requirement on Windows only +* [#128](https://github.com/enkessler/childprocess/issues/128): Convert environment variable values to strings when `posix_spawn` enabled +* [#141](https://github.com/enkessler/childprocess/pull/141): Support JRuby on Java >= 9 + +### Version 0.9.0 / 2018-03-10 + +* Added support for DragonFly BSD. + + +### Version 0.8.0 / 2017-09-23 + +* Added a method for determining whether or not a process had been started. + + +### Version 0.7.1 / 2017-06-26 + +* Fixed a noisy uninitialized variable warning + + +### Version 0.7.0 / 2017-05-07 + +* Debugging information now uses a Logger, which can be configured. + + +### Version 0.6.3 / 2017-03-24 + +See beta release notes. + + +### Version 0.6.3.beta.1 / 2017-03-10 + +* Bug fix: Fixed child process creation problems on Windows 7 when a child was declared as a leader. + + +### Version 0.6.2 / 2017-02-25 + +* Bug fix: Fixed a potentially broken edge case that could occur on older 32-bit OSX systems. + + +### Version 0.6.1 / 2017-01-22 + +* Bug fix: Fixed a dependency that was accidentally declared as a runtime + dependency instead of a development dependency. + + +### Version 0.6.0 / 2017-01-22 + +* Support for Ruby 2.4 added + + +### Version 0.5.9 / 2016-01-06 + +* The Great Before Times... diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/Gemfile b/path/ruby/2.6.0/gems/childprocess-3.0.0/Gemfile new file mode 100644 index 00000000..ba9393bd --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/Gemfile @@ -0,0 +1,21 @@ +source 'http://rubygems.org' + +# Specify your gem's dependencies in child_process.gemspec +gemspec + +# Used for local development/testing only +gem 'rake' + +if RUBY_VERSION =~ /^1\./ + gem 'tins', '< 1.7' # The 'tins' gem requires Ruby 2.x on/after this version + gem 'json', '< 2.0' # The 'json' gem drops pre-Ruby 2.x support on/after this version + gem 'term-ansicolor', '< 1.4' # The 'term-ansicolor' gem requires Ruby 2.x on/after this version + + # ffi gem for Windows requires Ruby 2.x on/after this version + gem 'ffi', '< 1.9.15' if ENV['CHILDPROCESS_POSIX_SPAWN'] == 'true' || Gem.win_platform? +elsif Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.2') + # Ruby 2.0/2.1 support only ffi before 1.10 + gem 'ffi', '~> 1.9.0' if ENV['CHILDPROCESS_POSIX_SPAWN'] == 'true' || Gem.win_platform? +else + gem 'ffi' if ENV['CHILDPROCESS_POSIX_SPAWN'] == 'true' || Gem.win_platform? +end diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/LICENSE b/path/ruby/2.6.0/gems/childprocess-3.0.0/LICENSE new file mode 100644 index 00000000..50cfbaec --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2010-2015 Jari Bakken + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/README.md b/path/ruby/2.6.0/gems/childprocess-3.0.0/README.md new file mode 100644 index 00000000..3875914f --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/README.md @@ -0,0 +1,218 @@ +# childprocess + +This gem aims at being a simple and reliable solution for controlling +external programs running in the background on any Ruby / OS combination. + +The code originated in the [selenium-webdriver](https://rubygems.org/gems/selenium-webdriver) gem, but should prove useful as +a standalone library. + +[![Build Status](https://secure.travis-ci.org/enkessler/childprocess.svg)](http://travis-ci.org/enkessler/childprocess) +[![Build status](https://ci.appveyor.com/api/projects/status/fn2snbcd7kku5myk/branch/dev?svg=true)](https://ci.appveyor.com/project/enkessler/childprocess/branch/dev) +[![Gem Version](https://badge.fury.io/rb/childprocess.svg)](http://badge.fury.io/rb/childprocess) +[![Code Climate](https://codeclimate.com/github/enkessler/childprocess.svg)](https://codeclimate.com/github/enkessler/childprocess) +[![Coverage Status](https://coveralls.io/repos/enkessler/childprocess/badge.svg?branch=master)](https://coveralls.io/r/enkessler/childprocess?branch=master) + +# Requirements + +* Ruby 2.3+, JRuby 9+ + +Windows users **must** ensure the `ffi` gem (`>= 1.0.11`) is installed in order to use ChildProcess. + +# Usage + +The object returned from `ChildProcess.build` will implement `ChildProcess::AbstractProcess`. + +### Basic examples + +```ruby +process = ChildProcess.build("ruby", "-e", "sleep") + +# inherit stdout/stderr from parent... +process.io.inherit! + +# ...or pass an IO +process.io.stdout = Tempfile.new("child-output") + +# modify the environment for the child +process.environment["a"] = "b" +process.environment["c"] = nil + +# set the child's working directory +process.cwd = '/some/path' + +# start the process +process.start + +# check process status +process.alive? #=> true +process.exited? #=> false + +# wait indefinitely for process to exit... +process.wait +process.exited? #=> true + +# get the exit code +process.exit_code #=> 0 + +# ...or poll for exit + force quit +begin + process.poll_for_exit(10) +rescue ChildProcess::TimeoutError + process.stop # tries increasingly harsher methods to kill the process. +end +``` + +### Advanced examples + +#### Output to pipe + +```ruby +r, w = IO.pipe + +proc = ChildProcess.build("echo", "foo") +proc.io.stdout = proc.io.stderr = w +proc.start + +Thread.new { + begin + loop do + print r.readpartial(8192) + end + rescue EOFError + end +} + +proc.wait +w.close +``` + +Note that if you just want to get the output of a command, the backtick method on Kernel may be a better fit. + +#### Write to stdin + +```ruby +process = ChildProcess.build("cat") + +out = Tempfile.new("duplex") +out.sync = true + +process.io.stdout = process.io.stderr = out +process.duplex = true # sets up pipe so process.io.stdin will be available after .start + +process.start +process.io.stdin.puts "hello world" +process.io.stdin.close + +process.poll_for_exit(exit_timeout_in_seconds) + +out.rewind +out.read #=> "hello world\n" +``` + +#### Pipe output to another ChildProcess + +```ruby +search = ChildProcess.build("grep", '-E', %w(redis memcached).join('|')) +search.duplex = true # sets up pipe so search.io.stdin will be available after .start +search.io.stdout = $stdout +search.start + +listing = ChildProcess.build("ps", "aux") +listing.io.stdout = search.io.stdin +listing.start +listing.wait + +search.io.stdin.close +search.wait +``` + +#### Prefer posix_spawn on *nix + +If the parent process is using a lot of memory, `fork+exec` can be very expensive. The `posix_spawn()` API removes this overhead. + +```ruby +ChildProcess.posix_spawn = true +process = ChildProcess.build(*args) +``` + +To be able to use this, please make sure that you have the `ffi` gem installed. + +### Ensure entire process tree dies + +By default, the child process does not create a new process group. This means there's no guarantee that the entire process tree will die when the child process is killed. To solve this: + +```ruby +process = ChildProcess.build(*args) +process.leader = true +process.start +``` + +#### Detach from parent + +```ruby +process = ChildProcess.build("sleep", "10") +process.detach = true +process.start +``` + +#### Invoking a shell + +As opposed to `Kernel#system`, `Kernel#exec` et al., ChildProcess will not automatically execute your command in a shell (like `/bin/sh` or `cmd.exe`) depending on the arguments. +This means that if you try to execute e.g. gem executables (like `bundle` or `gem`) or Windows executables (with `.com` or `.bat` extensions) you may see a `ChildProcess::LaunchError`. +You can work around this by being explicit about what interpreter to invoke: + +```ruby +ChildProcess.build("cmd.exe", "/c", "bundle") +ChildProcess.build("ruby", "-S", "bundle") +``` + +#### Log to file + +Errors and debugging information are logged to `$stderr` by default but a custom logger can be used instead. + +```ruby +logger = Logger.new('logfile.log') +logger.level = Logger::DEBUG +ChildProcess.logger = logger +``` + +## Caveats + +* With JRuby on Unix, modifying `ENV["PATH"]` before using childprocess could lead to 'Command not found' errors, since JRuby is unable to modify the environment used for PATH searches in `java.lang.ProcessBuilder`. This can be avoided by setting `ChildProcess.posix_spawn = true`. +* With JRuby on Java >= 9, the JVM may need to be configured to allow JRuby to access neccessary implementations; this can be done by adding `--add-opens java.base/java.io=org.jruby.dist` and `--add-opens java.base/sun.nio.ch=org.jruby.dist` to the `JAVA_OPTS` environment variable that is used by JRuby when launching the JVM. + +# Implementation + +How the process is launched and killed depends on the platform: + +* Unix : `fork + exec` (or `posix_spawn` if enabled) +* Windows : `CreateProcess()` and friends +* JRuby : `java.lang.{Process,ProcessBuilder}` + +# Note on Patches/Pull Requests + +1. Fork it +2. Create your feature branch (off of the development branch) + `git checkout -b my-new-feature dev` +3. Commit your changes + `git commit -am 'Add some feature'` +4. Push to the branch + `git push origin my-new-feature` +5. Create new Pull Request + +# Publishing a New Release + +When publishing a new gem release: + +1. Ensure [latest build is green on the `dev` branch](https://travis-ci.org/enkessler/childprocess/branches) +2. Ensure [CHANGELOG](CHANGELOG.md) is updated +3. Ensure [version is bumped](lib/childprocess/version.rb) following [Semantic Versioning](https://semver.org/) +4. Merge the `dev` branch into `master`: `git checkout master && git merge dev` +5. Ensure [latest build is green on the `master` branch](https://travis-ci.org/enkessler/childprocess/branches) +6. Build gem from the green `master` branch: `git checkout master && gem build childprocess.gemspec` +7. Push gem to RubyGems: `gem push childprocess-.gem` +8. Tag commit with version, annotated with release notes: `git tag -a ` + +# Copyright + +Copyright (c) 2010-2015 Jari Bakken. See [LICENSE](LICENSE) for details. diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/Rakefile b/path/ruby/2.6.0/gems/childprocess-3.0.0/Rakefile new file mode 100644 index 00000000..5e4d38e2 --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/Rakefile @@ -0,0 +1,61 @@ +require 'rubygems' +require 'rake' +require 'tmpdir' + +require 'bundler' +Bundler::GemHelper.install_tasks + +include Rake::DSL if defined?(::Rake::DSL) + +require 'rspec/core/rake_task' +RSpec::Core::RakeTask.new(:spec) do |spec| + spec.ruby_opts = "-I lib:spec -w" + spec.pattern = 'spec/**/*_spec.rb' +end + +desc 'Run specs for rcov' +RSpec::Core::RakeTask.new(:rcov) do |spec| + spec.ruby_opts = "-I lib:spec" + spec.pattern = 'spec/**/*_spec.rb' + spec.rcov = true + spec.rcov_opts = %w[--exclude spec,ruby-debug,/Library/Ruby,.gem --include lib/childprocess] +end + +task :default => :spec + +begin + require 'yard' + YARD::Rake::YardocTask.new +rescue LoadError + task :yardoc do + abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard" + end +end + +task :clean do + rm_rf "pkg" + rm_rf "childprocess.jar" +end + +desc 'Create jar to bundle in selenium-webdriver' +task :jar => [:clean, :build] do + tmpdir = Dir.mktmpdir("childprocess-jar") + gem_to_package = Dir['pkg/*.gem'].first + gem_name = File.basename(gem_to_package, ".gem") + p :gem_to_package => gem_to_package, :gem_name => gem_name + + sh "gem install -i #{tmpdir} #{gem_to_package} --ignore-dependencies --no-rdoc --no-ri" + sh "jar cf childprocess.jar -C #{tmpdir}/gems/#{gem_name}/lib ." + sh "jar tf childprocess.jar" +end + +task :env do + $:.unshift File.expand_path("../lib", __FILE__) + require 'childprocess' +end + +desc 'Calculate size of posix_spawn structs for the current platform' +task :generate => :env do + require 'childprocess/tools/generator' + ChildProcess::Tools::Generator.generate +end diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/appveyor.yml b/path/ruby/2.6.0/gems/childprocess-3.0.0/appveyor.yml new file mode 100644 index 00000000..c9f9581c --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/appveyor.yml @@ -0,0 +1,42 @@ +version: '1.0.{build}' + +environment: + matrix: + - CHILDPROCESS_POSIX_SPAWN: true + CHILDPROCESS_UNSET: should-be-unset + RUBY_VERSION: 23-x64 + - CHILDPROCESS_POSIX_SPAWN: false + CHILDPROCESS_UNSET: should-be-unset + RUBY_VERSION: 23-x64 + - CHILDPROCESS_POSIX_SPAWN: true + CHILDPROCESS_UNSET: should-be-unset + RUBY_VERSION: 24-x64 + - CHILDPROCESS_POSIX_SPAWN: false + CHILDPROCESS_UNSET: should-be-unset + RUBY_VERSION: 24-x64 + - CHILDPROCESS_POSIX_SPAWN: true + CHILDPROCESS_UNSET: should-be-unset + RUBY_VERSION: 25-x64 + - CHILDPROCESS_POSIX_SPAWN: false + CHILDPROCESS_UNSET: should-be-unset + RUBY_VERSION: 25-x64 + - CHILDPROCESS_POSIX_SPAWN: true + CHILDPROCESS_UNSET: should-be-unset + RUBY_VERSION: 26-x64 + - CHILDPROCESS_POSIX_SPAWN: false + CHILDPROCESS_UNSET: should-be-unset + RUBY_VERSION: 26-x64 + +install: + - set PATH=C:\Ruby%RUBY_VERSION%\bin;%PATH% + - bundle install + +build: off + +before_test: + - ruby -v + - gem -v + - bundle -v + +test_script: + - bundle exec rake diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/childprocess.gemspec b/path/ruby/2.6.0/gems/childprocess-3.0.0/childprocess.gemspec new file mode 100644 index 00000000..2825838c --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/childprocess.gemspec @@ -0,0 +1,26 @@ +# -*- encoding: utf-8 -*- +$:.push File.expand_path("../lib", __FILE__) +require "childprocess/version" + +Gem::Specification.new do |s| + s.name = "childprocess" + s.version = ChildProcess::VERSION + s.platform = Gem::Platform::RUBY + s.authors = ["Jari Bakken", "Eric Kessler", "Shane da Silva"] + s.email = ["morrow748@gmail.com", "shane@dasilva.io"] + s.homepage = "http://github.com/enkessler/childprocess" + s.summary = %q{A simple and reliable solution for controlling external programs running in the background on any Ruby / OS combination.} + s.description = %q{This gem aims at being a simple and reliable solution for controlling external programs running in the background on any Ruby / OS combination.} + + s.license = 'MIT' + + s.files = `git ls-files`.split("\n") + s.test_files = `git ls-files -- spec/*`.split("\n") + s.require_paths = ["lib"] + + s.required_ruby_version = '>= 2.3.0' + + s.add_development_dependency "rspec", "~> 3.0" + s.add_development_dependency "yard", "~> 0.0" + s.add_development_dependency 'coveralls', '< 1.0' +end diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess.rb new file mode 100644 index 00000000..09cc132c --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess.rb @@ -0,0 +1,210 @@ +require 'childprocess/version' +require 'childprocess/errors' +require 'childprocess/abstract_process' +require 'childprocess/abstract_io' +require "fcntl" +require 'logger' + +module ChildProcess + + @posix_spawn = false + + class << self + attr_writer :logger + + def new(*args) + case os + when :macosx, :linux, :solaris, :bsd, :cygwin, :aix + if posix_spawn? + Unix::PosixSpawnProcess.new(args) + elsif jruby? + JRuby::Process.new(args) + else + Unix::ForkExecProcess.new(args) + end + when :windows + Windows::Process.new(args) + else + raise Error, "unsupported platform #{platform_name.inspect}" + end + end + alias_method :build, :new + + def logger + return @logger if defined?(@logger) and @logger + + @logger = Logger.new($stderr) + @logger.level = $DEBUG ? Logger::DEBUG : Logger::INFO + + @logger + end + + def platform + if RUBY_PLATFORM == "java" + :jruby + elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == "ironruby" + :ironruby + else + os + end + end + + def platform_name + @platform_name ||= "#{arch}-#{os}" + end + + def unix? + !windows? + end + + def linux? + os == :linux + end + + def jruby? + platform == :jruby + end + + def windows? + os == :windows + end + + def posix_spawn? + enabled = @posix_spawn || %w[1 true].include?(ENV['CHILDPROCESS_POSIX_SPAWN']) + return false unless enabled + + begin + require 'ffi' + rescue LoadError + raise ChildProcess::MissingFFIError + end + + begin + require "childprocess/unix/platform/#{ChildProcess.platform_name}" + rescue LoadError + raise ChildProcess::MissingPlatformError + end + + require "childprocess/unix/lib" + require 'childprocess/unix/posix_spawn_process' + + true + rescue ChildProcess::MissingPlatformError => ex + warn_once ex.message + false + end + + # + # Set this to true to enable experimental use of posix_spawn. + # + + def posix_spawn=(bool) + @posix_spawn = bool + end + + def os + @os ||= ( + require "rbconfig" + host_os = RbConfig::CONFIG['host_os'].downcase + + case host_os + when /linux/ + :linux + when /darwin|mac os/ + :macosx + when /mswin|msys|mingw32/ + :windows + when /cygwin/ + :cygwin + when /solaris|sunos/ + :solaris + when /bsd|dragonfly/ + :bsd + when /aix/ + :aix + else + raise Error, "unknown os: #{host_os.inspect}" + end + ) + end + + def arch + @arch ||= ( + host_cpu = RbConfig::CONFIG['host_cpu'].downcase + case host_cpu + when /i[3456]86/ + if workaround_older_macosx_misreported_cpu? + # Workaround case: older 64-bit Darwin Rubies misreported as i686 + "x86_64" + else + "i386" + end + when /amd64|x86_64/ + "x86_64" + when /ppc|powerpc/ + "powerpc" + else + host_cpu + end + ) + end + + # + # By default, a child process will inherit open file descriptors from the + # parent process. This helper provides a cross-platform way of making sure + # that doesn't happen for the given file/io. + # + + def close_on_exec(file) + if file.respond_to?(:close_on_exec=) + file.close_on_exec = true + elsif file.respond_to?(:fcntl) && defined?(Fcntl::FD_CLOEXEC) + file.fcntl Fcntl::F_SETFD, Fcntl::FD_CLOEXEC + + if jruby? && posix_spawn? + # on JRuby, the fcntl call above apparently isn't enough when + # we're launching the process through posix_spawn. + fileno = JRuby.posix_fileno_for(file) + Unix::Lib.fcntl fileno, Fcntl::F_SETFD, Fcntl::FD_CLOEXEC + end + elsif windows? + Windows::Lib.dont_inherit file + else + raise Error, "not sure how to set close-on-exec for #{file.inspect} on #{platform_name.inspect}" + end + end + + private + + def warn_once(msg) + @warnings ||= {} + + unless @warnings[msg] + @warnings[msg] = true + logger.warn msg + end + end + + # Workaround: detect the situation that an older Darwin Ruby is actually + # 64-bit, but is misreporting cpu as i686, which would imply 32-bit. + # + # @return [Boolean] `true` if: + # (a) on Mac OS X + # (b) actually running in 64-bit mode + def workaround_older_macosx_misreported_cpu? + os == :macosx && is_64_bit? + end + + # @return [Boolean] `true` if this Ruby represents `1` in 64 bits (8 bytes). + def is_64_bit? + 1.size == 8 + end + + end # class << self +end # ChildProcess + +require 'jruby' if ChildProcess.jruby? + +require 'childprocess/unix' if ChildProcess.unix? +require 'childprocess/windows' if ChildProcess.windows? +require 'childprocess/jruby' if ChildProcess.jruby? diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/abstract_io.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/abstract_io.rb new file mode 100644 index 00000000..5d159b2b --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/abstract_io.rb @@ -0,0 +1,36 @@ +module ChildProcess + class AbstractIO + attr_reader :stderr, :stdout, :stdin + + def inherit! + @stdout = STDOUT + @stderr = STDERR + end + + def stderr=(io) + check_type io + @stderr = io + end + + def stdout=(io) + check_type io + @stdout = io + end + + # + # @api private + # + + def _stdin=(io) + check_type io + @stdin = io + end + + private + + def check_type(io) + raise SubclassResponsibility, "check_type" + end + + end +end diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/abstract_process.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/abstract_process.rb new file mode 100644 index 00000000..5cac4443 --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/abstract_process.rb @@ -0,0 +1,192 @@ +module ChildProcess + class AbstractProcess + POLL_INTERVAL = 0.1 + + attr_reader :exit_code + + # + # Set this to true if you do not care about when or if the process quits. + # + attr_accessor :detach + + # + # Set this to true if you want to write to the process' stdin (process.io.stdin) + # + attr_accessor :duplex + + # + # Modify the child's environment variables + # + attr_reader :environment + + # + # Set the child's current working directory. + # + attr_accessor :cwd + + # + # Set this to true to make the child process the leader of a new process group + # + # This can be used to make sure that all grandchildren are killed + # when the child process dies. + # + attr_accessor :leader + + # + # Create a new process with the given args. + # + # @api private + # @see ChildProcess.build + # + + def initialize(args) + unless args.all? { |e| e.kind_of?(String) } + raise ArgumentError, "all arguments must be String: #{args.inspect}" + end + + @args = args + @started = false + @exit_code = nil + @io = nil + @cwd = nil + @detach = false + @duplex = false + @leader = false + @environment = {} + end + + # + # Returns a ChildProcess::AbstractIO subclass to configure the child's IO streams. + # + + def io + raise SubclassResponsibility, "io" + end + + # + # @return [Integer] the pid of the process after it has started + # + + def pid + raise SubclassResponsibility, "pid" + end + + # + # Launch the child process + # + # @return [AbstractProcess] self + # + + def start + launch_process + @started = true + + self + end + + # + # Forcibly terminate the process, using increasingly harsher methods if possible. + # + # @param [Integer] timeout (3) Seconds to wait before trying the next method. + # + + def stop(timeout = 3) + raise SubclassResponsibility, "stop" + end + + # + # Block until the process has been terminated. + # + # @return [Integer] The exit status of the process + # + + def wait + raise SubclassResponsibility, "wait" + end + + # + # Did the process exit? + # + # @return [Boolean] + # + + def exited? + raise SubclassResponsibility, "exited?" + end + + # + # Has the process started? + # + # @return [Boolean] + # + + def started? + @started + end + + # + # Is this process running? + # + # @return [Boolean] + # + + def alive? + started? && !exited? + end + + # + # Returns true if the process has exited and the exit code was not 0. + # + # @return [Boolean] + # + + def crashed? + exited? && @exit_code != 0 + end + + # + # Wait for the process to exit, raising a ChildProcess::TimeoutError if + # the timeout expires. + # + + def poll_for_exit(timeout) + log "polling #{timeout} seconds for exit" + + end_time = Time.now + timeout + until (ok = exited?) || Time.now > end_time + sleep POLL_INTERVAL + end + + unless ok + raise TimeoutError, "process still alive after #{timeout} seconds" + end + end + + private + + def launch_process + raise SubclassResponsibility, "launch_process" + end + + def detach? + @detach + end + + def duplex? + @duplex + end + + def leader? + @leader + end + + def log(*args) + ChildProcess.logger.debug "#{self.inspect} : #{args.inspect}" + end + + def assert_started + raise Error, "process not started" unless started? + end + + end # AbstractProcess +end # ChildProcess diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/errors.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/errors.rb new file mode 100644 index 00000000..2245ad75 --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/errors.rb @@ -0,0 +1,37 @@ +module ChildProcess + class Error < StandardError + end + + class TimeoutError < Error + end + + class SubclassResponsibility < Error + end + + class InvalidEnvironmentVariable < Error + end + + class LaunchError < Error + end + + class MissingFFIError < Error + def initialize + message = "FFI is a required pre-requisite for Windows or posix_spawn support in the ChildProcess gem. " + + "Ensure the `ffi` gem is installed. " + + "If you believe this is an error, please file a bug at http://github.com/enkessler/childprocess/issues" + + super(message) + end + + end + + class MissingPlatformError < Error + def initialize + message = "posix_spawn is not yet supported on #{ChildProcess.platform_name} (#{RUBY_PLATFORM}), falling back to default implementation. " + + "If you believe this is an error, please file a bug at http://github.com/enkessler/childprocess/issues" + + super(message) + end + + end +end diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/jruby.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/jruby.rb new file mode 100644 index 00000000..f8bbc021 --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/jruby.rb @@ -0,0 +1,56 @@ +require 'java' +require 'jruby' + +class Java::SunNioCh::FileChannelImpl + field_reader :fd +end + +class Java::JavaIo::FileDescriptor + if ChildProcess.os == :windows + field_reader :handle + end + + field_reader :fd +end + +module ChildProcess + module JRuby + def self.posix_fileno_for(obj) + channel = ::JRuby.reference(obj).channel + begin + channel.getFDVal + rescue NoMethodError + fileno = channel.fd + if fileno.kind_of?(Java::JavaIo::FileDescriptor) + fileno = fileno.fd + end + + fileno == -1 ? obj.fileno : fileno + end + rescue + # fall back + obj.fileno + end + + def self.windows_handle_for(obj) + channel = ::JRuby.reference(obj).channel + fileno = obj.fileno + + begin + fileno = channel.getFDVal + rescue NoMethodError + fileno = channel.fd if channel.respond_to?(:fd) + end + + if fileno.kind_of? Java::JavaIo::FileDescriptor + fileno.handle + else + Windows::Lib.handle_for fileno + end + end + end +end + +require "childprocess/jruby/pump" +require "childprocess/jruby/io" +require "childprocess/jruby/process" diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/jruby/io.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/jruby/io.rb new file mode 100644 index 00000000..f4e8c2b1 --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/jruby/io.rb @@ -0,0 +1,16 @@ +module ChildProcess + module JRuby + class IO < AbstractIO + private + + def check_type(output) + unless output.respond_to?(:to_outputstream) && output.respond_to?(:write) + raise ArgumentError, "expected #{output.inspect} to respond to :to_outputstream" + end + end + + end # IO + end # Unix +end # ChildProcess + + diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/jruby/process.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/jruby/process.rb new file mode 100755 index 00000000..cd80fad8 --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/jruby/process.rb @@ -0,0 +1,184 @@ +require "java" + +module ChildProcess + module JRuby + class Process < AbstractProcess + def initialize(args) + super(args) + + @pumps = [] + end + + def io + @io ||= JRuby::IO.new + end + + def exited? + return true if @exit_code + + assert_started + @exit_code = @process.exitValue + stop_pumps + + true + rescue java.lang.IllegalThreadStateException => ex + log(ex.class => ex.message) + false + ensure + log(:exit_code => @exit_code) + end + + def stop(timeout = nil) + assert_started + + @process.destroy + wait # no way to actually use the timeout here.. + end + + def wait + if exited? + exit_code + else + @process.waitFor + + stop_pumps + @exit_code = @process.exitValue + end + end + + # Implementation of ChildProcess::JRuby::Process#pid depends heavily on + # what Java SDK is being used; here, we look it up once at load, then + # define the method once to avoid runtime overhead. + normalised_java_version_major = java.lang.System.get_property("java.version") + .slice(/^(1\.)?([0-9]+)/, 2) + .to_i + if normalised_java_version_major >= 9 + + # On modern Javas, we can simply delegate through to `Process#pid`, + # which was introduced in Java 9. + # + # @return [Integer] the pid of the process after it has started + # @raise [NotImplementedError] when trying to access pid on platform for + # which it is unsupported in Java + def pid + @process.pid + rescue java.lang.UnsupportedOperationException => e + raise NotImplementedError, "pid is not supported on this platform: #{e.message}" + end + + else + + # On Legacy Javas, fall back to reflection. + # + # Only supported in JRuby on a Unix operating system, thanks to limitations + # in Java's classes + # + # @return [Integer] the pid of the process after it has started + # @raise [NotImplementedError] when trying to access pid on non-Unix platform + # + def pid + if @process.getClass.getName != "java.lang.UNIXProcess" + raise NotImplementedError, "pid is only supported by JRuby child processes on Unix" + end + + # About the best way we can do this is with a nasty reflection-based impl + # Thanks to Martijn Courteaux + # http://stackoverflow.com/questions/2950338/how-can-i-kill-a-linux-process-in-java-with-sigkill-process-destroy-does-sigter/2951193#2951193 + field = @process.getClass.getDeclaredField("pid") + field.accessible = true + field.get(@process) + end + + end + + private + + def launch_process(&blk) + pb = java.lang.ProcessBuilder.new(@args) + + pb.directory java.io.File.new(@cwd || Dir.pwd) + set_env pb.environment + + begin + @process = pb.start + rescue java.io.IOException => ex + raise LaunchError, ex.message + end + + setup_io + end + + def setup_io + if @io + redirect(@process.getErrorStream, @io.stderr) + redirect(@process.getInputStream, @io.stdout) + else + @process.getErrorStream.close + @process.getInputStream.close + end + + if duplex? + io._stdin = create_stdin + else + @process.getOutputStream.close + end + end + + def redirect(input, output) + if output.nil? + input.close + return + end + + @pumps << Pump.new(input, output.to_outputstream).run + end + + def stop_pumps + @pumps.each { |pump| pump.stop } + end + + def set_env(env) + merged = ENV.to_hash + + @environment.each { |k, v| merged[k.to_s] = v } + + merged.each do |k, v| + if v + env.put(k, v.to_s) + elsif env.has_key? k + env.remove(k) + end + end + + removed_keys = env.key_set.to_a - merged.keys + removed_keys.each { |k| env.remove(k) } + end + + def create_stdin + output_stream = @process.getOutputStream + + stdin = output_stream.to_io + stdin.sync = true + stdin.instance_variable_set(:@childprocess_java_stream, output_stream) + + class << stdin + # The stream provided is a BufferedeOutputStream, so we + # have to flush it to make the bytes flow to the process + def __childprocess_flush__ + @childprocess_java_stream.flush + end + + [:flush, :print, :printf, :putc, :puts, :write, :write_nonblock].each do |m| + define_method(m) do |*args| + super(*args) + self.__childprocess_flush__ + end + end + end + + stdin + end + + end # Process + end # JRuby +end # ChildProcess diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/jruby/pump.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/jruby/pump.rb new file mode 100644 index 00000000..64ac32db --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/jruby/pump.rb @@ -0,0 +1,53 @@ +module ChildProcess + module JRuby + class Pump + BUFFER_SIZE = 2048 + + def initialize(input, output) + @input = input + @output = output + @stop = false + end + + def stop + @stop = true + @thread && @thread.join + end + + def run + @thread = Thread.new { pump } + + self + end + + private + + def pump + buffer = Java.byte[BUFFER_SIZE].new + + until @stop && (@input.available == 0) + read, avail = 0, 0 + + while read != -1 + avail = [@input.available, 1].max + avail = BUFFER_SIZE if avail > BUFFER_SIZE + read = @input.read(buffer, 0, avail) + + if read > 0 + @output.write(buffer, 0, read) + @output.flush + end + end + + sleep 0.1 + end + + @output.flush + rescue java.io.IOException => ex + ChildProcess.logger.debug ex.message + ChildProcess.logger.debug ex.backtrace + end + + end # Pump + end # JRuby +end # ChildProcess diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/tools/generator.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/tools/generator.rb new file mode 100644 index 00000000..bb863df4 --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/tools/generator.rb @@ -0,0 +1,146 @@ +require 'fileutils' + +module ChildProcess + module Tools + class Generator + EXE_NAME = "childprocess-sizeof-generator" + TMP_PROGRAM = "childprocess-sizeof-generator.c" + DEFAULT_INCLUDES = %w[stdio.h stddef.h] + + def self.generate + new.generate + end + + def initialize + @cc = ENV['CC'] || 'gcc' + @out = File.expand_path("../../unix/platform/#{ChildProcess.platform_name}.rb", __FILE__) + @sizeof = {} + @constants = {} + end + + def generate + fetch_size 'posix_spawn_file_actions_t', :include => "spawn.h" + fetch_size 'posix_spawnattr_t', :include => "spawn.h" + fetch_size 'sigset_t', :include => "signal.h" + + fetch_constant 'POSIX_SPAWN_RESETIDS', :include => 'spawn.h' + fetch_constant 'POSIX_SPAWN_SETPGROUP', :include => 'spawn.h' + fetch_constant 'POSIX_SPAWN_SETSIGDEF', :include => 'spawn.h' + fetch_constant 'POSIX_SPAWN_SETSIGMASK', :include => 'spawn.h' + + if ChildProcess.linux? + fetch_constant 'POSIX_SPAWN_USEVFORK', :include => 'spawn.h', :define => {'_GNU_SOURCE' => nil} + end + + write + end + + def write + FileUtils.mkdir_p(File.dirname(@out)) + File.open(@out, 'w') do |io| + io.puts result + end + + puts "wrote #{@out}" + end + + def fetch_size(type_name, opts = {}) + print "sizeof(#{type_name}): " + src = <<-EOF +int main() { + printf("%d", (unsigned int)sizeof(#{type_name})); + return 0; +} + EOF + + output = execute(src, opts) + + if output.to_i < 1 + raise "sizeof(#{type_name}) == #{output.to_i} (output=#{output})" + end + + size = output.to_i + @sizeof[type_name] = size + + puts size + end + + def fetch_constant(name, opts) + print "#{name}: " + src = <<-EOF +int main() { + printf("%d", (unsigned int)#{name}); + return 0; +} + EOF + + output = execute(src, opts) + value = Integer(output) + @constants[name] = value + + puts value + end + + + def execute(src, opts) + program = Array(opts[:define]).map do |key, value| + <<-SRC +#ifndef #{key} +#define #{key} #{value} +#endif + SRC + end.join("\n") + program << "\n" + + includes = Array(opts[:include]) + DEFAULT_INCLUDES + program << includes.map { |include| "#include <#{include}>" }.join("\n") + program << "\n#{src}" + + File.open(TMP_PROGRAM, 'w') do |file| + file << program + end + + cmd = "#{@cc} #{TMP_PROGRAM} -o #{EXE_NAME}" + system cmd + unless $?.success? + raise "failed to compile program: #{cmd.inspect}\n#{program}" + end + + output = `./#{EXE_NAME} 2>&1` + + unless $?.success? + raise "failed to run program: #{cmd.inspect}\n#{output}" + end + + output.chomp + ensure + File.delete TMP_PROGRAM if File.exist?(TMP_PROGRAM) + File.delete EXE_NAME if File.exist?(EXE_NAME) + end + + def result + if @sizeof.empty? && @constants.empty? + raise "no data collected, nothing to do" + end + + out = ['module ChildProcess::Unix::Platform'] + out << ' SIZEOF = {' + + max = @sizeof.keys.map { |e| e.length }.max + @sizeof.each_with_index do |(type, size), idx| + out << " :#{type.ljust max} => #{size}#{',' unless idx == @sizeof.size - 1}" + end + out << ' }' + + max = @constants.keys.map { |e| e.length }.max + @constants.each do |name, val| + out << " #{name.ljust max} = #{val}" + end + out << 'end' + + out.join "\n" + end + + end + end +end \ No newline at end of file diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix.rb new file mode 100644 index 00000000..09a80546 --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix.rb @@ -0,0 +1,9 @@ +module ChildProcess + module Unix + end +end + +require "childprocess/unix/io" +require "childprocess/unix/process" +require "childprocess/unix/fork_exec_process" +# PosixSpawnProcess + ffi is required on demand. diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix/fork_exec_process.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix/fork_exec_process.rb new file mode 100644 index 00000000..cc7a8507 --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix/fork_exec_process.rb @@ -0,0 +1,78 @@ +module ChildProcess + module Unix + class ForkExecProcess < Process + private + + def launch_process + if @io + stdout = @io.stdout + stderr = @io.stderr + end + + # pipe used to detect exec() failure + exec_r, exec_w = ::IO.pipe + ChildProcess.close_on_exec exec_w + + if duplex? + reader, writer = ::IO.pipe + end + + @pid = Kernel.fork { + # Children of the forked process will inherit its process group + # This is to make sure that all grandchildren dies when this Process instance is killed + ::Process.setpgid 0, 0 if leader? + + if @cwd + Dir.chdir(@cwd) + end + + exec_r.close + set_env + + if stdout + STDOUT.reopen(stdout) + else + STDOUT.reopen("/dev/null", "a+") + end + if stderr + STDERR.reopen(stderr) + else + STDERR.reopen("/dev/null", "a+") + end + + if duplex? + STDIN.reopen(reader) + writer.close + end + + executable, *args = @args + + begin + Kernel.exec([executable, executable], *args) + rescue SystemCallError => ex + exec_w << ex.message + end + } + + exec_w.close + + if duplex? + io._stdin = writer + reader.close + end + + # if we don't eventually get EOF, exec() failed + unless exec_r.eof? + raise LaunchError, exec_r.read || "executing command with #{@args.inspect} failed" + end + + ::Process.detach(@pid) if detach? + end + + def set_env + @environment.each { |k, v| ENV[k.to_s] = v.nil? ? nil : v.to_s } + end + + end # Process + end # Unix +end # ChildProcess diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix/io.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix/io.rb new file mode 100644 index 00000000..739f8c8d --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix/io.rb @@ -0,0 +1,21 @@ +module ChildProcess + module Unix + class IO < AbstractIO + private + + def check_type(io) + unless io.respond_to? :to_io + raise ArgumentError, "expected #{io.inspect} to respond to :to_io" + end + + result = io.to_io + unless result && result.kind_of?(::IO) + raise TypeError, "expected IO, got #{result.inspect}:#{result.class}" + end + end + + end # IO + end # Unix +end # ChildProcess + + diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix/lib.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix/lib.rb new file mode 100644 index 00000000..22f19f24 --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix/lib.rb @@ -0,0 +1,186 @@ +module ChildProcess + module Unix + module Lib + extend FFI::Library + ffi_lib FFI::Library::LIBC + + if ChildProcess.os == :macosx + attach_function :_NSGetEnviron, [], :pointer + def self.environ + _NSGetEnviron().read_pointer + end + elsif respond_to? :attach_variable + attach_variable :environ, :pointer + end + + attach_function :strerror, [:int], :string + attach_function :chdir, [:string], :int + attach_function :fcntl, [:int, :int, :int], :int # fcntl actually takes varags, but we only need this version. + + # int posix_spawnp( + # pid_t *restrict pid, + # const char *restrict file, + # const posix_spawn_file_actions_t *file_actions, + # const posix_spawnattr_t *restrict attrp, + # char *const argv[restrict], + # char *const envp[restrict] + # ); + + attach_function :posix_spawnp, [ + :pointer, + :string, + :pointer, + :pointer, + :pointer, + :pointer + ], :int + + # int posix_spawn_file_actions_init(posix_spawn_file_actions_t *file_actions); + attach_function :posix_spawn_file_actions_init, [:pointer], :int + + # int posix_spawn_file_actions_destroy(posix_spawn_file_actions_t *file_actions); + attach_function :posix_spawn_file_actions_destroy, [:pointer], :int + + # int posix_spawn_file_actions_addclose(posix_spawn_file_actions_t *file_actions, int filedes); + attach_function :posix_spawn_file_actions_addclose, [:pointer, :int], :int + + # int posix_spawn_file_actions_addopen( + # posix_spawn_file_actions_t *restrict file_actions, + # int filedes, + # const char *restrict path, + # int oflag, + # mode_t mode + # ); + attach_function :posix_spawn_file_actions_addopen, [:pointer, :int, :string, :int, :mode_t], :int + + # int posix_spawn_file_actions_adddup2( + # posix_spawn_file_actions_t *file_actions, + # int filedes, + # int newfiledes + # ); + attach_function :posix_spawn_file_actions_adddup2, [:pointer, :int, :int], :int + + # int posix_spawnattr_init(posix_spawnattr_t *attr); + attach_function :posix_spawnattr_init, [:pointer], :int + + # int posix_spawnattr_destroy(posix_spawnattr_t *attr); + attach_function :posix_spawnattr_destroy, [:pointer], :int + + # int posix_spawnattr_setflags(posix_spawnattr_t *attr, short flags); + attach_function :posix_spawnattr_setflags, [:pointer, :short], :int + + # int posix_spawnattr_getflags(const posix_spawnattr_t *restrict attr, short *restrict flags); + attach_function :posix_spawnattr_getflags, [:pointer, :pointer], :int + + # int posix_spawnattr_setpgroup(posix_spawnattr_t *attr, pid_t pgroup); + attach_function :posix_spawnattr_setpgroup, [:pointer, :pid_t], :int + + # int posix_spawnattr_getpgroup(const posix_spawnattr_t *restrict attr, pid_t *restrict pgroup); + attach_function :posix_spawnattr_getpgroup, [:pointer, :pointer], :int + + # int posix_spawnattr_setsigdefault(posix_spawnattr_t *restrict attr, const sigset_t *restrict sigdefault); + attach_function :posix_spawnattr_setsigdefault, [:pointer, :pointer], :int + + # int posix_spawnattr_getsigdefault(const posix_spawnattr_t *restrict attr, sigset_t *restrict sigdefault); + attach_function :posix_spawnattr_getsigdefault, [:pointer, :pointer], :int + + # int posix_spawnattr_setsigmask(posix_spawnattr_t *restrict attr, const sigset_t *restrict sigmask); + attach_function :posix_spawnattr_setsigmask, [:pointer, :pointer], :int + + # int posix_spawnattr_getsigmask(const posix_spawnattr_t *restrict attr, sigset_t *restrict sigmask); + attach_function :posix_spawnattr_getsigmask, [:pointer, :pointer], :int + + def self.check(errno) + if errno != 0 + raise Error, Lib.strerror(FFI.errno) + end + end + + class FileActions + def initialize + @ptr = FFI::MemoryPointer.new(1, Platform::SIZEOF.fetch(:posix_spawn_file_actions_t), false) + Lib.check Lib.posix_spawn_file_actions_init(@ptr) + end + + def add_close(fileno) + Lib.check Lib.posix_spawn_file_actions_addclose( + @ptr, + fileno + ) + end + + def add_open(fileno, path, oflag, mode) + Lib.check Lib.posix_spawn_file_actions_addopen( + @ptr, + fileno, + path, + oflag, + mode + ) + end + + def add_dup(fileno, new_fileno) + Lib.check Lib.posix_spawn_file_actions_adddup2( + @ptr, + fileno, + new_fileno + ) + end + + def free + Lib.check Lib.posix_spawn_file_actions_destroy(@ptr) + @ptr = nil + end + + def to_ptr + @ptr + end + end # FileActions + + class Attrs + def initialize + @ptr = FFI::MemoryPointer.new(1, Platform::SIZEOF.fetch(:posix_spawnattr_t), false) + Lib.check Lib.posix_spawnattr_init(@ptr) + end + + def free + Lib.check Lib.posix_spawnattr_destroy(@ptr) + @ptr = nil + end + + def flags=(flags) + Lib.check Lib.posix_spawnattr_setflags(@ptr, flags) + end + + def flags + ptr = FFI::MemoryPointer.new(:short) + Lib.check Lib.posix_spawnattr_getflags(@ptr, ptr) + + ptr.read_short + end + + def pgroup=(pid) + self.flags |= Platform::POSIX_SPAWN_SETPGROUP + Lib.check Lib.posix_spawnattr_setpgroup(@ptr, pid) + end + + def to_ptr + @ptr + end + end # Attrs + + end + end +end + +# missing on rubinius +class FFI::MemoryPointer + unless method_defined?(:from_string) + def self.from_string(str) + ptr = new(1, str.bytesize + 1) + ptr.write_string("#{str}\0") + + ptr + end + end +end diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix/platform/i386-linux.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix/platform/i386-linux.rb new file mode 100644 index 00000000..ecf86eda --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix/platform/i386-linux.rb @@ -0,0 +1,12 @@ +module ChildProcess::Unix::Platform + SIZEOF = { + :posix_spawn_file_actions_t => 76, + :posix_spawnattr_t => 336, + :sigset_t => 128 + } + POSIX_SPAWN_RESETIDS = 1 + POSIX_SPAWN_SETPGROUP = 2 + POSIX_SPAWN_SETSIGDEF = 4 + POSIX_SPAWN_SETSIGMASK = 8 + POSIX_SPAWN_USEVFORK = 64 +end diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix/platform/i386-solaris.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix/platform/i386-solaris.rb new file mode 100644 index 00000000..5b557883 --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix/platform/i386-solaris.rb @@ -0,0 +1,11 @@ +module ChildProcess::Unix::Platform + SIZEOF = { + :posix_spawn_file_actions_t => 4, + :posix_spawnattr_t => 4, + :sigset_t => 16 + } + POSIX_SPAWN_RESETIDS = 1 + POSIX_SPAWN_SETPGROUP = 2 + POSIX_SPAWN_SETSIGDEF = 4 + POSIX_SPAWN_SETSIGMASK = 8 +end diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix/platform/x86_64-linux.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix/platform/x86_64-linux.rb new file mode 100644 index 00000000..b0c8777b --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix/platform/x86_64-linux.rb @@ -0,0 +1,12 @@ +module ChildProcess::Unix::Platform + SIZEOF = { + :posix_spawn_file_actions_t => 80, + :posix_spawnattr_t => 336, + :sigset_t => 128 + } + POSIX_SPAWN_RESETIDS = 1 + POSIX_SPAWN_SETPGROUP = 2 + POSIX_SPAWN_SETSIGDEF = 4 + POSIX_SPAWN_SETSIGMASK = 8 + POSIX_SPAWN_USEVFORK = 64 +end diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix/platform/x86_64-macosx.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix/platform/x86_64-macosx.rb new file mode 100644 index 00000000..fc0383de --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix/platform/x86_64-macosx.rb @@ -0,0 +1,11 @@ +module ChildProcess::Unix::Platform + SIZEOF = { + :posix_spawn_file_actions_t => 8, + :posix_spawnattr_t => 8, + :sigset_t => 4 + } + POSIX_SPAWN_RESETIDS = 1 + POSIX_SPAWN_SETPGROUP = 2 + POSIX_SPAWN_SETSIGDEF = 4 + POSIX_SPAWN_SETSIGMASK = 8 +end diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix/posix_spawn_process.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix/posix_spawn_process.rb new file mode 100644 index 00000000..9622eeb6 --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix/posix_spawn_process.rb @@ -0,0 +1,134 @@ +require 'ffi' +require 'thread' + +module ChildProcess + module Unix + class PosixSpawnProcess < Process + private + + @@cwd_lock = Mutex.new + + def launch_process + pid_ptr = FFI::MemoryPointer.new(:pid_t) + actions = Lib::FileActions.new + attrs = Lib::Attrs.new + + if io.stdout + actions.add_dup fileno_for(io.stdout), fileno_for(STDOUT) + else + actions.add_open fileno_for(STDOUT), "/dev/null", File::WRONLY, 0644 + end + + if io.stderr + actions.add_dup fileno_for(io.stderr), fileno_for(STDERR) + else + actions.add_open fileno_for(STDERR), "/dev/null", File::WRONLY, 0644 + end + + if duplex? + reader, writer = ::IO.pipe + actions.add_dup fileno_for(reader), fileno_for(STDIN) + actions.add_close fileno_for(writer) + end + + attrs.pgroup = 0 if leader? + attrs.flags |= Platform::POSIX_SPAWN_USEVFORK if defined? Platform::POSIX_SPAWN_USEVFORK + + # wrap in helper classes in order to avoid GC'ed pointers + argv = Argv.new(@args) + envp = Envp.new(ENV.to_hash.merge(@environment)) + + ret = 0 + @@cwd_lock.synchronize do + Dir.chdir(@cwd || Dir.pwd) do + if ChildProcess.jruby? + # on JRuby, the current working directory is for some reason not inherited. + # We'll work around it by making a chdir call through FFI. + # TODO: report this to JRuby + Lib.chdir Dir.pwd + end + + ret = Lib.posix_spawnp( + pid_ptr, + @args.first, # TODO: not sure this matches exec() behaviour + actions, + attrs, + argv, + envp + ) + end + end + + if duplex? + io._stdin = writer + reader.close + end + + actions.free + attrs.free + + if ret != 0 + raise LaunchError, "#{Lib.strerror(ret)} (#{ret})" + end + + @pid = pid_ptr.read_int + ::Process.detach(@pid) if detach? + end + + if ChildProcess.jruby? + def fileno_for(obj) + ChildProcess::JRuby.posix_fileno_for(obj) + end + else + def fileno_for(obj) + obj.fileno + end + end + + class Argv + def initialize(args) + @ptrs = args.map do |e| + if e.include?("\0") + raise ArgumentError, "argument cannot contain null bytes: #{e.inspect}" + end + + FFI::MemoryPointer.from_string(e.to_s) + end + + @ptrs << FFI::Pointer.new(0) + end + + def to_ptr + argv = FFI::MemoryPointer.new(:pointer, @ptrs.size) + argv.put_array_of_pointer(0, @ptrs) + + argv + end + end # Argv + + class Envp + def initialize(env) + @ptrs = env.map do |key, val| + next if val.nil? + + if key =~ /=|\0/ || val.to_s.include?("\0") + raise InvalidEnvironmentVariable, "#{key.inspect} => #{val.to_s.inspect}" + end + + FFI::MemoryPointer.from_string("#{key}=#{val.to_s}") + end.compact + + @ptrs << FFI::Pointer.new(0) + end + + def to_ptr + env = FFI::MemoryPointer.new(:pointer, @ptrs.size) + env.put_array_of_pointer(0, @ptrs) + + env + end + end # Envp + + end + end +end diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix/process.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix/process.rb new file mode 100644 index 00000000..59330606 --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/unix/process.rb @@ -0,0 +1,90 @@ +module ChildProcess + module Unix + class Process < AbstractProcess + attr_reader :pid + + def io + @io ||= Unix::IO.new + end + + def stop(timeout = 3) + assert_started + send_term + + begin + return poll_for_exit(timeout) + rescue TimeoutError + # try next + end + + send_kill + wait + rescue Errno::ECHILD, Errno::ESRCH + # handle race condition where process dies between timeout + # and send_kill + true + end + + def exited? + return true if @exit_code + + assert_started + pid, status = ::Process.waitpid2(@pid, ::Process::WNOHANG | ::Process::WUNTRACED) + pid = nil if pid == 0 # may happen on jruby + + log(:pid => pid, :status => status) + + if pid + set_exit_code(status) + end + + !!pid + rescue Errno::ECHILD + # may be thrown for detached processes + true + end + + def wait + assert_started + + if exited? + exit_code + else + _, status = ::Process.waitpid2(@pid) + + set_exit_code(status) + end + end + + private + + def send_term + send_signal 'TERM' + end + + def send_kill + send_signal 'KILL' + end + + def send_signal(sig) + assert_started + + log "sending #{sig}" + ::Process.kill sig, _pid + end + + def set_exit_code(status) + @exit_code = status.exitstatus || status.termsig + end + + def _pid + if leader? + -@pid # negative pid == process group + else + @pid + end + end + + end # Process + end # Unix +end # ChildProcess diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/version.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/version.rb new file mode 100644 index 00000000..2a573843 --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/version.rb @@ -0,0 +1,3 @@ +module ChildProcess + VERSION = '3.0.0' +end diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/windows.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/windows.rb new file mode 100644 index 00000000..1470a7a7 --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/windows.rb @@ -0,0 +1,38 @@ +require "rbconfig" + +begin + require 'ffi' +rescue LoadError + raise ChildProcess::MissingFFIError +end + +module ChildProcess + module Windows + module Lib + extend FFI::Library + + def self.msvcrt_name + host_part = RbConfig::CONFIG['host_os'].split("_")[1] + manifest = File.join(RbConfig::CONFIG['bindir'], 'ruby.exe.manifest') + + if host_part && host_part.to_i > 80 && File.exists?(manifest) + "msvcr#{host_part}" + else + "msvcrt" + end + end + + ffi_lib "kernel32", msvcrt_name + ffi_convention :stdcall + + + end # Library + end # Windows +end # ChildProcess + +require "childprocess/windows/lib" +require "childprocess/windows/structs" +require "childprocess/windows/handle" +require "childprocess/windows/io" +require "childprocess/windows/process_builder" +require "childprocess/windows/process" diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/windows/handle.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/windows/handle.rb new file mode 100644 index 00000000..94356848 --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/windows/handle.rb @@ -0,0 +1,91 @@ +module ChildProcess + module Windows + class Handle + + class << self + private :new + + def open(pid, access = PROCESS_ALL_ACCESS) + handle = Lib.open_process(access, false, pid) + + if handle.null? + raise Error, Lib.last_error_message + end + + h = new(handle, pid) + return h unless block_given? + + begin + yield h + ensure + h.close + end + end + end + + attr_reader :pointer + + def initialize(pointer, pid) + unless pointer.kind_of?(FFI::Pointer) + raise TypeError, "invalid handle: #{pointer.inspect}" + end + + if pointer.null? + raise ArgumentError, "handle is null: #{pointer.inspect}" + end + + @pid = pid + @pointer = pointer + @closed = false + end + + def exit_code + code_pointer = FFI::MemoryPointer.new :ulong + ok = Lib.get_exit_code(@pointer, code_pointer) + + if ok + code_pointer.get_ulong(0) + else + close + raise Error, Lib.last_error_message + end + end + + def send(signal) + case signal + when 0 + exit_code == PROCESS_STILL_ALIVE + when WIN_SIGINT + Lib.generate_console_ctrl_event(CTRL_C_EVENT, @pid) + when WIN_SIGBREAK + Lib.generate_console_ctrl_event(CTRL_BREAK_EVENT, @pid) + when WIN_SIGKILL + ok = Lib.terminate_process(@pointer, @pid) + Lib.check_error ok + else + thread_id = FFI::MemoryPointer.new(:ulong) + module_handle = Lib.get_module_handle("kernel32") + proc_address = Lib.get_proc_address(module_handle, "ExitProcess") + + thread = Lib.create_remote_thread(@pointer, 0, 0, proc_address, 0, 0, thread_id) + check_error thread + + Lib.wait_for_single_object(thread, 5) + true + end + end + + def close + return if @closed + + Lib.close_handle(@pointer) + @closed = true + end + + def wait(milliseconds = nil) + Lib.wait_for_single_object(@pointer, milliseconds || INFINITE) + end + + end # Handle + end # Windows +end # ChildProcess diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/windows/io.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/windows/io.rb new file mode 100644 index 00000000..6658cc9a --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/windows/io.rb @@ -0,0 +1,25 @@ +module ChildProcess + module Windows + class IO < AbstractIO + private + + def check_type(io) + return if has_fileno?(io) + return if has_to_io?(io) + + raise ArgumentError, "#{io.inspect}:#{io.class} must have :fileno or :to_io" + end + + def has_fileno?(io) + io.respond_to?(:fileno) && io.fileno + end + + def has_to_io?(io) + io.respond_to?(:to_io) && io.to_io.kind_of?(::IO) + end + + end # IO + end # Windows +end # ChildProcess + + diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/windows/lib.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/windows/lib.rb new file mode 100644 index 00000000..fd7f4788 --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/windows/lib.rb @@ -0,0 +1,416 @@ +module ChildProcess + module Windows + FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000 + FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000 + + PROCESS_ALL_ACCESS = 0x1F0FFF + PROCESS_QUERY_INFORMATION = 0x0400 + PROCESS_VM_READ = 0x0010 + PROCESS_STILL_ACTIVE = 259 + + INFINITE = 0xFFFFFFFF + + WIN_SIGINT = 2 + WIN_SIGBREAK = 3 + WIN_SIGKILL = 9 + + CTRL_C_EVENT = 0 + CTRL_BREAK_EVENT = 1 + + CREATE_BREAKAWAY_FROM_JOB = 0x01000000 + DETACHED_PROCESS = 0x00000008 + + STARTF_USESTDHANDLES = 0x00000100 + INVALID_HANDLE_VALUE = -1 + HANDLE_FLAG_INHERIT = 0x00000001 + + DUPLICATE_SAME_ACCESS = 0x00000002 + CREATE_UNICODE_ENVIRONMENT = 0x00000400 + + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000 + JOB_OBJECT_LIMIT_BREAKAWAY_OK = 0x00000800 + JOB_OBJECT_EXTENDED_LIMIT_INFORMATION = 9 + JOB_OBJECT_BASIC_LIMIT_INFORMATION = 2 + + module Lib + enum :wait_status, [ + :wait_object_0, 0, + :wait_timeout, 0x102, + :wait_abandoned, 0x80, + :wait_failed, 0xFFFFFFFF + ] + + # + # BOOL WINAPI CreateProcess( + # __in_opt LPCTSTR lpApplicationName, + # __inout_opt LPTSTR lpCommandLine, + # __in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes, + # __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes, + # __in BOOL bInheritHandles, + # __in DWORD dwCreationFlags, + # __in_opt LPVOID lpEnvironment, + # __in_opt LPCTSTR lpCurrentDirectory, + # __in LPSTARTUPINFO lpStartupInfo, + # __out LPPROCESS_INFORMATION lpProcessInformation + # ); + # + + attach_function :create_process, :CreateProcessW, [ + :pointer, + :buffer_inout, + :pointer, + :pointer, + :bool, + :ulong, + :pointer, + :pointer, + :pointer, + :pointer], :bool + + # + # DWORD WINAPI FormatMessage( + # __in DWORD dwFlags, + # __in_opt LPCVOID lpSource, + # __in DWORD dwMessageId, + # __in DWORD dwLanguageId, + # __out LPTSTR lpBuffer, + # __in DWORD nSize, + # __in_opt va_list *Arguments + # ); + # + + attach_function :format_message, :FormatMessageA, [ + :ulong, + :pointer, + :ulong, + :ulong, + :pointer, + :ulong, + :pointer], :ulong + + + attach_function :close_handle, :CloseHandle, [:pointer], :bool + + # + # HANDLE WINAPI OpenProcess( + # __in DWORD dwDesiredAccess, + # __in BOOL bInheritHandle, + # __in DWORD dwProcessId + # ); + # + + attach_function :open_process, :OpenProcess, [:ulong, :bool, :ulong], :pointer + + # + # HANDLE WINAPI CreateJobObject( + # _In_opt_ LPSECURITY_ATTRIBUTES lpJobAttributes, + # _In_opt_ LPCTSTR lpName + # ); + # + + attach_function :create_job_object, :CreateJobObjectA, [:pointer, :pointer], :pointer + + # + # BOOL WINAPI AssignProcessToJobObject( + # _In_ HANDLE hJob, + # _In_ HANDLE hProcess + # ); + + attach_function :assign_process_to_job_object, :AssignProcessToJobObject, [:pointer, :pointer], :bool + + # + # BOOL WINAPI SetInformationJobObject( + # _In_ HANDLE hJob, + # _In_ JOBOBJECTINFOCLASS JobObjectInfoClass, + # _In_ LPVOID lpJobObjectInfo, + # _In_ DWORD cbJobObjectInfoLength + # ); + # + + attach_function :set_information_job_object, :SetInformationJobObject, [:pointer, :int, :pointer, :ulong], :bool + + # + # + # DWORD WINAPI WaitForSingleObject( + # __in HANDLE hHandle, + # __in DWORD dwMilliseconds + # ); + # + + attach_function :wait_for_single_object, :WaitForSingleObject, [:pointer, :ulong], :wait_status, :blocking => true + + # + # BOOL WINAPI GetExitCodeProcess( + # __in HANDLE hProcess, + # __out LPDWORD lpExitCode + # ); + # + + attach_function :get_exit_code, :GetExitCodeProcess, [:pointer, :pointer], :bool + + # + # BOOL WINAPI GenerateConsoleCtrlEvent( + # __in DWORD dwCtrlEvent, + # __in DWORD dwProcessGroupId + # ); + # + + attach_function :generate_console_ctrl_event, :GenerateConsoleCtrlEvent, [:ulong, :ulong], :bool + + # + # BOOL WINAPI TerminateProcess( + # __in HANDLE hProcess, + # __in UINT uExitCode + # ); + # + + attach_function :terminate_process, :TerminateProcess, [:pointer, :uint], :bool + + # + # intptr_t _get_osfhandle( + # int fd + # ); + # + + attach_function :get_osfhandle, :_get_osfhandle, [:int], :intptr_t + + # + # int _open_osfhandle ( + # intptr_t osfhandle, + # int flags + # ); + # + + attach_function :open_osfhandle, :_open_osfhandle, [:pointer, :int], :int + + # BOOL WINAPI SetHandleInformation( + # __in HANDLE hObject, + # __in DWORD dwMask, + # __in DWORD dwFlags + # ); + + attach_function :set_handle_information, :SetHandleInformation, [:pointer, :ulong, :ulong], :bool + + # BOOL WINAPI GetHandleInformation( + # __in HANDLE hObject, + # __out LPDWORD lpdwFlags + # ); + + attach_function :get_handle_information, :GetHandleInformation, [:pointer, :pointer], :bool + + # BOOL WINAPI CreatePipe( + # __out PHANDLE hReadPipe, + # __out PHANDLE hWritePipe, + # __in_opt LPSECURITY_ATTRIBUTES lpPipeAttributes, + # __in DWORD nSize + # ); + + attach_function :create_pipe, :CreatePipe, [:pointer, :pointer, :pointer, :ulong], :bool + + # + # HANDLE WINAPI GetCurrentProcess(void); + # + + attach_function :current_process, :GetCurrentProcess, [], :pointer + + # + # BOOL WINAPI DuplicateHandle( + # __in HANDLE hSourceProcessHandle, + # __in HANDLE hSourceHandle, + # __in HANDLE hTargetProcessHandle, + # __out LPHANDLE lpTargetHandle, + # __in DWORD dwDesiredAccess, + # __in BOOL bInheritHandle, + # __in DWORD dwOptions + # ); + # + + attach_function :_duplicate_handle, :DuplicateHandle, [ + :pointer, + :pointer, + :pointer, + :pointer, + :ulong, + :bool, + :ulong + ], :bool + + class << self + def kill(signal, *pids) + case signal + when 'SIGINT', 'INT', :SIGINT, :INT + signal = WIN_SIGINT + when 'SIGBRK', 'BRK', :SIGBREAK, :BRK + signal = WIN_SIGBREAK + when 'SIGKILL', 'KILL', :SIGKILL, :KILL + signal = WIN_SIGKILL + when 0..9 + # Do nothing + else + raise Error, "invalid signal #{signal.inspect}" + end + + pids.map { |pid| pid if Lib.send_signal(signal, pid) }.compact + end + + def waitpid(pid, flags = 0) + wait_for_pid(pid, no_hang?(flags)) + end + + def waitpid2(pid, flags = 0) + code = wait_for_pid(pid, no_hang?(flags)) + + [pid, code] if code + end + + def dont_inherit(file) + unless file.respond_to?(:fileno) + raise ArgumentError, "expected #{file.inspect} to respond to :fileno" + end + + set_handle_inheritance(handle_for(file.fileno), false) + end + + def last_error_message + errnum = FFI.errno + + buf = FFI::MemoryPointer.new :char, 512 + + size = format_message( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY, + nil, errnum, 0, buf, buf.size, nil + ) + + str = buf.read_string(size).strip + if errnum == 0 + "Unknown error (Windows says #{str.inspect}, but it did not.)" + else + "#{str} (#{errnum})" + end + end + + def each_child_of(pid, &blk) + raise NotImplementedError + + # http://stackoverflow.com/questions/1173342/terminate-a-process-tree-c-for-windows?rq=1 + + # for each process entry + # if pe.th32ParentProcessID == pid + # Handle.open(pe.pe.th32ProcessId, &blk) + # end + # + end + + def handle_for(fd_or_io) + if fd_or_io.kind_of?(IO) || fd_or_io.respond_to?(:fileno) + if ChildProcess.jruby? + handle = ChildProcess::JRuby.windows_handle_for(fd_or_io) + else + handle = get_osfhandle(fd_or_io.fileno) + end + elsif fd_or_io.kind_of?(Integer) + handle = get_osfhandle(fd_or_io) + elsif fd_or_io.respond_to?(:to_io) + io = fd_or_io.to_io + + unless io.kind_of?(IO) + raise TypeError, "expected #to_io to return an instance of IO" + end + + handle = get_osfhandle(io.fileno) + else + raise TypeError, "invalid type: #{fd_or_io.inspect}" + end + + if handle == INVALID_HANDLE_VALUE + raise Error, last_error_message + end + + FFI::Pointer.new handle + end + + def io_for(handle, flags = File::RDONLY) + fd = open_osfhandle(handle, flags) + if fd == -1 + raise Error, last_error_message + end + + FFI::IO.for_fd fd, flags + end + + def duplicate_handle(handle) + dup = FFI::MemoryPointer.new(:pointer) + proc = current_process + + ok = Lib._duplicate_handle( + proc, + handle, + proc, + dup, + 0, + false, + DUPLICATE_SAME_ACCESS + ) + + check_error ok + + dup.read_pointer + ensure + close_handle proc + end + + def set_handle_inheritance(handle, bool) + status = set_handle_information( + handle, + HANDLE_FLAG_INHERIT, + bool ? HANDLE_FLAG_INHERIT : 0 + ) + + check_error status + end + + def get_handle_inheritance(handle) + flags = FFI::MemoryPointer.new(:uint) + + status = get_handle_information( + handle, + flags + ) + + check_error status + + flags.read_uint + end + + def check_error(bool) + bool or raise Error, last_error_message + end + + def alive?(pid) + handle = Lib.open_process(PROCESS_ALL_ACCESS, false, pid) + if handle.null? + false + else + ptr = FFI::MemoryPointer.new :ulong + Lib.check_error Lib.get_exit_code(handle, ptr) + ptr.read_ulong == PROCESS_STILL_ACTIVE + end + end + + def no_hang?(flags) + (flags & Process::WNOHANG) == Process::WNOHANG + end + + def wait_for_pid(pid, no_hang) + code = Handle.open(pid) { |handle| + handle.wait unless no_hang + handle.exit_code + } + + code if code != PROCESS_STILL_ACTIVE + end + end + + end # Lib + end # Windows +end # ChildProcess diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/windows/process.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/windows/process.rb new file mode 100755 index 00000000..48020bfe --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/windows/process.rb @@ -0,0 +1,130 @@ +module ChildProcess + module Windows + class Process < AbstractProcess + + attr_reader :pid + + def io + @io ||= Windows::IO.new + end + + def stop(timeout = 3) + assert_started + + log "sending KILL" + @handle.send(WIN_SIGKILL) + + poll_for_exit(timeout) + ensure + close_handle + close_job_if_necessary + end + + def wait + if exited? + exit_code + else + @handle.wait + @exit_code = @handle.exit_code + + close_handle + close_job_if_necessary + + @exit_code + end + end + + def exited? + return true if @exit_code + assert_started + + code = @handle.exit_code + exited = code != PROCESS_STILL_ACTIVE + + log(:exited? => exited, :code => code) + + if exited + @exit_code = code + close_handle + close_job_if_necessary + end + + exited + end + + private + + def launch_process + builder = ProcessBuilder.new(@args) + builder.leader = leader? + builder.detach = detach? + builder.duplex = duplex? + builder.environment = @environment unless @environment.empty? + builder.cwd = @cwd + + if @io + builder.stdout = @io.stdout + builder.stderr = @io.stderr + end + + @pid = builder.start + @handle = Handle.open @pid + + if leader? + @job = Job.new + @job << @handle + end + + if duplex? + raise Error, "no stdin stream" unless builder.stdin + io._stdin = builder.stdin + end + + self + end + + def close_handle + @handle.close + end + + def close_job_if_necessary + @job.close if leader? + end + + + class Job + def initialize + @pointer = Lib.create_job_object(nil, nil) + + if @pointer.nil? || @pointer.null? + raise Error, "unable to create job object" + end + + basic = JobObjectBasicLimitInformation.new + basic[:LimitFlags] = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_BREAKAWAY_OK + + extended = JobObjectExtendedLimitInformation.new + extended[:BasicLimitInformation] = basic + + ret = Lib.set_information_job_object( + @pointer, + JOB_OBJECT_EXTENDED_LIMIT_INFORMATION, + extended, + extended.size + ) + + Lib.check_error ret + end + + def <<(handle) + Lib.check_error Lib.assign_process_to_job_object(@pointer, handle.pointer) + end + + def close + Lib.close_handle @pointer + end + end + + end # Process + end # Windows +end # ChildProcess diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/windows/process_builder.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/windows/process_builder.rb new file mode 100644 index 00000000..1e849d73 --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/windows/process_builder.rb @@ -0,0 +1,178 @@ +module ChildProcess + module Windows + class ProcessBuilder + attr_accessor :leader, :detach, :duplex, :environment, :stdout, :stderr, :cwd + attr_reader :stdin + + def initialize(args) + @args = args + + @detach = false + @duplex = false + @environment = nil + @cwd = nil + + @stdout = nil + @stderr = nil + @stdin = nil + + @flags = 0 + @job_ptr = nil + @cmd_ptr = nil + @env_ptr = nil + @cwd_ptr = nil + end + + def start + create_command_pointer + create_environment_pointer + create_cwd_pointer + + setup_flags + setup_io + + pid = create_process + close_handles + + pid + end + + private + + def to_wide_string(str) + newstr = str + "\0".encode(str.encoding) + newstr.encode!('UTF-16LE') + end + + def create_command_pointer + string = @args.map { |arg| quote_if_necessary(arg.to_s) }.join(' ') + @cmd_ptr = to_wide_string(string) + end + + def create_environment_pointer + return unless @environment.kind_of?(Hash) && @environment.any? + + strings = [] + + ENV.to_hash.merge(@environment).each do |key, val| + next if val.nil? + + if key.to_s =~ /=|\0/ || val.to_s.include?("\0") + raise InvalidEnvironmentVariable, "#{key.inspect} => #{val.inspect}" + end + + strings << "#{key}=#{val}\0" + end + + env_str = to_wide_string(strings.join) + @env_ptr = FFI::MemoryPointer.from_string(env_str) + end + + def create_cwd_pointer + @cwd_ptr = FFI::MemoryPointer.from_string(to_wide_string(@cwd || Dir.pwd)) + end + + def create_process + ok = Lib.create_process( + nil, # application name + @cmd_ptr, # command line + nil, # process attributes + nil, # thread attributes + true, # inherit handles + @flags, # creation flags + @env_ptr, # environment + @cwd_ptr, # current directory + startup_info, # startup info + process_info # process info + ) + + ok or raise LaunchError, Lib.last_error_message + + process_info[:dwProcessId] + end + + def startup_info + @startup_info ||= StartupInfo.new + end + + def process_info + @process_info ||= ProcessInfo.new + end + + def setup_flags + @flags |= CREATE_UNICODE_ENVIRONMENT + @flags |= DETACHED_PROCESS if @detach + @flags |= CREATE_BREAKAWAY_FROM_JOB if @leader + end + + def setup_io + startup_info[:dwFlags] ||= 0 + startup_info[:dwFlags] |= STARTF_USESTDHANDLES + + if @stdout + startup_info[:hStdOutput] = std_stream_handle_for(@stdout) + end + + if @stderr + startup_info[:hStdError] = std_stream_handle_for(@stderr) + end + + if @duplex + read_pipe_ptr = FFI::MemoryPointer.new(:pointer) + write_pipe_ptr = FFI::MemoryPointer.new(:pointer) + sa = SecurityAttributes.new(:inherit => true) + + ok = Lib.create_pipe(read_pipe_ptr, write_pipe_ptr, sa, 0) + Lib.check_error ok + + @read_pipe = read_pipe_ptr.read_pointer + @write_pipe = write_pipe_ptr.read_pointer + + Lib.set_handle_inheritance @read_pipe, true + Lib.set_handle_inheritance @write_pipe, false + + startup_info[:hStdInput] = @read_pipe + else + startup_info[:hStdInput] = std_stream_handle_for(STDIN) + end + end + + def std_stream_handle_for(io) + handle = Lib.handle_for(io) + + begin + Lib.set_handle_inheritance handle, true + rescue ChildProcess::Error + # If the IO was set to close on exec previously, this call will fail. + # That's probably OK, since the user explicitly asked for it to be + # closed (at least I have yet to find other cases where this will + # happen...) + end + + handle + end + + def close_handles + Lib.close_handle process_info[:hProcess] + Lib.close_handle process_info[:hThread] + + if @duplex + @stdin = Lib.io_for(Lib.duplicate_handle(@write_pipe), File::WRONLY) + Lib.close_handle @read_pipe + Lib.close_handle @write_pipe + end + end + + def quote_if_necessary(str) + quote = str.start_with?('"') ? "'" : '"' + + case str + when /[\s\\'"]/ + [quote, str, quote].join + else + str + end + end + end # ProcessBuilder + end # Windows +end # ChildProcess diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/windows/structs.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/windows/structs.rb new file mode 100644 index 00000000..92253c3f --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/lib/childprocess/windows/structs.rb @@ -0,0 +1,149 @@ +module ChildProcess + module Windows + # typedef struct _STARTUPINFO { + # DWORD cb; + # LPTSTR lpReserved; + # LPTSTR lpDesktop; + # LPTSTR lpTitle; + # DWORD dwX; + # DWORD dwY; + # DWORD dwXSize; + # DWORD dwYSize; + # DWORD dwXCountChars; + # DWORD dwYCountChars; + # DWORD dwFillAttribute; + # DWORD dwFlags; + # WORD wShowWindow; + # WORD cbReserved2; + # LPBYTE lpReserved2; + # HANDLE hStdInput; + # HANDLE hStdOutput; + # HANDLE hStdError; + # } STARTUPINFO, *LPSTARTUPINFO; + + class StartupInfo < FFI::Struct + layout :cb, :ulong, + :lpReserved, :pointer, + :lpDesktop, :pointer, + :lpTitle, :pointer, + :dwX, :ulong, + :dwY, :ulong, + :dwXSize, :ulong, + :dwYSize, :ulong, + :dwXCountChars, :ulong, + :dwYCountChars, :ulong, + :dwFillAttribute, :ulong, + :dwFlags, :ulong, + :wShowWindow, :ushort, + :cbReserved2, :ushort, + :lpReserved2, :pointer, + :hStdInput, :pointer, # void ptr + :hStdOutput, :pointer, # void ptr + :hStdError, :pointer # void ptr + end + + # + # typedef struct _PROCESS_INFORMATION { + # HANDLE hProcess; + # HANDLE hThread; + # DWORD dwProcessId; + # DWORD dwThreadId; + # } PROCESS_INFORMATION, *LPPROCESS_INFORMATION; + # + + class ProcessInfo < FFI::Struct + layout :hProcess, :pointer, # void ptr + :hThread, :pointer, # void ptr + :dwProcessId, :ulong, + :dwThreadId, :ulong + end + + # + # typedef struct _SECURITY_ATTRIBUTES { + # DWORD nLength; + # LPVOID lpSecurityDescriptor; + # BOOL bInheritHandle; + # } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES; + # + + class SecurityAttributes < FFI::Struct + layout :nLength, :ulong, + :lpSecurityDescriptor, :pointer, # void ptr + :bInheritHandle, :int + + def initialize(opts = {}) + super() + + self[:nLength] = self.class.size + self[:lpSecurityDescriptor] = nil + self[:bInheritHandle] = opts[:inherit] ? 1 : 0 + end + end + + # + # typedef struct _JOBOBJECT_BASIC_LIMIT_INFORMATION { + # LARGE_INTEGER PerProcessUserTimeLimit; + # LARGE_INTEGER PerJobUserTimeLimit; + # DWORD LimitFlags; + # SIZE_T MinimumWorkingSetSize; + # SIZE_T MaximumWorkingSetSize; + # DWORD ActiveProcessLimit; + # ULONG_PTR Affinity; + # DWORD PriorityClass; + # DWORD SchedulingClass; + # } JOBOBJECT_BASIC_LIMIT_INFORMATION, *PJOBOBJECT_BASIC_LIMIT_INFORMATION; + # + class JobObjectBasicLimitInformation < FFI::Struct + layout :PerProcessUserTimeLimit, :int64, + :PerJobUserTimeLimit, :int64, + :LimitFlags, :ulong, + :MinimumWorkingSetSize, :size_t, + :MaximumWorkingSetSize, :size_t, + :ActiveProcessLimit, :ulong, + :Affinity, :pointer, + :PriorityClass, :ulong, + :SchedulingClass, :ulong + end + + # + # typedef struct _IO_COUNTERS { + # ULONGLONG ReadOperationCount; + # ULONGLONG WriteOperationCount; + # ULONGLONG OtherOperationCount; + # ULONGLONG ReadTransferCount; + # ULONGLONG WriteTransferCount; + # ULONGLONG OtherTransferCount; + # } IO_COUNTERS, *PIO_COUNTERS; + # + + class IoCounters < FFI::Struct + layout :ReadOperationCount, :ulong_long, + :WriteOperationCount, :ulong_long, + :OtherOperationCount, :ulong_long, + :ReadTransferCount, :ulong_long, + :WriteTransferCount, :ulong_long, + :OtherTransferCount, :ulong_long + end + # + # typedef struct _JOBOBJECT_EXTENDED_LIMIT_INFORMATION { + # JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; + # IO_COUNTERS IoInfo; + # SIZE_T ProcessMemoryLimit; + # SIZE_T JobMemoryLimit; + # SIZE_T PeakProcessMemoryUsed; + # SIZE_T PeakJobMemoryUsed; + # } JOBOBJECT_EXTENDED_LIMIT_INFORMATION, *PJOBOBJECT_EXTENDED_LIMIT_INFORMATION; + # + + class JobObjectExtendedLimitInformation < FFI::Struct + layout :BasicLimitInformation, JobObjectBasicLimitInformation, + :IoInfo, IoCounters, + :ProcessMemoryLimit, :size_t, + :JobMemoryLimit, :size_t, + :PeakProcessMemoryUsed, :size_t, + :PeakJobMemoryUsed, :size_t + end + + + end # Windows +end # ChildProcess \ No newline at end of file diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/abstract_io_spec.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/abstract_io_spec.rb new file mode 100644 index 00000000..e677bbeb --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/abstract_io_spec.rb @@ -0,0 +1,12 @@ +require File.expand_path('../spec_helper', __FILE__) + +describe ChildProcess::AbstractIO do + let(:io) { ChildProcess::AbstractIO.new } + + it "inherits the parent's IO streams" do + io.inherit! + + expect(io.stdout).to eq STDOUT + expect(io.stderr).to eq STDERR + end +end diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/childprocess_spec.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/childprocess_spec.rb new file mode 100644 index 00000000..89bb235d --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/childprocess_spec.rb @@ -0,0 +1,447 @@ +# encoding: utf-8 + +require File.expand_path('../spec_helper', __FILE__) +require 'rubygems/mock_gem_ui' + + +describe ChildProcess do + + here = File.dirname(__FILE__) + + let(:gemspec) { eval(File.read "#{here}/../childprocess.gemspec") } + + it 'validates cleanly' do + mock_ui = Gem::MockGemUi.new + Gem::DefaultUserInteraction.use_ui(mock_ui) { gemspec.validate } + + expect(mock_ui.error).to_not match(/warn/i) + end + + + it "returns self when started" do + process = sleeping_ruby + + expect(process.start).to eq process + expect(process).to be_alive + end + + # We can't detect failure to execve() when using posix_spawn() on Linux + # without waiting for the child to exit with code 127. + # + # See e.g. http://repo.or.cz/w/glibc.git/blob/669704fd:/sysdeps/posix/spawni.c#l34 + # + # We could work around this by doing the PATH search ourselves, but not sure + # it's worth it. + it "raises ChildProcess::LaunchError if the process can't be started", :posix_spawn_on_linux => false do + expect { invalid_process.start }.to raise_error(ChildProcess::LaunchError) + end + + it 'raises ArgumentError if given a non-string argument' do + expect { ChildProcess.build(nil, "unlikelytoexist") }.to raise_error(ArgumentError) + expect { ChildProcess.build("foo", 1) }.to raise_error(ArgumentError) + end + + it "knows if the process crashed" do + process = exit_with(1).start + process.wait + + expect(process).to be_crashed + end + + it "knows if the process didn't crash" do + process = exit_with(0).start + process.wait + + expect(process).to_not be_crashed + end + + it "can wait for a process to finish" do + process = exit_with(0).start + return_value = process.wait + + expect(process).to_not be_alive + expect(return_value).to eq 0 + end + + it 'ignores #wait if process already finished' do + process = exit_with(0).start + sleep 0.01 until process.exited? + + expect(process.wait).to eql 0 + end + + it "escalates if TERM is ignored" do + process = ignored('TERM').start + process.stop + expect(process).to be_exited + end + + it "accepts a timeout argument to #stop" do + process = sleeping_ruby.start + process.stop(exit_timeout) + end + + it "lets child process inherit the environment of the current process" do + Tempfile.open("env-spec") do |file| + file.close + with_env('INHERITED' => 'yes') do + process = write_env(file.path).start + process.wait + end + + file.open + child_env = eval rewind_and_read(file) + expect(child_env['INHERITED']).to eql 'yes' + end + end + + it "can override env vars only for the current process" do + Tempfile.open("env-spec") do |file| + file.close + process = write_env(file.path) + process.environment['CHILD_ONLY'] = '1' + process.start + + expect(ENV['CHILD_ONLY']).to be_nil + + process.wait + + file.open + child_env = eval rewind_and_read(file) + expect(child_env['CHILD_ONLY']).to eql '1' + end + end + + it 'allows unicode characters in the environment' do + Tempfile.open("env-spec") do |file| + file.close + process = write_env(file.path) + process.environment['FOö'] = 'baör' + process.start + process.wait + + file.open + child_env = eval rewind_and_read(file) + + expect(child_env['FOö']).to eql 'baör' + end + end + + it "inherits the parent's env vars also when some are overridden" do + Tempfile.open("env-spec") do |file| + file.close + with_env('INHERITED' => 'yes', 'CHILD_ONLY' => 'no') do + process = write_env(file.path) + process.environment['CHILD_ONLY'] = 'yes' + + process.start + process.wait + + file.open + child_env = eval rewind_and_read(file) + + expect(child_env['INHERITED']).to eq 'yes' + expect(child_env['CHILD_ONLY']).to eq 'yes' + end + end + end + + it "can unset env vars" do + Tempfile.open("env-spec") do |file| + file.close + ENV['CHILDPROCESS_UNSET'] = '1' + process = write_env(file.path) + process.environment['CHILDPROCESS_UNSET'] = nil + process.start + + process.wait + + file.open + child_env = eval rewind_and_read(file) + expect(child_env).to_not have_key('CHILDPROCESS_UNSET') + end + end + + it 'does not see env vars unset in parent' do + Tempfile.open('env-spec') do |file| + file.close + ENV['CHILDPROCESS_UNSET'] = nil + process = write_env(file.path) + process.start + + process.wait + + file.open + child_env = eval rewind_and_read(file) + expect(child_env).to_not have_key('CHILDPROCESS_UNSET') + end + end + + + it "passes arguments to the child" do + args = ["foo", "bar"] + + Tempfile.open("argv-spec") do |file| + process = write_argv(file.path, *args).start + process.wait + + expect(rewind_and_read(file)).to eql args.inspect + end + end + + it "lets a detached child live on" do + p_pid = nil + c_pid = nil + + Tempfile.open('grandparent_out') do |gp_file| + # Create a parent and detached child process that will spit out their PID. Make sure that the child process lasts longer than the parent. + p_process = ruby("require 'childprocess' ; c_process = ChildProcess.build('ruby', '-e', 'puts \\\"Child PID: \#{Process.pid}\\\" ; sleep 5') ; c_process.io.inherit! ; c_process.detach = true ; c_process.start ; puts \"Child PID: \#{c_process.pid}\" ; puts \"Parent PID: \#{Process.pid}\"") + p_process.io.stdout = p_process.io.stderr = gp_file + + # Let the parent process die + p_process.start + p_process.wait + + + # Gather parent and child PIDs + pids = rewind_and_read(gp_file).split("\n") + pids.collect! { |pid| pid[/\d+/].to_i } + c_pid, p_pid = pids + end + + # Check that the parent process has dies but the child process is still alive + expect(alive?(p_pid)).to_not be true + expect(alive?(c_pid)).to be true + end + + it "preserves Dir.pwd in the child" do + Tempfile.open("dir-spec-out") do |file| + process = ruby("print Dir.pwd") + process.io.stdout = process.io.stderr = file + + expected_dir = nil + Dir.chdir(Dir.tmpdir) do + expected_dir = Dir.pwd + process.start + end + + process.wait + + expect(rewind_and_read(file)).to eq expected_dir + end + end + + it "can handle whitespace, special characters and quotes in arguments" do + args = ["foo bar", 'foo\bar', "'i-am-quoted'", '"i am double quoted"'] + + Tempfile.open("argv-spec") do |file| + process = write_argv(file.path, *args).start + process.wait + + expect(rewind_and_read(file)).to eq args.inspect + end + end + + it 'handles whitespace in the executable name' do + path = File.expand_path('foo bar') + + with_executable_at(path) do |proc| + expect(proc.start).to eq proc + expect(proc).to be_alive + end + end + + it "times out when polling for exit" do + process = sleeping_ruby.start + expect { process.poll_for_exit(0.1) }.to raise_error(ChildProcess::TimeoutError) + end + + it "can change working directory" do + process = ruby "print Dir.pwd" + + with_tmpdir { |dir| + process.cwd = dir + + orig_pwd = Dir.pwd + + Tempfile.open('cwd') do |file| + process.io.stdout = file + + process.start + process.wait + + expect(rewind_and_read(file)).to eq dir + end + + expect(Dir.pwd).to eq orig_pwd + } + end + + it 'kills the full process tree', :process_builder => false do + Tempfile.open('kill-process-tree') do |file| + process = write_pid_in_sleepy_grand_child(file.path) + process.leader = true + process.start + + pid = wait_until(30) do + Integer(rewind_and_read(file)) rescue nil + end + + process.stop + wait_until(3) { expect(alive?(pid)).to eql(false) } + end + end + + it 'releases the GIL while waiting for the process' do + time = Time.now + threads = [] + + threads << Thread.new { sleeping_ruby(1).start.wait } + threads << Thread.new(time) { expect(Time.now - time).to be < 0.5 } + + threads.each { |t| t.join } + end + + it 'can check if a detached child is alive' do + proc = ruby_process("-e", "sleep") + proc.detach = true + + proc.start + + expect(proc).to be_alive + proc.stop(0) + + expect(proc).to be_exited + end + + describe 'OS detection' do + + before(:all) do + # Save off original OS so that it can be restored later + @original_host_os = RbConfig::CONFIG['host_os'] + end + + after(:each) do + # Restore things to the real OS instead of the fake test OS + RbConfig::CONFIG['host_os'] = @original_host_os + ChildProcess.instance_variable_set(:@os, nil) + end + + + # TODO: add tests for other OSs + context 'on a BSD system' do + + let(:bsd_patterns) { ['bsd', 'dragonfly'] } + + it 'correctly identifies BSD systems' do + bsd_patterns.each do |pattern| + RbConfig::CONFIG['host_os'] = pattern + ChildProcess.instance_variable_set(:@os, nil) + + expect(ChildProcess.os).to eq(:bsd) + end + end + + end + + end + + it 'has a logger' do + expect(ChildProcess).to respond_to(:logger) + end + + it 'can change its logger' do + expect(ChildProcess).to respond_to(:logger=) + + original_logger = ChildProcess.logger + begin + ChildProcess.logger = :some_other_logger + expect(ChildProcess.logger).to eq(:some_other_logger) + ensure + ChildProcess.logger = original_logger + end + end + + + describe 'logger' do + + before(:each) do + ChildProcess.logger = logger + end + + after(:all) do + ChildProcess.logger = nil + end + + + context 'with the default logger' do + + let(:logger) { nil } + + + it 'logs at INFO level by default' do + expect(ChildProcess.logger.level).to eq(Logger::INFO) + end + + it 'logs at DEBUG level by default if $DEBUG is on' do + original_debug = $DEBUG + + begin + $DEBUG = true + + expect(ChildProcess.logger.level).to eq(Logger::DEBUG) + ensure + $DEBUG = original_debug + end + end + + it "logs to stderr by default" do + cap = capture_std { generate_log_messages } + + expect(cap.stdout).to be_empty + expect(cap.stderr).to_not be_empty + end + + end + + context 'with a custom logger' do + + let(:logger) { Logger.new($stdout) } + + it "logs to configured logger" do + cap = capture_std { generate_log_messages } + + expect(cap.stdout).to_not be_empty + expect(cap.stderr).to be_empty + end + + end + + end + + describe '#started?' do + subject { process.started? } + + context 'when not started' do + let(:process) { sleeping_ruby(1) } + + it { is_expected.to be false } + end + + context 'when started' do + let(:process) { sleeping_ruby(1).start } + + it { is_expected.to be true } + end + + context 'when finished' do + before(:each) { process.wait } + + let(:process) { sleeping_ruby(0).start } + + it { is_expected.to be true } + end + + end + +end diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/get_env.ps1 b/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/get_env.ps1 new file mode 100644 index 00000000..f3f7de7f --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/get_env.ps1 @@ -0,0 +1,13 @@ +param($p1) +$env_list = Get-ChildItem Env: + +# Builds a ruby hash compatible string +$hash_string = "{" + +foreach ($item in $env_list) +{ + $hash_string += "`"" + $item.Name + "`" => `"" + $item.value.replace('\','\\').replace('"','\"') + "`"," +} +$hash_string += "}" + +$hash_string | out-File -Encoding "UTF8" $p1 diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/io_spec.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/io_spec.rb new file mode 100644 index 00000000..17bd5263 --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/io_spec.rb @@ -0,0 +1,228 @@ +require File.expand_path('../spec_helper', __FILE__) + +describe ChildProcess do + it "can run even when $stdout is a StringIO" do + begin + stdout = $stdout + $stdout = StringIO.new + expect { sleeping_ruby.start }.to_not raise_error + ensure + $stdout = stdout + end + end + + it "can redirect stdout, stderr" do + process = ruby(<<-CODE) + [STDOUT, STDERR].each_with_index do |io, idx| + io.sync = true + io.puts idx + end + CODE + + out = Tempfile.new("stdout-spec") + err = Tempfile.new("stderr-spec") + + begin + process.io.stdout = out + process.io.stderr = err + + process.start + expect(process.io.stdin).to be_nil + process.wait + + expect(rewind_and_read(out)).to eq "0\n" + expect(rewind_and_read(err)).to eq "1\n" + ensure + out.close + err.close + end + end + + it "can redirect stdout only" do + process = ruby(<<-CODE) + [STDOUT, STDERR].each_with_index do |io, idx| + io.sync = true + io.puts idx + end + CODE + + out = Tempfile.new("stdout-spec") + + begin + process.io.stdout = out + + process.start + process.wait + + expect(rewind_and_read(out)).to eq "0\n" + ensure + out.close + end + end + + it "pumps all output" do + process = echo + + out = Tempfile.new("pump") + + begin + process.io.stdout = out + + process.start + process.wait + + expect(rewind_and_read(out)).to eq "hello\n" + ensure + out.close + end + end + + it "can write to stdin if duplex = true" do + process = cat + + out = Tempfile.new("duplex") + out.sync = true + + begin + process.io.stdout = out + process.io.stderr = out + process.duplex = true + + process.start + process.io.stdin.puts "hello world" + process.io.stdin.close + + process.poll_for_exit(exit_timeout) + + expect(rewind_and_read(out)).to eq "hello world\n" + ensure + out.close + end + end + + it "can write to stdin interactively if duplex = true" do + process = cat + + out = Tempfile.new("duplex") + out.sync = true + + out_receiver = File.open(out.path, "rb") + begin + process.io.stdout = out + process.io.stderr = out + process.duplex = true + + process.start + + stdin = process.io.stdin + + stdin.puts "hello" + stdin.flush + wait_until { expect(rewind_and_read(out_receiver)).to match(/\Ahello\r?\n\z/m) } + + stdin.putc "n" + stdin.flush + wait_until { expect(rewind_and_read(out_receiver)).to match(/\Ahello\r?\nn\z/m) } + + stdin.print "e" + stdin.flush + wait_until { expect(rewind_and_read(out_receiver)).to match(/\Ahello\r?\nne\z/m) } + + stdin.printf "w" + stdin.flush + wait_until { expect(rewind_and_read(out_receiver)).to match(/\Ahello\r?\nnew\z/m) } + + stdin.write "\nworld\n" + stdin.flush + wait_until { expect(rewind_and_read(out_receiver)).to match(/\Ahello\r?\nnew\r?\nworld\r?\n\z/m) } + + stdin.close + process.poll_for_exit(exit_timeout) + ensure + out_receiver.close + out.close + end + end + + # + # this works on JRuby 1.6.5 on my Mac, but for some reason + # hangs on Travis (running 1.6.5.1 + OpenJDK). + # + # http://travis-ci.org/#!/enkessler/childprocess/jobs/487331 + # + + it "works with pipes", :process_builder => false do + process = ruby(<<-CODE) + STDOUT.print "stdout" + STDERR.print "stderr" + CODE + + stdout, stdout_w = IO.pipe + stderr, stderr_w = IO.pipe + + process.io.stdout = stdout_w + process.io.stderr = stderr_w + + process.duplex = true + + process.start + process.wait + + # write streams are closed *after* the process + # has exited - otherwise it won't work on JRuby + # with the current Process implementation + + stdout_w.close + stderr_w.close + + out = stdout.read + err = stderr.read + + expect([out, err]).to eq %w[stdout stderr] + end + + it "can set close-on-exec when IO is inherited" do + port = random_free_port + server = TCPServer.new("127.0.0.1", port) + ChildProcess.close_on_exec server + + process = sleeping_ruby + process.io.inherit! + + process.start + server.close + + wait_until { can_bind? "127.0.0.1", port } + end + + it "handles long output" do + process = ruby <<-CODE + print 'a'*3000 + CODE + + out = Tempfile.new("long-output") + out.sync = true + + begin + process.io.stdout = out + + process.start + process.wait + + expect(rewind_and_read(out).size).to eq 3000 + ensure + out.close + end + end + + it 'should not inherit stdout and stderr by default' do + cap = capture_std do + process = echo + process.start + process.wait + end + + expect(cap.stdout).to eq '' + expect(cap.stderr).to eq '' + end +end diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/jruby_spec.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/jruby_spec.rb new file mode 100644 index 00000000..02ea9366 --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/jruby_spec.rb @@ -0,0 +1,24 @@ +require File.expand_path('../spec_helper', __FILE__) +require "pid_behavior" + +if ChildProcess.jruby? && !ChildProcess.windows? + describe ChildProcess::JRuby::IO do + let(:io) { ChildProcess::JRuby::IO.new } + + it "raises an ArgumentError if given IO does not respond to :to_outputstream" do + expect { io.stdout = nil }.to raise_error(ArgumentError) + end + end + + describe ChildProcess::JRuby::Process do + if ChildProcess.unix? + it_behaves_like "a platform that provides the child's pid" + else + it "raises an error when trying to access the child's pid" do + process = exit_with(0) + process.start + expect { process.pid }.to raise_error(NotImplementedError) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/pid_behavior.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/pid_behavior.rb new file mode 100644 index 00000000..5bcb15d2 --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/pid_behavior.rb @@ -0,0 +1,12 @@ +require File.expand_path('../spec_helper', __FILE__) + +shared_examples_for "a platform that provides the child's pid" do + it "knows the child's pid" do + Tempfile.open("pid-spec") do |file| + process = write_pid(file.path).start + process.wait + + expect(process.pid).to eq rewind_and_read(file).chomp.to_i + end + end +end diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/platform_detection_spec.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/platform_detection_spec.rb new file mode 100644 index 00000000..4af67e20 --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/platform_detection_spec.rb @@ -0,0 +1,86 @@ +require File.expand_path('../spec_helper', __FILE__) + +# Q: Should platform detection concern be extracted from ChildProcess? +describe ChildProcess do + + describe ".arch" do + subject { described_class.arch } + + before(:each) { described_class.instance_variable_set(:@arch, nil) } + + after(:each) { described_class.instance_variable_set(:@arch, nil) } + + shared_examples 'expected_arch_for_host_cpu' do |host_cpu, expected_arch| + context "when host_cpu is '#{host_cpu}'" do + before :each do + allow(RbConfig::CONFIG). + to receive(:[]). + with('host_cpu'). + and_return(expected_arch) + end + + it { is_expected.to eq expected_arch } + end + end + + # Normal cases: not macosx - depends only on host_cpu + context "when os is *not* 'macosx'" do + before :each do + allow(described_class).to receive(:os).and_return(:not_macosx) + end + + [ + { host_cpu: 'i386', expected_arch: 'i386' }, + { host_cpu: 'i486', expected_arch: 'i386' }, + { host_cpu: 'i586', expected_arch: 'i386' }, + { host_cpu: 'i686', expected_arch: 'i386' }, + { host_cpu: 'amd64', expected_arch: 'x86_64' }, + { host_cpu: 'x86_64', expected_arch: 'x86_64' }, + { host_cpu: 'ppc', expected_arch: 'powerpc' }, + { host_cpu: 'powerpc', expected_arch: 'powerpc' }, + { host_cpu: 'unknown', expected_arch: 'unknown' }, + ].each do |args| + include_context 'expected_arch_for_host_cpu', args.values + end + end + + # Special cases: macosx - when host_cpu is i686, have to re-check + context "when os is 'macosx'" do + before :each do + allow(described_class).to receive(:os).and_return(:macosx) + end + + context "when host_cpu is 'i686' " do + shared_examples 'expected_arch_on_macosx_i686' do |is_64, expected_arch| + context "when Ruby is #{is_64 ? 64 : 32}-bit" do + before :each do + allow(described_class). + to receive(:is_64_bit?). + and_return(is_64) + end + + include_context 'expected_arch_for_host_cpu', 'i686', expected_arch + end + end + + [ + { is_64: true, expected_arch: 'x86_64' }, + { is_64: false, expected_arch: 'i386' } + ].each do |args| + include_context 'expected_arch_on_macosx_i686', args.values + end + end + + [ + { host_cpu: 'amd64', expected_arch: 'x86_64' }, + { host_cpu: 'x86_64', expected_arch: 'x86_64' }, + { host_cpu: 'ppc', expected_arch: 'powerpc' }, + { host_cpu: 'powerpc', expected_arch: 'powerpc' }, + { host_cpu: 'unknown', expected_arch: 'unknown' }, + ].each do |args| + include_context 'expected_arch_for_host_cpu', args.values + end + end + end + +end diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/spec_helper.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/spec_helper.rb new file mode 100644 index 00000000..127c4564 --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/spec_helper.rb @@ -0,0 +1,270 @@ +$LOAD_PATH.unshift(File.dirname(__FILE__)) +$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) + +unless defined?(JRUBY_VERSION) + require 'coveralls' + Coveralls.wear! +end + +require 'childprocess' +require 'rspec' +require 'tempfile' +require 'socket' +require 'stringio' +require 'ostruct' + +module ChildProcessSpecHelper + RUBY = defined?(Gem) ? Gem.ruby : 'ruby' + + def ruby_process(*args) + @process = ChildProcess.build(RUBY , *args) + end + + def windows_process(*args) + @process = ChildProcess.build("powershell", *args) + end + + def sleeping_ruby(seconds = nil) + if seconds + ruby_process("-e", "sleep #{seconds}") + else + ruby_process("-e", "sleep") + end + end + + def invalid_process + @process = ChildProcess.build("unlikelytoexist") + end + + def ignored(signal) + code = <<-RUBY + trap(#{signal.inspect}, "IGNORE") + sleep + RUBY + + ruby_process tmp_script(code) + end + + def write_env(path) + if ChildProcess.os == :windows + ps_env_file_path = File.expand_path(File.dirname(__FILE__)) + args = ['-File', "#{ps_env_file_path}/get_env.ps1", path] + windows_process(*args) + else + code = <<-RUBY + File.open(#{path.inspect}, "w") { |f| f << ENV.inspect } + RUBY + ruby_process tmp_script(code) + end + end + + def write_argv(path, *args) + code = <<-RUBY + File.open(#{path.inspect}, "w") { |f| f << ARGV.inspect } + RUBY + + ruby_process(tmp_script(code), *args) + end + + def write_pid(path) + code = <<-RUBY + File.open(#{path.inspect}, "w") { |f| f << Process.pid } + RUBY + + ruby_process tmp_script(code) + end + + def write_pid_in_sleepy_grand_child(path) + code = <<-RUBY + system "ruby", "-e", 'File.open(#{path.inspect}, "w") { |f| f << Process.pid; f.flush }; sleep' + RUBY + + ruby_process tmp_script(code) + end + + def exit_with(exit_code) + ruby_process(tmp_script("exit(#{exit_code})")) + end + + def with_env(hash) + hash.each { |k,v| ENV[k] = v } + begin + yield + ensure + hash.each_key { |k| ENV[k] = nil } + end + end + + def tmp_script(code) + # use an ivar to avoid GC + @tf = Tempfile.new("childprocess-temp") + @tf << code + @tf.close + + puts code if $DEBUG + + @tf.path + end + + def cat + if ChildProcess.os == :windows + ruby(<<-CODE) + STDIN.sync = STDOUT.sync = true + IO.copy_stream(STDIN, STDOUT) + CODE + else + ChildProcess.build("cat") + end + end + + def echo + if ChildProcess.os == :windows + ruby(<<-CODE) + STDIN.sync = true + STDOUT.sync = true + + puts "hello" + CODE + else + ChildProcess.build("echo", "hello") + end + end + + def ruby(code) + ruby_process(tmp_script(code)) + end + + def with_executable_at(path, &blk) + if ChildProcess.os == :windows + path << ".cmd" + content = "#{RUBY} -e 'sleep 10' \n @echo foo" + else + content = "#!/bin/sh\nsleep 10\necho foo" + end + + File.open(path, 'w', 0744) { |io| io << content } + proc = ChildProcess.build(path) + + begin + yield proc + ensure + proc.stop if proc.alive? + File.delete path + end + end + + def exit_timeout + 10 + end + + def random_free_port + server = TCPServer.new('127.0.0.1', 0) + port = server.addr[1] + server.close + + port + end + + def with_tmpdir(&blk) + name = "#{Time.now.strftime("%Y%m%d")}-#{$$}-#{rand(0x100000000).to_s(36)}" + FileUtils.mkdir_p(name) + + begin + yield File.expand_path(name) + ensure + FileUtils.rm_rf name + end + end + + def wait_until(timeout = 10, &blk) + end_time = Time.now + timeout + last_exception = nil + + until Time.now >= end_time + begin + result = yield + return result if result + rescue RSpec::Expectations::ExpectationNotMetError => ex + last_exception = ex + end + + sleep 0.01 + end + + msg = "timed out after #{timeout} seconds" + msg << ":\n#{last_exception.message}" if last_exception + + raise msg + end + + def can_bind?(host, port) + TCPServer.new(host, port).close + true + rescue + false + end + + def rewind_and_read(io) + io.rewind + io.read + end + + def alive?(pid) + if ChildProcess.windows? + ChildProcess::Windows::Lib.alive?(pid) + else + begin + Process.getpgid pid + true + rescue Errno::ESRCH + false + end + end + end + + def capture_std + orig_out = STDOUT.clone + orig_err = STDERR.clone + + out = Tempfile.new 'captured-stdout' + err = Tempfile.new 'captured-stderr' + out.sync = true + err.sync = true + + STDOUT.reopen out + STDERR.reopen err + + yield + + OpenStruct.new stdout: rewind_and_read(out), stderr: rewind_and_read(err) + ensure + STDOUT.reopen orig_out + STDERR.reopen orig_err + end + + def generate_log_messages + ChildProcess.logger.level = Logger::DEBUG + + process = exit_with(0).start + process.wait + process.poll_for_exit(0.1) + end + +end # ChildProcessSpecHelper + +Thread.abort_on_exception = true + +RSpec.configure do |c| + c.include(ChildProcessSpecHelper) + c.after(:each) { + defined?(@process) && @process.alive? && @process.stop + } + + if ChildProcess.jruby? && ChildProcess.new("true").instance_of?(ChildProcess::JRuby::Process) + c.filter_run_excluding :process_builder => false + end + + if ChildProcess.linux? && ChildProcess.posix_spawn? + c.filter_run_excluding :posix_spawn_on_linux => false + end +end diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/unix_spec.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/unix_spec.rb new file mode 100644 index 00000000..b80e4c5e --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/unix_spec.rb @@ -0,0 +1,57 @@ +require File.expand_path('../spec_helper', __FILE__) +require "pid_behavior" + +if ChildProcess.unix? && !ChildProcess.jruby? && !ChildProcess.posix_spawn? + + describe ChildProcess::Unix::Process do + it_behaves_like "a platform that provides the child's pid" + + it "handles ECHILD race condition where process dies between timeout and KILL" do + process = sleeping_ruby + + allow(process).to receive(:fork).and_return('fakepid') + allow(process).to receive(:send_term) + allow(process).to receive(:poll_for_exit).and_raise(ChildProcess::TimeoutError) + allow(process).to receive(:send_kill).and_raise(Errno::ECHILD.new) + + process.start + expect { process.stop }.not_to raise_error + + allow(process).to receive(:alive?).and_return(false) + + process.send(:send_signal, 'TERM') + end + + it "handles ESRCH race condition where process dies between timeout and KILL" do + process = sleeping_ruby + + allow(process).to receive(:fork).and_return('fakepid') + allow(process).to receive(:send_term) + allow(process).to receive(:poll_for_exit).and_raise(ChildProcess::TimeoutError) + allow(process).to receive(:send_kill).and_raise(Errno::ESRCH.new) + + process.start + expect { process.stop }.not_to raise_error + + allow(process).to receive(:alive?).and_return(false) + + process.send(:send_signal, 'TERM') + end + end + + describe ChildProcess::Unix::IO do + let(:io) { ChildProcess::Unix::IO.new } + + it "raises an ArgumentError if given IO does not respond to :to_io" do + expect { io.stdout = nil }.to raise_error(ArgumentError, /to respond to :to_io/) + end + + it "raises a TypeError if #to_io does not return an IO" do + fake_io = Object.new + def fake_io.to_io() StringIO.new end + + expect { io.stdout = fake_io }.to raise_error(TypeError, /expected IO, got/) + end + end + +end diff --git a/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/windows_spec.rb b/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/windows_spec.rb new file mode 100644 index 00000000..e1688bd7 --- /dev/null +++ b/path/ruby/2.6.0/gems/childprocess-3.0.0/spec/windows_spec.rb @@ -0,0 +1,23 @@ +require File.expand_path('../spec_helper', __FILE__) +require "pid_behavior" + +if ChildProcess.windows? + describe ChildProcess::Windows::Process do + it_behaves_like "a platform that provides the child's pid" + end + + describe ChildProcess::Windows::IO do + let(:io) { ChildProcess::Windows::IO.new } + + it "raises an ArgumentError if given IO does not respond to :fileno" do + expect { io.stdout = nil }.to raise_error(ArgumentError, /must have :fileno or :to_io/) + end + + it "raises an ArgumentError if the #to_io does not return an IO " do + fake_io = Object.new + def fake_io.to_io() StringIO.new end + + expect { io.stdout = fake_io }.to raise_error(ArgumentError, /must have :fileno or :to_io/) + end + end +end diff --git a/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/.gitignore b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/.gitignore new file mode 100644 index 00000000..9e95e26d --- /dev/null +++ b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/.gitignore @@ -0,0 +1,7 @@ +*.gem +.bundle +.tags +Gemfile.lock +chromedriver.log +concourse/private.yml +pkg/* diff --git a/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/.rspec b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/.rspec new file mode 100644 index 00000000..f58a7d44 --- /dev/null +++ b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/.rspec @@ -0,0 +1,3 @@ +--color +--format=documentation +--order=random diff --git a/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/.travis.yml b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/.travis.yml new file mode 100644 index 00000000..348d5059 --- /dev/null +++ b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/.travis.yml @@ -0,0 +1,8 @@ +language: ruby +rvm: + - 2.1.0 + - 2.2.10 + - 2.3.8 + - 2.4.5 + - 2.5.3 + - 2.6.0 diff --git a/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/CHANGELOG.md b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/CHANGELOG.md new file mode 100644 index 00000000..991af930 --- /dev/null +++ b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/CHANGELOG.md @@ -0,0 +1,111 @@ +chromedriver-helper changelog +========== + +2.1.0 - 2018-09-18 +---------- + +Enhancement: + +* Use the `LATEST_RELEASE` file to determine the newest version, to avoid downloading the experimental versions the Chrome team is posting recently. [#62](https://github.com/flavorjones/chromedriver-helper/issues/62) (Thanks, @ibrahima!) + +Bug fix: + +* `lib/chromedriver-helper.rb` explicitly requires `chromedriver/helper` to avoid "uninitialized constant Chromedriver" exception when calling `Chromedriver.set_version` in a default-configured Rails app. [#65](https://github.com/flavorjones/chromedriver-helper/issues/65) + + +2.0.1 - 2018-09-17 +---------- + +Bug fix: + +* Explicitly require 'selenium-webdriver' for projects who don't have the default Rails ordering in their Gemfile. [#60](https://github.com/flavorjones/chromedriver-helper/issues/60) (Thanks, @mattbrictson!) + + +2.0.0 - 2018-09-15 +---------- + +**Backwards-incompatible change:** + +The shadow executable `chromedriver` has been renamed to `chromedriver-helper` to work around issues with projects _not_ using the gem on a system on which the gem is installed. See [#57](https://github.com/flavorjones/chromedriver-helper/issues/57) for details. + +Many thanks to Miklós Fazekas (@mfazekas) for both reporting the issue and submitting the fix. + + + +1.2.0 - 2018-02-03 +---------- + +Dependencies: + +* Bump dependencies on `nokogiri` and `archive-zip`. (Thanks, @odlp and @ksylvest!) + + +Bug fixes: + +* Use `https` for the URL used to download. [#41] (Thanks, @saraid!) +* Better platform detection, no longer run Windows on unrecognized platforms. [#49] (Thanks, @duncan-bayne!) +* `chromedriver-update` without a version specified will update to the latest version available on the internets. [#47] + + + +1.1.0 - 2017-03-19 +---------- + +Features: + +* Allow user to choose what version of chromedriver runs. [#34] (Thanks, @jonny5!) + + +1.0.0 - 2015-06-06 +---------- + +* Updated gemspec info. Happy 1.0! + + +0.0.9 - 2015-06-06 +---------- + +* No longer require 'curl' or 'wget', or 'unzip' utilities to be installed. You know, for Windows. (Thanks, @elementc!) +* Support JRuby by removing dependency on native-C-extension gem. (Thanks, Marques Lee!) + + +0.0.8 - 2015-01-23 +---------- + +* Guaranteeing that we get the *latest* version of chromedriver. (#15) (Thanks, @AlexRiedler!) + + +0.0.7 - 26 Aug 2014 +---------- + +* Added support for windows binaries. (Thanks, @elementc!) + + +0.0.6 - 26 Aug 2014 +---------- + +* Fixed to work with new Google download page. #7 (Thanks, @mars!) + + +0.0.5 - 15 Aug 2012 +---------- + +* Fixed support for JRuby on non-Windows platforms. #4 (Thanks, Tim Olsen!) + + +0.0.4 - 1 Aug 2012 +---------- + +* Including `chromedriver-update` to easily allow people to force-upgrade. #3 + + +0.0.3 - 20 Mar 2012 +---------- + +* Updated download URL. #2 (Thanks, Alistair Hutchison!) + + +0.0.2 - 6 December 2011 +---------- + +* Birthday! diff --git a/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/Gemfile b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/Gemfile new file mode 100644 index 00000000..78035ef7 --- /dev/null +++ b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/Gemfile @@ -0,0 +1,4 @@ +source "http://rubygems.org" + +# Specify your gem's dependencies in chromedriver-helper.gemspec +gemspec diff --git a/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/LICENSE.txt b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/LICENSE.txt new file mode 100644 index 00000000..2f9e8ca8 --- /dev/null +++ b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/LICENSE.txt @@ -0,0 +1,22 @@ +(The MIT License) + +Copyright (c) 2011,2012,2013,2014,2015: [Mike Dalessio](http://mike.daless.io) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/README.md b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/README.md new file mode 100644 index 00000000..e7dd5ba2 --- /dev/null +++ b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/README.md @@ -0,0 +1,182 @@ +# NOTICE: This gem is out of support as of 2019-03-31 + +## Please use https://github.com/titusfortner/webdrivers instead. + +See https://github.com/flavorjones/chromedriver-helper/issues/83 for details. + +---- + +# [chromedriver-helper](http://github.com/flavorjones/chromedriver-helper) + +[![Concourse CI](https://ci.nokogiri.org/api/v1/teams/flavorjones/pipelines/chromedriver-helper/jobs/ruby-2.6/badge)](https://ci.nokogiri.org/teams/flavorjones/pipelines/chromedriver-helper) + +Easy installation and use of [`chromedriver`](https://sites.google.com/a/chromium.org/chromedriver/), the Chromium project's Selenium webdriver adapter. + + +# Description + +`chromedriver-helper` installs an executable, `chromedriver-helper`, in your gem path, and configures Selenium to invoke it as the web driver. + +This script will, if necessary, download the appropriate binary for your platform and install it into `~/.chromedriver-helper`, then exec it. Easy peasy! + +Individual projects can even select which version of `chromedriver` they want to run. + +Make sure the gem is being required in order to configure the `Selenium::WebDriver::Chrome.driver_path`: + +``` ruby +require "chromedriver-helper" +``` + +Otherwise you may see failures like "unable to connect to chromedriver 127.0.0.1:9515 (Selenium::WebDriver::Error::WebDriverError)" when Selenium runs the wrong executable. + + +# Usage + +## In a Rails project + +If you're using Bundler and Capybara in a Rails project, it's as easy as: + +``` ruby +# Gemfile +gem "selenium-webdriver" +gem "chromedriver-helper" +``` + +then, in your spec setup: + +``` ruby +Capybara.register_driver :selenium do |app| + Capybara::Selenium::Driver.new(app, :browser => :chrome) +end +``` + + +## Standalone + +If you're using it standlone just to manage chromedriver binaries, + + # Gemfile + gem "chromedriver-helper" + +Then just run the executable script: + + chromedriver-helper + +which will download `chromedriver` if necessary and exec it. + + +# Configuration + +There are some commandline options that can be sent to `chromedriver` as options to `Capybara::Selenium::Driver.new`. The supported options can be discovered by looking at the Selenium source code here: + + https://github.com/SeleniumHQ/selenium/blob/master/rb/lib/selenium/webdriver/chrome/service.rb + +As of this writing, the supported options are: + +* `log_path` +* `url_base` +* `port_server` +* `whitelisted_ips` +* `verbose` +* `silent` + +An example usage would be: + +``` ruby +Capybara::Selenium::Driver.new(app, browser: :chrome, + driver_opts: { + log_path: '/tmp/chrome.log', + verbose: true + }) +``` + + +# Updating to latest `chromedriver` + +You can always update to the latest version of `chromedriver`: + + chromedriver-update + + +## Having problems updating? + +If for whatever reason you're having problems getting `chromedriver-helper` to update to the latest `chromedriver`, try this: + +1. Delete the directory `$HOME/.chromedriver-helper` +2. Run `chromedriver-update` + + +# Specifying a version + +If you want to run a specific version of `chromedriver` in your project, you can set the version in you testing setup like so: + + Chromedriver.set_version "2.24" + +Or, from the command line, you can choose a system-wide default: + + chromedriver-update 2.24 + + +# Support + +The code lives at [http://github.com/flavorjones/chromedriver-helper](http://github.com/flavorjones/chromedriver-helper). Open a Github Issue, or send a pull request! Thanks! You're the best. + + +# Known Issues + +## `chromedriver-helper` affects other projects on my system + +v1.2.0 and earlier installed an executable named `chromedriver`, which may cause confusion for apps on your system that are _not_ using `chromedriver-helper`. v2.0.0 and later do not cause this problem. + +The common symptom is an error message that looks like this: + +``` +Selenium::WebDriver::Error::WebDriverError: unable to connect to chromedriver 127.0.0.1:9515 +``` + +First, confirm that we're talking about the same thing by running: + +``` sh +bundle exec ruby -e "system('chromedriver -v')" +``` + +and making sure you see something like: + +``` +.../rubygems_integration.rb:462:in `block in replace_bin_path': can't find executable chromedriver for gem chromedriver-helper (Gem::Exception) +``` + +If you see this message, then **uninstall all versions of `chromedriver-helper` prior to v2.0.0**; and make sure your other projects have updated to v2.0.0 or later. + +(You can read more about this issue at https://github.com/flavorjones/chromedriver-helper/issues/57.) + + +## CentOS 6 and 7 + +Some versions of `chromedriver` won't run on CentOS 6 and 7 due to the [problems explained here](https://chrome.richardlloyd.org.uk/). The error messages look something like: + +``` +chromedriver: /usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.15' not found (required by /home/vagrant/.chromedriver-helper/linux64/chromedriver) +chromedriver: /usr/lib64/libstdc++.so.6: version `CXXABI_1.3.5' not found (required by /home/vagrant/.chromedriver-helper/linux64/chromedriver) +chromedriver: /usr/lib64/libstdc++.so.6: version `GLIBCXX_3.4.14' not found (required by /home/vagrant/.chromedriver-helper/linux64/chromedriver) + +``` + +You can get `chromedriver` to work on these systems by running the `install_chrome.sh` script on the page linked to above, and then making sure your `chromedriver` process has `LD_LIBRARY_PATH` set so that `/opt/google/chrome/lib` is present, e.g. + +``` +$ LD_LIBRARY_PATH=/opt/google/chrome/lib chromedriver-helper +Starting ChromeDriver 2.28.455506 (18f6627e265f442aeec9b6661a49fe819aeeea1f) on port 9515 +Only local connections are allowed. +``` + +# License + +MIT licensed, see LICENSE.txt for full details. + + +# Credit + +The idea for this gem comes from @brianhempel's project `chromedriver-gem` which, despite the name, is not currently published on http://rubygems.org/. + +Some improvements on the idea were taken from the installation process for standalone Phusion Passenger. diff --git a/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/Rakefile b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/Rakefile new file mode 100644 index 00000000..20eb7169 --- /dev/null +++ b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/Rakefile @@ -0,0 +1,12 @@ +require "bundler/gem_tasks" +require 'rspec/core/rake_task' +require 'concourse' + +RSpec::Core::RakeTask.new(:spec) + +task :default => :spec + +Concourse.new("chromedriver-helper", fly_target: "flavorjones") do |c| + c.add_pipeline "chromedriver-helper", "chromedriver-helper.yml" + c.add_pipeline "chromedriver-helper-pr", "chromedriver-helper-pr.yml" +end diff --git a/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/bin/chromedriver-helper b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/bin/chromedriver-helper new file mode 100755 index 00000000..f8323649 --- /dev/null +++ b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/bin/chromedriver-helper @@ -0,0 +1,5 @@ +#! /usr/bin/env ruby + +require "chromedriver/helper" + +Chromedriver::Helper.new.run(*ARGV) diff --git a/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/bin/chromedriver-update b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/bin/chromedriver-update new file mode 100755 index 00000000..c3a304f6 --- /dev/null +++ b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/bin/chromedriver-update @@ -0,0 +1,5 @@ +#! /usr/bin/env ruby + +require "chromedriver/helper" + +Chromedriver::Helper.new.update(ARGV[0]) diff --git a/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/chromedriver-helper.gemspec b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/chromedriver-helper.gemspec new file mode 100644 index 00000000..5f026868 --- /dev/null +++ b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/chromedriver-helper.gemspec @@ -0,0 +1,47 @@ +# -*- encoding: utf-8 -*- +$:.push File.expand_path("../lib", __FILE__) +require "chromedriver/helper/version" + +def windows? + RbConfig::CONFIG["target_os"] =~ /mingw32|mswin/ +end + +Gem::Specification.new do |s| + s.name = "chromedriver-helper" + s.version = Chromedriver::Helper::VERSION + s.authors = ["Mike Dalessio"] + s.email = ["mike.dalessio@gmail.com"] + s.homepage = "https://github.com/flavorjones/chromedriver-helper" + s.summary = "Deprecated in favor of the 'webdrivers' gem." + s.description = "Deprecated in favor of the 'webdrivers' gem as of 2019-03-31. See https://github.com/flavorjones/chromedriver-helper/issues/83" + s.licenses = ["MIT"] + + s.post_install_message = < e + warn "WARNING: could not set spec.files: #{e.class}: #{e}" + end + s.require_paths = ["lib"] + + s.add_development_dependency "rspec", "~> 3.0" + s.add_development_dependency "rake", "~> 10.0" + s.add_development_dependency "concourse", "~> 0.23" + + s.add_runtime_dependency "nokogiri", "~> 1.8" + s.add_runtime_dependency "archive-zip", "~> 0.10" +end diff --git a/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/concourse/chromedriver-helper-pr.yml b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/concourse/chromedriver-helper-pr.yml new file mode 100644 index 00000000..4735f548 --- /dev/null +++ b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/concourse/chromedriver-helper-pr.yml @@ -0,0 +1,110 @@ +anchors: + notify_failure_to_pr: ¬ify_failure_to_pr + put: chromedriver-helper-pr + params: {path: chromedriver-helper-pr, status: FAILURE} + +resource_types: +- name: pull-request + type: docker-image + source: + repository: teliaoss/github-pr-resource + +resources: + - name: ci + type: git + source: + uri: https://github.com/flavorjones/chromedriver-helper/ + branch: master + disable_ci_skip: true # always get the latest pipeline configuration + + - name: chromedriver-helper-pr + type: pull-request + check_every: 15m + # webhook: https://ci.nokogiri.org/api/v1/teams/flavorjones/pipelines/chromedriver-helper-pr/resources/chromedriver-helper-pr/check/webhook?webhook_token=WEBHOOK_TOKEN + webhook_token: ((chromedriver-helper-pr-webhook-token)) + source: + repository: flavorjones/chromedriver-helper + access_token: {{github-repo-status-access-token}} + ignore_paths: + - concourse/ + +jobs: + - name: pr-pending + public: true + plan: + - get: ci + - get: chromedriver-helper-pr + trigger: true + version: every + - put: chromedriver-helper-pr + params: {path: chromedriver-helper-pr, status: PENDING} + +% RUBIES[:mri].last(2).each do |ruby_version| + - name: ruby-<%= ruby_version %> + public: true + max_in_flight: 1 + plan: + - get: ci + - get: chromedriver-helper-pr + trigger: true + version: every + passed: [pr-pending] + - task: rake-test + config: + platform: linux + image_resource: + type: docker-image + source: {repository: ruby, tag: "<%= ruby_version %>"} + inputs: + - name: ci + - name: chromedriver-helper-pr + path: chromedriver-helper + run: + path: ci/concourse/tasks/rake-test/run.sh + on_failure: *notify_failure_to_pr +% end + +% RUBIES[:jruby].last(1).each do |ruby_version| + - name: jruby-<%= ruby_version %> + public: true + max_in_flight: 1 + plan: + - get: ci + - get: chromedriver-helper-pr + trigger: true + version: every + passed: [pr-pending] + - task: rake-test + attempts: 3 + config: + platform: linux + image_resource: + type: docker-image + source: {repository: jruby, tag: "<%= ruby_version %>-jdk"} + inputs: + - name: ci + - name: chromedriver-helper-pr + path: chromedriver-helper + params: + JAVA_OPTS: "-Dfile.encoding=UTF8" # https://github.com/docker-library/openjdk/issues/32 + run: + path: ci/concourse/tasks/rake-test/run.sh + on_failure: *notify_failure_to_pr +% end + + - name: pr-success + public: true + disable_manual_trigger: true + plan: + - get: chromedriver-helper-pr + trigger: true + version: every + passed: +% RUBIES[:mri].last(2).each do |ruby_version| + - ruby-<%= ruby_version %> +% end +% RUBIES[:jruby].last(1).each do |ruby_version| + - jruby-<%= ruby_version %> +% end + - put: chromedriver-helper-pr + params: {path: chromedriver-helper-pr, status: SUCCESS} diff --git a/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/concourse/chromedriver-helper-pr.yml.generated b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/concourse/chromedriver-helper-pr.yml.generated new file mode 100644 index 00000000..57c576bd --- /dev/null +++ b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/concourse/chromedriver-helper-pr.yml.generated @@ -0,0 +1,125 @@ +anchors: + notify_failure_to_pr: ¬ify_failure_to_pr + put: chromedriver-helper-pr + params: {path: chromedriver-helper-pr, status: FAILURE} + +resource_types: +- name: pull-request + type: docker-image + source: + repository: teliaoss/github-pr-resource + +resources: + - name: ci + type: git + source: + uri: https://github.com/flavorjones/chromedriver-helper/ + branch: master + disable_ci_skip: true # always get the latest pipeline configuration + + - name: chromedriver-helper-pr + type: pull-request + check_every: 15m + # webhook: https://ci.nokogiri.org/api/v1/teams/flavorjones/pipelines/chromedriver-helper-pr/resources/chromedriver-helper-pr/check/webhook?webhook_token=WEBHOOK_TOKEN + webhook_token: ((chromedriver-helper-pr-webhook-token)) + source: + repository: flavorjones/chromedriver-helper + access_token: {{github-repo-status-access-token}} + ignore_paths: + - concourse/ + +jobs: + - name: pr-pending + public: true + plan: + - get: ci + - get: chromedriver-helper-pr + trigger: true + version: every + - put: chromedriver-helper-pr + params: {path: chromedriver-helper-pr, status: PENDING} + + - name: ruby-2.5 + public: true + max_in_flight: 1 + plan: + - get: ci + - get: chromedriver-helper-pr + trigger: true + version: every + passed: [pr-pending] + - task: rake-test + config: + platform: linux + image_resource: + type: docker-image + source: {repository: ruby, tag: "2.5"} + inputs: + - name: ci + - name: chromedriver-helper-pr + path: chromedriver-helper + run: + path: ci/concourse/tasks/rake-test/run.sh + on_failure: *notify_failure_to_pr + - name: ruby-2.6 + public: true + max_in_flight: 1 + plan: + - get: ci + - get: chromedriver-helper-pr + trigger: true + version: every + passed: [pr-pending] + - task: rake-test + config: + platform: linux + image_resource: + type: docker-image + source: {repository: ruby, tag: "2.6"} + inputs: + - name: ci + - name: chromedriver-helper-pr + path: chromedriver-helper + run: + path: ci/concourse/tasks/rake-test/run.sh + on_failure: *notify_failure_to_pr + + - name: jruby-9.2 + public: true + max_in_flight: 1 + plan: + - get: ci + - get: chromedriver-helper-pr + trigger: true + version: every + passed: [pr-pending] + - task: rake-test + attempts: 3 + config: + platform: linux + image_resource: + type: docker-image + source: {repository: jruby, tag: "9.2-jdk"} + inputs: + - name: ci + - name: chromedriver-helper-pr + path: chromedriver-helper + params: + JAVA_OPTS: "-Dfile.encoding=UTF8" # https://github.com/docker-library/openjdk/issues/32 + run: + path: ci/concourse/tasks/rake-test/run.sh + on_failure: *notify_failure_to_pr + + - name: pr-success + public: true + disable_manual_trigger: true + plan: + - get: chromedriver-helper-pr + trigger: true + version: every + passed: + - ruby-2.5 + - ruby-2.6 + - jruby-9.2 + - put: chromedriver-helper-pr + params: {path: chromedriver-helper-pr, status: SUCCESS} diff --git a/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/concourse/chromedriver-helper.yml b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/concourse/chromedriver-helper.yml new file mode 100644 index 00000000..f0a35147 --- /dev/null +++ b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/concourse/chromedriver-helper.yml @@ -0,0 +1,78 @@ +resources: + - name: ci + type: git + source: + uri: https://github.com/flavorjones/chromedriver-helper/ + branch: master + disable_ci_skip: true # always get the latest pipeline configuration + + - name: chromedriver-helper + type: git + source: + uri: https://github.com/flavorjones/chromedriver-helper/ + branch: master + ignore_paths: + - concourse/** + +jobs: +% RUBIES[:mri].last(2).each do |ruby_version| + - name: ruby-<%= ruby_version %> + public: true + plan: + - get: ci + - get: chromedriver-helper + trigger: true + - task: rake-test + config: + platform: linux + image_resource: + type: docker-image + source: {repository: ruby, tag: "<%= ruby_version %>"} + inputs: + - name: ci + - name: chromedriver-helper + run: + path: ci/concourse/tasks/rake-test/run.sh +% end + +% RUBIES[:windows].last(2).each do |ruby_version| + - name: win-ruby-<%= ruby_version %>-devkit + public: true + plan: + - get: ci + - get: chromedriver-helper + trigger: true + - task: rake-test + config: + platform: windows + inputs: + - name: ci + - name: chromedriver-helper + params: + WIN_RUBY_VERSION: "<%= ruby_version %>" + run: + path: powershell + args: ["-File", "ci/concourse/tasks/rake-test/run.ps1"] +% end + +% RUBIES[:jruby].last(2).each do |ruby_version| + - name: jruby-<%= ruby_version %> + public: true + plan: + - get: ci + - get: chromedriver-helper + trigger: true + - task: rake-test + config: + platform: linux + image_resource: + type: docker-image + source: {repository: jruby, tag: "<%= ruby_version %>-jdk"} + inputs: + - name: ci + - name: chromedriver-helper + params: + JAVA_OPTS: "-Dfile.encoding=UTF8" # https://github.com/docker-library/openjdk/issues/32 + run: + path: ci/concourse/tasks/rake-test/run.sh +% end diff --git a/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/concourse/chromedriver-helper.yml.generated b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/concourse/chromedriver-helper.yml.generated new file mode 100644 index 00000000..1bb60d78 --- /dev/null +++ b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/concourse/chromedriver-helper.yml.generated @@ -0,0 +1,125 @@ +resources: + - name: ci + type: git + source: + uri: https://github.com/flavorjones/chromedriver-helper/ + branch: master + disable_ci_skip: true # always get the latest pipeline configuration + + - name: chromedriver-helper + type: git + source: + uri: https://github.com/flavorjones/chromedriver-helper/ + branch: master + ignore_paths: + - concourse/** + +jobs: + - name: ruby-2.5 + public: true + plan: + - get: ci + - get: chromedriver-helper + trigger: true + - task: rake-test + config: + platform: linux + image_resource: + type: docker-image + source: {repository: ruby, tag: "2.5"} + inputs: + - name: ci + - name: chromedriver-helper + run: + path: ci/concourse/tasks/rake-test/run.sh + - name: ruby-2.6 + public: true + plan: + - get: ci + - get: chromedriver-helper + trigger: true + - task: rake-test + config: + platform: linux + image_resource: + type: docker-image + source: {repository: ruby, tag: "2.6"} + inputs: + - name: ci + - name: chromedriver-helper + run: + path: ci/concourse/tasks/rake-test/run.sh + + - name: win-ruby-2.5-devkit + public: true + plan: + - get: ci + - get: chromedriver-helper + trigger: true + - task: rake-test + config: + platform: windows + inputs: + - name: ci + - name: chromedriver-helper + params: + WIN_RUBY_VERSION: "2.5" + run: + path: powershell + args: ["-File", "ci/concourse/tasks/rake-test/run.ps1"] + - name: win-ruby-2.6-devkit + public: true + plan: + - get: ci + - get: chromedriver-helper + trigger: true + - task: rake-test + config: + platform: windows + inputs: + - name: ci + - name: chromedriver-helper + params: + WIN_RUBY_VERSION: "2.6" + run: + path: powershell + args: ["-File", "ci/concourse/tasks/rake-test/run.ps1"] + + - name: jruby-9.1 + public: true + plan: + - get: ci + - get: chromedriver-helper + trigger: true + - task: rake-test + config: + platform: linux + image_resource: + type: docker-image + source: {repository: jruby, tag: "9.1-jdk"} + inputs: + - name: ci + - name: chromedriver-helper + params: + JAVA_OPTS: "-Dfile.encoding=UTF8" # https://github.com/docker-library/openjdk/issues/32 + run: + path: ci/concourse/tasks/rake-test/run.sh + - name: jruby-9.2 + public: true + plan: + - get: ci + - get: chromedriver-helper + trigger: true + - task: rake-test + config: + platform: linux + image_resource: + type: docker-image + source: {repository: jruby, tag: "9.2-jdk"} + inputs: + - name: ci + - name: chromedriver-helper + params: + JAVA_OPTS: "-Dfile.encoding=UTF8" # https://github.com/docker-library/openjdk/issues/32 + run: + path: ci/concourse/tasks/rake-test/run.sh diff --git a/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/concourse/tasks/rake-test/run.ps1 b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/concourse/tasks/rake-test/run.ps1 new file mode 100644 index 00000000..10f38fc6 --- /dev/null +++ b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/concourse/tasks/rake-test/run.ps1 @@ -0,0 +1,9 @@ +. "c:\var\vcap\packages\windows-ruby-dev-tools\prelude.ps1" + +push-location chromedriver-helper + + system-cmd "gem install bundler" + system-cmd "bundle install" + system-cmd "bundle exec rake spec" + +pop-location diff --git a/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/concourse/tasks/rake-test/run.sh b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/concourse/tasks/rake-test/run.sh new file mode 100755 index 00000000..242c90d1 --- /dev/null +++ b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/concourse/tasks/rake-test/run.sh @@ -0,0 +1,12 @@ +#! /usr/bin/env bash + +set -e -x -u + +export NOKOGIRI_USE_SYSTEM_LIBRARIES=t + +pushd chromedriver-helper + + bundle install + bundle exec rake spec + +popd diff --git a/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/lib/chromedriver-helper.rb b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/lib/chromedriver-helper.rb new file mode 100644 index 00000000..88301e59 --- /dev/null +++ b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/lib/chromedriver-helper.rb @@ -0,0 +1,4 @@ +require 'chromedriver/helper' +require 'selenium-webdriver' + +Selenium::WebDriver::Chrome.driver_path=Gem.bin_path("chromedriver-helper","chromedriver-helper") diff --git a/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/lib/chromedriver/helper.rb b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/lib/chromedriver/helper.rb new file mode 100644 index 00000000..c21a3631 --- /dev/null +++ b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/lib/chromedriver/helper.rb @@ -0,0 +1,114 @@ +require "chromedriver/helper/version" +require "chromedriver/helper/google_code_parser" +require 'fileutils' +require 'rbconfig' +require 'open-uri' +require 'archive/zip' + +module Chromedriver + def self.set_version(version) + Chromedriver::Helper.new.update(version) + end + + class Helper + def run(*args) + download + exec binary_path, *args + end + + def download(hit_network = false) + return if File.exist?(binary_path) && !hit_network + + raise "Version not found for #{download_version}" unless download_url + + filename = File.basename download_url + Dir.chdir platform_install_dir do + FileUtils.rm_f filename + File.open(filename, "wb") do |saved_file| + URI.parse(download_url).open("rb") do |read_file| + saved_file.write(read_file.read) + end + end + + raise "Could not download #{download_url}" unless File.exists? filename + Archive::Zip.extract(filename, '.', :overwrite => :all) + end + raise "Could not unzip #{filename} to get #{binary_path}" unless File.exists? binary_path + FileUtils.chmod "ugo+rx", binary_path + File.open(version_path, 'w') { |file| file.write(download_version) } + end + + def update(version = nil) + @download_version = version || google_code_parser.newest_download_version.to_s + + hit_network = (current_version != download_version) ? true : false + download(hit_network) + end + + def current_version + @current_version ||= if File.exist?(version_path) + File.read(version_path).strip + end + end + + def download_version + @download_version ||= current_version || google_code_parser.newest_download_version.to_s + end + + def download_url + @download_url ||= google_code_parser.version_download_url(download_version) + end + + def google_code_parser + @google_code_parser ||= GoogleCodeParser.new(platform) + end + + def version_path + @version_path ||= File.join(base_install_dir, ".chromedriver-version") + end + + def binary_path + if platform == "win" + File.join platform_install_dir, "chromedriver.exe" + else + File.join platform_install_dir, "chromedriver" + end + end + + def platform_install_dir + dir = File.join version_install_dir, platform + FileUtils.mkdir_p dir + dir + end + + def version_install_dir + dir = File.expand_path File.join(base_install_dir, download_version) + FileUtils.mkdir_p dir + dir + end + + def base_install_dir + @base_install_dir ||= begin + dir = File.expand_path File.join(ENV['HOME'], ".chromedriver-helper") + FileUtils.mkdir_p dir + dir + end + end + + def platform + cfg = RbConfig::CONFIG + host_cpu = cfg["host_cpu"] + host_os = cfg["host_os"] + + case host_os + when /linux/ then + host_cpu =~ /x86_64|amd64/ ? "linux64" : "linux32" + when /darwin/ then "mac" + when /mswin/ then "win" + when /mingw/ then "win" + else + raise("Unsupported host OS '#{host_os}'") + end + end + end +end diff --git a/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/lib/chromedriver/helper/google_code_parser.rb b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/lib/chromedriver/helper/google_code_parser.rb new file mode 100644 index 00000000..bbd6700b --- /dev/null +++ b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/lib/chromedriver/helper/google_code_parser.rb @@ -0,0 +1,44 @@ +require 'nokogiri' +require 'open-uri' +require 'uri' + +module Chromedriver + class Helper + class GoogleCodeParser + BUCKET_URL = 'https://chromedriver.storage.googleapis.com' + + attr_reader :source, :platform, :newest_download_version + + def initialize(platform, open_uri_provider=OpenURI) + @platform = platform + @source = open_uri_provider.open_uri(BUCKET_URL) + @newest_download_version = Gem::Version.new(open_uri_provider.open_uri(URI.join(BUCKET_URL, "LATEST_RELEASE")).read) + end + + def downloads + @downloads ||= begin + doc = Nokogiri::XML.parse(source) + items = doc.css("Contents Key").collect {|k| k.text } + items.reject! {|k| !(/chromedriver_#{platform}/===k) } + items.map {|k| "#{BUCKET_URL}/#{k}"} + end + end + + def version_download_url(version) + gem_version = Gem::Version.new(version) + downloads.find { |download_url| version_of(download_url) == gem_version } + end + + private + + def version_of url + Gem::Version.new grab_version_string_from(url) + end + + def grab_version_string_from url + # assumes url is of form similar to http://chromedriver.storage.googleapis.com/2.3/chromedriver_mac32.zip + url.split("/")[3] + end + end + end +end diff --git a/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/lib/chromedriver/helper/version.rb b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/lib/chromedriver/helper/version.rb new file mode 100644 index 00000000..5342d70b --- /dev/null +++ b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/lib/chromedriver/helper/version.rb @@ -0,0 +1,5 @@ +module Chromedriver + class Helper + VERSION = "2.1.1" + end +end diff --git a/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/spec/assets/google-code-bucket.xml b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/spec/assets/google-code-bucket.xml new file mode 100644 index 00000000..639b9dc3 --- /dev/null +++ b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/spec/assets/google-code-bucket.xml @@ -0,0 +1 @@ +chromedriverfalse2.0/chromedriver_linux32.zip138014985953000022013-09-25T22:57:39.349Z"c0d96102715c4916b872f91f5bf9b12c"726213400b4903a97149f98c03d9e192e5baa7ac7cdac7c396df4e4648822c1c13b5bcd2.0/chromedriver_linux64.zip138014986066400022013-09-25T22:57:40.449Z"858ebaf47e13dce7600191ed59974c09"743359300b4903a97149f98c03d9e192e5baa7ac7cdac7c396df4e4648822c1c13b5bcd2.0/chromedriver_mac32.zip138014985742500022013-09-25T22:57:37.204Z"efc13db5afc518000d886c2bdcb3a4bc"761460100b4903a97149f98c03d9e192e5baa7ac7cdac7c396df4e4648822c1c13b5bcd2.0/chromedriver_win32.zip138014985837000022013-09-25T22:57:38.165Z"bbf8fd0fe525a06dda162619cac2b200"304883100b4903a97149f98c03d9e192e5baa7ac7cdac7c396df4e4648822c1c13b5bcd2.1/chromedriver_linux32.zip138014986967500022013-09-25T22:57:49.481Z"1d7e908253f7240d1596332082cc5742"719776000b4903a97149f98c03d9e192e5baa7ac7cdac7c396df4e4648822c1c13b5bcd2.1/chromedriver_linux64.zip138014987088900022013-09-25T22:57:50.738Z"de406b5a1aac2bfb2f419ac01d7231e2"736707400b4903a97149f98c03d9e192e5baa7ac7cdac7c396df4e4648822c1c13b5bcd2.1/chromedriver_mac32.zip138014986747100022013-09-25T22:57:47.251Z"41d718a956392c78d788eedd2e0723a5"761129400b4903a97149f98c03d9e192e5baa7ac7cdac7c396df4e4648822c1c13b5bcd2.1/chromedriver_win32.zip138014986837400022013-09-25T22:57:48.143Z"d48fd6bce0c6131caa8aad6b5b02b9aa"296144300b4903a97149f98c03d9e192e5baa7ac7cdac7c396df4e4648822c1c13b5bcd2.2/chromedriver_linux32.zip138014987851900022013-09-25T22:57:58.374Z"801b9f6c28a32575d8eae2abb1cdecd5"725287900b4903a97149f98c03d9e192e5baa7ac7cdac7c396df4e4648822c1c13b5bcd2.2/chromedriver_linux64.zip138014987960000022013-09-25T22:57:59.432Z"d5b73ee424717e45601553e91e204ad6"741783500b4903a97149f98c03d9e192e5baa7ac7cdac7c396df4e4648822c1c13b5bcd2.2/chromedriver_mac32.zip138014987656300022013-09-25T22:57:56.408Z"e904e2ed0ebcc453492a9fe0550665ee"763456500b4903a97149f98c03d9e192e5baa7ac7cdac7c396df4e4648822c1c13b5bcd2.2/chromedriver_win32.zip138014987746700022013-09-25T22:57:57.313Z"c86ce20925db2d16118559cbe6693c6f"297875200b4903a97149f98c03d9e192e5baa7ac7cdac7c396df4e4648822c1c13b5bcd2.3/chromedriver_linux32.zip138014988808100022013-09-25T22:58:07.947Z"f3af4d92060e6d61c2d2ed86ad584246"731030100b4903a97149f98c03d9e192e5baa7ac7cdac7c396df4e4648822c1c13b5bcd2.3/chromedriver_linux64.zip138014988981700022013-09-25T22:58:09.620Z"1a816cc185a15af4d450805629790b0a"748113800b4903a97149f98c03d9e192e5baa7ac7cdac7c396df4e4648822c1c13b5bcd2.3/chromedriver_mac32.zip138014988573600022013-09-25T22:58:05.567Z"4d72ca0fa6dbddfa1e42dbd1ef0045cc"767802100b4903a97149f98c03d9e192e5baa7ac7cdac7c396df4e4648822c1c13b5bcd2.3/chromedriver_win32.zip138014988682300022013-09-25T22:58:06.499Z"3226a146a6b97f2a111d3b2be9b0193f"300113800b4903a97149f98c03d9e192e5baa7ac7cdac7c396df4e4648822c1c13b5bcd2.3/notes.txt138014988847400022013-09-25T22:58:08.474Z"3ddc6ac82f5acc8d248b117146ab9b1b"59100b4903a97149f98c03d9e192e5baa7ac7cdac7c396df4e4648822c1c13b5bcd2.4/chromedriver_linux32.zip138060615643600022013-10-01T05:42:36.371Z"3f8ef2f01a7fb5805bed2d644569acba"736531600b4903a97149f98c03d9e192e5baa7ac7cdac7c396df4e4648822c1c13b5bcd2.4/chromedriver_linux64.zip138060512457200022013-10-01T05:25:24.406Z"5e70555cbbf75e3480dd1629a35bc7e3"753682600b4903a97149f98c03d9e192e5baa7ac7cdac7c396df4e4648822c1c13b5bcd2.4/chromedriver_mac32.zip138061515423900022013-10-01T08:12:34.120Z"0816b2a06428962b1a2da103eb59323c"772758000b4903a97149f98c03d9e192e5baa7ac7cdac7c396df4e4648822c1c13b5bcd2.4/chromedriver_win32.zip138066739998700012013-10-01T22:43:19.814Z"0280d3a9e713a38216a4e34a9ec1fba2"298041400b4903a97c5747d42831d561f5c5006971ba7454104797e276e7489aa9357d72.4/notes.txt138060513107900042013-10-01T05:25:31.079Z"91ac3af6739a33188186e16c6a76d179"108900b4903a97149f98c03d9e192e5baa7ac7cdac7c396df4e4648822c1c13b5bcdicons/back.gif138013092497200022013-09-25T17:42:04.972Z"4bce9846e05d3bffdfb293d47c840a8e"21600b4903a97149f98c03d9e192e5baa7ac7cdac7c396df4e4648822c1c13b5bcdicons/binary.gif138013093362700022013-09-25T17:42:13.627Z"96bd4beed88ff93356586485c13e5d89"24600b4903a97149f98c03d9e192e5baa7ac7cdac7c396df4e4648822c1c13b5bcdicons/blank.gif138013094133400022013-09-25T17:42:21.333Z"19517fb39a31be6b8d7ccf53ad84908f"14800b4903a97149f98c03d9e192e5baa7ac7cdac7c396df4e4648822c1c13b5bcdicons/folder.gif138013095068000022013-09-25T17:42:30.680Z"d342cba375fea336967317bdb5d7cf19"22500b4903a97149f98c03d9e192e5baa7ac7cdac7c396df4e4648822c1c13b5bcdindex.html138012835891200032013-09-25T16:59:18.911Z"704b0f841aad1b1428481b7ff3c759c0"1057400b4903a97149f98c03d9e192e5baa7ac7cdac7c396df4e4648822c1c13b5bcd \ No newline at end of file diff --git a/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/spec/assets/google-code.html b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/spec/assets/google-code.html new file mode 100644 index 00000000..d73705a8 --- /dev/null +++ b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/spec/assets/google-code.html @@ -0,0 +1,1396 @@ + + + + + + + + + + + + Downloads - + chromium - + + + An open-source browser project to help move the web forward. - Google Project Hosting + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + + My favorites + | Sign in + + + +
    + +
    +
    + + +
    + + + + + + + + + + + + +
    + +
    + chromium +
    + + + + +
    + +
    + + + +
    + +
    + +
    + + +
    + Project Home + + + + + Downloads + + + + + + Wiki + + + + + + Issues + + + + + + + +
    +
    + + + + + + + + + + + + + + +
    +
    +
    + + + Search +
    + + for + + + + + +
    +
    +
    +
    + +
    + + + +
    + + + + + + + + + + + +
    +
    + + + + + + + +    + +
    + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Filename Summary + Labels Uploaded ReleaseDate Size DownloadCount ...
    + + + + + + + + + chromedriver_win_16.0.902.0.zip + + + + + + ChromeDriver server for windows + + + + + + + + + Oct 6 + + + + + Oct 6 + + + + + 1011 KB + + + + + 7352 + + + 
    + + + + + + + + + chromedriver_mac_16.0.902.0.zip + + + + + + ChromeDriver server for Mac OS X + + + + + + + + + Oct 6 + + + + + Oct 6 + + + + + 5.1 MB + + + + + 2818 + + + 
    + + + + + + + + + chromedriver_linux64_16.0.902.0.zip + + + + + + ChromeDriver server for linux64 + + + + + + + + + Oct 6 + + + + + Oct 6 + + + + + 6.5 MB + + + + + 2550 + + + 
    + + + + + + + + + chromedriver_linux32_16.0.902.0.zip + + + + + + ChromeDriver server for linux32 + + + + + + + + + Oct 6 + + + + + Oct 6 + + + + + 6.4 MB + + + + + 2899 + + + 
    + + + + + + + + + Chrome552.215.exe + + + + + + 552.15 standalone installer + + + + + + + + + Dec 2010 + + + + + + + + + + 24.3 MB + + + + + 64720 + + + 
    + + + + + + + + + Chrome517.41.exe + + + + + + 517.41 standalone installer + + + + + + + + + Dec 2010 + + + + + + + + + + 23.3 MB + + + + + 2152 + + + 
    + + + + + + + + + codesite.crx + + + + + + Google Code enhancements for Chromium (Inlined images and fix svn path in issues) + + + + + + + + + Jul 2009 + + + + + + + + + + 1.3 KB + + + + + 20575 + + + 
    + + + + + + + + + chromecomicJPGS.zip + + + + + + Google Chrome Comic + + + + + + + + + Sep 2008 + + + + + + + + + + 9.9 MB + + + + + 20927 + + + 
    +
    + + + + + + +
    +    +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + +
    + +
    + Powered by Google Project Hosting +
    + + + + + + + + diff --git a/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/spec/google_code_parser_spec.rb b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/spec/google_code_parser_spec.rb new file mode 100644 index 00000000..6f6a4041 --- /dev/null +++ b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/spec/google_code_parser_spec.rb @@ -0,0 +1,40 @@ +require "spec_helper" + +describe Chromedriver::Helper::GoogleCodeParser do + let!(:open_uri_provider) do + double("open_uri_provider").tap do |oup| + allow(oup).to receive(:open_uri) do |uri| + case uri.to_s + when "https://chromedriver.storage.googleapis.com/LATEST_RELEASE" + StringIO.new("2.42") + when "https://chromedriver.storage.googleapis.com" + StringIO.new(File.read(File.join(File.dirname(__FILE__), "assets/google-code-bucket.xml"))) + end + end + end + end + let!(:parser) { Chromedriver::Helper::GoogleCodeParser.new('mac', open_uri_provider) } + + describe "#downloads" do + it "returns an array of URLs for the platform" do + expect(parser.downloads).to eq [ + "https://chromedriver.storage.googleapis.com/2.0/chromedriver_mac32.zip", + "https://chromedriver.storage.googleapis.com/2.1/chromedriver_mac32.zip", + "https://chromedriver.storage.googleapis.com/2.2/chromedriver_mac32.zip", + "https://chromedriver.storage.googleapis.com/2.3/chromedriver_mac32.zip", + "https://chromedriver.storage.googleapis.com/2.4/chromedriver_mac32.zip"] + end + end + + describe "#newest_download_version" do + it "returns the last URL for the platform" do + expect(parser.newest_download_version).to eq Gem::Version.new("2.42") + end + end + + describe '#version_download_url' do + it 'returns the version asked for' do + expect(parser.version_download_url(2.0)).to eq "https://chromedriver.storage.googleapis.com/2.0/chromedriver_mac32.zip" + end + end +end diff --git a/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/spec/helper_spec.rb b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/spec/helper_spec.rb new file mode 100644 index 00000000..f7f5e939 --- /dev/null +++ b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/spec/helper_spec.rb @@ -0,0 +1,56 @@ +require "spec_helper" + +describe Chromedriver::Helper do + let(:helper) { Chromedriver::Helper.new } + + describe "#binary_path" do + context "on a linux platform" do + before { allow(helper).to receive(:platform) { "linux32" } } + it { expect(helper.binary_path).to match(/chromedriver$/) } + end + + context "on a windows platform" do + before { allow(helper).to receive(:platform) { "win" } } + it { expect(helper.binary_path).to match(/chromedriver\.exe$/) } + end + end + + describe '#platform' do + os_cpu_matrix = [ + { 'host_os' => 'darwin','host_cpu' => 'irrelevant', 'expected_platform' => 'mac' }, + { 'host_os' => 'linux', 'host_cpu' => 'amd64', 'expected_platform' => 'linux64' }, + { 'host_os' => 'linux', 'host_cpu' => 'irrelevant', 'expected_platform' => 'linux32' }, + { 'host_os' => 'linux', 'host_cpu' => 'x86_64', 'expected_platform' => 'linux64' }, + { 'host_os' => 'mingw', 'host_cpu' => 'irrelevant', 'expected_platform' => 'win' }, + { 'host_os' => 'mswin', 'host_cpu' => 'irrelevant', 'expected_platform' => 'win' } + ] + + os_cpu_matrix.each do |config| + expected_platform = config['expected_platform'] + host_cpu = config['host_cpu'] + host_os = config['host_os'] + + context "given host OS #{host_os} and host CPU #{host_cpu}" do + before do + RbConfig.send(:remove_const, :CONFIG) + RbConfig::CONFIG = { 'host_os' => host_os, 'host_cpu' => host_cpu } + end + + it "returns #{expected_platform}" do + expect(helper.platform).to eq(expected_platform) + end + end + end + + context 'given an unknown host OS' do + before do + RbConfig.send(:remove_const, :CONFIG) + RbConfig::CONFIG = { 'host_os' => 'freebsd', 'host_cpu' => 'irrelevant' } + end + + it 'raises an exception' do + expect { helper.platform }.to raise_error("Unsupported host OS 'freebsd'") + end + end + end +end diff --git a/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/spec/spec_helper.rb b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/spec/spec_helper.rb new file mode 100644 index 00000000..e847f488 --- /dev/null +++ b/path/ruby/2.6.0/gems/chromedriver-helper-2.1.1/spec/spec_helper.rb @@ -0,0 +1,2 @@ +require "rspec" +require File.expand_path(File.join(File.dirname(__FILE__), "..", "lib/chromedriver/helper")) diff --git a/path/ruby/2.6.0/gems/coffee-rails-4.2.2/CHANGELOG.md b/path/ruby/2.6.0/gems/coffee-rails-4.2.2/CHANGELOG.md new file mode 100644 index 00000000..2e5a34c7 --- /dev/null +++ b/path/ruby/2.6.0/gems/coffee-rails-4.2.2/CHANGELOG.md @@ -0,0 +1,65 @@ +## 4.2.2 (May 24, 2017) ## + +* Support digest resolution for coffee templates. + +## 4.2.1 (June 30, 2016) ## + +* Fix error in the gem package. + +## 4.2.0 (June 30, 2016) ## + +* Override `js_template` hook in the Rails generator to allow Rails to + be CoffeeScript agnostic. + +## 4.1.1 (December 18, 2015) ## + +* Allow Rails 5. + + *Rafael Mendonça França* + +## 4.1.0 (October 12, 2014) ## + +* Default to .coffee extension instead of .js.coffee + + *Joshua Peek* + +* Register coffee extension for rake notes. + + *Roberto Miranda* + +## 4.0.1 (October 17, 2013) ## + +* Drop support to Rails `4.0.0.rc` releases + + *Rafael Mendonça França* + + +## 4.0.0 (April 18, 2013) ## + +* Bump railties version to 4.0.0.beta. + + *José Valim* + + +## 3.2.2 (January 26, 2012) ## + +* Bump railties version to ~> 3.2.0. + + *Aaron Patterson* + + +## 3.2.1 (January 5, 2012) ## + +* No changes. + + +## 3.2.0 (December 17, 2011) ## + +* Add coffee-script.js for asset pipeline. Now your app will support + `" + +doc = Loofah.fragment(unsafe_html).scrub!(:prune) +doc.to_s # => "ohai!
    div is safe
    " +``` + +and `text` to return plain text: + +``` ruby +doc.text # => "ohai! div is safe " +``` + +Also, `to_text` is available, which does the right thing with +whitespace around block-level elements. + +``` ruby +doc = Loofah.fragment("

    Title

    Content
    ") +doc.text # => "TitleContent" # probably not what you want +doc.to_text # => "\nTitle\n\nContent\n" # better +``` + +### Loofah::XML::Document and Loofah::XML::DocumentFragment + +These classes are subclasses of Nokogiri::XML::Document and +Nokogiri::XML::DocumentFragment, so you get all the markup +fixer-uppery and API goodness of Nokogiri. + +The module methods Loofah.xml_document and Loofah.xml_fragment will +parse an XML document and an XML fragment, respectively. + +``` ruby +Loofah.xml_document(bad_xml).is_a?(Nokogiri::XML::Document) # => true +Loofah.xml_fragment(bad_xml).is_a?(Nokogiri::XML::DocumentFragment) # => true +``` + +### Nodes and NodeSets + +Nokogiri::XML::Node and Nokogiri::XML::NodeSet also get a `scrub!` +method, which makes it easy to scrub subtrees. + +The following code will apply the `employee_scrubber` only to the +`employee` nodes (and their subtrees) in the document: + +``` ruby +Loofah.xml_document(bad_xml).xpath("//employee").scrub!(employee_scrubber) +``` + +And this code will only scrub the first `employee` node and its subtree: + +``` ruby +Loofah.xml_document(bad_xml).at_xpath("//employee").scrub!(employee_scrubber) +``` + +### Loofah::Scrubber + +A Scrubber wraps up a block (or method) that is run on a document node: + +``` ruby +# change all tags to
    tags +span2div = Loofah::Scrubber.new do |node| + node.name = "div" if node.name == "span" +end +``` + +This can then be run on a document: + +``` ruby +Loofah.fragment("foo

    bar

    ").scrub!(span2div).to_s +# => "
    foo

    bar

    " +``` + +Scrubbers can be run on a document in either a top-down traversal (the +default) or bottom-up. Top-down scrubbers can optionally return +Scrubber::STOP to terminate the traversal of a subtree. Read below and +in the Loofah::Scrubber class for more detailed usage. + +Here's an XML example: + +``` ruby +# remove all tags that have a "deceased" attribute set to true +bring_out_your_dead = Loofah::Scrubber.new do |node| + if node.name == "employee" and node["deceased"] == "true" + node.remove + Loofah::Scrubber::STOP # don't bother with the rest of the subtree + end +end +Loofah.xml_document(File.read('plague.xml')).scrub!(bring_out_your_dead) +``` + +=== Built-In HTML Scrubbers + +Loofah comes with a set of sanitizing scrubbers that use HTML5lib's +safelist algorithm: + +``` ruby +doc.scrub!(:strip) # replaces unknown/unsafe tags with their inner text +doc.scrub!(:prune) # removes unknown/unsafe tags and their children +doc.scrub!(:escape) # escapes unknown/unsafe tags, like this: <script> +doc.scrub!(:whitewash) # removes unknown/unsafe/namespaced tags and their children, + # and strips all node attributes +``` + +Loofah also comes with some common transformation tasks: + +``` ruby +doc.scrub!(:nofollow) # adds rel="nofollow" attribute to links +doc.scrub!(:unprintable) # removes unprintable characters from text nodes +``` + +See Loofah::Scrubbers for more details and example usage. + + +### Chaining Scrubbers + +You can chain scrubbers: + +``` ruby +Loofah.fragment("hello ") \ + .scrub!(:prune) \ + .scrub!(span2div).to_s +# => "
    hello
    " +``` + +### Shorthand + +The class methods Loofah.scrub_fragment and Loofah.scrub_document are +shorthand. + +``` ruby +Loofah.scrub_fragment(unsafe_html, :prune) +Loofah.scrub_document(unsafe_html, :prune) +Loofah.scrub_xml_fragment(bad_xml, custom_scrubber) +Loofah.scrub_xml_document(bad_xml, custom_scrubber) +``` + +are the same thing as (and arguably semantically clearer than): + +``` ruby +Loofah.fragment(unsafe_html).scrub!(:prune) +Loofah.document(unsafe_html).scrub!(:prune) +Loofah.xml_fragment(bad_xml).scrub!(custom_scrubber) +Loofah.xml_document(bad_xml).scrub!(custom_scrubber) +``` + + +### View Helpers + +Loofah has two "view helpers": Loofah::Helpers.sanitize and +Loofah::Helpers.strip_tags, both of which are drop-in replacements for +the Rails ActionView helpers of the same name. +These are no longer required automatically. You must require `loofah/helpers`. + + +## Requirements + +* Nokogiri >= 1.5.9 + + +## Installation + +Unsurprisingly: + +* gem install loofah + + +## Support + +The bug tracker is available here: + +* https://github.com/flavorjones/loofah/issues + +And the mailing list is on Google Groups: + +* Mail: loofah-talk@googlegroups.com +* Archive: https://groups.google.com/forum/#!forum/loofah-talk + +And the IRC channel is \#loofah on freenode. + + +## Security + +See [`SECURITY.md`](SECURITY.md) for vulnerability reporting details. + + +### "Secure by Default" + +Some tools may incorrectly report Loofah as a potential security +vulnerability. + +Loofah depends on Nokogiri, and it's _possible_ to use Nokogiri in a +dangerous way (by enabling its DTDLOAD option and disabling its NONET +option). This specifically allows the opportunity for an XML External +Entity (XXE) vulnerability if the XML data is untrusted. + +However, Loofah __never enables this Nokogiri configuration__; Loofah +never enables DTDLOAD, and it never disables NONET, thereby protecting +you by default from this XXE vulnerability. + + +## Related Links + +* Nokogiri: http://nokogiri.org +* libxml2: http://xmlsoft.org +* html5lib: https://code.google.com/p/html5lib + + +## Authors + +* [Mike Dalessio](http://mike.daless.io) ([@flavorjones](https://twitter.com/flavorjones)) +* Bryan Helmkamp + +Featuring code contributed by: + +* Aaron Patterson +* John Barnette +* Josh Owens +* Paul Dix +* Luke Melia + +And a big shout-out to Corey Innis for the name, and feedback on the API. + + +## Thank You + +The following people have generously donated via the [Pledgie](http://pledgie.com) badge on the [Loofah github page](https://github.com/flavorjones/loofah): + +* Bill Harding + + +## Historical Note + +This library was formerly known as Dryopteris, which was a very bad +name that nobody could spell properly. + + +## License + +Distributed under the MIT License. See `MIT-LICENSE.txt` for details. diff --git a/path/ruby/2.6.0/gems/loofah-2.3.0/Rakefile b/path/ruby/2.6.0/gems/loofah-2.3.0/Rakefile new file mode 100644 index 00000000..118e0439 --- /dev/null +++ b/path/ruby/2.6.0/gems/loofah-2.3.0/Rakefile @@ -0,0 +1,81 @@ +require "rubygems" +require "hoe" +require "concourse" + +Hoe.plugin :git +Hoe.plugin :gemspec +Hoe.plugin :bundler +Hoe.plugin :debugging + +Hoe.spec "loofah" do + developer "Mike Dalessio", "mike.dalessio@gmail.com" + developer "Bryan Helmkamp", "bryan@brynary.com" + + self.extra_rdoc_files = FileList["*.md"] + self.history_file = "CHANGELOG.md" + self.readme_file = "README.md" + self.license "MIT" + + extra_deps << ["nokogiri", ">=1.5.9"] + extra_deps << ["crass", "~> 1.0.2"] + + extra_dev_deps << ["rake", "~> 12.3"] + extra_dev_deps << ["minitest", "~>2.2"] + extra_dev_deps << ["rr", "~>1.2.0"] + extra_dev_deps << ["json", "~> 2.2.0"] + extra_dev_deps << ["hoe-gemspec", "~> 1.0"] + extra_dev_deps << ["hoe-debugging", "~> 2.0"] + extra_dev_deps << ["hoe-bundler", "~> 1.5"] + extra_dev_deps << ["hoe-git", "~> 1.6"] + extra_dev_deps << ["concourse", ">=0.26.0"] +end + +task :gemspec do + system %q(rake debug_gem | grep -v "^\(in " > loofah.gemspec) +end + +task :redocs => :fix_css +task :docs => :fix_css +task :fix_css do + better_css = <<-EOT + .method-description pre { + margin : 1em 0 ; + } + + .method-description ul { + padding : .5em 0 .5em 2em ; + } + + .method-description p { + margin-top : .5em ; + } + + #main ul, div#documentation ul { + list-style-type : disc ! IMPORTANT ; + list-style-position : inside ! IMPORTANT ; + } + + h2 + ul { + margin-top : 1em; + } + EOT + puts "* fixing css" + File.open("doc/rdoc.css", "a") { |f| f.write better_css } +end + +desc "generate and upload docs to rubyforge" +task :doc_upload_to_rubyforge => :docs do + Dir.chdir "doc" do + system "rsync -avz --delete * rubyforge.org:/var/www/gforge-projects/loofah/loofah" + end +end + +desc "generate safelists from W3C specifications" +task :generate_safelists do + load "tasks/generate-safelists" +end + +Concourse.new("loofah", fly_target: "ci") do |c| + c.add_pipeline "loofah", "loofah.yml" + c.add_pipeline "loofah-pr", "loofah-pr.yml" +end diff --git a/path/ruby/2.6.0/gems/loofah-2.3.0/SECURITY.md b/path/ruby/2.6.0/gems/loofah-2.3.0/SECURITY.md new file mode 100644 index 00000000..3eba31ba --- /dev/null +++ b/path/ruby/2.6.0/gems/loofah-2.3.0/SECURITY.md @@ -0,0 +1,18 @@ +# Security and Vulnerability Reporting + +The Loofah core contributors take security very seriously and investigate all reported vulnerabilities. + +If you would like to report a vulnerablity or have a security concern regarding Loofah, please [report it via HackerOne](https://hackerone.com/loofah/reports/new). + +Your report will be acknowledged within 24 hours, and you'll receive a more detailed response within 72 hours indicating next steps in handling your report. + +If you have not received a reply to your submission within 48 hours, there are a few steps you can take: + +* Contact the current security coordinator (Mike Dalessio ) +* Email the Loofah user group at loofah-talk@googlegroups.com (archive at https://groups.google.com/forum/#!forum/loofah-talk) + +Please note, the user group list is a public area. When escalating in that venue, please do not discuss your issue. Simply say that you're trying to get a hold of someone from the core team. + +The information you share with the Loofah core contributors as part of this process will be kept confidential within the team, unless or until we need to share information upstream with our dependent libraries' core teams, at which point we will notify you. + +If a vulnerability is first reported by you, we will credit you with the discovery in the public disclosure. diff --git a/path/ruby/2.6.0/gems/loofah-2.3.0/benchmark/benchmark.rb b/path/ruby/2.6.0/gems/loofah-2.3.0/benchmark/benchmark.rb new file mode 100755 index 00000000..d9142ca2 --- /dev/null +++ b/path/ruby/2.6.0/gems/loofah-2.3.0/benchmark/benchmark.rb @@ -0,0 +1,149 @@ +#!/usr/bin/env ruby +require "#{File.dirname(__FILE__)}/helper.rb" + +def compare_scrub_methods + snip = "
    foo
    fuxx quux" + puts "starting with:\n#{snip}" + puts + puts RailsSanitize.new.sanitize(snip) # => Rails.sanitize / scrub!(:prune).to_s + puts Loofah::Helpers.sanitize(snip) + puts "--" + puts RailsSanitize.new.strip_tags(snip) # => Rails.strip_tags / parse().text + puts Loofah::Helpers.strip_tags(snip) + puts "--" + puts Sanitize.clean(snip, Sanitize::Config::RELAXED) # => scrub!(:strip).to_s + puts Loofah.scrub_fragment(snip, :strip).to_s + puts "--" + puts HTML5libSanitize.new.sanitize(snip) # => scrub!(:escape).to_s + puts Loofah.scrub_fragment(snip, :escape).to_s + puts "--" + puts HTMLFilter.new.filter(snip) + puts Loofah.scrub_fragment(snip, :strip).to_s + puts +end + +module TestSet + def test_set options={} + scale = options[:rehearse] ? 10 : 1 + puts self.class.name + + n = 100 / scale + puts " Large document, #{BIG_FILE.length} bytes (x#{n})" + bench BIG_FILE, n, false + puts + + n = 1000 / scale + puts " Small fragment, #{FRAGMENT.length} bytes (x#{n})" + bench FRAGMENT, n, true + puts + + n = 10_000 / scale + puts " Text snippet, #{SNIPPET.length} bytes (x#{n})" + bench SNIPPET, n, true + puts + end +end + +class HeadToHead < Measure +end + +class HeadToHeadRailsSanitize < Measure + include TestSet + def bench(content, ntimes, fragment_p) + clear_measure + + measure "Loofah::Helpers.sanitize", ntimes do + Loofah::Helpers.sanitize content + end + + sanitizer = RailsSanitize.new + measure "ActionView sanitize", ntimes do + sanitizer.sanitize(content) + end + end +end + +class HeadToHeadRailsStripTags < Measure + include TestSet + def bench(content, ntimes, fragment_p) + clear_measure + + measure "Loofah::Helpers.strip_tags", ntimes do + Loofah::Helpers.strip_tags content + end + + sanitizer = RailsSanitize.new + measure "ActionView strip_tags", ntimes do + sanitizer.strip_tags(content) + end + end +end + +class HeadToHeadSanitizerSanitize < Measure + include TestSet + def bench(content, ntimes, fragment_p) + clear_measure + + measure "Loofah :strip", ntimes do + if fragment_p + Loofah.scrub_fragment(content, :strip).to_s + else + Loofah.scrub_document(content, :strip).to_s + end + end + + measure "Sanitize.clean", ntimes do + Sanitize.clean(content, Sanitize::Config::RELAXED) + end + end +end + +class HeadToHeadHtml5LibSanitize < Measure + include TestSet + def bench(content, ntimes, fragment_p) + clear_measure + + measure "Loofah :escape", ntimes do + if fragment_p + Loofah.scrub_fragment(content, :escape).to_s + else + Loofah.scrub_document(content, :escape).to_s + end + end + + html5_sanitizer = HTML5libSanitize.new + measure "HTML5lib.sanitize", ntimes do + html5_sanitizer.sanitize(content) + end + end +end + +class HeadToHeadHTMLFilter < Measure + include TestSet + def bench(content, ntimes, fragment_p) + clear_measure + + measure "Loofah::Helpers.sanitize", ntimes do + Loofah::Helpers.sanitize content + end + + sanitizer = HTMLFilter.new + measure "HTMLFilter.filter", ntimes do + sanitizer.filter(content) + end + end +end + +puts "Nokogiri version: #{Nokogiri::VERSION_INFO.inspect}" +puts "Loofah version: #{Loofah::VERSION.inspect}" + +benches = [] +benches << HeadToHeadRailsSanitize.new +benches << HeadToHeadRailsStripTags.new +benches << HeadToHeadSanitizerSanitize.new +benches << HeadToHeadHtml5LibSanitize.new +benches << HeadToHeadHTMLFilter.new +puts "---------- rehearsal ----------" +benches.each { |bench| bench.test_set :rehearse => true } +puts "---------- realsies ----------" +benches.each { |bench| bench.test_set } diff --git a/path/ruby/2.6.0/gems/loofah-2.3.0/benchmark/fragment.html b/path/ruby/2.6.0/gems/loofah-2.3.0/benchmark/fragment.html new file mode 100644 index 00000000..9ddda1c1 --- /dev/null +++ b/path/ruby/2.6.0/gems/loofah-2.3.0/benchmark/fragment.html @@ -0,0 +1,96 @@ +
    + + + +
    +
    + + + + + + + + + +HTML + +FOOTER = < + +ENDFOOTER + +BODY = HEADER + < + +
    + #{METHOD_LIST} +
    + + #{FOOTER} +ENDBODY + +########################## Source code ########################## + +SRC_PAGE = XHTML_PREAMBLE + < +%title% + + + + +
    %code%
    + + +HTML + +########################## Index ################################ + +FR_INDEX_BODY = < + + + + + + + +
    +START:entries +%name%
    +END:entries +
    + +HTML + +CLASS_INDEX = FILE_INDEX +METHOD_INDEX = FILE_INDEX + +INDEX = XHTML_PREAMBLE + < + + %title% + + + + + + + + + +IF:inline_source + +ENDIF:inline_source +IFNOT:inline_source + + + + +ENDIF:inline_source + + <body bgcolor="white"> + Click <a href="html/index.html">here</a> for a non-frames + version of this page. + </body> + + + + +HTML + +end +end + + diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/doc/proto_rake.rdoc b/path/ruby/2.6.0/gems/rake-13.0.0/doc/proto_rake.rdoc new file mode 100644 index 00000000..a9e33d11 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/doc/proto_rake.rdoc @@ -0,0 +1,127 @@ += Original Prototype Rake + +This is the original 100 line prototype rake program. + +--- + #!/usr/bin/env ruby + + require 'ftools' + + class Task + TASKS = Hash.new + + attr_reader :prerequisites + + def initialize(task_name) + @name = task_name + @prerequisites = [] + @actions = [] + end + + def enhance(deps=nil, &block) + @prerequisites |= deps if deps + @actions << block if block_given? + self + end + + def name + @name.to_s + end + + def invoke + @prerequisites.each { |n| Task[n].invoke } + execute if needed? + end + + def execute + return if @triggered + @triggered = true + @actions.collect { |act| result = act.call(self) }.last + end + + def needed? + true + end + + def timestamp + Time.now + end + + class << self + def [](task_name) + TASKS[intern(task_name)] or fail "Don't know how to rake #{task_name}" + end + + def define_task(args, &block) + case args + when Hash + fail "Too Many Target Names: #{args.keys.join(' ')}" if args.size > 1 + fail "No Task Name Given" if args.size < 1 + task_name = args.keys[0] + deps = args[task_name] + else + task_name = args + deps = [] + end + deps = deps.collect {|d| intern(d) } + get(task_name).enhance(deps, &block) + end + + def get(task_name) + name = intern(task_name) + TASKS[name] ||= self.new(name) + end + + def intern(task_name) + (Symbol === task_name) ? task_name : task_name.intern + end + end + end + + class FileTask < Task + def needed? + return true unless File.exist?(name) + latest_prereq = @prerequisites.collect{|n| Task[n].timestamp}.max + return false if latest_prereq.nil? + timestamp < latest_prereq + end + + def timestamp + File.new(name.to_s).mtime + end + end + + def task(args, &block) + Task.define_task(args, &block) + end + + def file(args, &block) + FileTask.define_task(args, &block) + end + + def sys(cmd) + puts cmd + system(cmd) or fail "Command Failed: [#{cmd}]" + end + + def rake + begin + here = Dir.pwd + while ! File.exist?("Rakefile") + Dir.chdir("..") + fail "No Rakefile found" if Dir.pwd == here + here = Dir.pwd + end + puts "(in #{Dir.pwd})" + load "./Rakefile" + ARGV.push("default") if ARGV.size == 0 + ARGV.each { |task_name| Task[task_name].invoke } + rescue Exception => ex + puts "rake aborted ... #{ex.message}" + puts ex.backtrace.find {|str| str =~ /Rakefile/ } || "" + end + end + + if __FILE__ == $0 then + rake + end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/doc/rake.1 b/path/ruby/2.6.0/gems/rake-13.0.0/doc/rake.1 new file mode 100644 index 00000000..c6bfa25c --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/doc/rake.1 @@ -0,0 +1,156 @@ +.Dd June 12, 2016 +.Dt RAKE 1 +.Os rake 11.2.2 +.Sh NAME +.Nm rake +.Nd make-like build utility for Ruby +.Sh SYNOPSIS +.Nm +.Op Fl f Ar rakefile +.Op Ar options +.Ar targets ... +.Sh DESCRIPTION +.Nm +is a +.Xr make 1 Ns -like +build utility for Ruby. +Tasks and dependencies are specified in standard Ruby syntax. +.Sh OPTIONS +.Bl -tag -width Ds +.It Fl m , Fl -multitask +Treat all tasks as multitasks. +.It Fl B , Fl -build-all +Build all prerequisites, including those which are up\-to\-date. +.It Fl j , Fl -jobs Ar num_jobs +Specifies the maximum number of tasks to execute in parallel (default is number of CPU cores + 4). +.El +.Ss Modules +.Bl -tag -width Ds +.It Fl I , Fl -libdir Ar libdir +Include +.Ar libdir +in the search path for required modules. +.It Fl r , Fl -require Ar module +Require +.Ar module +before executing +.Pa rakefile . +.El +.Ss Rakefile location +.Bl -tag -width Ds +.It Fl f , Fl -rakefile Ar filename +Use +.Ar filename +as the rakefile to search for. +.It Fl N , Fl -no-search , Fl -nosearch +Do not search parent directories for the Rakefile. +.It Fl G , Fl -no-system , Fl -nosystem +Use standard project Rakefile search paths, ignore system wide rakefiles. +.It Fl R , Fl -rakelib Ar rakelibdir , Fl -rakelibdir Ar rakelibdir +Auto-import any .rake files in +.Ar rakelibdir +(default is +.Sq rakelib ) +.It Fl g , Fl -system +Use system-wide (global) rakefiles (usually +.Pa ~/.rake/*.rake ) . +.El +.Ss Debugging +.Bl -tag -width Ds +.It Fl -backtrace Ns = Ns Ar out +Enable full backtrace. +.Ar out +can be +.Dv stderr +(default) or +.Dv stdout . +.It Fl t , Fl -trace Ns = Ns Ar out +Turn on invoke/execute tracing, enable full backtrace. +.Ar out +can be +.Dv stderr +(default) or +.Dv stdout . +.It Fl -suppress-backtrace Ar pattern +Suppress backtrace lines matching regexp +.Ar pattern . +Ignored if +.Fl -trace +is on. +.It Fl -rules +Trace the rules resolution. +.It Fl n , Fl -dry-run +Do a dry run without executing actions. +.It Fl T , Fl -tasks Op Ar pattern +Display the tasks (matching optional +.Ar pattern ) +with descriptions, then exit. +.It Fl D , Fl -describe Op Ar pattern +Describe the tasks (matching optional +.Ar pattern ) , +then exit. +.It Fl W , Fl -where Op Ar pattern +Describe the tasks (matching optional +.Ar pattern ) , +then exit. +.It Fl P , Fl -prereqs +Display the tasks and dependencies, then exit. +.It Fl e , Fl -execute Ar code +Execute some Ruby code and exit. +.It Fl p , Fl -execute-print Ar code +Execute some Ruby code, print the result, then exit. +.It Fl E , Fl -execute-continue Ar code +Execute some Ruby code, then continue with normal task processing. +.El +.Ss Information +.Bl -tag -width Ds +.It Fl v , Fl -verbose +Log message to standard output. +.It Fl q , Fl -quiet +Do not log messages to standard output. +.It Fl s , Fl -silent +Like +.Fl -quiet , +but also suppresses the +.Sq in directory +announcement. +.It Fl X , Fl -no-deprecation-warnings +Disable the deprecation warnings. +.It Fl -comments +Show commented tasks only +.It Fl A , Fl -all +Show all tasks, even uncommented ones (in combination with +.Fl T +or +.Fl D ) +.It Fl -job-stats Op Ar level +Display job statistics. +If +.Ar level +is +.Sq history , +displays a complete job list. +.It Fl V , Fl -version +Display the program version. +.It Fl h , Fl H , Fl -help +Display a help message. +.El +.Sh SEE ALSO +The complete documentation for +.Nm rake +has been installed at +.Pa /usr/share/doc/rake-doc/html/index.html . +It is also available online at +.Lk https://ruby.github.io/rake . +.Sh AUTHORS +.An -nosplit +.Nm +was written by +.An Jim Weirich Aq Mt jim@weirichhouse.org . +.Pp +This manual was created by +.An Caitlin Matos Aq Mt caitlin.matos@zoho.com +for the Debian project (but may be used by others). +It was inspired by the manual by +.An Jani Monoses Aq Mt jani@iv.ro +for the Ubuntu project. diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/doc/rakefile.rdoc b/path/ruby/2.6.0/gems/rake-13.0.0/doc/rakefile.rdoc new file mode 100644 index 00000000..4014306a --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/doc/rakefile.rdoc @@ -0,0 +1,622 @@ += Rakefile Format + +First of all, there is no special format for a Rakefile. A Rakefile +contains executable Ruby code. Anything legal in a ruby script is +allowed in a Rakefile. + +Now that we understand there is no special syntax in a Rakefile, there +are some conventions that are used in a Rakefile that are a little +unusual in a typical Ruby program. Since a Rakefile is tailored to +specifying tasks and actions, the idioms used in a Rakefile are +designed to support that. + +So, what goes into a Rakefile? + +== Tasks + +Tasks are the main unit of work in a Rakefile. Tasks have a name +(usually given as a symbol or a string), a list of prerequisites (more +symbols or strings) and a list of actions (given as a block). + +=== Simple Tasks + +A task is declared by using the +task+ method. +task+ takes a single +parameter that is the name of the task. + + task :name + +=== Tasks with Prerequisites + +Any prerequisites are given as a list (enclosed in square brackets) +following the name and an arrow (=>). + + task name: [:prereq1, :prereq2] + +*NOTE:* Although this syntax looks a little funky, it is legal +Ruby. We are constructing a hash where the key is :name and the value +for that key is the list of prerequisites. It is equivalent to the +following ... + + hash = Hash.new + hash[:name] = [:prereq1, :prereq2] + task(hash) + +You can also use strings for task names and prerequisites, rake doesn't care. +This is the same task definition: + + task 'name' => %w[prereq1 prereq2] + +As is this: + + task name: %w[prereq1 prereq2] + +We'll prefer this style for regular tasks with prerequisites throughout the +rest of the document. Using an array of strings for the prerequisites means +you will need to make fewer changes if you need to move tasks into namespaces +or perform other refactorings. + +=== Tasks with Actions + +Actions are defined by passing a block to the +task+ method. Any Ruby +code can be placed in the block. The block may reference the task +object via the block parameter. + + task name: [:prereq1, :prereq2] do |t| + # actions (may reference t) + end + +=== Multiple Definitions + +A task may be specified more than once. Each specification adds its +prerequisites and actions to the existing definition. This allows one +part of a rakefile to specify the actions and a different rakefile +(perhaps separately generated) to specify the dependencies. + +For example, the following is equivalent to the single task +specification given above. + + task :name + task name: :prereq1 + task name: %w[prereq2] + task :name do |t| + # actions + end + +== File Tasks + +Some tasks are designed to create a file from one or more other files. +Tasks that generate these files may be skipped if the file already +exists. File tasks are used to specify file creation tasks. + +File tasks are declared using the +file+ method (instead of the +task+ +method). In addition, file tasks are usually named with a string +rather than a symbol. + +The following file task creates a executable program (named +prog+) +given two object files named +a.o+ and +b.o+. The tasks +for creating +a.o+ and +b.o+ are not shown. + + file "prog" => ["a.o", "b.o"] do |t| + sh "cc -o #{t.name} #{t.prerequisites.join(' ')}" + end + +== Directory Tasks + +It is common to need to create directories upon demand. The ++directory+ convenience method is a short-hand for creating a FileTask +that creates the directory. For example, the following declaration +... + + directory "testdata/examples/doc" + +is equivalent to ... + + file "testdata" do |t| mkdir t.name end + file "testdata/examples" => ["testdata"] do |t| mkdir t.name end + file "testdata/examples/doc" => ["testdata/examples"] do |t| mkdir t.name end + +The +directory+ method does not accept prerequisites or actions, but +both prerequisites and actions can be added later. For example ... + + directory "testdata" + file "testdata" => ["otherdata"] + file "testdata" do + cp Dir["standard_data/*.data"], "testdata" + end + +== Tasks with Parallel Prerequisites + +Rake allows parallel execution of prerequisites using the following syntax: + + multitask copy_files: %w[copy_src copy_doc copy_bin] do + puts "All Copies Complete" + end + +In this example, +copy_files+ is a normal rake task. Its actions are +executed whenever all of its prerequisites are done. The big +difference is that the prerequisites (+copy_src+, +copy_bin+ and ++copy_doc+) are executed in parallel. Each of the prerequisites are +run in their own Ruby thread, possibly allowing faster overall runtime. + +=== Secondary Prerequisites + +If any of the primary prerequisites of a multitask have common secondary +prerequisites, all of the primary/parallel prerequisites will wait +until the common prerequisites have been run. + +For example, if the copy_xxx tasks have the +following prerequisites: + + task copy_src: :prep_for_copy + task copy_bin: :prep_for_copy + task copy_doc: :prep_for_copy + +Then the +prep_for_copy+ task is run before starting all the copies in +parallel. Once +prep_for_copy+ is complete, +copy_src+, +copy_bin+, +and +copy_doc+ are all run in parallel. Note that +prep_for_copy+ is +run only once, even though it is referenced in multiple threads. + +=== Thread Safety + +The Rake internal data structures are thread-safe with respect +to the multitask parallel execution, so there is no need for the user +to do extra synchronization for Rake's benefit. However, if there are +user data structures shared between the parallel prerequisites, the +user must do whatever is necessary to prevent race conditions. + +== Tasks with Arguments + +Prior to version 0.8.0, rake was only able to handle command line +arguments of the form NAME=VALUE that were passed into Rake via the +ENV hash. Many folks had asked for some kind of simple command line +arguments, perhaps using "--" to separate regular task names from +argument values on the command line. The problem is that there was no +easy way to associate positional arguments on the command line with +different tasks. Suppose both tasks :a and :b expect a command line +argument: does the first value go with :a? What if :b is run first? +Should it then get the first command line argument. + +Rake 0.8.0 solves this problem by explicitly passing values directly +to the tasks that need them. For example, if I had a release task +that required a version number, I could say: + + rake release[0.8.2] + +And the string "0.8.2" will be passed to the :release task. Multiple +arguments can be passed by separating them with a comma, for example: + + rake name[john,doe] + +Just a few words of caution. The rake task name and its arguments +need to be a single command line argument to rake. This generally +means no spaces. If spaces are needed, then the entire name + +argument string should be quoted. Something like this: + + rake "name[billy bob, smith]" + +(Quoting rules vary between operating systems and shells, so make sure +you consult the proper docs for your OS/shell). + +=== Tasks that Expect Parameters + +Parameters are only given to tasks that are setup to expect them. In +order to handle named parameters, the task declaration syntax for +tasks has been extended slightly. + +For example, a task that needs a first name and last name might be +declared as: + + task :name, [:first_name, :last_name] + +The first argument is still the name of the task (:name in this case). +The next two arguments are the names of the parameters expected by +:name in an array (:first_name and :last_name in the example). + +To access the values of the parameters, the block defining the task +behaviour can now accept a second parameter: + + task :name, [:first_name, :last_name] do |t, args| + puts "First name is #{args.first_name}" + puts "Last name is #{args.last_name}" + end + +The first argument of the block "t" is always bound to the current +task object. The second argument "args" is an open-struct like object +that allows access to the task arguments. Extra command line +arguments to a task are ignored. + +If you wish to specify default values for the arguments, you can use +the with_defaults method in the task body. Here is the above example +where we specify default values for the first and last names: + + task :name, [:first_name, :last_name] do |t, args| + args.with_defaults(:first_name => "John", :last_name => "Dough") + puts "First name is #{args.first_name}" + puts "Last name is #{args.last_name}" + end + +=== Tasks that Expect Parameters and Have Prerequisites + +Tasks that use parameters have a slightly different format for +prerequisites. Use the arrow notation to indicate the prerequisites +for tasks with arguments. For example: + + task :name, [:first_name, :last_name] => [:pre_name] do |t, args| + args.with_defaults(:first_name => "John", :last_name => "Dough") + puts "First name is #{args.first_name}" + puts "Last name is #{args.last_name}" + end + +=== Tasks that take Variable-length Parameters + +Tasks that need to handle a list of values as a parameter can use the +extras method of the args variable. This allows for tasks that can +loop over a variable number of values, and its compatible with named +parameters as well: + + task :email, [:message] do |t, args| + mail = Mail.new(args.message) + recipients = args.extras + recipients.each do |target| + mail.send_to(target) + end + end + +There is also the convenience method to_a that returns all parameters +in the sequential order they were given, including those associated +with named parameters. + +=== Deprecated Task Parameters Format + +There is an older format for declaring task parameters that omitted +the task argument array and used the :needs keyword to introduce the +dependencies. That format is still supported for compatibility, but +is not recommended for use. The older format may be dropped in future +versions of rake. + +== Accessing Task Programmatically + +Sometimes it is useful to manipulate tasks programmatically in a +Rakefile. To find a task object use Rake::Task.[]. + +=== Programmatic Task Example + +For example, the following Rakefile defines two tasks. The :doit task +simply prints a simple "DONE" message. The :dont class will lookup +the doit class and remove (clear) all of its prerequisites and +actions. + + task :doit do + puts "DONE" + end + + task :dont do + Rake::Task[:doit].clear + end + +Running this example: + + $ rake doit + (in /Users/jim/working/git/rake/x) + DONE + $ rake dont doit + (in /Users/jim/working/git/rake/x) + $ + +The ability to programmatically manipulate tasks gives rake very +powerful meta-programming capabilities w.r.t. task execution, but +should be used with caution. + +== Rules + +When a file is named as a prerequisite, but does not have a file task +defined for it, Rake will attempt to synthesize a task by looking at a +list of rules supplied in the Rakefile. + +Suppose we were trying to invoke task "mycode.o", but no task is +defined for it. But the rakefile has a rule that look like this ... + + rule '.o' => ['.c'] do |t| + sh "cc #{t.source} -c -o #{t.name}" + end + +This rule will synthesize any task that ends in ".o". It has a +prerequisite a source file with an extension of ".c" must exist. If +Rake is able to find a file named "mycode.c", it will automatically +create a task that builds "mycode.o" from "mycode.c". + +If the file "mycode.c" does not exist, rake will attempt +to recursively synthesize a rule for it. + +When a task is synthesized from a rule, the +source+ attribute of the +task is set to the matching source file. This allows us to write +rules with actions that reference the source file. + +=== Advanced Rules + +Any regular expression may be used as the rule pattern. Additionally, +a proc may be used to calculate the name of the source file. This +allows for complex patterns and sources. + +The following rule is equivalent to the example above. + + rule( /\.o$/ => [ + proc {|task_name| task_name.sub(/\.[^.]+$/, '.c') } + ]) do |t| + sh "cc #{t.source} -c -o #{t.name}" + end + +*NOTE:* Because of a _quirk_ in Ruby syntax, parenthesis are +required on *rule* when the first argument is a regular expression. + +The following rule might be used for Java files ... + + rule '.class' => [ + proc { |tn| tn.sub(/\.class$/, '.java').sub(/^classes\//, 'src/') } + ] do |t| + java_compile(t.source, t.name) + end + +*NOTE:* +java_compile+ is a hypothetical method that invokes the +java compiler. + +== Importing Dependencies + +Any ruby file (including other rakefiles) can be included with a +standard Ruby +require+ command. The rules and declarations in the +required file are just added to the definitions already accumulated. + +Because the files are loaded _before_ the rake targets are evaluated, +the loaded files must be "ready to go" when the rake command is +invoked. This makes generated dependency files difficult to use. By +the time rake gets around to updating the dependencies file, it is too +late to load it. + +The +import+ command addresses this by specifying a file to be loaded +_after_ the main rakefile is loaded, but _before_ any targets on the +command line are invoked. In addition, if the file name matches an +explicit task, that task is invoked before loading the file. This +allows dependency files to be generated and used in a single rake +command invocation. + +Example: + + require 'rake/loaders/makefile' + + file ".depends.mf" => [SRC_LIST] do |t| + sh "makedepend -f- -- #{CFLAGS} -- #{t.prerequisites} > #{t.name}" + end + + import ".depends.mf" + +If ".depends" does not exist, or is out of date w.r.t. the source +files, a new ".depends" file is generated using +makedepend+ before +loading. + +== Comments + +Standard Ruby comments (beginning with "#") can be used anywhere it is +legal in Ruby source code, including comments for tasks and rules. +However, if you wish a task to be described using the "-T" switch, +then you need to use the +desc+ command to describe the task. + +Example: + + desc "Create a distribution package" + task package: %w[ ... ] do ... end + +The "-T" switch (or "--tasks" if you like to spell things out) will +display a list of tasks that have a description. If you use +desc+ to +describe your major tasks, you have a semi-automatic way of generating +a summary of your Rake file. + + $ rake -T + (in /home/.../rake) + rake clean # Remove any temporary products. + rake clobber # Remove any generated file. + rake clobber_rdoc # Remove rdoc products + rake contrib_test # Run tests for contrib_test + rake default # Default Task + rake install # Install the application + rake lines # Count lines in the main rake file + rake rdoc # Build the rdoc HTML Files + rake rerdoc # Force a rebuild of the RDOC files + rake test # Run tests + rake testall # Run all test targets + +Only tasks with descriptions will be displayed with the "-T" switch. +Use "-P" (or "--prereqs") to get a list of all tasks and their +prerequisites. + +== Namespaces + +As projects grow (and along with it, the number of tasks), it is +common for task names to begin to clash. For example, if you might +have a main program and a set of sample programs built by a single +Rakefile. By placing the tasks related to the main program in one +namespace, and the tasks for building the sample programs in a +different namespace, the task names will not interfere with each other. + +For example: + + namespace "main" do + task :build do + # Build the main program + end + end + + namespace "samples" do + task :build do + # Build the sample programs + end + end + + task build: %w[main:build samples:build] + +Referencing a task in a separate namespace can be achieved by +prefixing the task name with the namespace and a colon +(e.g. "main:build" refers to the :build task in the +main+ namespace). +Nested namespaces are supported. + +Note that the name given in the +task+ command is always the unadorned +task name without any namespace prefixes. The +task+ command always +defines a task in the current namespace. + +=== FileTasks + +File task names are not scoped by the namespace command. Since the +name of a file task is the name of an actual file in the file system, +it makes little sense to include file task names in name space. +Directory tasks (created by the +directory+ command) are a type of +file task and are also not affected by namespaces. + +=== Name Resolution + +When looking up a task name, rake will start with the current +namespace and attempt to find the name there. If it fails to find a +name in the current namespace, it will search the parent namespaces +until a match is found (or an error occurs if there is no match). + +The "rake" namespace is a special implicit namespace that refers to +the toplevel names. + +If a task name begins with a "^" character, the name resolution will +start in the parent namespace. Multiple "^" characters are allowed. + +Here is an example file with multiple :run tasks and how various names +resolve in different locations. + + task :run + + namespace "one" do + task :run + + namespace "two" do + task :run + + # :run => "one:two:run" + # "two:run" => "one:two:run" + # "one:two:run" => "one:two:run" + # "one:run" => "one:run" + # "^run" => "one:run" + # "^^run" => "rake:run" (the top level task) + # "rake:run" => "rake:run" (the top level task) + end + + # :run => "one:run" + # "two:run" => "one:two:run" + # "^run" => "rake:run" + end + + # :run => "rake:run" + # "one:run" => "one:run" + # "one:two:run" => "one:two:run" + +== FileLists + +FileLists are the way Rake manages lists of files. You can treat a +FileList as an array of strings for the most part, but FileLists +support some additional operations. + +=== Creating a FileList + +Creating a file list is easy. Just give it the list of file names: + + fl = FileList['file1.rb', file2.rb'] + +Or give it a glob pattern: + + fl = FileList['*.rb'] + +== Odds and Ends + +=== do/end versus { } + +Blocks may be specified with either a +do+/+end+ pair, or with curly +braces in Ruby. We _strongly_ recommend using +do+/+end+ to specify the +actions for tasks and rules. Because the rakefile idiom tends to +leave off parentheses on the task/file/rule methods, unusual +ambiguities can arise when using curly braces. + +For example, suppose that the method +object_files+ returns a list of +object files in a project. Now we use +object_files+ as the +prerequisites in a rule specified with actions in curly braces. + + # DON'T DO THIS! + file "prog" => object_files { + # Actions are expected here (but it doesn't work)! + } + +Because curly braces have a higher precedence than +do+/+end+, the +block is associated with the +object_files+ method rather than the ++file+ method. + +This is the proper way to specify the task ... + + # THIS IS FINE + file "prog" => object_files do + # Actions go here + end + +== Rakefile Path + +When issuing the +rake+ command in a terminal, Rake will look +for a Rakefile in the current directory. If a Rakefile is not found, +it will search parent directories until one is found. + +For example, if a Rakefile resides in the +project/+ directory, +moving deeper into the project's directory tree will not have an adverse +effect on rake tasks: + + $ pwd + /home/user/project + + $ cd lib/foo/bar + $ pwd + /home/user/project/lib/foo/bar + + $ rake run_pwd + /home/user/project + +As far as rake is concerned, all tasks are run from the directory in +which the Rakefile resides. + +=== Multiple Rake Files + +Not all tasks need to be included in a single Rakefile. Additional +rake files (with the file extension "+.rake+") may be placed in ++rakelib+ directory located at the top level of a project (i.e. +the same directory that contains the main +Rakefile+). + +Also, rails projects may include additional rake files in the ++lib/tasks+ directory. + +=== Clean and Clobber Tasks + +Through require 'rake/clean' Rake provides +clean+ and +clobber+ +tasks: + ++clean+ :: + Clean up the project by deleting scratch files and backup files. Add files + to the +CLEAN+ FileList to have the +clean+ target handle them. + ++clobber+ :: + Clobber all generated and non-source files in a project. The task depends + on +clean+, so all the +CLEAN+ files will be deleted as well as files in the + +CLOBBER+ FileList. The intent of this task is to return a project to its + pristine, just unpacked state. + +You can add file names or glob patterns to both the +CLEAN+ and +CLOBBER+ +lists. + +=== Phony Task + +The phony task can be used as a dependency to allow file-based tasks to use +non-file-based-tasks as prerequisites without forcing them to rebuild. You +can require 'rake/phony' to add the +phony+ task. + +---- + +== See + +* README.rdoc -- Main documentation for Rake. diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/doc/rational.rdoc b/path/ruby/2.6.0/gems/rake-13.0.0/doc/rational.rdoc new file mode 100644 index 00000000..0e1c3387 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/doc/rational.rdoc @@ -0,0 +1,151 @@ += Why rake? + +Ok, let me state from the beginning that I never intended to write this +code. I'm not convinced it is useful, and I'm not convinced anyone +would even be interested in it. All I can say is that Why's onion truck +must by been passing through the Ohio valley. + +What am I talking about? ... A Ruby version of Make. + +See, I can sense you cringing already, and I agree. The world certainly +doesn't need yet another reworking of the "make" program. I mean, we +already have "ant". Isn't that enough? + +It started yesterday. I was helping a coworker fix a problem in one of +the Makefiles we use in our project. Not a particularly tough problem, +but during the course of the conversation I began lamenting some of the +shortcomings of make. In particular, in one of my makefiles I wanted to +determine the name of a file dynamically and had to resort to some +simple scripting (in Ruby) to make it work. "Wouldn't it be nice if you +could just use Ruby inside a Makefile" I said. + +My coworker (a recent convert to Ruby) agreed, but wondered what it +would look like. So I sketched the following on the whiteboard... + + "What if you could specify the make tasks in Ruby, like this ..." + + task "build" do + java_compile(...args, etc ...) + end + + "The task function would register "build" as a target to be made, + and the block would be the action executed whenever the build + system determined that it was time to do the build target." + +We agreed that would be cool, but writing make from scratch would be WAY +too much work. And that was the end of that! + +... Except I couldn't get the thought out of my head. What exactly +would be needed to make the about syntax work as a make file? Hmmm, you +would need to register the tasks, you need some way of specifying +dependencies between tasks, and some way of kicking off the process. +Hey! What if we did ... and fifteen minutes later I had a working +prototype of Ruby make, complete with dependencies and actions. + +I showed the code to my coworker and we had a good laugh. It was just +about a page worth of code that reproduced an amazing amount of the +functionality of make. We were both truly stunned with the power of +Ruby. + +But it didn't do everything make did. In particular, it didn't have +timestamp based file dependencies (where a file is rebuilt if any of its +prerequisite files have a later timestamp). Obviously THAT would be a +pain to add and so Ruby Make would remain an interesting experiment. + +... Except as I walked back to my desk, I started thinking about what +file based dependencies would really need. Rats! I was hooked again, +and by adding a new class and two new methods, file/timestamp +dependencies were implemented. + +Ok, now I was really hooked. Last night (during CSI!) I massaged the +code and cleaned it up a bit. The result is a bare-bones replacement +for make in exactly 100 lines of code. + +For the curious, you can see it at ... +* doc/proto_rake.rdoc + +Oh, about the name. When I wrote the example Ruby Make task on my +whiteboard, my coworker exclaimed "Oh! I have the perfect name: Rake ... +Get it? Ruby-Make. Rake!" He said he envisioned the tasks as leaves +and Rake would clean them up ... or something like that. Anyways, the +name stuck. + +Some quick examples ... + +A simple task to delete backup files ... + + task :clean do + Dir['*~'].each {|fn| rm fn rescue nil} + end + +Note that task names are symbols (they are slightly easier to type +than quoted strings ... but you may use quoted string if you would +rather). Rake makes the methods of the FileUtils module directly +available, so we take advantage of the rm command. Also note +the use of "rescue nil" to trap and ignore errors in the rm +command. + +To run it, just type "rake clean". Rake will automatically find a +Rakefile in the current directory (or above!) and will invoke the +targets named on the command line. If there are no targets explicitly +named, rake will invoke the task "default". + +Here's another task with dependencies ... + + task :clobber => [:clean] do + rm_r "tempdir" + end + +Task :clobber depends upon task :clean, so :clean will be run before +:clobber is executed. + +Files are specified by using the "file" command. It is similar to the +task command, except that the task name represents a file, and the task +will be run only if the file doesn't exist, or if its modification time +is earlier than any of its prerequisites. + +Here is a file based dependency that will compile "hello.cc" to +"hello.o". + + file "hello.cc" + file "hello.o" => ["hello.cc"] do |t| + srcfile = t.name.sub(/\.o$/, ".cc") + sh %{g++ #{srcfile} -c -o #{t.name}} + end + +I normally specify file tasks with string (rather than symbols). Some +file names can't be represented by symbols. Plus it makes the +distinction between them more clear to the casual reader. + +Currently writing a task for each and every file in the project would be +tedious at best. I envision a set of libraries to make this job +easier. For instance, perhaps something like this ... + + require 'rake/ctools' + Dir['*.c'].each do |fn| + c_source_file(fn) + end + +where "c_source_file" will create all the tasks need to compile all the +C source files in a directory. Any number of useful libraries could be +created for rake. + +That's it. There's no documentation (other than whats in this +message). Does this sound interesting to anyone? If so, I'll continue +to clean it up and write it up and publish it on RAA. Otherwise, I'll +leave it as an interesting exercise and a tribute to the power of Ruby. + +Why /might/ rake be interesting to Ruby programmers. I don't know, +perhaps ... + +* No weird make syntax (only weird Ruby syntax :-) +* No need to edit or read XML (a la ant) +* Platform independent build scripts. +* Will run anywhere Ruby exists, so no need to have "make" installed. + If you stay away from the "sys" command and use things like + 'ftools', you can have a perfectly platform independent + build script. Also rake is only 100 lines of code, so it can + easily be packaged along with the rest of your code. + +So ... Sorry for the long rambling message. Like I said, I never +intended to write this code at all. diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/exe/rake b/path/ruby/2.6.0/gems/rake-13.0.0/exe/rake new file mode 100755 index 00000000..a00975f3 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/exe/rake @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby + +#-- +# Copyright (c) 2003, 2004, 2005, 2006, 2007 Jim Weirich +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +#++ + +require "rake" + +Rake.application.run diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake.rb new file mode 100644 index 00000000..0dfd0531 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true +#-- +# Copyright 2003-2010 by Jim Weirich (jim.weirich@gmail.com) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +#++ + +module Rake; end + +require "rake/version" + +require "rbconfig" +require "fileutils" +require "singleton" +require "monitor" +require "optparse" +require "ostruct" + +require "rake/ext/string" + +require "rake/win32" + +require "rake/linked_list" +require "rake/cpu_counter" +require "rake/scope" +require "rake/task_argument_error" +require "rake/rule_recursion_overflow_error" +require "rake/rake_module" +require "rake/trace_output" +require "rake/pseudo_status" +require "rake/task_arguments" +require "rake/invocation_chain" +require "rake/task" +require "rake/file_task" +require "rake/file_creation_task" +require "rake/multi_task" +require "rake/dsl_definition" +require "rake/file_utils_ext" +require "rake/file_list" +require "rake/default_loader" +require "rake/early_time" +require "rake/late_time" +require "rake/name_space" +require "rake/task_manager" +require "rake/application" +require "rake/backtrace" + +$trace = false + +# :stopdoc: +# +# Some top level Constants. + +FileList = Rake::FileList +RakeFileUtils = Rake::FileUtilsExt diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/application.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/application.rb new file mode 100644 index 00000000..9ac9b213 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/application.rb @@ -0,0 +1,824 @@ +# frozen_string_literal: true +require "optparse" + +require "rake/task_manager" +require "rake/file_list" +require "rake/thread_pool" +require "rake/thread_history_display" +require "rake/trace_output" +require "rake/win32" + +module Rake + + CommandLineOptionError = Class.new(StandardError) + + ## + # Rake main application object. When invoking +rake+ from the + # command line, a Rake::Application object is created and run. + + class Application + include TaskManager + include TraceOutput + + # The name of the application (typically 'rake') + attr_reader :name + + # The original directory where rake was invoked. + attr_reader :original_dir + + # Name of the actual rakefile used. + attr_reader :rakefile + + # Number of columns on the terminal + attr_accessor :terminal_columns + + # List of the top level task names (task names from the command line). + attr_reader :top_level_tasks + + # Override the detected TTY output state (mostly for testing) + attr_writer :tty_output + + DEFAULT_RAKEFILES = [ + "rakefile", + "Rakefile", + "rakefile.rb", + "Rakefile.rb" + ].freeze + + # Initialize a Rake::Application object. + def initialize + super + @name = "rake" + @rakefiles = DEFAULT_RAKEFILES.dup + @rakefile = nil + @pending_imports = [] + @imported = [] + @loaders = {} + @default_loader = Rake::DefaultLoader.new + @original_dir = Dir.pwd + @top_level_tasks = [] + add_loader("rb", DefaultLoader.new) + add_loader("rf", DefaultLoader.new) + add_loader("rake", DefaultLoader.new) + @tty_output = STDOUT.tty? + @terminal_columns = ENV["RAKE_COLUMNS"].to_i + + set_default_options + end + + # Run the Rake application. The run method performs the following + # three steps: + # + # * Initialize the command line options (+init+). + # * Define the tasks (+load_rakefile+). + # * Run the top level tasks (+top_level+). + # + # If you wish to build a custom rake command, you should call + # +init+ on your application. Then define any tasks. Finally, + # call +top_level+ to run your top level tasks. + def run(argv = ARGV) + standard_exception_handling do + init "rake", argv + load_rakefile + top_level + end + end + + # Initialize the command line parameters and app name. + def init(app_name="rake", argv = ARGV) + standard_exception_handling do + @name = app_name + begin + args = handle_options argv + rescue ArgumentError + # Backward compatibility for capistrano + args = handle_options + end + collect_command_line_tasks(args) + end + end + + # Find the rakefile and then load it and any pending imports. + def load_rakefile + standard_exception_handling do + raw_load_rakefile + end + end + + # Run the top level tasks of a Rake application. + def top_level + run_with_threads do + if options.show_tasks + display_tasks_and_comments + elsif options.show_prereqs + display_prerequisites + else + top_level_tasks.each { |task_name| invoke_task(task_name) } + end + end + end + + # Run the given block with the thread startup and shutdown. + def run_with_threads + thread_pool.gather_history if options.job_stats == :history + + yield + + thread_pool.join + if options.job_stats + stats = thread_pool.statistics + puts "Maximum active threads: #{stats[:max_active_threads]} + main" + puts "Total threads in play: #{stats[:total_threads_in_play]} + main" + end + ThreadHistoryDisplay.new(thread_pool.history).show if + options.job_stats == :history + end + + # Add a loader to handle imported files ending in the extension + # +ext+. + def add_loader(ext, loader) + ext = ".#{ext}" unless ext =~ /^\./ + @loaders[ext] = loader + end + + # Application options from the command line + def options + @options ||= OpenStruct.new + end + + # Return the thread pool used for multithreaded processing. + def thread_pool # :nodoc: + @thread_pool ||= ThreadPool.new(options.thread_pool_size || Rake.suggested_thread_count-1) + end + + # internal ---------------------------------------------------------------- + + # Invokes a task with arguments that are extracted from +task_string+ + def invoke_task(task_string) # :nodoc: + name, args = parse_task_string(task_string) + t = self[name] + t.invoke(*args) + end + + def parse_task_string(string) # :nodoc: + /^([^\[]+)(?:\[(.*)\])$/ =~ string.to_s + + name = $1 + remaining_args = $2 + + return string, [] unless name + return name, [] if remaining_args.empty? + + args = [] + + begin + /\s*((?:[^\\,]|\\.)*?)\s*(?:,\s*(.*))?$/ =~ remaining_args + + remaining_args = $2 + args << $1.gsub(/\\(.)/, '\1') + end while remaining_args + + return name, args + end + + # Provide standard exception handling for the given block. + def standard_exception_handling # :nodoc: + yield + rescue SystemExit + # Exit silently with current status + raise + rescue OptionParser::InvalidOption => ex + $stderr.puts ex.message + exit(false) + rescue Exception => ex + # Exit with error message + display_error_message(ex) + exit_because_of_exception(ex) + end + + # Exit the program because of an unhandled exception. + # (may be overridden by subclasses) + def exit_because_of_exception(ex) # :nodoc: + exit(false) + end + + # Display the error message that caused the exception. + def display_error_message(ex) # :nodoc: + trace "#{name} aborted!" + display_exception_details(ex) + trace "Tasks: #{ex.chain}" if has_chain?(ex) + trace "(See full trace by running task with --trace)" unless + options.backtrace + end + + def display_exception_details(ex) # :nodoc: + display_exception_details_seen << ex + + display_exception_message_details(ex) + display_exception_backtrace(ex) + display_cause_details(ex.cause) if has_cause?(ex) + end + + def display_cause_details(ex) # :nodoc: + return if display_exception_details_seen.include? ex + + trace "\nCaused by:" + display_exception_details(ex) + end + + def display_exception_details_seen # :nodoc: + Thread.current[:rake_display_exception_details_seen] ||= [] + end + + def has_cause?(ex) # :nodoc: + ex.respond_to?(:cause) && ex.cause + end + + def display_exception_message_details(ex) # :nodoc: + if ex.instance_of?(RuntimeError) + trace ex.message + else + trace "#{ex.class.name}: #{ex.message}" + end + end + + def display_exception_backtrace(ex) # :nodoc: + if options.backtrace + trace ex.backtrace.join("\n") + else + trace Backtrace.collapse(ex.backtrace).join("\n") + end + end + + # Warn about deprecated usage. + # + # Example: + # Rake.application.deprecate("import", "Rake.import", caller.first) + # + def deprecate(old_usage, new_usage, call_site) # :nodoc: + unless options.ignore_deprecate + $stderr.puts "WARNING: '#{old_usage}' is deprecated. " + + "Please use '#{new_usage}' instead.\n" + + " at #{call_site}" + end + end + + # Does the exception have a task invocation chain? + def has_chain?(exception) # :nodoc: + exception.respond_to?(:chain) && exception.chain + end + private :has_chain? + + # True if one of the files in RAKEFILES is in the current directory. + # If a match is found, it is copied into @rakefile. + def have_rakefile # :nodoc: + @rakefiles.each do |fn| + if File.exist?(fn) + others = FileList.glob(fn, File::FNM_CASEFOLD) + return others.size == 1 ? others.first : fn + elsif fn == "" + return fn + end + end + return nil + end + + # True if we are outputting to TTY, false otherwise + def tty_output? # :nodoc: + @tty_output + end + + # We will truncate output if we are outputting to a TTY or if we've been + # given an explicit column width to honor + def truncate_output? # :nodoc: + tty_output? || @terminal_columns.nonzero? + end + + # Display the tasks and comments. + def display_tasks_and_comments # :nodoc: + displayable_tasks = tasks.select { |t| + (options.show_all_tasks || t.comment) && + t.name =~ options.show_task_pattern + } + case options.show_tasks + when :tasks + width = displayable_tasks.map { |t| t.name_with_args.length }.max || 10 + if truncate_output? + max_column = terminal_width - name.size - width - 7 + else + max_column = nil + end + + displayable_tasks.each do |t| + printf("#{name} %-#{width}s # %s\n", + t.name_with_args, + max_column ? truncate(t.comment, max_column) : t.comment) + end + when :describe + displayable_tasks.each do |t| + puts "#{name} #{t.name_with_args}" + comment = t.full_comment || "" + comment.split("\n").each do |line| + puts " #{line}" + end + puts + end + when :lines + displayable_tasks.each do |t| + t.locations.each do |loc| + printf "#{name} %-30s %s\n", t.name_with_args, loc + end + end + else + fail "Unknown show task mode: '#{options.show_tasks}'" + end + end + + def terminal_width # :nodoc: + if @terminal_columns.nonzero? + result = @terminal_columns + else + result = unix? ? dynamic_width : 80 + end + (result < 10) ? 80 : result + rescue + 80 + end + + # Calculate the dynamic width of the + def dynamic_width # :nodoc: + @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput) + end + + def dynamic_width_stty # :nodoc: + %x{stty size 2>/dev/null}.split[1].to_i + end + + def dynamic_width_tput # :nodoc: + %x{tput cols 2>/dev/null}.to_i + end + + def unix? # :nodoc: + RbConfig::CONFIG["host_os"] =~ + /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i + end + + def windows? # :nodoc: + Win32.windows? + end + + def truncate(string, width) # :nodoc: + if string.nil? + "" + elsif string.length <= width + string + else + (string[0, width - 3] || "") + "..." + end + end + + # Display the tasks and prerequisites + def display_prerequisites # :nodoc: + tasks.each do |t| + puts "#{name} #{t.name}" + t.prerequisites.each { |pre| puts " #{pre}" } + end + end + + def trace(*strings) # :nodoc: + options.trace_output ||= $stderr + trace_on(options.trace_output, *strings) + end + + def sort_options(options) # :nodoc: + options.sort_by { |opt| + opt.select { |o| o.is_a?(String) && o =~ /^-/ }.map(&:downcase).sort.reverse + } + end + private :sort_options + + # A list of all the standard options used in rake, suitable for + # passing to OptionParser. + def standard_rake_options # :nodoc: + sort_options( + [ + ["--all", "-A", + "Show all tasks, even uncommented ones (in combination with -T or -D)", + lambda { |value| + options.show_all_tasks = value + } + ], + ["--backtrace=[OUT]", + "Enable full backtrace. OUT can be stderr (default) or stdout.", + lambda { |value| + options.backtrace = true + select_trace_output(options, "backtrace", value) + } + ], + ["--build-all", "-B", + "Build all prerequisites, including those which are up-to-date.", + lambda { |value| + options.build_all = true + } + ], + ["--comments", + "Show commented tasks only", + lambda { |value| + options.show_all_tasks = !value + } + ], + ["--describe", "-D [PATTERN]", + "Describe the tasks (matching optional PATTERN), then exit.", + lambda { |value| + select_tasks_to_show(options, :describe, value) + } + ], + ["--dry-run", "-n", + "Do a dry run without executing actions.", + lambda { |value| + Rake.verbose(true) + Rake.nowrite(true) + options.dryrun = true + options.trace = true + } + ], + ["--execute", "-e CODE", + "Execute some Ruby code and exit.", + lambda { |value| + eval(value) + exit + } + ], + ["--execute-print", "-p CODE", + "Execute some Ruby code, print the result, then exit.", + lambda { |value| + puts eval(value) + exit + } + ], + ["--execute-continue", "-E CODE", + "Execute some Ruby code, " + + "then continue with normal task processing.", + lambda { |value| eval(value) } + ], + ["--jobs", "-j [NUMBER]", + "Specifies the maximum number of tasks to execute in parallel. " + + "(default is number of CPU cores + 4)", + lambda { |value| + if value.nil? || value == "" + value = Float::INFINITY + elsif value =~ /^\d+$/ + value = value.to_i + else + value = Rake.suggested_thread_count + end + value = 1 if value < 1 + options.thread_pool_size = value - 1 + } + ], + ["--job-stats [LEVEL]", + "Display job statistics. " + + "LEVEL=history displays a complete job list", + lambda { |value| + if value =~ /^history/i + options.job_stats = :history + else + options.job_stats = true + end + } + ], + ["--libdir", "-I LIBDIR", + "Include LIBDIR in the search path for required modules.", + lambda { |value| $:.push(value) } + ], + ["--multitask", "-m", + "Treat all tasks as multitasks.", + lambda { |value| options.always_multitask = true } + ], + ["--no-search", "--nosearch", + "-N", "Do not search parent directories for the Rakefile.", + lambda { |value| options.nosearch = true } + ], + ["--prereqs", "-P", + "Display the tasks and dependencies, then exit.", + lambda { |value| options.show_prereqs = true } + ], + ["--quiet", "-q", + "Do not log messages to standard output.", + lambda { |value| Rake.verbose(false) } + ], + ["--rakefile", "-f [FILENAME]", + "Use FILENAME as the rakefile to search for.", + lambda { |value| + value ||= "" + @rakefiles.clear + @rakefiles << value + } + ], + ["--rakelibdir", "--rakelib", "-R RAKELIBDIR", + "Auto-import any .rake files in RAKELIBDIR. " + + "(default is 'rakelib')", + lambda { |value| + options.rakelib = value.split(File::PATH_SEPARATOR) + } + ], + ["--require", "-r MODULE", + "Require MODULE before executing rakefile.", + lambda { |value| + begin + require value + rescue LoadError => ex + begin + rake_require value + rescue LoadError + raise ex + end + end + } + ], + ["--rules", + "Trace the rules resolution.", + lambda { |value| options.trace_rules = true } + ], + ["--silent", "-s", + "Like --quiet, but also suppresses the " + + "'in directory' announcement.", + lambda { |value| + Rake.verbose(false) + options.silent = true + } + ], + ["--suppress-backtrace PATTERN", + "Suppress backtrace lines matching regexp PATTERN. " + + "Ignored if --trace is on.", + lambda { |value| + options.suppress_backtrace_pattern = Regexp.new(value) + } + ], + ["--system", "-g", + "Using system wide (global) rakefiles " + + "(usually '~/.rake/*.rake').", + lambda { |value| options.load_system = true } + ], + ["--no-system", "--nosystem", "-G", + "Use standard project Rakefile search paths, " + + "ignore system wide rakefiles.", + lambda { |value| options.ignore_system = true } + ], + ["--tasks", "-T [PATTERN]", + "Display the tasks (matching optional PATTERN) " + + "with descriptions, then exit. " + + "-AT combination displays all of tasks contained no description.", + lambda { |value| + select_tasks_to_show(options, :tasks, value) + } + ], + ["--trace=[OUT]", "-t", + "Turn on invoke/execute tracing, enable full backtrace. " + + "OUT can be stderr (default) or stdout.", + lambda { |value| + options.trace = true + options.backtrace = true + select_trace_output(options, "trace", value) + Rake.verbose(true) + } + ], + ["--verbose", "-v", + "Log message to standard output.", + lambda { |value| Rake.verbose(true) } + ], + ["--version", "-V", + "Display the program version.", + lambda { |value| + puts "rake, version #{Rake::VERSION}" + exit + } + ], + ["--where", "-W [PATTERN]", + "Describe the tasks (matching optional PATTERN), then exit.", + lambda { |value| + select_tasks_to_show(options, :lines, value) + options.show_all_tasks = true + } + ], + ["--no-deprecation-warnings", "-X", + "Disable the deprecation warnings.", + lambda { |value| + options.ignore_deprecate = true + } + ], + ]) + end + + def select_tasks_to_show(options, show_tasks, value) # :nodoc: + options.show_tasks = show_tasks + options.show_task_pattern = Regexp.new(value || "") + Rake::TaskManager.record_task_metadata = true + end + private :select_tasks_to_show + + def select_trace_output(options, trace_option, value) # :nodoc: + value = value.strip unless value.nil? + case value + when "stdout" + options.trace_output = $stdout + when "stderr", nil + options.trace_output = $stderr + else + fail CommandLineOptionError, + "Unrecognized --#{trace_option} option '#{value}'" + end + end + private :select_trace_output + + # Read and handle the command line options. Returns the command line + # arguments that we didn't understand, which should (in theory) be just + # task names and env vars. + def handle_options(argv) # :nodoc: + set_default_options + + OptionParser.new do |opts| + opts.banner = "#{Rake.application.name} [-f rakefile] {options} targets..." + opts.separator "" + opts.separator "Options are ..." + + opts.on_tail("-h", "--help", "-H", "Display this help message.") do + puts opts + exit + end + + standard_rake_options.each { |args| opts.on(*args) } + opts.environment("RAKEOPT") + end.parse(argv) + end + + # Similar to the regular Ruby +require+ command, but will check + # for *.rake files in addition to *.rb files. + def rake_require(file_name, paths=$LOAD_PATH, loaded=$") # :nodoc: + fn = file_name + ".rake" + return false if loaded.include?(fn) + paths.each do |path| + full_path = File.join(path, fn) + if File.exist?(full_path) + Rake.load_rakefile(full_path) + loaded << fn + return true + end + end + fail LoadError, "Can't find #{file_name}" + end + + def find_rakefile_location # :nodoc: + here = Dir.pwd + until (fn = have_rakefile) + Dir.chdir("..") + return nil if Dir.pwd == here || options.nosearch + here = Dir.pwd + end + [fn, here] + ensure + Dir.chdir(Rake.original_dir) + end + + def print_rakefile_directory(location) # :nodoc: + $stderr.puts "(in #{Dir.pwd})" unless + options.silent or original_dir == location + end + + def raw_load_rakefile # :nodoc: + rakefile, location = find_rakefile_location + if (!options.ignore_system) && + (options.load_system || rakefile.nil?) && + system_dir && File.directory?(system_dir) + print_rakefile_directory(location) + glob("#{system_dir}/*.rake") do |name| + add_import name + end + else + fail "No Rakefile found (looking for: #{@rakefiles.join(', ')})" if + rakefile.nil? + @rakefile = rakefile + Dir.chdir(location) + print_rakefile_directory(location) + Rake.load_rakefile(File.expand_path(@rakefile)) if + @rakefile && @rakefile != "" + options.rakelib.each do |rlib| + glob("#{rlib}/*.rake") do |name| + add_import name + end + end + end + load_imports + end + + def glob(path, &block) # :nodoc: + FileList.glob(path.tr("\\", "/")).each(&block) + end + private :glob + + # The directory path containing the system wide rakefiles. + def system_dir # :nodoc: + @system_dir ||= + begin + if ENV["RAKE_SYSTEM"] + ENV["RAKE_SYSTEM"] + else + standard_system_dir + end + end + end + + # The standard directory containing system wide rake files. + if Win32.windows? + def standard_system_dir #:nodoc: + Win32.win32_system_dir + end + else + def standard_system_dir #:nodoc: + File.join(File.expand_path("~"), ".rake") + end + end + private :standard_system_dir + + # Collect the list of tasks on the command line. If no tasks are + # given, return a list containing only the default task. + # Environmental assignments are processed at this time as well. + # + # `args` is the list of arguments to peruse to get the list of tasks. + # It should be the command line that was given to rake, less any + # recognised command-line options, which OptionParser.parse will + # have taken care of already. + def collect_command_line_tasks(args) # :nodoc: + @top_level_tasks = [] + args.each do |arg| + if arg =~ /^(\w+)=(.*)$/m + ENV[$1] = $2 + else + @top_level_tasks << arg unless arg =~ /^-/ + end + end + @top_level_tasks.push(default_task_name) if @top_level_tasks.empty? + end + + # Default task name ("default"). + # (May be overridden by subclasses) + def default_task_name # :nodoc: + "default" + end + + # Add a file to the list of files to be imported. + def add_import(fn) # :nodoc: + @pending_imports << fn + end + + # Load the pending list of imported files. + def load_imports # :nodoc: + while fn = @pending_imports.shift + next if @imported.member?(fn) + fn_task = lookup(fn) and fn_task.invoke + ext = File.extname(fn) + loader = @loaders[ext] || @default_loader + loader.load(fn) + if fn_task = lookup(fn) and fn_task.needed? + fn_task.reenable + fn_task.invoke + loader.load(fn) + end + @imported << fn + end + end + + def rakefile_location(backtrace=caller) # :nodoc: + backtrace.map { |t| t[/([^:]+):/, 1] } + + re = /^#{@rakefile}$/ + re = /#{re.source}/i if windows? + + backtrace.find { |str| str =~ re } || "" + end + + def set_default_options # :nodoc: + options.always_multitask = false + options.backtrace = false + options.build_all = false + options.dryrun = false + options.ignore_deprecate = false + options.ignore_system = false + options.job_stats = false + options.load_system = false + options.nosearch = false + options.rakelib = %w[rakelib] + options.show_all_tasks = false + options.show_prereqs = false + options.show_task_pattern = nil + options.show_tasks = nil + options.silent = false + options.suppress_backtrace_pattern = nil + options.thread_pool_size = Rake.suggested_thread_count + options.trace = false + options.trace_output = $stderr + options.trace_rules = false + end + + end +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/backtrace.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/backtrace.rb new file mode 100644 index 00000000..31ff0545 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/backtrace.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true +module Rake + module Backtrace # :nodoc: all + SYS_KEYS = RbConfig::CONFIG.keys.grep(/(?:[a-z]prefix|libdir)\z/) + SYS_PATHS = RbConfig::CONFIG.values_at(*SYS_KEYS).uniq + + [ File.join(File.dirname(__FILE__), "..") ] + + SUPPRESSED_PATHS = SYS_PATHS. + map { |s| s.tr("\\", "/") }. + map { |f| File.expand_path(f) }. + reject { |s| s.nil? || s =~ /^ *$/ } + SUPPRESSED_PATHS_RE = SUPPRESSED_PATHS.map { |f| Regexp.quote(f) }.join("|") + SUPPRESSED_PATHS_RE << "|^org\\/jruby\\/\\w+\\.java" if + Object.const_defined?(:RUBY_ENGINE) and RUBY_ENGINE == "jruby" + + SUPPRESS_PATTERN = %r!(\A(#{SUPPRESSED_PATHS_RE})|bin/rake:\d+)!i + + def self.collapse(backtrace) + pattern = Rake.application.options.suppress_backtrace_pattern || + SUPPRESS_PATTERN + backtrace.reject { |elem| elem =~ pattern } + end + end +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/clean.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/clean.rb new file mode 100644 index 00000000..b52e832a --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/clean.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true +# The 'rake/clean' file defines two file lists (CLEAN and CLOBBER) and +# two rake tasks (:clean and :clobber). +# +# [:clean] Clean up the project by deleting scratch files and backup +# files. Add files to the CLEAN file list to have the :clean +# target handle them. +# +# [:clobber] Clobber all generated and non-source files in a project. +# The task depends on :clean, so all the clean files will +# be deleted as well as files in the CLOBBER file list. +# The intent of this task is to return a project to its +# pristine, just unpacked state. + +require "rake" + +# :stopdoc: + +module Rake + module Cleaner + extend FileUtils + + module_function + + def cleanup_files(file_names) + file_names.each do |file_name| + cleanup(file_name) + end + end + + def cleanup(file_name, **opts) + begin + opts = { verbose: Rake.application.options.trace }.merge(opts) + rm_r file_name, **opts + rescue StandardError => ex + puts "Failed to remove #{file_name}: #{ex}" unless file_already_gone?(file_name) + end + end + + def file_already_gone?(file_name) + return false if File.exist?(file_name) + + path = file_name + prev = nil + + while path = File.dirname(path) + return false if cant_be_deleted?(path) + break if [prev, "."].include?(path) + prev = path + end + true + end + private_class_method :file_already_gone? + + def cant_be_deleted?(path_name) + File.exist?(path_name) && + (!File.readable?(path_name) || !File.executable?(path_name)) + end + private_class_method :cant_be_deleted? + end +end + +CLEAN = ::Rake::FileList["**/*~", "**/*.bak", "**/core"] +CLEAN.clear_exclude.exclude { |fn| + fn.pathmap("%f").downcase == "core" && File.directory?(fn) +} + +desc "Remove any temporary products." +task :clean do + Rake::Cleaner.cleanup_files(CLEAN) +end + +CLOBBER = ::Rake::FileList.new + +desc "Remove any generated files." +task clobber: [:clean] do + Rake::Cleaner.cleanup_files(CLOBBER) +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/cloneable.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/cloneable.rb new file mode 100644 index 00000000..eddb77e2 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/cloneable.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true +module Rake + ## + # Mixin for creating easily cloned objects. + + module Cloneable # :nodoc: + # The hook that is invoked by 'clone' and 'dup' methods. + def initialize_copy(source) + super + source.instance_variables.each do |var| + src_value = source.instance_variable_get(var) + value = src_value.clone rescue src_value + instance_variable_set(var, value) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/cpu_counter.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/cpu_counter.rb new file mode 100644 index 00000000..564a6285 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/cpu_counter.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true +module Rake + + # Based on a script at: + # http://stackoverflow.com/questions/891537/ruby-detect-number-of-cpus-installed + class CpuCounter # :nodoc: all + def self.count + new.count_with_default + end + + def count_with_default(default=4) + count || default + rescue StandardError + default + end + + begin + require "etc" + rescue LoadError + else + if Etc.respond_to?(:nprocessors) + def count + return Etc.nprocessors + end + end + end + end +end + +unless Rake::CpuCounter.method_defined?(:count) + Rake::CpuCounter.class_eval <<-'end;', __FILE__, __LINE__+1 + require 'rbconfig' + + def count + if RUBY_PLATFORM == 'java' + count_via_java_runtime + else + case RbConfig::CONFIG['host_os'] + when /linux/ + count_via_cpuinfo + when /darwin|bsd/ + count_via_sysctl + when /mswin|mingw/ + count_via_win32 + else + # Try everything + count_via_win32 || + count_via_sysctl || + count_via_cpuinfo + end + end + end + + def count_via_java_runtime + Java::Java.lang.Runtime.getRuntime.availableProcessors + rescue StandardError + nil + end + + def count_via_win32 + require 'win32ole' + wmi = WIN32OLE.connect("winmgmts://") + cpu = wmi.ExecQuery("select NumberOfCores from Win32_Processor") # TODO count hyper-threaded in this + cpu.to_enum.first.NumberOfCores + rescue StandardError, LoadError + nil + end + + def count_via_cpuinfo + open('/proc/cpuinfo') { |f| f.readlines }.grep(/processor/).size + rescue StandardError + nil + end + + def count_via_sysctl + run 'sysctl', '-n', 'hw.ncpu' + end + + def run(command, *args) + cmd = resolve_command(command) + if cmd + IO.popen [cmd, *args] do |io| + io.read.to_i + end + else + nil + end + end + + def resolve_command(command) + look_for_command("/usr/sbin", command) || + look_for_command("/sbin", command) || + in_path_command(command) + end + + def look_for_command(dir, command) + path = File.join(dir, command) + File.exist?(path) ? path : nil + end + + def in_path_command(command) + IO.popen ['which', command] do |io| + io.eof? ? nil : command + end + end + end; +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/default_loader.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/default_loader.rb new file mode 100644 index 00000000..d3b4650d --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/default_loader.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true +module Rake + + # Default Rakefile loader used by +import+. + class DefaultLoader + + ## + # Loads a rakefile into the current application from +fn+ + + def load(fn) + Rake.load_rakefile(File.expand_path(fn)) + end + end + +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/dsl_definition.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/dsl_definition.rb new file mode 100644 index 00000000..c8046402 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/dsl_definition.rb @@ -0,0 +1,195 @@ +# frozen_string_literal: true +# Rake DSL functions. +require "rake/file_utils_ext" + +module Rake + + ## + # DSL is a module that provides #task, #desc, #namespace, etc. Use this + # when you'd like to use rake outside the top level scope. + # + # For a Rakefile you run from the command line this module is automatically + # included. + + module DSL + + #-- + # Include the FileUtils file manipulation functions in the top + # level module, but mark them private so that they don't + # unintentionally define methods on other objects. + #++ + + include FileUtilsExt + private(*FileUtils.instance_methods(false)) + private(*FileUtilsExt.instance_methods(false)) + + private + + # :call-seq: + # task(task_name) + # task(task_name: dependencies) + # task(task_name, arguments => dependencies) + # + # Declare a basic task. The +task_name+ is always the first argument. If + # the task name contains a ":" it is defined in that namespace. + # + # The +dependencies+ may be a single task name or an Array of task names. + # The +argument+ (a single name) or +arguments+ (an Array of names) define + # the arguments provided to the task. + # + # The task, argument and dependency names may be either symbols or + # strings. + # + # A task with a single dependency: + # + # task clobber: %w[clean] do + # rm_rf "html" + # end + # + # A task with an argument and a dependency: + # + # task :package, [:version] => :test do |t, args| + # # ... + # end + # + # To invoke this task from the command line: + # + # $ rake package[1.2.3] + # + def task(*args, &block) # :doc: + Rake::Task.define_task(*args, &block) + end + + # Declare a file task. + # + # Example: + # file "config.cfg" => ["config.template"] do + # open("config.cfg", "w") do |outfile| + # open("config.template") do |infile| + # while line = infile.gets + # outfile.puts line + # end + # end + # end + # end + # + def file(*args, &block) # :doc: + Rake::FileTask.define_task(*args, &block) + end + + # Declare a file creation task. + # (Mainly used for the directory command). + def file_create(*args, &block) + Rake::FileCreationTask.define_task(*args, &block) + end + + # Declare a set of files tasks to create the given directories on + # demand. + # + # Example: + # directory "testdata/doc" + # + def directory(*args, &block) # :doc: + result = file_create(*args, &block) + dir, _ = *Rake.application.resolve_args(args) + dir = Rake.from_pathname(dir) + Rake.each_dir_parent(dir) do |d| + file_create d do |t| + mkdir_p t.name unless File.exist?(t.name) + end + end + result + end + + # Declare a task that performs its prerequisites in + # parallel. Multitasks does *not* guarantee that its prerequisites + # will execute in any given order (which is obvious when you think + # about it) + # + # Example: + # multitask deploy: %w[deploy_gem deploy_rdoc] + # + def multitask(*args, &block) # :doc: + Rake::MultiTask.define_task(*args, &block) + end + + # Create a new rake namespace and use it for evaluating the given + # block. Returns a NameSpace object that can be used to lookup + # tasks defined in the namespace. + # + # Example: + # + # ns = namespace "nested" do + # # the "nested:run" task + # task :run + # end + # task_run = ns[:run] # find :run in the given namespace. + # + # Tasks can also be defined in a namespace by using a ":" in the task + # name: + # + # task "nested:test" do + # # ... + # end + # + def namespace(name=nil, &block) # :doc: + name = name.to_s if name.kind_of?(Symbol) + name = name.to_str if name.respond_to?(:to_str) + unless name.kind_of?(String) || name.nil? + raise ArgumentError, "Expected a String or Symbol for a namespace name" + end + Rake.application.in_namespace(name, &block) + end + + # Declare a rule for auto-tasks. + # + # Example: + # rule '.o' => '.c' do |t| + # sh 'cc', '-o', t.name, t.source + # end + # + def rule(*args, &block) # :doc: + Rake::Task.create_rule(*args, &block) + end + + # Describes the next rake task. Duplicate descriptions are discarded. + # Descriptions are shown with rake -T (up to the first + # sentence) and rake -D (the entire description). + # + # Example: + # desc "Run the Unit Tests" + # task test: [:build] + # # ... run tests + # end + # + def desc(description) # :doc: + Rake.application.last_description = description + end + + # Import the partial Rakefiles +fn+. Imported files are loaded + # _after_ the current file is completely loaded. This allows the + # import statement to appear anywhere in the importing file, and yet + # allowing the imported files to depend on objects defined in the + # importing file. + # + # A common use of the import statement is to include files + # containing dependency declarations. + # + # See also the --rakelibdir command line option. + # + # Example: + # import ".depend", "my_rules" + # + def import(*fns) # :doc: + fns.each do |fn| + Rake.application.add_import(fn) + end + end + end + extend FileUtilsExt +end + +# Extend the main object with the DSL commands. This allows top-level +# calls to task, etc. to work from a Rakefile without polluting the +# object inheritance tree. +self.extend Rake::DSL diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/early_time.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/early_time.rb new file mode 100644 index 00000000..80cc6bfa --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/early_time.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true +module Rake + + # EarlyTime is a fake timestamp that occurs _before_ any other time value. + class EarlyTime + include Comparable + include Singleton + + ## + # The EarlyTime always comes before +other+! + + def <=>(other) + -1 + end + + def to_s # :nodoc: + "" + end + end + + EARLY = EarlyTime.instance +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/ext/core.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/ext/core.rb new file mode 100644 index 00000000..226f2125 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/ext/core.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true +class Module + # Check for an existing method in the current class before extending. If + # the method already exists, then a warning is printed and the extension is + # not added. Otherwise the block is yielded and any definitions in the + # block will take effect. + # + # Usage: + # + # class String + # rake_extension("xyz") do + # def xyz + # ... + # end + # end + # end + # + def rake_extension(method) # :nodoc: + if method_defined?(method) + $stderr.puts "WARNING: Possible conflict with Rake extension: " + + "#{self}##{method} already exists" + else + yield + end + end +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/ext/string.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/ext/string.rb new file mode 100644 index 00000000..c70236ae --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/ext/string.rb @@ -0,0 +1,176 @@ +# frozen_string_literal: true +require "rake/ext/core" + +class String + + rake_extension("ext") do + # Replace the file extension with +newext+. If there is no extension on + # the string, append the new extension to the end. If the new extension + # is not given, or is the empty string, remove any existing extension. + # + # +ext+ is a user added method for the String class. + # + # This String extension comes from Rake + def ext(newext="") + return self.dup if [".", ".."].include? self + if newext != "" + newext = "." + newext unless newext =~ /^\./ + end + self.chomp(File.extname(self)) << newext + end + end + + rake_extension("pathmap") do + # Explode a path into individual components. Used by +pathmap+. + # + # This String extension comes from Rake + def pathmap_explode + head, tail = File.split(self) + return [self] if head == self + return [tail] if head == "." || tail == "/" + return [head, tail] if head == "/" + return head.pathmap_explode + [tail] + end + protected :pathmap_explode + + # Extract a partial path from the path. Include +n+ directories from the + # front end (left hand side) if +n+ is positive. Include |+n+| + # directories from the back end (right hand side) if +n+ is negative. + # + # This String extension comes from Rake + def pathmap_partial(n) + dirs = File.dirname(self).pathmap_explode + partial_dirs = + if n > 0 + dirs[0...n] + elsif n < 0 + dirs.reverse[0...-n].reverse + else + "." + end + File.join(partial_dirs) + end + protected :pathmap_partial + + # Perform the pathmap replacement operations on the given path. The + # patterns take the form 'pat1,rep1;pat2,rep2...'. + # + # This String extension comes from Rake + def pathmap_replace(patterns, &block) + result = self + patterns.split(";").each do |pair| + pattern, replacement = pair.split(",") + pattern = Regexp.new(pattern) + if replacement == "*" && block_given? + result = result.sub(pattern, &block) + elsif replacement + result = result.sub(pattern, replacement) + else + result = result.sub(pattern, "") + end + end + result + end + protected :pathmap_replace + + # Map the path according to the given specification. The specification + # controls the details of the mapping. The following special patterns are + # recognized: + # + # %p :: The complete path. + # %f :: The base file name of the path, with its file extension, + # but without any directories. + # %n :: The file name of the path without its file extension. + # %d :: The directory list of the path. + # %x :: The file extension of the path. An empty string if there + # is no extension. + # %X :: Everything *but* the file extension. + # %s :: The alternate file separator if defined, otherwise use # + # the standard file separator. + # %% :: A percent sign. + # + # The %d specifier can also have a numeric prefix (e.g. '%2d'). + # If the number is positive, only return (up to) +n+ directories in the + # path, starting from the left hand side. If +n+ is negative, return (up + # to) +n+ directories from the right hand side of the path. + # + # Examples: + # + # 'a/b/c/d/file.txt'.pathmap("%2d") => 'a/b' + # 'a/b/c/d/file.txt'.pathmap("%-2d") => 'c/d' + # + # Also the %d, %p, %f, %n, + # %x, and %X operators can take a pattern/replacement + # argument to perform simple string substitutions on a particular part of + # the path. The pattern and replacement are separated by a comma and are + # enclosed by curly braces. The replacement spec comes after the % + # character but before the operator letter. (e.g. "%{old,new}d"). + # Multiple replacement specs should be separated by semi-colons (e.g. + # "%{old,new;src,bin}d"). + # + # Regular expressions may be used for the pattern, and back refs may be + # used in the replacement text. Curly braces, commas and semi-colons are + # excluded from both the pattern and replacement text (let's keep parsing + # reasonable). + # + # For example: + # + # "src/org/onestepback/proj/A.java".pathmap("%{^src,class}X.class") + # + # returns: + # + # "class/org/onestepback/proj/A.class" + # + # If the replacement text is '*', then a block may be provided to perform + # some arbitrary calculation for the replacement. + # + # For example: + # + # "/path/to/file.TXT".pathmap("%X%{.*,*}x") { |ext| + # ext.downcase + # } + # + # Returns: + # + # "/path/to/file.txt" + # + # This String extension comes from Rake + def pathmap(spec=nil, &block) + return self if spec.nil? + result = "".dup + spec.scan(/%\{[^}]*\}-?\d*[sdpfnxX%]|%-?\d+d|%.|[^%]+/) do |frag| + case frag + when "%f" + result << File.basename(self) + when "%n" + result << File.basename(self).ext + when "%d" + result << File.dirname(self) + when "%x" + result << File.extname(self) + when "%X" + result << self.ext + when "%p" + result << self + when "%s" + result << (File::ALT_SEPARATOR || File::SEPARATOR) + when "%-" + # do nothing + when "%%" + result << "%" + when /%(-?\d+)d/ + result << pathmap_partial($1.to_i) + when /^%\{([^}]*)\}(\d*[dpfnxX])/ + patterns, operator = $1, $2 + result << pathmap("%" + operator).pathmap_replace(patterns, &block) + when /^%/ + fail ArgumentError, "Unknown pathmap specifier #{frag} in '#{spec}'" + else + result << frag + end + end + result + end + end + +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/file_creation_task.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/file_creation_task.rb new file mode 100644 index 00000000..5a4c6849 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/file_creation_task.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true +require "rake/file_task" +require "rake/early_time" + +module Rake + + # A FileCreationTask is a file task that when used as a dependency will be + # needed if and only if the file has not been created. Once created, it is + # not re-triggered if any of its dependencies are newer, nor does trigger + # any rebuilds of tasks that depend on it whenever it is updated. + # + class FileCreationTask < FileTask + # Is this file task needed? Yes if it doesn't exist. + def needed? + !File.exist?(name) + end + + # Time stamp for file creation task. This time stamp is earlier + # than any other time stamp. + def timestamp + Rake::EARLY + end + end + +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/file_list.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/file_list.rb new file mode 100644 index 00000000..22c339f2 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/file_list.rb @@ -0,0 +1,435 @@ +# frozen_string_literal: true +require "rake/cloneable" +require "rake/file_utils_ext" +require "rake/ext/string" + +module Rake + + ## + # A FileList is essentially an array with a few helper methods defined to + # make file manipulation a bit easier. + # + # FileLists are lazy. When given a list of glob patterns for possible files + # to be included in the file list, instead of searching the file structures + # to find the files, a FileList holds the pattern for latter use. + # + # This allows us to define a number of FileList to match any number of + # files, but only search out the actual files when then FileList itself is + # actually used. The key is that the first time an element of the + # FileList/Array is requested, the pending patterns are resolved into a real + # list of file names. + # + class FileList + + include Cloneable + + # == Method Delegation + # + # The lazy evaluation magic of FileLists happens by implementing all the + # array specific methods to call +resolve+ before delegating the heavy + # lifting to an embedded array object (@items). + # + # In addition, there are two kinds of delegation calls. The regular kind + # delegates to the @items array and returns the result directly. Well, + # almost directly. It checks if the returned value is the @items object + # itself, and if so will return the FileList object instead. + # + # The second kind of delegation call is used in methods that normally + # return a new Array object. We want to capture the return value of these + # methods and wrap them in a new FileList object. We enumerate these + # methods in the +SPECIAL_RETURN+ list below. + + # List of array methods (that are not in +Object+) that need to be + # delegated. + ARRAY_METHODS = (Array.instance_methods - Object.instance_methods).map(&:to_s) + + # List of additional methods that must be delegated. + MUST_DEFINE = %w[inspect <=>] + + # List of methods that should not be delegated here (we define special + # versions of them explicitly below). + MUST_NOT_DEFINE = %w[to_a to_ary partition * <<] + + # List of delegated methods that return new array values which need + # wrapping. + SPECIAL_RETURN = %w[ + map collect sort sort_by select find_all reject grep + compact flatten uniq values_at + + - & | + ] + + DELEGATING_METHODS = (ARRAY_METHODS + MUST_DEFINE - MUST_NOT_DEFINE).map(&:to_s).sort.uniq + + # Now do the delegation. + DELEGATING_METHODS.each do |sym| + if SPECIAL_RETURN.include?(sym) + ln = __LINE__ + 1 + class_eval %{ + def #{sym}(*args, &block) + resolve + result = @items.send(:#{sym}, *args, &block) + self.class.new.import(result) + end + }, __FILE__, ln + else + ln = __LINE__ + 1 + class_eval %{ + def #{sym}(*args, &block) + resolve + result = @items.send(:#{sym}, *args, &block) + result.object_id == @items.object_id ? self : result + end + }, __FILE__, ln + end + end + + GLOB_PATTERN = %r{[*?\[\{]} + + # Create a file list from the globbable patterns given. If you wish to + # perform multiple includes or excludes at object build time, use the + # "yield self" pattern. + # + # Example: + # file_list = FileList.new('lib/**/*.rb', 'test/test*.rb') + # + # pkg_files = FileList.new('lib/**/*') do |fl| + # fl.exclude(/\bCVS\b/) + # end + # + def initialize(*patterns) + @pending_add = [] + @pending = false + @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup + @exclude_procs = DEFAULT_IGNORE_PROCS.dup + @items = [] + patterns.each { |pattern| include(pattern) } + yield self if block_given? + end + + # Add file names defined by glob patterns to the file list. If an array + # is given, add each element of the array. + # + # Example: + # file_list.include("*.java", "*.cfg") + # file_list.include %w( math.c lib.h *.o ) + # + def include(*filenames) + # TODO: check for pending + filenames.each do |fn| + if fn.respond_to? :to_ary + include(*fn.to_ary) + else + @pending_add << Rake.from_pathname(fn) + end + end + @pending = true + self + end + alias :add :include + + # Register a list of file name patterns that should be excluded from the + # list. Patterns may be regular expressions, glob patterns or regular + # strings. In addition, a block given to exclude will remove entries that + # return true when given to the block. + # + # Note that glob patterns are expanded against the file system. If a file + # is explicitly added to a file list, but does not exist in the file + # system, then an glob pattern in the exclude list will not exclude the + # file. + # + # Examples: + # FileList['a.c', 'b.c'].exclude("a.c") => ['b.c'] + # FileList['a.c', 'b.c'].exclude(/^a/) => ['b.c'] + # + # If "a.c" is a file, then ... + # FileList['a.c', 'b.c'].exclude("a.*") => ['b.c'] + # + # If "a.c" is not a file, then ... + # FileList['a.c', 'b.c'].exclude("a.*") => ['a.c', 'b.c'] + # + def exclude(*patterns, &block) + patterns.each do |pat| + if pat.respond_to? :to_ary + exclude(*pat.to_ary) + else + @exclude_patterns << Rake.from_pathname(pat) + end + end + @exclude_procs << block if block_given? + resolve_exclude unless @pending + self + end + + # Clear all the exclude patterns so that we exclude nothing. + def clear_exclude + @exclude_patterns = [] + @exclude_procs = [] + self + end + + # A FileList is equal through array equality. + def ==(array) + to_ary == array + end + + # Return the internal array object. + def to_a + resolve + @items + end + + # Return the internal array object. + def to_ary + to_a + end + + # Lie about our class. + def is_a?(klass) + klass == Array || super(klass) + end + alias kind_of? is_a? + + # Redefine * to return either a string or a new file list. + def *(other) + result = @items * other + case result + when Array + self.class.new.import(result) + else + result + end + end + + def <<(obj) + resolve + @items << Rake.from_pathname(obj) + self + end + + # Resolve all the pending adds now. + def resolve + if @pending + @pending = false + @pending_add.each do |fn| resolve_add(fn) end + @pending_add = [] + resolve_exclude + end + self + end + + def resolve_add(fn) # :nodoc: + case fn + when GLOB_PATTERN + add_matching(fn) + else + self << fn + end + end + private :resolve_add + + def resolve_exclude # :nodoc: + reject! { |fn| excluded_from_list?(fn) } + self + end + private :resolve_exclude + + # Return a new FileList with the results of running +sub+ against each + # element of the original list. + # + # Example: + # FileList['a.c', 'b.c'].sub(/\.c$/, '.o') => ['a.o', 'b.o'] + # + def sub(pat, rep) + inject(self.class.new) { |res, fn| res << fn.sub(pat, rep) } + end + + # Return a new FileList with the results of running +gsub+ against each + # element of the original list. + # + # Example: + # FileList['lib/test/file', 'x/y'].gsub(/\//, "\\") + # => ['lib\\test\\file', 'x\\y'] + # + def gsub(pat, rep) + inject(self.class.new) { |res, fn| res << fn.gsub(pat, rep) } + end + + # Same as +sub+ except that the original file list is modified. + def sub!(pat, rep) + each_with_index { |fn, i| self[i] = fn.sub(pat, rep) } + self + end + + # Same as +gsub+ except that the original file list is modified. + def gsub!(pat, rep) + each_with_index { |fn, i| self[i] = fn.gsub(pat, rep) } + self + end + + # Apply the pathmap spec to each of the included file names, returning a + # new file list with the modified paths. (See String#pathmap for + # details.) + def pathmap(spec=nil, &block) + collect { |fn| fn.pathmap(spec, &block) } + end + + # Return a new FileList with String#ext method applied to + # each member of the array. + # + # This method is a shortcut for: + # + # array.collect { |item| item.ext(newext) } + # + # +ext+ is a user added method for the Array class. + def ext(newext="") + collect { |fn| fn.ext(newext) } + end + + # Grep each of the files in the filelist using the given pattern. If a + # block is given, call the block on each matching line, passing the file + # name, line number, and the matching line of text. If no block is given, + # a standard emacs style file:linenumber:line message will be printed to + # standard out. Returns the number of matched items. + def egrep(pattern, *options) + matched = 0 + each do |fn| + begin + File.open(fn, "r", *options) do |inf| + count = 0 + inf.each do |line| + count += 1 + if pattern.match(line) + matched += 1 + if block_given? + yield fn, count, line + else + puts "#{fn}:#{count}:#{line}" + end + end + end + end + rescue StandardError => ex + $stderr.puts "Error while processing '#{fn}': #{ex}" + end + end + matched + end + + # Return a new file list that only contains file names from the current + # file list that exist on the file system. + def existing + select { |fn| File.exist?(fn) }.uniq + end + + # Modify the current file list so that it contains only file name that + # exist on the file system. + def existing! + resolve + @items = @items.select { |fn| File.exist?(fn) }.uniq + self + end + + # FileList version of partition. Needed because the nested arrays should + # be FileLists in this version. + def partition(&block) # :nodoc: + resolve + result = @items.partition(&block) + [ + self.class.new.import(result[0]), + self.class.new.import(result[1]), + ] + end + + # Convert a FileList to a string by joining all elements with a space. + def to_s + resolve + self.join(" ") + end + + # Add matching glob patterns. + def add_matching(pattern) + self.class.glob(pattern).each do |fn| + self << fn unless excluded_from_list?(fn) + end + end + private :add_matching + + # Should the given file name be excluded from the list? + # + # NOTE: This method was formerly named "exclude?", but Rails + # introduced an exclude? method as an array method and setup a + # conflict with file list. We renamed the method to avoid + # confusion. If you were using "FileList#exclude?" in your user + # code, you will need to update. + def excluded_from_list?(fn) + return true if @exclude_patterns.any? do |pat| + case pat + when Regexp + fn =~ pat + when GLOB_PATTERN + flags = File::FNM_PATHNAME + # Ruby <= 1.9.3 does not support File::FNM_EXTGLOB + flags |= File::FNM_EXTGLOB if defined? File::FNM_EXTGLOB + File.fnmatch?(pat, fn, flags) + else + fn == pat + end + end + @exclude_procs.any? { |p| p.call(fn) } + end + + DEFAULT_IGNORE_PATTERNS = [ + /(^|[\/\\])CVS([\/\\]|$)/, + /(^|[\/\\])\.svn([\/\\]|$)/, + /\.bak$/, + /~$/ + ] + DEFAULT_IGNORE_PROCS = [ + proc { |fn| fn =~ /(^|[\/\\])core$/ && !File.directory?(fn) } + ] + + def import(array) # :nodoc: + @items = array + self + end + + class << self + # Create a new file list including the files listed. Similar to: + # + # FileList.new(*args) + def [](*args) + new(*args) + end + + # Get a sorted list of files matching the pattern. This method + # should be preferred to Dir[pattern] and Dir.glob(pattern) because + # the files returned are guaranteed to be sorted. + def glob(pattern, *args) + Dir.glob(pattern, *args).sort + end + end + end +end + +module Rake + class << self + + # Yield each file or directory component. + def each_dir_parent(dir) # :nodoc: + old_length = nil + while dir != "." && dir.length != old_length + yield(dir) + old_length = dir.length + dir = File.dirname(dir) + end + end + + # Convert Pathname and Pathname-like objects to strings; + # leave everything else alone + def from_pathname(path) # :nodoc: + path = path.to_path if path.respond_to?(:to_path) + path = path.to_str if path.respond_to?(:to_str) + path + end + end +end # module Rake diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/file_task.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/file_task.rb new file mode 100644 index 00000000..db790e39 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/file_task.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true +require "rake/task" +require "rake/early_time" + +module Rake + + # A FileTask is a task that includes time based dependencies. If any of a + # FileTask's prerequisites have a timestamp that is later than the file + # represented by this task, then the file must be rebuilt (using the + # supplied actions). + # + class FileTask < Task + + # Is this file task needed? Yes if it doesn't exist, or if its time stamp + # is out of date. + def needed? + !File.exist?(name) || out_of_date?(timestamp) || @application.options.build_all + end + + # Time stamp for file task. + def timestamp + if File.exist?(name) + File.mtime(name.to_s) + else + Rake::LATE + end + end + + private + + # Are there any prerequisites with a later time than the given time stamp? + def out_of_date?(stamp) + all_prerequisite_tasks.any? { |prereq| + prereq_task = application[prereq, @scope] + if prereq_task.instance_of?(Rake::FileTask) + prereq_task.timestamp > stamp || @application.options.build_all + else + prereq_task.timestamp > stamp + end + } + end + + # ---------------------------------------------------------------- + # Task class methods. + # + class << self + # Apply the scope to the task name according to the rules for this kind + # of task. File based tasks ignore the scope when creating the name. + def scope_name(scope, task_name) + Rake.from_pathname(task_name) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/file_utils.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/file_utils.rb new file mode 100644 index 00000000..e979eedb --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/file_utils.rb @@ -0,0 +1,134 @@ +# frozen_string_literal: true +require "rbconfig" +require "fileutils" + +#-- +# This a FileUtils extension that defines several additional commands to be +# added to the FileUtils utility functions. +module FileUtils + # Path to the currently running Ruby program + RUBY = ENV["RUBY"] || File.join( + RbConfig::CONFIG["bindir"], + RbConfig::CONFIG["ruby_install_name"] + RbConfig::CONFIG["EXEEXT"]). + sub(/.*\s.*/m, '"\&"') + + # Run the system command +cmd+. If multiple arguments are given the command + # is run directly (without the shell, same semantics as Kernel::exec and + # Kernel::system). + # + # It is recommended you use the multiple argument form over interpolating + # user input for both usability and security reasons. With the multiple + # argument form you can easily process files with spaces or other shell + # reserved characters in them. With the multiple argument form your rake + # tasks are not vulnerable to users providing an argument like + # ; rm # -rf /. + # + # If a block is given, upon command completion the block is called with an + # OK flag (true on a zero exit status) and a Process::Status object. + # Without a block a RuntimeError is raised when the command exits non-zero. + # + # Examples: + # + # sh 'ls -ltr' + # + # sh 'ls', 'file with spaces' + # + # # check exit status after command runs + # sh %{grep pattern file} do |ok, res| + # if !ok + # puts "pattern not found (status = #{res.exitstatus})" + # end + # end + # + def sh(*cmd, &block) + options = (Hash === cmd.last) ? cmd.pop : {} + shell_runner = block_given? ? block : create_shell_runner(cmd) + + set_verbose_option(options) + verbose = options.delete :verbose + noop = options.delete(:noop) || Rake::FileUtilsExt.nowrite_flag + + Rake.rake_output_message sh_show_command cmd if verbose + + unless noop + res = (Hash === cmd.last) ? system(*cmd) : system(*cmd, options) + status = $? + status = Rake::PseudoStatus.new(1) if !res && status.nil? + shell_runner.call(res, status) + end + end + + def create_shell_runner(cmd) # :nodoc: + show_command = sh_show_command cmd + show_command = show_command[0, 42] + "..." unless $trace + + lambda do |ok, status| + ok or + fail "Command failed with status (#{status.exitstatus}): " + + "[#{show_command}]" + end + end + private :create_shell_runner + + def sh_show_command(cmd) # :nodoc: + cmd = cmd.dup + + if Hash === cmd.first + env = cmd.first + env = env.map { |name, value| "#{name}=#{value}" }.join " " + cmd[0] = env + end + + cmd.join " " + end + private :sh_show_command + + def set_verbose_option(options) # :nodoc: + unless options.key? :verbose + options[:verbose] = + (Rake::FileUtilsExt.verbose_flag == Rake::FileUtilsExt::DEFAULT) || + Rake::FileUtilsExt.verbose_flag + end + end + private :set_verbose_option + + # Run a Ruby interpreter with the given arguments. + # + # Example: + # ruby %{-pe '$_.upcase!' 1 + sh(RUBY, *args, **options, &block) + else + sh("#{RUBY} #{args.first}", **options, &block) + end + end + + LN_SUPPORTED = [true] + + # Attempt to do a normal file link, but fall back to a copy if the link + # fails. + def safe_ln(*args, **options) + if LN_SUPPORTED[0] + begin + return options.empty? ? ln(*args) : ln(*args, **options) + rescue StandardError, NotImplementedError + LN_SUPPORTED[0] = false + end + end + options.empty? ? cp(*args) : cp(*args, **options) + end + + # Split a file path into individual directory names. + # + # Example: + # split_all("a/b/c") => ['a', 'b', 'c'] + # + def split_all(path) + head, tail = File.split(path) + return [tail] if head == "." || tail == "/" + return [head, tail] if head == "/" + return split_all(head) + [tail] + end +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/file_utils_ext.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/file_utils_ext.rb new file mode 100644 index 00000000..e91ad595 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/file_utils_ext.rb @@ -0,0 +1,134 @@ +# frozen_string_literal: true +require "rake/file_utils" + +module Rake + # + # FileUtilsExt provides a custom version of the FileUtils methods + # that respond to the verbose and nowrite + # commands. + # + module FileUtilsExt + include FileUtils + + class << self + attr_accessor :verbose_flag, :nowrite_flag + end + + DEFAULT = Object.new + + FileUtilsExt.verbose_flag = DEFAULT + FileUtilsExt.nowrite_flag = false + + FileUtils.commands.each do |name| + opts = FileUtils.options_of name + default_options = [] + if opts.include?("verbose") + default_options << "verbose: FileUtilsExt.verbose_flag" + end + if opts.include?("noop") + default_options << "noop: FileUtilsExt.nowrite_flag" + end + + next if default_options.empty? + module_eval(<<-EOS, __FILE__, __LINE__ + 1) + def #{name}(*args, **options, &block) + super(*args, + #{default_options.join(', ')}, + **options, &block) + end + EOS + end + + # Get/set the verbose flag controlling output from the FileUtils + # utilities. If verbose is true, then the utility method is + # echoed to standard output. + # + # Examples: + # verbose # return the current value of the + # # verbose flag + # verbose(v) # set the verbose flag to _v_. + # verbose(v) { code } # Execute code with the verbose flag set + # # temporarily to _v_. Return to the + # # original value when code is done. + def verbose(value=nil) + oldvalue = FileUtilsExt.verbose_flag + FileUtilsExt.verbose_flag = value unless value.nil? + if block_given? + begin + yield + ensure + FileUtilsExt.verbose_flag = oldvalue + end + end + FileUtilsExt.verbose_flag + end + + # Get/set the nowrite flag controlling output from the FileUtils + # utilities. If verbose is true, then the utility method is + # echoed to standard output. + # + # Examples: + # nowrite # return the current value of the + # # nowrite flag + # nowrite(v) # set the nowrite flag to _v_. + # nowrite(v) { code } # Execute code with the nowrite flag set + # # temporarily to _v_. Return to the + # # original value when code is done. + def nowrite(value=nil) + oldvalue = FileUtilsExt.nowrite_flag + FileUtilsExt.nowrite_flag = value unless value.nil? + if block_given? + begin + yield + ensure + FileUtilsExt.nowrite_flag = oldvalue + end + end + oldvalue + end + + # Use this function to prevent potentially destructive ruby code + # from running when the :nowrite flag is set. + # + # Example: + # + # when_writing("Building Project") do + # project.build + # end + # + # The following code will build the project under normal + # conditions. If the nowrite(true) flag is set, then the example + # will print: + # + # DRYRUN: Building Project + # + # instead of actually building the project. + # + def when_writing(msg=nil) + if FileUtilsExt.nowrite_flag + $stderr.puts "DRYRUN: #{msg}" if msg + else + yield + end + end + + # Send the message to the default rake output (which is $stderr). + def rake_output_message(message) + $stderr.puts(message) + end + + # Check that the options do not contain options not listed in + # +optdecl+. An ArgumentError exception is thrown if non-declared + # options are found. + def rake_check_options(options, *optdecl) + h = options.dup + optdecl.each do |name| + h.delete name + end + raise ArgumentError, "no such option: #{h.keys.join(' ')}" unless + h.empty? + end + + extend self + end +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/invocation_chain.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/invocation_chain.rb new file mode 100644 index 00000000..44a99549 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/invocation_chain.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true +module Rake + + # InvocationChain tracks the chain of task invocations to detect + # circular dependencies. + class InvocationChain < LinkedList + + # Is the invocation already in the chain? + def member?(invocation) + head == invocation || tail.member?(invocation) + end + + # Append an invocation to the chain of invocations. It is an error + # if the invocation already listed. + def append(invocation) + if member?(invocation) + fail RuntimeError, "Circular dependency detected: #{to_s} => #{invocation}" + end + conj(invocation) + end + + # Convert to string, ie: TOP => invocation => invocation + def to_s + "#{prefix}#{head}" + end + + # Class level append. + def self.append(invocation, chain) + chain.append(invocation) + end + + private + + def prefix + "#{tail} => " + end + + # Null object for an empty chain. + class EmptyInvocationChain < LinkedList::EmptyLinkedList + @parent = InvocationChain + + def member?(obj) + false + end + + def append(invocation) + conj(invocation) + end + + def to_s + "TOP" + end + end + + EMPTY = EmptyInvocationChain.new + end +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/invocation_exception_mixin.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/invocation_exception_mixin.rb new file mode 100644 index 00000000..b0d307a4 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/invocation_exception_mixin.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true +module Rake + module InvocationExceptionMixin + # Return the invocation chain (list of Rake tasks) that were in + # effect when this exception was detected by rake. May be null if + # no tasks were active. + def chain + @rake_invocation_chain ||= nil + end + + # Set the invocation chain in effect when this exception was + # detected. + def chain=(value) + @rake_invocation_chain = value + end + end +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/late_time.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/late_time.rb new file mode 100644 index 00000000..8fe02494 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/late_time.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true +module Rake + # LateTime is a fake timestamp that occurs _after_ any other time value. + class LateTime + include Comparable + include Singleton + + def <=>(other) + 1 + end + + def to_s + "" + end + end + + LATE = LateTime.instance +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/linked_list.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/linked_list.rb new file mode 100644 index 00000000..11fa46f0 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/linked_list.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true +module Rake + + # Polylithic linked list structure used to implement several data + # structures in Rake. + class LinkedList + include Enumerable + attr_reader :head, :tail + + # Polymorphically add a new element to the head of a list. The + # type of head node will be the same list type as the tail. + def conj(item) + self.class.cons(item, self) + end + + # Is the list empty? + # .make guards against a list being empty making any instantiated LinkedList + # object not empty by default + # You should consider overriding this method if you implement your own .make method + def empty? + false + end + + # Lists are structurally equivalent. + def ==(other) + current = self + while !current.empty? && !other.empty? + return false if current.head != other.head + current = current.tail + other = other.tail + end + current.empty? && other.empty? + end + + # Convert to string: LL(item, item...) + def to_s + items = map(&:to_s).join(", ") + "LL(#{items})" + end + + # Same as +to_s+, but with inspected items. + def inspect + items = map(&:inspect).join(", ") + "LL(#{items})" + end + + # For each item in the list. + def each + current = self + while !current.empty? + yield(current.head) + current = current.tail + end + self + end + + # Make a list out of the given arguments. This method is + # polymorphic + def self.make(*args) + # return an EmptyLinkedList if there are no arguments + return empty if !args || args.empty? + + # build a LinkedList by starting at the tail and iterating + # through each argument + # inject takes an EmptyLinkedList to start + args.reverse.inject(empty) do |list, item| + list = cons(item, list) + list # return the newly created list for each item in the block + end + end + + # Cons a new head onto the tail list. + def self.cons(head, tail) + new(head, tail) + end + + # The standard empty list class for the given LinkedList class. + def self.empty + self::EMPTY + end + + protected + + def initialize(head, tail=EMPTY) + @head = head + @tail = tail + end + + # Represent an empty list, using the Null Object Pattern. + # + # When inheriting from the LinkedList class, you should implement + # a type specific Empty class as well. Make sure you set the class + # instance variable @parent to the associated list class (this + # allows conj, cons and make to work polymorphically). + class EmptyLinkedList < LinkedList + @parent = LinkedList + + def initialize + end + + def empty? + true + end + + def self.cons(head, tail) + @parent.cons(head, tail) + end + end + + EMPTY = EmptyLinkedList.new + end +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/loaders/makefile.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/loaders/makefile.rb new file mode 100644 index 00000000..46f4beaa --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/loaders/makefile.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true +module Rake + + # Makefile loader to be used with the import file loader. Use this to + # import dependencies from make dependency tools: + # + # require 'rake/loaders/makefile' + # + # file ".depends.mf" => [SRC_LIST] do |t| + # sh "makedepend -f- -- #{CFLAGS} -- #{t.prerequisites} > #{t.name}" + # end + # + # import ".depends.mf" + # + # See {Importing Dependencies}[link:doc/rakefile_rdoc.html#label-Importing+Dependencies] + # for further details. + + class MakefileLoader + include Rake::DSL + + SPACE_MARK = "\0" # :nodoc: + + # Load the makefile dependencies in +fn+. + def load(fn) # :nodoc: + lines = File.read fn + lines.gsub!(/\\ /, SPACE_MARK) + lines.gsub!(/#[^\n]*\n/m, "") + lines.gsub!(/\\\n/, " ") + lines.each_line do |line| + process_line(line) + end + end + + private + + # Process one logical line of makefile data. + def process_line(line) # :nodoc: + file_tasks, args = line.split(":", 2) + return if args.nil? + dependents = args.split.map { |d| respace(d) } + file_tasks.scan(/\S+/) do |file_task| + file_task = respace(file_task) + file file_task => dependents + end + end + + def respace(str) # :nodoc: + str.tr SPACE_MARK, " " + end + end + + # Install the handler + Rake.application.add_loader("mf", MakefileLoader.new) +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/multi_task.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/multi_task.rb new file mode 100644 index 00000000..3ae363cb --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/multi_task.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true +module Rake + + # Same as a regular task, but the immediate prerequisites are done in + # parallel using Ruby threads. + # + class MultiTask < Task + private + + def invoke_prerequisites(task_args, invocation_chain) # :nodoc: + invoke_prerequisites_concurrently(task_args, invocation_chain) + end + end +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/name_space.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/name_space.rb new file mode 100644 index 00000000..32f8139f --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/name_space.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true +## +# The NameSpace class will lookup task names in the scope defined by a +# +namespace+ command. + +class Rake::NameSpace + + ## + # Create a namespace lookup object using the given task manager + # and the list of scopes. + + def initialize(task_manager, scope_list) + @task_manager = task_manager + @scope = scope_list.dup + end + + ## + # Lookup a task named +name+ in the namespace. + + def [](name) + @task_manager.lookup(name, @scope) + end + + ## + # The scope of the namespace (a LinkedList) + + def scope + @scope.dup + end + + ## + # Return the list of tasks defined in this and nested namespaces. + + def tasks + @task_manager.tasks_in_scope(@scope) + end + +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/packagetask.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/packagetask.rb new file mode 100644 index 00000000..aeff81c2 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/packagetask.rb @@ -0,0 +1,222 @@ +# frozen_string_literal: true +# Define a package task library to aid in the definition of +# redistributable package files. + +require "rake" +require "rake/tasklib" + +module Rake + + # Create a packaging task that will package the project into + # distributable files (e.g zip archive or tar files). + # + # The PackageTask will create the following targets: + # + # +:package+ :: + # Create all the requested package files. + # + # +:clobber_package+ :: + # Delete all the package files. This target is automatically + # added to the main clobber target. + # + # +:repackage+ :: + # Rebuild the package files from scratch, even if they are not out + # of date. + # + # "package_dir/name-version.tgz" :: + # Create a gzipped tar package (if need_tar is true). + # + # "package_dir/name-version.tar.gz" :: + # Create a gzipped tar package (if need_tar_gz is true). + # + # "package_dir/name-version.tar.bz2" :: + # Create a bzip2'd tar package (if need_tar_bz2 is true). + # + # "package_dir/name-version.zip" :: + # Create a zip package archive (if need_zip is true). + # + # Example: + # + # Rake::PackageTask.new("rake", "1.2.3") do |p| + # p.need_tar = true + # p.package_files.include("lib/**/*.rb") + # end + # + class PackageTask < TaskLib + # Name of the package (from the GEM Spec). + attr_accessor :name + + # Version of the package (e.g. '1.3.2'). + attr_accessor :version + + # Directory used to store the package files (default is 'pkg'). + attr_accessor :package_dir + + # True if a gzipped tar file (tgz) should be produced (default is + # false). + attr_accessor :need_tar + + # True if a gzipped tar file (tar.gz) should be produced (default + # is false). + attr_accessor :need_tar_gz + + # True if a bzip2'd tar file (tar.bz2) should be produced (default + # is false). + attr_accessor :need_tar_bz2 + + # True if a xz'd tar file (tar.xz) should be produced (default is false) + attr_accessor :need_tar_xz + + # True if a zip file should be produced (default is false) + attr_accessor :need_zip + + # List of files to be included in the package. + attr_accessor :package_files + + # Tar command for gzipped or bzip2ed archives. The default is 'tar'. + attr_accessor :tar_command + + # Zip command for zipped archives. The default is 'zip'. + attr_accessor :zip_command + + # True if parent directory should be omited (default is false) + attr_accessor :without_parent_dir + + # Create a Package Task with the given name and version. Use +:noversion+ + # as the version to build a package without a version or to provide a + # fully-versioned package name. + + def initialize(name=nil, version=nil) + init(name, version) + yield self if block_given? + define unless name.nil? + end + + # Initialization that bypasses the "yield self" and "define" step. + def init(name, version) + @name = name + @version = version + @package_files = Rake::FileList.new + @package_dir = "pkg" + @need_tar = false + @need_tar_gz = false + @need_tar_bz2 = false + @need_tar_xz = false + @need_zip = false + @tar_command = "tar" + @zip_command = "zip" + @without_parent_dir = false + end + + # Create the tasks defined by this task library. + def define + fail "Version required (or :noversion)" if @version.nil? + @version = nil if :noversion == @version + + desc "Build all the packages" + task :package + + desc "Force a rebuild of the package files" + task repackage: [:clobber_package, :package] + + desc "Remove package products" + task :clobber_package do + rm_r package_dir rescue nil + end + + task clobber: [:clobber_package] + + [ + [need_tar, tgz_file, "z"], + [need_tar_gz, tar_gz_file, "z"], + [need_tar_bz2, tar_bz2_file, "j"], + [need_tar_xz, tar_xz_file, "J"] + ].each do |need, file, flag| + if need + task package: ["#{package_dir}/#{file}"] + file "#{package_dir}/#{file}" => + [package_dir_path] + package_files do + chdir(working_dir) { sh @tar_command, "#{flag}cvf", file, target_dir } + mv "#{package_dir_path}/#{target_dir}", package_dir if without_parent_dir + end + end + end + + if need_zip + task package: ["#{package_dir}/#{zip_file}"] + file "#{package_dir}/#{zip_file}" => + [package_dir_path] + package_files do + chdir(working_dir) { sh @zip_command, "-r", zip_file, target_dir } + mv "#{package_dir_path}/#{zip_file}", package_dir if without_parent_dir + end + end + + directory package_dir_path => @package_files do + @package_files.each do |fn| + f = File.join(package_dir_path, fn) + fdir = File.dirname(f) + mkdir_p(fdir) unless File.exist?(fdir) + if File.directory?(fn) + mkdir_p(f) + else + rm_f f + safe_ln(fn, f) + end + end + end + self + end + + # The name of this package + + def package_name + @version ? "#{@name}-#{@version}" : @name + end + + # The directory this package will be built in + + def package_dir_path + "#{package_dir}/#{package_name}" + end + + # The package name with .tgz added + + def tgz_file + "#{package_name}.tgz" + end + + # The package name with .tar.gz added + + def tar_gz_file + "#{package_name}.tar.gz" + end + + # The package name with .tar.bz2 added + + def tar_bz2_file + "#{package_name}.tar.bz2" + end + + # The package name with .tar.xz added + + def tar_xz_file + "#{package_name}.tar.xz" + end + + # The package name with .zip added + + def zip_file + "#{package_name}.zip" + end + + def working_dir + without_parent_dir ? package_dir_path : package_dir + end + + # target directory relative to working_dir + def target_dir + without_parent_dir ? "." : package_name + end + end + +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/phony.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/phony.rb new file mode 100644 index 00000000..8caa5de1 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/phony.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true +# Defines a :phony task that you can use as a dependency. This allows +# file-based tasks to use non-file-based tasks as prerequisites +# without forcing them to rebuild. +# +# See FileTask#out_of_date? and Task#timestamp for more info. + +require "rake" + +task :phony + +Rake::Task[:phony].tap do |task| + def task.timestamp # :nodoc: + Time.at 0 + end +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/private_reader.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/private_reader.rb new file mode 100644 index 00000000..2815ce64 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/private_reader.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true +module Rake + + # Include PrivateReader to use +private_reader+. + module PrivateReader # :nodoc: all + + def self.included(base) + base.extend(ClassMethods) + end + + module ClassMethods + + # Declare a list of private accessors + def private_reader(*names) + attr_reader(*names) + private(*names) + end + end + + end +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/promise.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/promise.rb new file mode 100644 index 00000000..f45af4f3 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/promise.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true +module Rake + + # A Promise object represents a promise to do work (a chore) in the + # future. The promise is created with a block and a list of + # arguments for the block. Calling value will return the value of + # the promised chore. + # + # Used by ThreadPool. + # + class Promise # :nodoc: all + NOT_SET = Object.new.freeze # :nodoc: + + attr_accessor :recorder + + # Create a promise to do the chore specified by the block. + def initialize(args, &block) + @mutex = Mutex.new + @result = NOT_SET + @error = NOT_SET + @args = args + @block = block + end + + # Return the value of this promise. + # + # If the promised chore is not yet complete, then do the work + # synchronously. We will wait. + def value + unless complete? + stat :sleeping_on, item_id: object_id + @mutex.synchronize do + stat :has_lock_on, item_id: object_id + chore + stat :releasing_lock_on, item_id: object_id + end + end + error? ? raise(@error) : @result + end + + # If no one else is working this promise, go ahead and do the chore. + def work + stat :attempting_lock_on, item_id: object_id + if @mutex.try_lock + stat :has_lock_on, item_id: object_id + chore + stat :releasing_lock_on, item_id: object_id + @mutex.unlock + else + stat :bailed_on, item_id: object_id + end + end + + private + + # Perform the chore promised + def chore + if complete? + stat :found_completed, item_id: object_id + return + end + stat :will_execute, item_id: object_id + begin + @result = @block.call(*@args) + rescue Exception => e + @error = e + end + stat :did_execute, item_id: object_id + discard + end + + # Do we have a result for the promise + def result? + !@result.equal?(NOT_SET) + end + + # Did the promise throw an error + def error? + !@error.equal?(NOT_SET) + end + + # Are we done with the promise + def complete? + result? || error? + end + + # free up these items for the GC + def discard + @args = nil + @block = nil + end + + # Record execution statistics if there is a recorder + def stat(*args) + @recorder.call(*args) if @recorder + end + + end + +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/pseudo_status.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/pseudo_status.rb new file mode 100644 index 00000000..8b3c9894 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/pseudo_status.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true +module Rake + + ## + # Exit status class for times the system just gives us a nil. + class PseudoStatus # :nodoc: all + attr_reader :exitstatus + + def initialize(code=0) + @exitstatus = code + end + + def to_i + @exitstatus << 8 + end + + def >>(n) + to_i >> n + end + + def stopped? + false + end + + def exited? + true + end + end + +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/rake_module.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/rake_module.rb new file mode 100644 index 00000000..03c29562 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/rake_module.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true +require "rake/application" + +module Rake + + class << self + # Current Rake Application + def application + @application ||= Rake::Application.new + end + + # Set the current Rake application object. + def application=(app) + @application = app + end + + def suggested_thread_count # :nodoc: + @cpu_count ||= Rake::CpuCounter.count + @cpu_count + 4 + end + + # Return the original directory where the Rake application was started. + def original_dir + application.original_dir + end + + # Load a rakefile. + def load_rakefile(path) + load(path) + end + + # Add files to the rakelib list + def add_rakelib(*files) + application.options.rakelib ||= [] + application.options.rakelib.concat(files) + end + + # Make +block_application+ the default rake application inside a block so + # you can load rakefiles into a different application. + # + # This is useful when you want to run rake tasks inside a library without + # running rake in a sub-shell. + # + # Example: + # + # Dir.chdir 'other/directory' + # + # other_rake = Rake.with_application do |rake| + # rake.load_rakefile + # end + # + # puts other_rake.tasks + + def with_application(block_application = Rake::Application.new) + orig_application = Rake.application + + Rake.application = block_application + + yield block_application + + block_application + ensure + Rake.application = orig_application + end + end + +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/rake_test_loader.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/rake_test_loader.rb new file mode 100644 index 00000000..f0f7772b --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/rake_test_loader.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true +require "rake" + +# Load the test files from the command line. +argv = ARGV.select do |argument| + begin + case argument + when /^-/ then + argument + when /\*/ then + FileList[argument].to_a.each do |file| + require File.expand_path file + end + + false + else + require File.expand_path argument + + false + end + rescue LoadError => e + raise unless e.path + abort "\nFile does not exist: #{e.path}\n\n" + end +end + +ARGV.replace argv diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/rule_recursion_overflow_error.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/rule_recursion_overflow_error.rb new file mode 100644 index 00000000..a51e7748 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/rule_recursion_overflow_error.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true +module Rake + + # Error indicating a recursion overflow error in task selection. + class RuleRecursionOverflowError < StandardError + def initialize(*args) + super + @targets = [] + end + + def add_target(target) + @targets << target + end + + def message + super + ": [" + @targets.reverse.join(" => ") + "]" + end + end + +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/scope.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/scope.rb new file mode 100644 index 00000000..fc1eb6c3 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/scope.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true +module Rake + class Scope < LinkedList # :nodoc: all + + # Path for the scope. + def path + map(&:to_s).reverse.join(":") + end + + # Path for the scope + the named path. + def path_with_task_name(task_name) + "#{path}:#{task_name}" + end + + # Trim +n+ innermost scope levels from the scope. In no case will + # this trim beyond the toplevel scope. + def trim(n) + result = self + while n > 0 && !result.empty? + result = result.tail + n -= 1 + end + result + end + + # Scope lists always end with an EmptyScope object. See Null + # Object Pattern) + class EmptyScope < EmptyLinkedList + @parent = Scope + + def path + "" + end + + def path_with_task_name(task_name) + task_name + end + end + + # Singleton null object for an empty scope. + EMPTY = EmptyScope.new + end +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/task.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/task.rb new file mode 100644 index 00000000..80824f37 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/task.rb @@ -0,0 +1,433 @@ +# frozen_string_literal: true +require "rake/invocation_exception_mixin" + +module Rake + + ## + # A Task is the basic unit of work in a Rakefile. Tasks have associated + # actions (possibly more than one) and a list of prerequisites. When + # invoked, a task will first ensure that all of its prerequisites have an + # opportunity to run and then it will execute its own actions. + # + # Tasks are not usually created directly using the new method, but rather + # use the +file+ and +task+ convenience methods. + # + class Task + # List of prerequisites for a task. + attr_reader :prerequisites + alias prereqs prerequisites + + # List of order only prerequisites for a task. + attr_reader :order_only_prerequisites + + # List of actions attached to a task. + attr_reader :actions + + # Application owning this task. + attr_accessor :application + + # Array of nested namespaces names used for task lookup by this task. + attr_reader :scope + + # File/Line locations of each of the task definitions for this + # task (only valid if the task was defined with the detect + # location option set). + attr_reader :locations + + # Has this task already been invoked? Already invoked tasks + # will be skipped unless you reenable them. + attr_reader :already_invoked + + # Return task name + def to_s + name + end + + def inspect # :nodoc: + "<#{self.class} #{name} => [#{prerequisites.join(', ')}]>" + end + + # List of sources for task. + attr_writer :sources + def sources + if defined?(@sources) + @sources + else + prerequisites + end + end + + # List of prerequisite tasks + def prerequisite_tasks + (prerequisites + order_only_prerequisites).map { |pre| lookup_prerequisite(pre) } + end + + def lookup_prerequisite(prerequisite_name) # :nodoc: + scoped_prerequisite_task = application[prerequisite_name, @scope] + if scoped_prerequisite_task == self + unscoped_prerequisite_task = application[prerequisite_name] + end + unscoped_prerequisite_task || scoped_prerequisite_task + end + private :lookup_prerequisite + + # List of all unique prerequisite tasks including prerequisite tasks' + # prerequisites. + # Includes self when cyclic dependencies are found. + def all_prerequisite_tasks + seen = {} + collect_prerequisites(seen) + seen.values + end + + def collect_prerequisites(seen) # :nodoc: + prerequisite_tasks.each do |pre| + next if seen[pre.name] + seen[pre.name] = pre + pre.collect_prerequisites(seen) + end + end + protected :collect_prerequisites + + # First source from a rule (nil if no sources) + def source + sources.first + end + + # Create a task named +task_name+ with no actions or prerequisites. Use + # +enhance+ to add actions and prerequisites. + def initialize(task_name, app) + @name = task_name.to_s + @prerequisites = [] + @actions = [] + @already_invoked = false + @comments = [] + @lock = Monitor.new + @application = app + @scope = app.current_scope + @arg_names = nil + @locations = [] + @invocation_exception = nil + @order_only_prerequisites = [] + end + + # Enhance a task with prerequisites or actions. Returns self. + def enhance(deps=nil, &block) + @prerequisites |= deps if deps + @actions << block if block_given? + self + end + + # Name of the task, including any namespace qualifiers. + def name + @name.to_s + end + + # Name of task with argument list description. + def name_with_args # :nodoc: + if arg_description + "#{name}#{arg_description}" + else + name + end + end + + # Argument description (nil if none). + def arg_description # :nodoc: + @arg_names ? "[#{arg_names.join(',')}]" : nil + end + + # Name of arguments for this task. + def arg_names + @arg_names || [] + end + + # Reenable the task, allowing its tasks to be executed if the task + # is invoked again. + def reenable + @already_invoked = false + end + + # Clear the existing prerequisites, actions, comments, and arguments of a rake task. + def clear + clear_prerequisites + clear_actions + clear_comments + clear_args + self + end + + # Clear the existing prerequisites of a rake task. + def clear_prerequisites + prerequisites.clear + self + end + + # Clear the existing actions on a rake task. + def clear_actions + actions.clear + self + end + + # Clear the existing comments on a rake task. + def clear_comments + @comments = [] + self + end + + # Clear the existing arguments on a rake task. + def clear_args + @arg_names = nil + self + end + + # Invoke the task if it is needed. Prerequisites are invoked first. + def invoke(*args) + task_args = TaskArguments.new(arg_names, args) + invoke_with_call_chain(task_args, InvocationChain::EMPTY) + end + + # Same as invoke, but explicitly pass a call chain to detect + # circular dependencies. + # + # If multiple tasks depend on this + # one in parallel, they will all fail if the first execution of + # this task fails. + def invoke_with_call_chain(task_args, invocation_chain) + new_chain = Rake::InvocationChain.append(self, invocation_chain) + @lock.synchronize do + begin + if application.options.trace + application.trace "** Invoke #{name} #{format_trace_flags}" + end + + if @already_invoked + if @invocation_exception + if application.options.trace + application.trace "** Previous invocation of #{name} failed #{format_trace_flags}" + end + raise @invocation_exception + else + return + end + end + + @already_invoked = true + + invoke_prerequisites(task_args, new_chain) + execute(task_args) if needed? + rescue Exception => ex + add_chain_to(ex, new_chain) + @invocation_exception = ex + raise ex + end + end + end + protected :invoke_with_call_chain + + def add_chain_to(exception, new_chain) # :nodoc: + exception.extend(InvocationExceptionMixin) unless + exception.respond_to?(:chain) + exception.chain = new_chain if exception.chain.nil? + end + private :add_chain_to + + # Invoke all the prerequisites of a task. + def invoke_prerequisites(task_args, invocation_chain) # :nodoc: + if application.options.always_multitask + invoke_prerequisites_concurrently(task_args, invocation_chain) + else + prerequisite_tasks.each { |p| + prereq_args = task_args.new_scope(p.arg_names) + p.invoke_with_call_chain(prereq_args, invocation_chain) + } + end + end + + # Invoke all the prerequisites of a task in parallel. + def invoke_prerequisites_concurrently(task_args, invocation_chain)# :nodoc: + futures = prerequisite_tasks.map do |p| + prereq_args = task_args.new_scope(p.arg_names) + application.thread_pool.future(p) do |r| + r.invoke_with_call_chain(prereq_args, invocation_chain) + end + end + # Iterate in reverse to improve performance related to thread waiting and switching + futures.reverse_each(&:value) + end + + # Format the trace flags for display. + def format_trace_flags + flags = [] + flags << "first_time" unless @already_invoked + flags << "not_needed" unless needed? + flags.empty? ? "" : "(" + flags.join(", ") + ")" + end + private :format_trace_flags + + # Execute the actions associated with this task. + def execute(args=nil) + args ||= EMPTY_TASK_ARGS + if application.options.dryrun + application.trace "** Execute (dry run) #{name}" + return + end + application.trace "** Execute #{name}" if application.options.trace + application.enhance_with_matching_rule(name) if @actions.empty? + if opts = Hash.try_convert(args) and !opts.empty? + @actions.each { |act| act.call(self, args, **opts)} + else + @actions.each { |act| act.call(self, args)} + end + end + + # Is this task needed? + def needed? + true + end + + # Timestamp for this task. Basic tasks return the current time for their + # time stamp. Other tasks can be more sophisticated. + def timestamp + Time.now + end + + # Add a description to the task. The description can consist of an option + # argument list (enclosed brackets) and an optional comment. + def add_description(description) + return unless description + comment = description.strip + add_comment(comment) if comment && !comment.empty? + end + + def comment=(comment) # :nodoc: + add_comment(comment) + end + + def add_comment(comment) # :nodoc: + return if comment.nil? + @comments << comment unless @comments.include?(comment) + end + private :add_comment + + # Full collection of comments. Multiple comments are separated by + # newlines. + def full_comment + transform_comments("\n") + end + + # First line (or sentence) of all comments. Multiple comments are + # separated by a "/". + def comment + transform_comments(" / ") { |c| first_sentence(c) } + end + + # Transform the list of comments as specified by the block and + # join with the separator. + def transform_comments(separator, &block) + if @comments.empty? + nil + else + block ||= lambda { |c| c } + @comments.map(&block).join(separator) + end + end + private :transform_comments + + # Get the first sentence in a string. The sentence is terminated + # by the first period, exclamation mark, or the end of the line. + # Decimal points do not count as periods. + def first_sentence(string) + string.split(/(?<=\w)(\.|!)[ \t]|(\.$|!)|\n/).first + end + private :first_sentence + + # Set the names of the arguments for this task. +args+ should be + # an array of symbols, one for each argument name. + def set_arg_names(args) + @arg_names = args.map(&:to_sym) + end + + # Return a string describing the internal state of a task. Useful for + # debugging. + def investigation + result = "------------------------------\n".dup + result << "Investigating #{name}\n" + result << "class: #{self.class}\n" + result << "task needed: #{needed?}\n" + result << "timestamp: #{timestamp}\n" + result << "pre-requisites: \n" + prereqs = prerequisite_tasks + prereqs.sort! { |a, b| a.timestamp <=> b.timestamp } + prereqs.each do |p| + result << "--#{p.name} (#{p.timestamp})\n" + end + latest_prereq = prerequisite_tasks.map(&:timestamp).max + result << "latest-prerequisite time: #{latest_prereq}\n" + result << "................................\n\n" + return result + end + + # Format dependencies parameter to pass to task. + def self.format_deps(deps) + deps = [deps] unless deps.respond_to?(:to_ary) + deps.map { |d| Rake.from_pathname(d).to_s } + end + + # Add order only dependencies. + def |(deps) + @order_only_prerequisites |= Task.format_deps(deps) - @prerequisites + self + end + + # ---------------------------------------------------------------- + # Rake Module Methods + # + class << self + + # Clear the task list. This cause rake to immediately forget all the + # tasks that have been assigned. (Normally used in the unit tests.) + def clear + Rake.application.clear + end + + # List of all defined tasks. + def tasks + Rake.application.tasks + end + + # Return a task with the given name. If the task is not currently + # known, try to synthesize one from the defined rules. If no rules are + # found, but an existing file matches the task name, assume it is a file + # task with no dependencies or actions. + def [](task_name) + Rake.application[task_name] + end + + # TRUE if the task name is already defined. + def task_defined?(task_name) + Rake.application.lookup(task_name) != nil + end + + # Define a task given +args+ and an option block. If a rule with the + # given name already exists, the prerequisites and actions are added to + # the existing task. Returns the defined task. + def define_task(*args, &block) + Rake.application.define_task(self, *args, &block) + end + + # Define a rule for synthesizing tasks. + def create_rule(*args, &block) + Rake.application.create_rule(*args, &block) + end + + # Apply the scope to the task name according to the rules for + # this kind of task. Generic tasks will accept the scope as + # part of the name. + def scope_name(scope, task_name) + scope.path_with_task_name(task_name) + end + + end # class << Rake::Task + end # class Rake::Task +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/task_argument_error.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/task_argument_error.rb new file mode 100644 index 00000000..ef20076c --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/task_argument_error.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true +module Rake + + # Error indicating an ill-formed task declaration. + class TaskArgumentError < ArgumentError + end + +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/task_arguments.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/task_arguments.rb new file mode 100644 index 00000000..0d3001af --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/task_arguments.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true +module Rake + + ## + # TaskArguments manage the arguments passed to a task. + # + class TaskArguments + include Enumerable + + # Argument names + attr_reader :names + + # Create a TaskArgument object with a list of argument +names+ and a set + # of associated +values+. +parent+ is the parent argument object. + def initialize(names, values, parent=nil) + @names = names + @parent = parent + @hash = {} + @values = values + names.each_with_index { |name, i| + next if values[i].nil? || values[i] == "" + @hash[name.to_sym] = values[i] + } + end + + # Retrieve the complete array of sequential values + def to_a + @values.dup + end + + # Retrieve the list of values not associated with named arguments + def extras + @values[@names.length..-1] || [] + end + + # Create a new argument scope using the prerequisite argument + # names. + def new_scope(names) + values = names.map { |n| self[n] } + self.class.new(names, values + extras, self) + end + + # Find an argument value by name or index. + def [](index) + lookup(index.to_sym) + end + + # Specify a hash of default values for task arguments. Use the + # defaults only if there is no specific value for the given + # argument. + def with_defaults(defaults) + @hash = defaults.merge(@hash) + end + + # Enumerates the arguments and their values + def each(&block) + @hash.each(&block) + end + + # Extracts the argument values at +keys+ + def values_at(*keys) + keys.map { |k| lookup(k) } + end + + # Returns the value of the given argument via method_missing + def method_missing(sym, *args) + lookup(sym.to_sym) + end + + # Returns a Hash of arguments and their values + def to_hash + @hash.dup + end + + def to_s # :nodoc: + inspect + end + + def inspect # :nodoc: + inspection = @hash.map do |k,v| + "#{k.to_s}: #{v.to_s}" + end.join(", ") + + "#<#{self.class} #{inspection}>" + end + + # Returns true if +key+ is one of the arguments + def has_key?(key) + @hash.has_key?(key) + end + alias key? has_key? + + def fetch(*args, &block) + @hash.fetch(*args, &block) + end + + protected + + def lookup(name) # :nodoc: + if @hash.has_key?(name) + @hash[name] + elsif @parent + @parent.lookup(name) + end + end + end + + EMPTY_TASK_ARGS = TaskArguments.new([], []) # :nodoc: +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/task_manager.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/task_manager.rb new file mode 100644 index 00000000..1d3cb1cf --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/task_manager.rb @@ -0,0 +1,328 @@ +# frozen_string_literal: true +module Rake + + # The TaskManager module is a mixin for managing tasks. + module TaskManager + # Track the last comment made in the Rakefile. + attr_accessor :last_description + + def initialize # :nodoc: + super + @tasks = Hash.new + @rules = Array.new + @scope = Scope.make + @last_description = nil + end + + def create_rule(*args, &block) # :nodoc: + pattern, args, deps, order_only = resolve_args(args) + pattern = Regexp.new(Regexp.quote(pattern) + "$") if String === pattern + @rules << [pattern, args, deps, order_only, block] + end + + def define_task(task_class, *args, &block) # :nodoc: + task_name, arg_names, deps, order_only = resolve_args(args) + + original_scope = @scope + if String === task_name and + not task_class.ancestors.include? Rake::FileTask + task_name, *definition_scope = *(task_name.split(":").reverse) + @scope = Scope.make(*(definition_scope + @scope.to_a)) + end + + task_name = task_class.scope_name(@scope, task_name) + task = intern(task_class, task_name) + task.set_arg_names(arg_names) unless arg_names.empty? + if Rake::TaskManager.record_task_metadata + add_location(task) + task.add_description(get_description(task)) + end + task.enhance(Task.format_deps(deps), &block) + task | order_only unless order_only.nil? + task + ensure + @scope = original_scope + end + + # Lookup a task. Return an existing task if found, otherwise + # create a task of the current type. + def intern(task_class, task_name) + @tasks[task_name.to_s] ||= task_class.new(task_name, self) + end + + # Find a matching task for +task_name+. + def [](task_name, scopes=nil) + task_name = task_name.to_s + self.lookup(task_name, scopes) or + enhance_with_matching_rule(task_name) or + synthesize_file_task(task_name) or + fail generate_message_for_undefined_task(task_name) + end + + def generate_message_for_undefined_task(task_name) + message = "Don't know how to build task '#{task_name}' "\ + "(See the list of available tasks with `#{Rake.application.name} --tasks`)" + message + generate_did_you_mean_suggestions(task_name) + end + + def generate_did_you_mean_suggestions(task_name) + return "" unless defined?(::DidYouMean::SpellChecker) + + suggestions = ::DidYouMean::SpellChecker.new(dictionary: @tasks.keys).correct(task_name.to_s) + if ::DidYouMean.respond_to?(:formatter)# did_you_mean v1.2.0 or later + ::DidYouMean.formatter.message_for(suggestions) + elsif defined?(::DidYouMean::Formatter) # before did_you_mean v1.2.0 + ::DidYouMean::Formatter.new(suggestions).to_s + else + "" + end + end + + def synthesize_file_task(task_name) # :nodoc: + return nil unless File.exist?(task_name) + define_task(Rake::FileTask, task_name) + end + + # Resolve the arguments for a task/rule. Returns a triplet of + # [task_name, arg_name_list, prerequisites]. + def resolve_args(args) + if args.last.is_a?(Hash) + deps = args.pop + resolve_args_with_dependencies(args, deps) + else + resolve_args_without_dependencies(args) + end + end + + # Resolve task arguments for a task or rule when there are no + # dependencies declared. + # + # The patterns recognized by this argument resolving function are: + # + # task :t + # task :t, [:a] + # + def resolve_args_without_dependencies(args) + task_name = args.shift + if args.size == 1 && args.first.respond_to?(:to_ary) + arg_names = args.first.to_ary + else + arg_names = args + end + [task_name, arg_names, [], nil] + end + private :resolve_args_without_dependencies + + # Resolve task arguments for a task or rule when there are + # dependencies declared. + # + # The patterns recognized by this argument resolving function are: + # + # task :t => [:d] + # task :t, [a] => [:d] + # + def resolve_args_with_dependencies(args, hash) # :nodoc: + fail "Task Argument Error" if + hash.size != 1 && + (hash.size != 2 || !hash.key?(:order_only)) + order_only = hash.delete(:order_only) + key, value = hash.map { |k, v| [k, v] }.first + if args.empty? + task_name = key + arg_names = [] + deps = value || [] + else + task_name = args.shift + arg_names = key + deps = value + end + deps = [deps] unless deps.respond_to?(:to_ary) + [task_name, arg_names, deps, order_only] + end + private :resolve_args_with_dependencies + + # If a rule can be found that matches the task name, enhance the + # task with the prerequisites and actions from the rule. Set the + # source attribute of the task appropriately for the rule. Return + # the enhanced task or nil of no rule was found. + def enhance_with_matching_rule(task_name, level=0) + fail Rake::RuleRecursionOverflowError, + "Rule Recursion Too Deep" if level >= 16 + @rules.each do |pattern, args, extensions, order_only, block| + if pattern && pattern.match(task_name) + task = attempt_rule(task_name, pattern, args, extensions, block, level) + task | order_only unless order_only.nil? + return task if task + end + end + nil + rescue Rake::RuleRecursionOverflowError => ex + ex.add_target(task_name) + fail ex + end + + # List of all defined tasks in this application. + def tasks + @tasks.values.sort_by { |t| t.name } + end + + # List of all the tasks defined in the given scope (and its + # sub-scopes). + def tasks_in_scope(scope) + prefix = scope.path + tasks.select { |t| + /^#{prefix}:/ =~ t.name + } + end + + # Clear all tasks in this application. + def clear + @tasks.clear + @rules.clear + end + + # Lookup a task, using scope and the scope hints in the task name. + # This method performs straight lookups without trying to + # synthesize file tasks or rules. Special scope names (e.g. '^') + # are recognized. If no scope argument is supplied, use the + # current scope. Return nil if the task cannot be found. + def lookup(task_name, initial_scope=nil) + initial_scope ||= @scope + task_name = task_name.to_s + if task_name =~ /^rake:/ + scopes = Scope.make + task_name = task_name.sub(/^rake:/, "") + elsif task_name =~ /^(\^+)/ + scopes = initial_scope.trim($1.size) + task_name = task_name.sub(/^(\^+)/, "") + else + scopes = initial_scope + end + lookup_in_scope(task_name, scopes) + end + + # Lookup the task name + def lookup_in_scope(name, scope) + loop do + tn = scope.path_with_task_name(name) + task = @tasks[tn] + return task if task + break if scope.empty? + scope = scope.tail + end + nil + end + private :lookup_in_scope + + # Return the list of scope names currently active in the task + # manager. + def current_scope + @scope + end + + # Evaluate the block in a nested namespace named +name+. Create + # an anonymous namespace if +name+ is nil. + def in_namespace(name) + name ||= generate_name + @scope = Scope.new(name, @scope) + ns = NameSpace.new(self, @scope) + yield(ns) + ns + ensure + @scope = @scope.tail + end + + private + + # Add a location to the locations field of the given task. + def add_location(task) + loc = find_location + task.locations << loc if loc + task + end + + # Find the location that called into the dsl layer. + def find_location + locations = caller + i = 0 + while locations[i] + return locations[i + 1] if locations[i] =~ /rake\/dsl_definition.rb/ + i += 1 + end + nil + end + + # Generate an anonymous namespace name. + def generate_name + @seed ||= 0 + @seed += 1 + "_anon_#{@seed}" + end + + def trace_rule(level, message) # :nodoc: + options.trace_output.puts "#{" " * level}#{message}" if + Rake.application.options.trace_rules + end + + # Attempt to create a rule given the list of prerequisites. + def attempt_rule(task_name, task_pattern, args, extensions, block, level) + sources = make_sources(task_name, task_pattern, extensions) + prereqs = sources.map { |source| + trace_rule level, "Attempting Rule #{task_name} => #{source}" + if File.exist?(source) || Rake::Task.task_defined?(source) + trace_rule level, "(#{task_name} => #{source} ... EXIST)" + source + elsif parent = enhance_with_matching_rule(source, level + 1) + trace_rule level, "(#{task_name} => #{source} ... ENHANCE)" + parent.name + else + trace_rule level, "(#{task_name} => #{source} ... FAIL)" + return nil + end + } + task = FileTask.define_task(task_name, { args => prereqs }, &block) + task.sources = prereqs + task + end + + # Make a list of sources from the list of file name extensions / + # translation procs. + def make_sources(task_name, task_pattern, extensions) + result = extensions.map { |ext| + case ext + when /%/ + task_name.pathmap(ext) + when %r{/} + ext + when /^\./ + source = task_name.sub(task_pattern, ext) + source == ext ? task_name.ext(ext) : source + when String + ext + when Proc, Method + if ext.arity == 1 + ext.call(task_name) + else + ext.call + end + else + fail "Don't know how to handle rule dependent: #{ext.inspect}" + end + } + result.flatten + end + + # Return the current description, clearing it in the process. + def get_description(task) + desc = @last_description + @last_description = nil + desc + end + + class << self + attr_accessor :record_task_metadata # :nodoc: + TaskManager.record_task_metadata = false + end + end + +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/tasklib.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/tasklib.rb new file mode 100644 index 00000000..5354b4f9 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/tasklib.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true +require "rake" + +module Rake + + # Base class for Task Libraries. + class TaskLib + include Cloneable + include Rake::DSL + end + +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/testtask.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/testtask.rb new file mode 100644 index 00000000..53762756 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/testtask.rb @@ -0,0 +1,224 @@ +# frozen_string_literal: true +require "rake" +require "rake/tasklib" + +module Rake + + # Create a task that runs a set of tests. + # + # Example: + # require "rake/testtask" + # + # Rake::TestTask.new do |t| + # t.libs << "test" + # t.test_files = FileList['test/test*.rb'] + # t.verbose = true + # end + # + # If rake is invoked with a "TEST=filename" command line option, + # then the list of test files will be overridden to include only the + # filename specified on the command line. This provides an easy way + # to run just one test. + # + # If rake is invoked with a "TESTOPTS=options" command line option, + # then the given options are passed to the test process after a + # '--'. This allows Test::Unit options to be passed to the test + # suite. + # + # Examples: + # + # rake test # run tests normally + # rake test TEST=just_one_file.rb # run just one test file. + # rake test TESTOPTS="-v" # run in verbose mode + # rake test TESTOPTS="--runner=fox" # use the fox test runner + # + class TestTask < TaskLib + + # Name of test task. (default is :test) + attr_accessor :name + + # List of directories added to $LOAD_PATH before running the + # tests. (default is 'lib') + attr_accessor :libs + + # True if verbose test output desired. (default is false) + attr_accessor :verbose + + # Test options passed to the test suite. An explicit + # TESTOPTS=opts on the command line will override this. (default + # is NONE) + attr_accessor :options + + # Request that the tests be run with the warning flag set. + # E.g. warning=true implies "ruby -w" used to run the tests. + # (default is true) + attr_accessor :warning + + # Glob pattern to match test files. (default is 'test/test*.rb') + attr_accessor :pattern + + # Style of test loader to use. Options are: + # + # * :rake -- Rake provided test loading script (default). + # * :testrb -- Ruby provided test loading script. + # * :direct -- Load tests using command line loader. + # + attr_accessor :loader + + # Array of command line options to pass to ruby when running test loader. + attr_accessor :ruby_opts + + # Description of the test task. (default is 'Run tests') + attr_accessor :description + + # Task prerequisites. + attr_accessor :deps + + # Explicitly define the list of test files to be included in a + # test. +list+ is expected to be an array of file names (a + # FileList is acceptable). If both +pattern+ and +test_files+ are + # used, then the list of test files is the union of the two. + def test_files=(list) + @test_files = list + end + + # Create a testing task. + def initialize(name=:test) + @name = name + @libs = ["lib"] + @pattern = nil + @options = nil + @test_files = nil + @verbose = false + @warning = true + @loader = :rake + @ruby_opts = [] + @description = "Run tests" + (@name == :test ? "" : " for #{@name}") + @deps = [] + if @name.is_a?(Hash) + @deps = @name.values.first + @name = @name.keys.first + end + yield self if block_given? + @pattern = "test/test*.rb" if @pattern.nil? && @test_files.nil? + define + end + + # Create the tasks defined by this task lib. + def define + desc @description + task @name => Array(deps) do + FileUtilsExt.verbose(@verbose) do + puts "Use TESTOPTS=\"--verbose\" to pass --verbose" \ + ", etc. to runners." if ARGV.include? "--verbose" + args = + "#{ruby_opts_string} #{run_code} " + + "#{file_list_string} #{option_list}" + ruby args do |ok, status| + if !ok && status.respond_to?(:signaled?) && status.signaled? + raise SignalException.new(status.termsig) + elsif !ok + status = "Command failed with status (#{status.exitstatus})" + details = ": [ruby #{args}]" + message = + if Rake.application.options.trace or @verbose + status + details + else + status + end + + fail message + end + end + end + end + self + end + + def option_list # :nodoc: + (ENV["TESTOPTS"] || + ENV["TESTOPT"] || + ENV["TEST_OPTS"] || + ENV["TEST_OPT"] || + @options || + "") + end + + def ruby_opts_string # :nodoc: + opts = @ruby_opts.dup + opts.unshift("-I\"#{lib_path}\"") unless @libs.empty? + opts.unshift("-w") if @warning + opts.join(" ") + end + + def lib_path # :nodoc: + @libs.join(File::PATH_SEPARATOR) + end + + def file_list_string # :nodoc: + file_list.map { |fn| "\"#{fn}\"" }.join(" ") + end + + def file_list # :nodoc: + if ENV["TEST"] + FileList[ENV["TEST"]] + else + result = [] + result += @test_files.to_a if @test_files + result += FileList[@pattern].to_a if @pattern + result + end + end + + def ruby_version # :nodoc: + RUBY_VERSION + end + + def run_code # :nodoc: + case @loader + when :direct + "-e \"ARGV.each{|f| require f}\"" + when :testrb + "-S testrb" + when :rake + "#{rake_include_arg} \"#{rake_loader}\"" + end + end + + def rake_loader # :nodoc: + find_file("rake/rake_test_loader") or + fail "unable to find rake test loader" + end + + def find_file(fn) # :nodoc: + $LOAD_PATH.each do |path| + file_path = File.join(path, "#{fn}.rb") + return file_path if File.exist? file_path + end + nil + end + + def rake_include_arg # :nodoc: + spec = Gem.loaded_specs["rake"] + if spec.respond_to?(:default_gem?) && spec.default_gem? + "" + else + "-I\"#{rake_lib_dir}\"" + end + end + + def rake_lib_dir # :nodoc: + find_dir("rake") or + fail "unable to find rake lib" + end + + def find_dir(fn) # :nodoc: + $LOAD_PATH.each do |path| + file_path = File.join(path, "#{fn}.rb") + return path if File.exist? file_path + end + nil + end + + end +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/thread_history_display.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/thread_history_display.rb new file mode 100644 index 00000000..412ea37b --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/thread_history_display.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true +require "rake/private_reader" + +module Rake + + class ThreadHistoryDisplay # :nodoc: all + include Rake::PrivateReader + + private_reader :stats, :items, :threads + + def initialize(stats) + @stats = stats + @items = { _seq_: 1 } + @threads = { _seq_: "A" } + end + + def show + puts "Job History:" + stats.each do |stat| + stat[:data] ||= {} + rename(stat, :thread, threads) + rename(stat[:data], :item_id, items) + rename(stat[:data], :new_thread, threads) + rename(stat[:data], :deleted_thread, threads) + printf("%8d %2s %-20s %s\n", + (stat[:time] * 1_000_000).round, + stat[:thread], + stat[:event], + stat[:data].map do |k, v| "#{k}:#{v}" end.join(" ")) + end + end + + private + + def rename(hash, key, renames) + if hash && hash[key] + original = hash[key] + value = renames[original] + unless value + value = renames[:_seq_] + renames[:_seq_] = renames[:_seq_].succ + renames[original] = value + end + hash[key] = value + end + end + end + +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/thread_pool.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/thread_pool.rb new file mode 100644 index 00000000..b01a5efe --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/thread_pool.rb @@ -0,0 +1,163 @@ +# frozen_string_literal: true +require "set" + +require "rake/promise" + +module Rake + + class ThreadPool # :nodoc: all + + # Creates a ThreadPool object. The +thread_count+ parameter is the size + # of the pool. + def initialize(thread_count) + @max_active_threads = [thread_count, 0].max + @threads = Set.new + @threads_mon = Monitor.new + @queue = Queue.new + @join_cond = @threads_mon.new_cond + + @history_start_time = nil + @history = [] + @history_mon = Monitor.new + @total_threads_in_play = 0 + end + + # Creates a future executed by the +ThreadPool+. + # + # The args are passed to the block when executing (similarly to + # Thread#new) The return value is an object representing + # a future which has been created and added to the queue in the + # pool. Sending #value to the object will sleep the + # current thread until the future is finished and will return the + # result (or raise an exception thrown from the future) + def future(*args, &block) + promise = Promise.new(args, &block) + promise.recorder = lambda { |*stats| stat(*stats) } + + @queue.enq promise + stat :queued, item_id: promise.object_id + start_thread + promise + end + + # Waits until the queue of futures is empty and all threads have exited. + def join + @threads_mon.synchronize do + begin + stat :joining + @join_cond.wait unless @threads.empty? + stat :joined + rescue Exception => e + stat :joined + $stderr.puts e + $stderr.print "Queue contains #{@queue.size} items. " + + "Thread pool contains #{@threads.count} threads\n" + $stderr.print "Current Thread #{Thread.current} status = " + + "#{Thread.current.status}\n" + $stderr.puts e.backtrace.join("\n") + @threads.each do |t| + $stderr.print "Thread #{t} status = #{t.status}\n" + $stderr.puts t.backtrace.join("\n") + end + raise e + end + end + end + + # Enable the gathering of history events. + def gather_history #:nodoc: + @history_start_time = Time.now if @history_start_time.nil? + end + + # Return a array of history events for the thread pool. + # + # History gathering must be enabled to be able to see the events + # (see #gather_history). Best to call this when the job is + # complete (i.e. after ThreadPool#join is called). + def history # :nodoc: + @history_mon.synchronize { @history.dup }. + sort_by { |i| i[:time] }. + each { |i| i[:time] -= @history_start_time } + end + + # Return a hash of always collected statistics for the thread pool. + def statistics # :nodoc: + { + total_threads_in_play: @total_threads_in_play, + max_active_threads: @max_active_threads, + } + end + + private + + # processes one item on the queue. Returns true if there was an + # item to process, false if there was no item + def process_queue_item #:nodoc: + return false if @queue.empty? + + # Even though we just asked if the queue was empty, it + # still could have had an item which by this statement + # is now gone. For this reason we pass true to Queue#deq + # because we will sleep indefinitely if it is empty. + promise = @queue.deq(true) + stat :dequeued, item_id: promise.object_id + promise.work + return true + + rescue ThreadError # this means the queue is empty + false + end + + def safe_thread_count + @threads_mon.synchronize do + @threads.count + end + end + + def start_thread # :nodoc: + @threads_mon.synchronize do + next unless @threads.count < @max_active_threads + + t = Thread.new do + begin + while safe_thread_count <= @max_active_threads + break unless process_queue_item + end + ensure + @threads_mon.synchronize do + @threads.delete Thread.current + stat :ended, thread_count: @threads.count + @join_cond.broadcast if @threads.empty? + end + end + end + + @threads << t + stat( + :spawned, + new_thread: t.object_id, + thread_count: @threads.count) + @total_threads_in_play = @threads.count if + @threads.count > @total_threads_in_play + end + end + + def stat(event, data=nil) # :nodoc: + return if @history_start_time.nil? + info = { + event: event, + data: data, + time: Time.now, + thread: Thread.current.object_id, + } + @history_mon.synchronize { @history << info } + end + + # for testing only + + def __queue__ # :nodoc: + @queue + end + end + +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/trace_output.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/trace_output.rb new file mode 100644 index 00000000..d713a092 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/trace_output.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true +module Rake + module TraceOutput # :nodoc: all + + # Write trace output to output stream +out+. + # + # The write is done as a single IO call (to print) to lessen the + # chance that the trace output is interrupted by other tasks also + # producing output. + def trace_on(out, *strings) + sep = $\ || "\n" + if strings.empty? + output = sep + else + output = strings.map { |s| + next if s.nil? + s.end_with?(sep) ? s : s + sep + }.join + end + out.print(output) + end + end +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/version.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/version.rb new file mode 100644 index 00000000..7f47740c --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/version.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true +module Rake + VERSION = "13.0.0" + + module Version # :nodoc: all + MAJOR, MINOR, BUILD, *OTHER = Rake::VERSION.split "." + + NUMBERS = [MAJOR, MINOR, BUILD, *OTHER] + end +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/win32.rb b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/win32.rb new file mode 100644 index 00000000..6e620318 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/lib/rake/win32.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true +require "rbconfig" + +module Rake + # Win 32 interface methods for Rake. Windows specific functionality + # will be placed here to collect that knowledge in one spot. + module Win32 # :nodoc: all + + # Error indicating a problem in locating the home directory on a + # Win32 system. + class Win32HomeError < RuntimeError + end + + class << self + # True if running on a windows system. + def windows? + RbConfig::CONFIG["host_os"] =~ %r!(msdos|mswin|djgpp|mingw|[Ww]indows)! + end + + # The standard directory containing system wide rake files on + # Win 32 systems. Try the following environment variables (in + # order): + # + # * HOME + # * HOMEDRIVE + HOMEPATH + # * APPDATA + # * USERPROFILE + # + # If the above are not defined, the return nil. + def win32_system_dir #:nodoc: + win32_shared_path = ENV["HOME"] + if win32_shared_path.nil? && ENV["HOMEDRIVE"] && ENV["HOMEPATH"] + win32_shared_path = ENV["HOMEDRIVE"] + ENV["HOMEPATH"] + end + + win32_shared_path ||= ENV["APPDATA"] + win32_shared_path ||= ENV["USERPROFILE"] + raise Win32HomeError, + "Unable to determine home path environment variable." if + win32_shared_path.nil? or win32_shared_path.empty? + normalize(File.join(win32_shared_path, "Rake")) + end + + # Normalize a win32 path so that the slashes are all forward slashes. + def normalize(path) + path.gsub(/\\/, "/") + end + + end + end +end diff --git a/path/ruby/2.6.0/gems/rake-13.0.0/rake.gemspec b/path/ruby/2.6.0/gems/rake-13.0.0/rake.gemspec new file mode 100644 index 00000000..20591cb3 --- /dev/null +++ b/path/ruby/2.6.0/gems/rake-13.0.0/rake.gemspec @@ -0,0 +1,36 @@ +# frozen_string_literal: true +$LOAD_PATH.unshift File.expand_path('../lib', __FILE__) +require 'rake/version' + +Gem::Specification.new do |s| + s.name = "rake".freeze + s.version = Rake::VERSION + s.authors = ["Hiroshi SHIBATA".freeze, "Eric Hodel".freeze, "Jim Weirich".freeze] + s.email = ["hsbt@ruby-lang.org".freeze, "drbrain@segment7.net".freeze, "".freeze] + + s.summary = "Rake is a Make-like program implemented in Ruby".freeze + s.description = <<-DESCRIPTION +Rake is a Make-like program implemented in Ruby. Tasks and dependencies are +specified in standard Ruby syntax. +Rake has the following features: + * Rakefiles (rake's version of Makefiles) are completely defined in standard Ruby syntax. + No XML files to edit. No quirky Makefile syntax to worry about (is that a tab or a space?) + * Users can specify tasks with prerequisites. + * Rake supports rule patterns to synthesize implicit tasks. + * Flexible FileLists that act like arrays but know about manipulating file names and paths. + * Supports parallel execution of tasks. + DESCRIPTION + s.homepage = "https://github.com/ruby/rake".freeze + s.licenses = ["MIT".freeze] + + s.files = %x[git ls-files -z].split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - + %w[.rubocop.yml .gitignore .travis.yml appveyor.yml] + s.bindir = "exe" + s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) } + s.require_paths = ["lib".freeze] + + s.required_ruby_version = Gem::Requirement.new(">= 2.2".freeze) + s.rubygems_version = "2.6.1".freeze + s.required_rubygems_version = Gem::Requirement.new(">= 1.3.2".freeze) + s.rdoc_options = ["--main".freeze, "README.rdoc".freeze] +end diff --git a/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/.gitignore b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/.gitignore new file mode 100644 index 00000000..25a0e9a7 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/.gitignore @@ -0,0 +1,13 @@ +*.gem +.DS_Store +.Trashes +.bundle +.com.apple.timemachine.supported +.fseventsd +.idea +.rbx +/ext/build +Desktop DB +Desktop DF +Gemfile.lock +pkg/* diff --git a/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/Gemfile b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/Gemfile new file mode 100644 index 00000000..b4e2a20b --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/Gemfile @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gemspec diff --git a/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/Guardfile b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/Guardfile new file mode 100644 index 00000000..63a666e5 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/Guardfile @@ -0,0 +1,8 @@ +# A sample Guardfile +# More info at http://github.com/guard/guard#readme + +guard :rspec do + watch(%r(^spec/(.*)_spec.rb)) + watch(%r(^lib/(.*)\.rb)) { |m| "spec/#{m[1]}_spec.rb" } + watch('spec/spec_helper.rb') { 'spec' } +end diff --git a/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/LICENSE.txt b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/LICENSE.txt new file mode 100644 index 00000000..b083ecdd --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2010-2014 Thibaud Guillaume-Gentil & Travis Tilley + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/README.md b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/README.md new file mode 100644 index 00000000..5dd3310c --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/README.md @@ -0,0 +1,260 @@ +[![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/thibaudgg/rb-fsevent) +[![endorse](https://api.coderwall.com/ttilley/endorsecount.png)](https://coderwall.com/ttilley) + +# rb-fsevent + +Very simple & usable Mac OSX FSEvents API + +* Signals are working (really) +* Tested on MRI 2.4.1, RBX 3.72, JRuby 1.7.26 and 9.1.8.0 +* Tested on 10.8 + +## HFS+ filename corruption bug + +There is a _very_ long-standing (since 2011) OSX bug where sometimes the filename metadata for HFS+ filesystems will get corrupted, resulting in some APIs returning one case for a file, and other APIs returning another. The result is that sometimes, _for no visible reason to the user_, fsevents would simply not work. As of rb-fsevent 0.9.5 this issue is properly detected and an insanely hacky (but effective) workaround is used that replaces the system `realpath()` with a custom implementation that should almost always return the same value as the kernel reporting (thus fixing fsevents). The major flaw in the workaround is that it may return the wrong path for hard links. + +Please note that this doesn't repair the underlying issue on disk. Other apps and libraries using fsevents will continue to break with no warning. There may be other issues unrelated to fsevents. + +__This bug is resolved in MacOS 10.12 and all users are strongly encouraged to upgrade.__ + +## Install + + gem install rb-fsevent + +### re-compilation + +rb-fsevent comes with a pre-compiled fsevent\_watch binary supporting x86\_64 on 10.9 and above. The binary is codesigned with my (Travis Tilley) Developer ID as an extra precaution when distributing pre-compiled code and contains an embedded plist describing its build environment. This should be sufficient for most users, but if you need to use rb-fsevent on 10.8 or lower then recompilation is necessary. This can be done by entering the installed gem's ext directory and running: + + MACOSX_DEPLOYMENT_TARGET="10.7" rake replace_exe + +The following ENV vars are recognized: + +* CC +* CFLAGS +* ARCHFLAGS +* MACOSX\_DEPLOYMENT\_TARGET +* FWDEBUG (enables debug mode, printing an obscene number of informational + messages to STDERR) + +### embedded plist + +You can retrieve the values in the embedded plist via the CLI: + + fsevent_watch --show-plist + +The output is essentially formatted as `"#{key}:\n #{value}\n"` to make it easier to read than plist style xml. The result looks like this: + + DTSDKName: + macosx10.5 + FSEWBuildTriple: + i386-apple-darwin10.8.0 + FSEWCC: + /usr/bin/gcc-4.2 + DTSDKPath: + /Developer/SDKs/MacOSX10.5.sdk + FSEWCCVersion: + i686-apple-darwin10-gcc-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5666) (dot 3) + FSEWCFLAGS: + -fconstant-cfstrings -fno-strict-aliasing -Wall -mmacosx-version-min=10.5 -O3 + +If, for some perverse reason, you prefer to look at the xml... it can be retrieved via: + + otool -s __TEXT __info_plist ./bin/fsevent_watch | grep ^0 | xxd -r - + +### codesign + +You can verify code signing information for a specific fsevent\_watch via: + + codesign -d -vvv ./bin/fsevent_watch + +If you're using the pre-compiled binary, then the output should contain something to the effect of: + + Authority=Developer ID Application: Travis Tilley + Authority=Developer ID Certification Authority + Authority=Apple Root CA + Timestamp=Dec 31, 2012 12:49:13 PM + +## Usage + +### Singular path + +```ruby +require 'rb-fsevent' + +fsevent = FSEvent.new +fsevent.watch Dir.pwd do |directories| + puts "Detected change inside: #{directories.inspect}" +end +fsevent.run +``` + +### Multiple paths + +```ruby +require 'rb-fsevent' + +paths = ['/tmp/path/one', '/tmp/path/two', Dir.pwd] + +fsevent = FSEvent.new +fsevent.watch paths do |directories| + puts "Detected change inside: #{directories.inspect}" +end +fsevent.run +``` + +### Multiple paths and additional options as a Hash + +```ruby +require 'rb-fsevent' + +paths = ['/tmp/path/one', '/tmp/path/two', Dir.pwd] +options = {:latency => 1.5, :no_defer => true } + +fsevent = FSEvent.new +fsevent.watch paths, options do |directories| + puts "Detected change inside: #{directories.inspect}" +end +fsevent.run +``` + +### Multiple paths and additional options as an Array + +```ruby +require 'rb-fsevent' + +paths = ['/tmp/path/one', '/tmp/path/two', Dir.pwd] +options = ['--latency', 1.5, '--no-defer'] + +fsevent = FSEvent.new +fsevent.watch paths, options do |directories| + puts "Detected change inside: #{directories.inspect}" +end +fsevent.run +``` + +### Using _full_ event information + +```ruby +require 'rb-fsevent' +fsevent = FSEvent.new +fsevent.watch Dir.pwd do |paths, event_meta| + event_meta.events.each do |event| + puts "event ID: #{event.id}" + puts "path: #{event.path}" + puts "c flags: #{event.cflags}" + puts "named flags: #{event.flags.join(', ')}" + # named flags will include strings such as `ItemInodeMetaMod` or `OwnEvent` + end +end +fsevent.run +``` + +## Options + +When defining options using a hash or hash-like object, it gets checked for validity and converted to the appropriate fsevent\_watch commandline arguments array when the FSEvent class is instantiated. This is obviously the safest and preferred method of passing in options. + +You may, however, choose to pass in an array of commandline arguments as your options value and it will be passed on, unmodified, to the fsevent\_watch binary when called. + +So far, the following options are supported: + +* :latency => 0.5 # in seconds +* :no\_defer => true +* :watch\_root => true +* :since\_when => 18446744073709551615 # an FSEventStreamEventId +* :file\_events => true + +### Latency + +The :latency parameter determines how long the service should wait after the first event before passing that information along to the client. If your latency is set to 4 seconds, and 300 changes occur in the first three, then the callback will be fired only once. If latency is set to 0.1 in the exact same scenario, you will see that callback fire somewhere closer to between 25 and 30 times. + +Setting a higher latency value allows for more effective temporal coalescing, resulting in fewer callbacks and greater overall efficiency... at the cost of apparent responsiveness. Setting this to a reasonably high value (and NOT setting :no\_defer) is particularly well suited for background, daemon, or batch processing applications. + +Implementation note: It appears that FSEvents will only coalesce events from a maximum of 32 distinct subpaths, making the above completely accurate only when events are to fewer than 32 subpaths. Creating 300 files in one directory, for example, or 30 files in 10 subdirectories, but not 300 files within 300 subdirectories. In the latter case, you may receive 31 callbacks in one go after the latency period. As this appears to be an implementation detail, the number could potentially differ across OS revisions. It is entirely possible that this number is somehow configurable, but I have not yet discovered an accepted method of doing so. + +### NoDefer + +The :no\_defer option changes the behavior of the latency parameter completely. Rather than waiting for $latency period of time before sending along events in an attempt to coalesce a potential deluge ahead of time, that first event is sent along to the client immediately and is followed by a $latency period of silence before sending along any additional events that occurred within that period. + +This behavior is particularly useful for interactive applications where that feeling of apparent responsiveness is most important, but you still don't want to get overwhelmed by a series of events that occur in rapid succession. + +### WatchRoot + +The :watch\_root option allows for catching the scenario where you start watching "~/src/demo\_project" and either it is later renamed to "~/src/awesome\_sauce\_3000" or the path changes in such a manner that the original directory is now at "~/clients/foo/iteration4/demo\_project". + +Unfortunately, while this behavior is somewhat supported in the fsevent\_watch binary built as part of this project, support for passing across detailed metadata is not (yet). As a result, you would not receive the appropriate RootChanged event and be able to react appropriately. Also, since the C code doesn't open watched directories and retain that file descriptor as part of path-specific callback metadata, we are unable to issue an F\_GETPATH fcntl() to determine the directory's new path. + +Please do not use this option until proper support is added (or, even better, add it and submit a pull request). + +### SinceWhen + +The FSEventStreamEventId passed in to :since\_when is used as a base for reacting to historic events. Unfortunately, not only is the metadata for transitioning from historic to live events not currently passed along, but it is incorrectly passed as a change event on the root path, and only per-host event streams are currently supported. When using per-host event streams, the event IDs are not guaranteed to be unique or contiguous when shared volumes (firewire/USB/net/etc) are used on multiple macs. + +Please do not use this option until proper support is added, unless it's acceptable for you to receive that one fake event that's handled incorrectly when events transition from historical to live. Even in that scenario, there's no metadata available for determining the FSEventStreamEventId of the last received event. + +WARNING: passing in 0 as the parameter to :since\_when will return events for every directory modified since "the beginning of time". + +### FileEvents ### + +Prepare yourself for an obscene number of callbacks. Realistically, an "Atomic Save" could easily fire maybe 6 events for the combination of creating the new file, changing metadata/permissions, writing content, swapping out the old file for the new may itself result in multiple events being fired, and so forth. By the time you get the event for the temporary file being created as part of the atomic save, it will already be gone and swapped with the original file. This and issues of a similar nature have prevented me from adding the option to the ruby code despite the fsevent\_watch binary supporting file level events for quite some time now. Mountain Lion seems to be better at coalescing needless events, but that might just be my imagination. + +## Debugging output + +If the gem is re-compiled with the environment variable FWDEBUG set, then fsevent\_watch will be built with its various DEBUG sections defined, and the output to STDERR is truly verbose (and hopefully helpful in debugging your application and not just fsevent\_watch itself). If enough people find this to be directly useful when developing code that makes use of rb-fsevent, then it wouldn't be hard to clean this up and make it a feature enabled by a commandline argument instead. Until somebody files an issue, however, I will assume otherwise. + + append_path called for: /tmp/moo/cow/ + resolved path to: /private/tmp/moo/cow + + config.sinceWhen 18446744073709551615 + config.latency 0.300000 + config.flags 00000000 + config.paths + /private/tmp/moo/cow + + FSEventStreamRef @ 0x100108540: + allocator = 0x7fff705a4ee0 + callback = 0x10000151e + context = {0, 0x0, 0x0, 0x0, 0x0} + numPathsToWatch = 1 + pathsToWatch = 0x7fff705a4ee0 + pathsToWatch[0] = '/private/tmp/moo/cow' + latestEventId = -1 + latency = 300000 (microseconds) + flags = 0x00000000 + runLoop = 0x0 + runLoopMode = 0x0 + + FSEventStreamCallback fired! + numEvents: 32 + event path: /private/tmp/moo/cow/1/a/ + event flags: 00000000 + event ID: 1023767 + event path: /private/tmp/moo/cow/1/b/ + event flags: 00000000 + event ID: 1023782 + event path: /private/tmp/moo/cow/1/c/ + event flags: 00000000 + event ID: 1023797 + event path: /private/tmp/moo/cow/1/d/ + event flags: 00000000 + event ID: 1023812 + [etc] + + +## Development + +* Source hosted at [GitHub](http://github.com/thibaudgg/rb-fsevent) +* Report issues/Questions/Feature requests on [GitHub Issues](http://github.com/thibaudgg/rb-fsevent/issues) + +Pull requests are quite welcome! Please ensure that your commits are in a topic branch for each individual changeset that can be reasonably isolated. It is also important to ensure that your changes are well tested... whether that means new tests, modified tests, or fixing a scenario where the existing tests currently fail. If you have rbenv and ruby-build, we have a helper task for running the testsuite in all of them: + + rake spec:portability + +The list of tested targets is currently: + + %w[2.4.1 rbx-3.72 jruby-1.7.26 jruby-9.1.8.0] + +## Authors + +* [Travis Tilley](http://github.com/ttilley) +* [Thibaud Guillaume-Gentil](http://github.com/thibaudgg) +* [Andrey Tarantsov](https://github.com/andreyvit) diff --git a/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/Rakefile b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/Rakefile new file mode 100644 index 00000000..53a08a14 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/Rakefile @@ -0,0 +1,33 @@ +# -*- encoding: utf-8 -*- +require 'bundler' +Bundler::GemHelper.install_tasks + +require 'rspec/core/rake_task' +RSpec::Core::RakeTask.new(:spec) +task :default => :spec + +namespace(:spec) do + desc "Run all specs on multiple ruby versions" + task(:portability) do + versions = %w[2.4.1 rbx-3.72 jruby-1.7.26 jruby-9.1.8.0] + versions.each do |version| + # system <<-BASH + # bash -c 'source ~/.rvm/scripts/rvm; + # rvm #{version}; + # echo "--------- version #{version} ----------\n"; + # bundle install; + # rake spec' + # BASH + system <<-BASH + bash -c 'export PATH="$HOME/.rbenv/bin:$PATH"; + [[ `which rbenv` ]] && eval "$(rbenv init -)"; + [[ ! -a $HOME/.rbenv/versions/#{version} ]] && rbenv install #{version}; + rbenv shell #{version}; + rbenv which bundle 2> /dev/null || gem install bundler; + rm Gemfile.lock; + bundle install; + rake spec;' + BASH + end + end +end diff --git a/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/bin/fsevent_watch b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/bin/fsevent_watch new file mode 100755 index 00000000..889204f8 Binary files /dev/null and b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/bin/fsevent_watch differ diff --git a/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/LICENSE b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/LICENSE new file mode 100644 index 00000000..a35e1957 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2011-2013 Travis Tilley + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/FSEventsFix.c b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/FSEventsFix.c new file mode 100644 index 00000000..60e3d37b --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/FSEventsFix.c @@ -0,0 +1,626 @@ +/* + * FSEventsFix + * + * Resolves a long-standing bug in realpath() that prevents FSEvents API from + * monitoring certain folders on a wide range of OS X released (10.6-10.10 at least). + * + * The underlying issue is that for some folders, realpath() call starts returning + * a path with incorrect casing (e.g. "/users/smt" instead of "/Users/smt"). + * FSEvents is case-sensitive and calls realpath() on the paths you pass in, so + * an incorrect value returned by realpath() prevents FSEvents from seeing any + * change events. + * + * See the discussion at https://github.com/thibaudgg/rb-fsevent/issues/10 about + * the history of this bug and how this library came to exist. + * + * This library uses Facebook's fishhook to replace a custom implementation of + * realpath in place of the system realpath; FSEvents will then invoke our custom + * implementation (which does not screw up the names) and will thus work correctly. + * + * Our implementation of realpath is based on the open-source implementation from + * OS X 10.10, with a single change applied (enclosed in "BEGIN WORKAROUND FOR + * OS X BUG" ... "END WORKAROUND FOR OS X BUG"). + * + * Include FSEventsFix.{h,c} into your project and call FSEventsFixInstall(). + * + * It is recommended that you install FSEventsFix on demand, using FSEventsFixIsBroken + * to check if the folder you're about to pass to FSEventStreamCreate needs the fix. + * Note that the fix must be applied before calling FSEventStreamCreate. + * + * FSEventsFixIsBroken requires a path that uses the correct case for all folder names, + * i.e. a path provided by the system APIs or constructed from folder names provided + * by the directory enumeration APIs. + * + * Copyright (c) 2015 Andrey Tarantsov + * Copyright (c) 2003 Constantin S. Svintsoff + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * Based on a realpath implementation from Apple libc 498.1.7, taken from + * http://www.opensource.apple.com/source/Libc/Libc-498.1.7/stdlib/FreeBSD/realpath.c + * and provided under the following license: + * + * Copyright (c) 2003 Constantin S. Svintsoff + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the authors may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + + +#include "FSEventsFix.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +const char *const FSEventsFixVersionString = "0.11.0"; + + +#pragma mark - Forward declarations + +static char *(*orig_realpath)(const char *restrict file_name, char resolved_name[PATH_MAX]); +static char *CFURL_realpath(const char *restrict file_name, char resolved_name[PATH_MAX]); +static char *FSEventsFix_realpath_wrapper(const char *restrict src, char *restrict dst); + +static void _FSEventsFixHookInstall(); +static void _FSEventsFixHookUninstall(); + + +#pragma mark - Internal state + +static dispatch_queue_t g_queue = NULL; + +static int64_t g_enable_refcount = 0; + +static bool g_in_self_test = false; +static bool g_hook_operational = false; + +static void(^g_logging_block)(FSEventsFixMessageType type, const char *message); +static FSEventsFixDebugOptions g_debug_opt = 0; + +typedef struct { + char *name; + void *replacement; + void *original; + uint hooked_symbols; +} rebinding_t; + +static rebinding_t g_rebindings[] = { + { "_realpath$DARWIN_EXTSN", (void *) &FSEventsFix_realpath_wrapper, (void *) &realpath, 0 } +}; +static const uint g_rebindings_nel = sizeof(g_rebindings) / sizeof(g_rebindings[0]); + + +#pragma mark - Logging + +static void _FSEventsFixLog(FSEventsFixMessageType type, const char *__restrict fmt, ...) __attribute__((__format__ (__printf__, 2, 3))); + +static void _FSEventsFixLog(FSEventsFixMessageType type, const char *__restrict fmt, ...) { + if (g_logging_block) { + char *message = NULL; + va_list va; + va_start(va, fmt); + vasprintf(&message, fmt, va); + va_end(va); + + if (message) { + if (!!(g_debug_opt & FSEventsFixDebugOptionLogToStderr)) { + fprintf(stderr, "FSEventsFix: %s\n", message); + } + if (g_logging_block) { + g_logging_block(type, message); + } + free(message); + } + } +} + + +#pragma mark - API + +void _FSEventsFixInitialize() { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + g_queue = dispatch_queue_create("FSEventsFix", DISPATCH_QUEUE_SERIAL); + }); +} + +void FSEventsFixConfigure(FSEventsFixDebugOptions debugOptions, void(^loggingBlock)(FSEventsFixMessageType severity, const char *message)) { + _FSEventsFixInitialize(); + loggingBlock = Block_copy(loggingBlock); + dispatch_sync(g_queue, ^{ + g_debug_opt = debugOptions; + g_logging_block = loggingBlock; + }); +} + +// Must be called from the private serial queue. +void _FSEventsFixSelfTest() { + g_in_self_test = true; + g_hook_operational = false; + static char result[1024]; + realpath("/Etc/__!FSEventsFixSelfTest!__", result); + g_in_self_test = false; +} + +void FSEventsFixEnable() { + _FSEventsFixInitialize(); + dispatch_sync(g_queue, ^{ + if (++g_enable_refcount == 1) { + orig_realpath = dlsym(RTLD_DEFAULT, "realpath"); + _FSEventsFixHookInstall(); + _FSEventsFixSelfTest(); + if (g_hook_operational) { + _FSEventsFixLog(FSEventsFixMessageTypeStatusChange, "Enabled"); + } else { + _FSEventsFixLog(FSEventsFixMessageTypeFatalError, "Failed to enable (hook not called)"); + } + } + }); +} + +void FSEventsFixDisable() { + _FSEventsFixInitialize(); + dispatch_sync(g_queue, ^{ + if (g_enable_refcount == 0) { + abort(); + } + if (--g_enable_refcount == 0) { + _FSEventsFixHookUninstall(); + _FSEventsFixSelfTest(); + if (!g_hook_operational) { + _FSEventsFixLog(FSEventsFixMessageTypeStatusChange, "Disabled"); + } else { + _FSEventsFixLog(FSEventsFixMessageTypeFatalError, "Failed to disable (hook still called)"); + } + } + }); +} + +bool FSEventsFixIsOperational() { + _FSEventsFixInitialize(); + __block bool result = false; + dispatch_sync(g_queue, ^{ + result = g_hook_operational; + }); + return result; +} + +bool _FSEventsFixIsBroken_noresolve(const char *resolved) { + if (!!(g_debug_opt & FSEventsFixDebugOptionSimulateBroken)) { + if (strstr(resolved, FSEventsFixSimulatedBrokenFolderMarker)) { + return true; + } + } + + char *reresolved = realpath(resolved, NULL); + if (reresolved) { + bool broken = (0 != strcmp(resolved, reresolved)); + free(reresolved); + return broken; + } else { + return true; + } +} + +bool FSEventsFixIsBroken(const char *path) { + char *resolved = CFURL_realpath(path, NULL); + if (!resolved) { + return true; + } + bool broken = _FSEventsFixIsBroken_noresolve(resolved); + free(resolved); + return broken; +} + +char *FSEventsFixCopyRootBrokenFolderPath(const char *inpath) { + if (!FSEventsFixIsBroken(inpath)) { + return NULL; + } + + // get a mutable copy of an absolute path + char *path = CFURL_realpath(inpath, NULL); + if (!path) { + return NULL; + } + + for (;;) { + char *sep = strrchr(path, '/'); + if ((sep == NULL) || (sep == path)) { + break; + } + *sep = 0; + if (!_FSEventsFixIsBroken_noresolve(path)) { + *sep = '/'; + break; + } + } + + return path; +} + +static void _FSEventsFixAttemptRepair(const char *folder) { + int rv = rename(folder, folder); + + if (!!(g_debug_opt & FSEventsFixDebugOptionSimulateRepair)) { + const char *pos = strstr(folder, FSEventsFixSimulatedBrokenFolderMarker); + if (pos) { + char *fixed = strdup(folder); + fixed[pos - folder] = 0; + strcat(fixed, pos + strlen(FSEventsFixSimulatedBrokenFolderMarker)); + + rv = rename(folder, fixed); + free(fixed); + } + } + + if (rv != 0) { + if (errno == EPERM) { + _FSEventsFixLog(FSEventsFixMessageTypeResult, "Permission error when trying to repair '%s'", folder); + } else { + _FSEventsFixLog(FSEventsFixMessageTypeExpectedFailure, "Unknown error when trying to repair '%s': errno = %d", folder, errno); + } + } +} + +FSEventsFixRepairStatus FSEventsFixRepairIfNeeded(const char *inpath) { + char *root = FSEventsFixCopyRootBrokenFolderPath(inpath); + if (root == NULL) { + return FSEventsFixRepairStatusNotBroken; + } + + for (;;) { + _FSEventsFixAttemptRepair(root); + char *newRoot = FSEventsFixCopyRootBrokenFolderPath(inpath); + if (newRoot == NULL) { + _FSEventsFixLog(FSEventsFixMessageTypeResult, "Repaired '%s' in '%s'", root, inpath); + free(root); + return FSEventsFixRepairStatusRepaired; + } + if (0 == strcmp(root, newRoot)) { + _FSEventsFixLog(FSEventsFixMessageTypeResult, "Failed to repair '%s' in '%s'", root, inpath); + free(root); + free(newRoot); + return FSEventsFixRepairStatusFailed; + } + _FSEventsFixLog(FSEventsFixMessageTypeResult, "Partial success, repaired '%s' in '%s'", root, inpath); + free(root); + root = newRoot; + } +} + + +#pragma mark - FSEventsFix realpath wrapper + +static char *FSEventsFix_realpath_wrapper(const char * __restrict src, char * __restrict dst) { + if (g_in_self_test) { + if (strstr(src, "__!FSEventsFixSelfTest!__")) { + g_hook_operational = true; + } + } + + // CFURL_realpath doesn't support putting where resolution failed into the + // dst buffer, so we call the original realpath here first and if it gets a + // result, replace that with the output of CFURL_realpath. that way all the + // features of the original realpath are available. + char *rv = NULL; + char *orv = orig_realpath(src, dst); + if (orv != NULL) { rv = CFURL_realpath(src, dst); } + + if (!!(g_debug_opt & FSEventsFixDebugOptionLogCalls)) { + char *result = rv ?: dst; + _FSEventsFixLog(FSEventsFixMessageTypeCall, "realpath(%s) => %s\n", src, result); + } + + if (!!(g_debug_opt & FSEventsFixDebugOptionUppercaseReturn)) { + char *result = rv ?: dst; + if (result) { + for (char *pch = result; *pch; ++pch) { + *pch = (char)toupper(*pch); + } + } + } + + return rv; +} + + +#pragma mark - realpath + +// naive implementation of realpath on top of CFURL +// NOTE: doesn't quite support the full range of errno results one would +// expect here, in part because some of these functions just return a boolean, +// and in part because i'm not dealing with messy CFErrorRef objects and +// attempting to translate those to sane errno values. +// NOTE: the OSX realpath will return _where_ resolution failed in resolved_name +// if passed in and return NULL. we can't properly support that extension here +// since the resolution happens entirely behind the scenes to us in CFURL. +static char* CFURL_realpath(const char *file_name, char resolved_name[PATH_MAX]) +{ + char* resolved; + CFURLRef url1; + CFURLRef url2; + CFStringRef path; + + if (file_name == NULL) { + errno = EINVAL; + return (NULL); + } + +#if __DARWIN_UNIX03 + if (*file_name == 0) { + errno = ENOENT; + return (NULL); + } +#endif + + // create a buffer to store our result if we weren't passed one + if (!resolved_name) { + if ((resolved = malloc(PATH_MAX)) == NULL) return (NULL); + } else { + resolved = resolved_name; + } + + url1 = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8*)file_name, (CFIndex)strlen(file_name), false); + if (url1 == NULL) { goto error_return; } + + url2 = CFURLCopyAbsoluteURL(url1); + CFRelease(url1); + if (url2 == NULL) { goto error_return; } + + url1 = CFURLCreateFileReferenceURL(NULL, url2, NULL); + CFRelease(url2); + if (url1 == NULL) { goto error_return; } + + // if there are multiple hard links to the original path, this may end up + // being _completely_ different from what was intended + url2 = CFURLCreateFilePathURL(NULL, url1, NULL); + CFRelease(url1); + if (url2 == NULL) { goto error_return; } + + path = CFURLCopyFileSystemPath(url2, kCFURLPOSIXPathStyle); + CFRelease(url2); + if (path == NULL) { goto error_return; } + + bool success = CFStringGetCString(path, resolved, PATH_MAX, kCFStringEncodingUTF8); + CFRelease(path); + if (!success) { goto error_return; } + + return resolved; + +error_return: + if (!resolved_name) { + // we weren't passed in an output buffer and created our own. free it + int e = errno; + free(resolved); + errno = e; + } + return (NULL); +} + + +#pragma mark - fishhook + +// Copyright (c) 2013, Facebook, Inc. +// All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name Facebook nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific +// prior written permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#import +#import +#import +#import +#import +#import + +#ifdef __LP64__ +typedef struct mach_header_64 mach_header_t; +typedef struct segment_command_64 segment_command_t; +typedef struct section_64 section_t; +typedef struct nlist_64 nlist_t; +#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64 +#else +typedef struct mach_header mach_header_t; +typedef struct segment_command segment_command_t; +typedef struct section section_t; +typedef struct nlist nlist_t; +#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT +#endif + +static volatile bool g_hook_installed = false; + +static void _FSEventsFixHookUpdateSection(section_t *section, intptr_t slide, nlist_t *symtab, char *strtab, uint32_t *indirect_symtab) +{ + uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1; + void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr); + for (uint i = 0; i < section->size / sizeof(void *); i++) { + uint32_t symtab_index = indirect_symbol_indices[i]; + if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL || + symtab_index == (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) { + continue; + } + uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx; + char *symbol_name = strtab + strtab_offset; + for (rebinding_t *cur = g_rebindings, *end = g_rebindings + g_rebindings_nel; cur < end; ++cur) { + if (strcmp(symbol_name, cur->name) == 0) { + if (g_hook_installed) { + if (indirect_symbol_bindings[i] != cur->replacement) { + indirect_symbol_bindings[i] = cur->replacement; + ++cur->hooked_symbols; + } + } else if (cur->original != NULL) { + if (indirect_symbol_bindings[i] == cur->replacement) { + indirect_symbol_bindings[i] = cur->original; + if (cur->hooked_symbols > 0) { + --cur->hooked_symbols; + } + } + } + goto symbol_loop; + } + } + symbol_loop:; + } +} + +static void _FSEventsFixHookUpdateImage(const struct mach_header *header, intptr_t slide) { + Dl_info info; + if (dladdr(header, &info) == 0) { + return; + } + + segment_command_t *cur_seg_cmd; + segment_command_t *linkedit_segment = NULL; + struct symtab_command* symtab_cmd = NULL; + struct dysymtab_command* dysymtab_cmd = NULL; + + uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t); + for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) { + cur_seg_cmd = (segment_command_t *)cur; + if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) { + if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) { + linkedit_segment = cur_seg_cmd; + } + } else if (cur_seg_cmd->cmd == LC_SYMTAB) { + symtab_cmd = (struct symtab_command*)cur_seg_cmd; + } else if (cur_seg_cmd->cmd == LC_DYSYMTAB) { + dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd; + } + } + + if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment || + !dysymtab_cmd->nindirectsyms) { + return; + } + + // Find base symbol/string table addresses + uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff; + nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff); + char *strtab = (char *)(linkedit_base + symtab_cmd->stroff); + + // Get indirect symbol table (array of uint32_t indices into symbol table) + uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff); + + cur = (uintptr_t)header + sizeof(mach_header_t); + for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) { + cur_seg_cmd = (segment_command_t *)cur; + if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) { + if (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0) { + continue; + } + for (uint j = 0; j < cur_seg_cmd->nsects; j++) { + section_t *sect = + (section_t *)(cur + sizeof(segment_command_t)) + j; + if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) { + _FSEventsFixHookUpdateSection(sect, slide, symtab, strtab, indirect_symtab); + } + if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) { + _FSEventsFixHookUpdateSection(sect, slide, symtab, strtab, indirect_symtab); + } + } + } + } +} + +static void _FSEventsFixHookSaveOriginals() { + for (rebinding_t *cur = g_rebindings, *end = g_rebindings + g_rebindings_nel; cur < end; ++cur) { + void *original = cur->original = dlsym(RTLD_DEFAULT, cur->name+1); + if (!original) { + const char *error = dlerror(); + _FSEventsFixLog(FSEventsFixMessageTypeFatalError, "Cannot find symbol %s, dlsym says: %s\n", cur->name, error); + } + } +} + +static void _FSEventsFixHookUpdate() { + uint32_t c = _dyld_image_count(); + for (uint32_t i = 0; i < c; i++) { + _FSEventsFixHookUpdateImage(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i)); + } +} + +static void _FSEventsFixHookInstall() { + static bool first_rebinding_done = false; + + if (!g_hook_installed) { + g_hook_installed = true; + + if (!first_rebinding_done) { + first_rebinding_done = true; + _FSEventsFixHookSaveOriginals(); + _dyld_register_func_for_add_image(_FSEventsFixHookUpdateImage); + } else { + _FSEventsFixHookUpdate(); + } + } +} + +static void _FSEventsFixHookUninstall() { + if (g_hook_installed) { + g_hook_installed = false; + _FSEventsFixHookUpdate(); + } +} diff --git a/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/FSEventsFix.h b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/FSEventsFix.h new file mode 100644 index 00000000..b70b8800 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/FSEventsFix.h @@ -0,0 +1,105 @@ +/* + * FSEventsFix + * + * Works around a long-standing bug in realpath() that prevents FSEvents API from + * monitoring certain folders on a wide range of OS X releases (10.6-10.10 at least). + * + * The underlying issue is that for some folders, realpath() call starts returning + * a path with incorrect casing (e.g. "/users/smt" instead of "/Users/smt"). + * FSEvents is case-sensitive and calls realpath() on the paths you pass in, so + * an incorrect value returned by realpath() prevents FSEvents from seeing any + * change events. + * + * See the discussion at https://github.com/thibaudgg/rb-fsevent/issues/10 about + * the history of this bug and how this library came to exist. + * + * This library uses Facebook's fishhook to replace a custom implementation of + * realpath in place of the system realpath; FSEvents will then invoke our custom + * implementation (which does not screw up the names) and will thus work correctly. + * + * Our implementation of realpath is based on the open-source implementation from + * OS X 10.10, with a single change applied (enclosed in "BEGIN WORKAROUND FOR + * OS X BUG" ... "END WORKAROUND FOR OS X BUG"). + * + * Include FSEventsFix.{h,c} into your project and call FSEventsFixInstall(). + * + * It is recommended that you install FSEventsFix on demand, using FSEventsFixIsBroken + * to check if the folder you're about to pass to FSEventStreamCreate needs the fix. + * Note that the fix must be applied before calling FSEventStreamCreate. + * + * FSEventsFixIsBroken requires a path that uses the correct case for all folder names, + * i.e. a path provided by the system APIs or constructed from folder names provided + * by the directory enumeration APIs. + * + * See .c file for license & copyrights, but basically this is available under a mix + * of MIT and BSD licenses. + */ + +#ifndef __FSEventsFix__ +#define __FSEventsFix__ + +#include + +/// A library version string (e.g. 1.2.3) for displaying and logging purposes +extern const char *const FSEventsFixVersionString; + +/// See FSEventsFixDebugOptionSimulateBroken +#define FSEventsFixSimulatedBrokenFolderMarker "__!FSEventsBroken!__" + +typedef CF_OPTIONS(unsigned, FSEventsFixDebugOptions) { + /// Always return an uppercase string from realpath + FSEventsFixDebugOptionUppercaseReturn = 0x01, + + /// Log all calls to realpath using the logger configured via FSEventsFixConfigure + FSEventsFixDebugOptionLogCalls = 0x02, + + /// In addition to the logging block (if any), log everything to stderr + FSEventsFixDebugOptionLogToStderr = 0x08, + + /// Report paths containing FSEventsFixSimulatedBrokenFolderMarker as broken + FSEventsFixDebugOptionSimulateBroken = 0x10, + + /// Repair paths containing FSEventsFixSimulatedBrokenFolderMarker by renaming them + FSEventsFixDebugOptionSimulateRepair = 0x20, +}; + +typedef CF_ENUM(int, FSEventsFixMessageType) { + /// Call logging requested via FSEventsFixDebugOptionLogCalls + FSEventsFixMessageTypeCall, + + /// Results of actions like repair, and other pretty verbose, but notable, stuff. + FSEventsFixMessageTypeResult, + + /// Enabled/disabled status change + FSEventsFixMessageTypeStatusChange, + + /// Expected failure (treat as a warning) + FSEventsFixMessageTypeExpectedFailure, + + /// Severe failure that most likely means that the library won't work + FSEventsFixMessageTypeFatalError +}; + +typedef CF_ENUM(int, FSEventsFixRepairStatus) { + FSEventsFixRepairStatusNotBroken, + FSEventsFixRepairStatusRepaired, + FSEventsFixRepairStatusFailed, +}; + +/// Note that the logging block can be called on any dispatch queue. +void FSEventsFixConfigure(FSEventsFixDebugOptions debugOptions, void(^loggingBlock)(FSEventsFixMessageType type, const char *message)); + +void FSEventsFixEnable(); +void FSEventsFixDisable(); + +bool FSEventsFixIsOperational(); + +bool FSEventsFixIsBroken(const char *path); + +/// If the path is broken, returns a string identifying the root broken folder, +/// otherwise, returns NULL. You need to free() the returned string. +char *FSEventsFixCopyRootBrokenFolderPath(const char *path); + +FSEventsFixRepairStatus FSEventsFixRepairIfNeeded(const char *path); + +#endif diff --git a/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/TSICTString.c b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/TSICTString.c new file mode 100644 index 00000000..6e033d06 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/TSICTString.c @@ -0,0 +1,373 @@ +// +// TSICTString.c +// TSITString +// +// Created by Travis Tilley on 9/27/11. +// + +#include "TSICTString.h" + + +const char* const TNetstringTypes = ",#^!~}]Z"; +const char* const OTNetstringTypes = ",#^!~{[Z"; +const UInt8 TNetstringSeparator = ':'; + +TSITStringFormat TSITStringDefaultFormat = kTSITStringFormatTNetstring; + +static const CFRange BeginningRange = {0,0}; + +static CFTypeID kCFDataTypeID = -1UL; +static CFTypeID kCFStringTypeID = -1UL; +static CFTypeID kCFNumberTypeID = -1UL; +static CFTypeID kCFBooleanTypeID = -1UL; +static CFTypeID kCFNullTypeID = -1UL; +static CFTypeID kCFArrayTypeID = -1UL; +static CFTypeID kCFDictionaryTypeID = -1UL; + + +__attribute__((constructor)) void Init_TSICTString(void) +{ + kCFDataTypeID = CFDataGetTypeID(); + kCFStringTypeID = CFStringGetTypeID(); + kCFNumberTypeID = CFNumberGetTypeID(); + kCFBooleanTypeID = CFBooleanGetTypeID(); + kCFNullTypeID = CFNullGetTypeID(); + kCFArrayTypeID = CFArrayGetTypeID(); + kCFDictionaryTypeID = CFDictionaryGetTypeID(); +} + + +void TSICTStringSetDefaultFormat(TSITStringFormat format) +{ + if (format == kTSITStringFormatDefault) { + TSITStringDefaultFormat = kTSITStringFormatTNetstring; + } else { + TSITStringDefaultFormat = format; + } +} + +TSITStringFormat TSICTStringGetDefaultFormat(void) +{ + return TSITStringDefaultFormat; +} + + +void TSICTStringDestroy(TStringIRep* rep) +{ + CFRelease(rep->data); + free(rep->length); + free(rep); +} + + +static inline TStringIRep* TSICTStringCreateWithDataOfTypeAndFormat(CFDataRef data, TSITStringTag type, TSITStringFormat format) +{ + if (format == kTSITStringFormatDefault) { + format = TSICTStringGetDefaultFormat(); + } + + TStringIRep* rep = calloc(1, sizeof(TStringIRep)); + rep->data = CFDataCreateCopy(kCFAllocatorDefault, data); + rep->type = type; + rep->format = format; + rep->length = calloc(10, sizeof(char)); + + CFIndex len = CFDataGetLength(rep->data); + if (snprintf(rep->length, 10, "%lu", len)) { + return rep; + } else { + TSICTStringDestroy(rep); + return NULL; + } +} + +static inline CFDataRef TSICTStringCreateDataFromIntermediateRepresentation(TStringIRep* rep) +{ + CFIndex len = CFDataGetLength(rep->data); + CFMutableDataRef buffer = CFDataCreateMutableCopy(kCFAllocatorDefault, (len + 12), rep->data); + UInt8* bufferBytes = CFDataGetMutableBytePtr(buffer); + + size_t prefixLength = strlen(rep->length) + 1; + CFDataReplaceBytes(buffer, BeginningRange, (const UInt8*)rep->length, (CFIndex)prefixLength); + + if (rep->format == kTSITStringFormatTNetstring) { + const UInt8 ftag = (UInt8)TNetstringTypes[rep->type]; + CFDataAppendBytes(buffer, &ftag, 1); + bufferBytes[(prefixLength - 1)] = TNetstringSeparator; + } else if (rep->format == kTSITStringFormatOTNetstring) { + const UInt8 ftag = (UInt8)OTNetstringTypes[rep->type]; + bufferBytes[(prefixLength - 1)] = ftag; + } + + CFDataRef dataRep = CFDataCreateCopy(kCFAllocatorDefault, buffer); + CFRelease(buffer); + + return dataRep; +} + +static inline CFStringRef TSICTStringCreateStringFromIntermediateRepresentation(TStringIRep* rep) +{ + CFDataRef data = TSICTStringCreateDataFromIntermediateRepresentation(rep); + CFStringRef string = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, data, kCFStringEncodingUTF8); + CFRelease(data); + return string; +} + +static inline void TSICTStringAppendObjectToMutableDataWithFormat(CFTypeRef object, CFMutableDataRef buffer, TSITStringFormat format) +{ + if (object == NULL) { + object = kCFNull; + } + + CFRetain(object); + + TStringIRep* objRep = TSICTStringCreateWithObjectAndFormat(object, format); + CFDataRef objData = TSICTStringCreateDataFromIntermediateRepresentation(objRep); + CFDataAppendBytes(buffer, (CFDataGetBytePtr(objData)), CFDataGetLength(objData)); + CFRelease(objData); + TSICTStringDestroy(objRep); + + CFRelease(object); +} + +static void ArrayBufferAppendCallback(const void* item, void* context) +{ + TStringCollectionCallbackContext* cx = (TStringCollectionCallbackContext*)context; + CFMutableDataRef buffer = cx->buffer; + TSITStringFormat format = cx->format; + + TSICTStringAppendObjectToMutableDataWithFormat(item, buffer, format); +} + +static void DictionaryBufferAppendCallback(const void* key, const void* value, void* context) +{ + TStringCollectionCallbackContext* cx = (TStringCollectionCallbackContext*)context; + CFMutableDataRef buffer = cx->buffer; + TSITStringFormat format = cx->format; + + TSICTStringAppendObjectToMutableDataWithFormat(key, buffer, format); + TSICTStringAppendObjectToMutableDataWithFormat(value, buffer, format); +} + + +CFDataRef TSICTStringCreateRenderedData(TStringIRep* rep) +{ + return TSICTStringCreateDataFromIntermediateRepresentation(rep); +} + +CFDataRef TSICTStringCreateRenderedDataFromObjectWithFormat(CFTypeRef object, TSITStringFormat format) +{ + if (object == NULL) { + object = kCFNull; + } + + CFRetain(object); + + TStringIRep* rep = TSICTStringCreateWithObjectAndFormat(object, format); + CFDataRef data = TSICTStringCreateDataFromIntermediateRepresentation(rep); + + TSICTStringDestroy(rep); + CFRelease(object); + + return data; +} + +CFStringRef TSICTStringCreateRenderedString(TStringIRep* rep) +{ + return TSICTStringCreateStringFromIntermediateRepresentation(rep); +} + +CFStringRef TSICTStringCreateRenderedStringFromObjectWithFormat(CFTypeRef object, TSITStringFormat format) +{ + if (object == NULL) { + object = kCFNull; + } + + CFRetain(object); + + TStringIRep* rep = TSICTStringCreateWithObjectAndFormat(object, format); + CFStringRef string = TSICTStringCreateStringFromIntermediateRepresentation(rep); + + TSICTStringDestroy(rep); + CFRelease(object); + + return string; +} + + +TStringIRep* TSICTStringCreateWithObjectAndFormat(CFTypeRef object, TSITStringFormat format) +{ + if (object == NULL) { + return TSICTStringCreateNullWithFormat(format); + } + CFRetain(object); + + CFTypeID cfType = CFGetTypeID(object); + TStringIRep* rep = NULL; + + if (cfType == kCFDataTypeID) { + rep = TSICTStringCreateWithDataOfTypeAndFormat(object, kTSITStringTagString, format); + } else if (cfType == kCFStringTypeID) { + rep = TSICTStringCreateWithStringAndFormat(object, format); + } else if (cfType == kCFNumberTypeID) { + rep = TSICTStringCreateWithNumberAndFormat(object, format); + } else if (cfType == kCFBooleanTypeID) { + if (CFBooleanGetValue(object)) { + rep = TSICTStringCreateTrueWithFormat(format); + } else { + rep = TSICTStringCreateFalseWithFormat(format); + } + } else if (cfType == kCFNullTypeID) { + rep = TSICTStringCreateNullWithFormat(format); + } else if (cfType == kCFArrayTypeID) { + rep = TSICTStringCreateWithArrayAndFormat(object, format); + } else if (cfType == kCFDictionaryTypeID) { + rep = TSICTStringCreateWithDictionaryAndFormat(object, format); + } else { + rep = TSICTStringCreateInvalidWithFormat(format); + } + + CFRelease(object); + return rep; +} + +TStringIRep* TSICTStringCreateWithStringAndFormat(CFStringRef string, TSITStringFormat format) +{ + CFRetain(string); + CFDataRef data = CFStringCreateExternalRepresentation(kCFAllocatorDefault, string, kCFStringEncodingUTF8, '?'); + TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(data, kTSITStringTagString, format); + CFRelease(data); + CFRelease(string); + return rep; +} + +TStringIRep* TSICTStringCreateWithNumberAndFormat(CFNumberRef number, TSITStringFormat format) +{ + CFRetain(number); + TSITStringTag tag = kTSITStringTagNumber; + CFDataRef data; + CFNumberType numType = CFNumberGetType(number); + + switch(numType) { + case kCFNumberCharType: + { + int value; + if (CFNumberGetValue(number, kCFNumberIntType, &value)) { + if (value == 0 || value == 1) { + tag = kTSITStringTagBool; + } else { + tag = kTSITStringTagString; + } + } + break; + } + case kCFNumberFloat32Type: + case kCFNumberFloat64Type: + case kCFNumberFloatType: + case kCFNumberDoubleType: + { + tag = kTSITStringTagFloat; + break; + } + } + + if (tag == kTSITStringTagBool) { + bool value; + CFNumberGetValue(number, kCFNumberIntType, &value); + if (value) { + data = CFDataCreate(kCFAllocatorDefault, (UInt8*)"true", 4); + } else { + data = CFDataCreate(kCFAllocatorDefault, (UInt8*)"false", 5); + } + } else if (tag == kTSITStringTagFloat) { + char buf[32]; + char *p, *e; + double value; + + CFNumberGetValue(number, numType, &value); + sprintf(buf, "%#.15g", value); + + e = buf + strlen(buf); + p = e; + while (p[-1]=='0' && ('0' <= p[-2] && p[-2] <= '9')) { + p--; + } + memmove(p, e, strlen(e)+1); + + data = CFDataCreate(kCFAllocatorDefault, (UInt8*)buf, (CFIndex)strlen(buf)); + } else { + char buf[32]; + SInt64 value; + CFNumberGetValue(number, numType, &value); + sprintf(buf, "%lli", value); + data = CFDataCreate(kCFAllocatorDefault, (UInt8*)buf, (CFIndex)strlen(buf)); + } + + TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(data, tag, format); + CFRelease(data); + CFRelease(number); + return rep; +} + +TStringIRep* TSICTStringCreateTrueWithFormat(TSITStringFormat format) +{ + CFDataRef data = CFDataCreate(kCFAllocatorDefault, (UInt8*)"true", 4); + TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(data, kTSITStringTagBool, format); + CFRelease(data); + return rep; +} + +TStringIRep* TSICTStringCreateFalseWithFormat(TSITStringFormat format) +{ + CFDataRef data = CFDataCreate(kCFAllocatorDefault, (UInt8*)"false", 5); + TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(data, kTSITStringTagBool, format); + CFRelease(data); + return rep; +} + +TStringIRep* TSICTStringCreateNullWithFormat(TSITStringFormat format) +{ + CFDataRef data = CFDataCreate(kCFAllocatorDefault, NULL, 0); + TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(data, kTSITStringTagNull, format); + CFRelease(data); + return rep; +} + +TStringIRep* TSICTStringCreateInvalidWithFormat(TSITStringFormat format) +{ + CFDataRef data = CFDataCreate(kCFAllocatorDefault, NULL, 0); + TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(data, kTSITStringTagInvalid, format); + CFRelease(data); + return rep; +} + +TStringIRep* TSICTStringCreateWithArrayAndFormat(CFArrayRef array, TSITStringFormat format) +{ + CFRetain(array); + + CFMutableDataRef buffer = CFDataCreateMutable(kCFAllocatorDefault, 0); + + CFRange all = CFRangeMake(0, CFArrayGetCount(array)); + TStringCollectionCallbackContext cx = {buffer, format}; + CFArrayApplyFunction(array, all, ArrayBufferAppendCallback, &cx); + + TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(buffer, kTSITStringTagList, format); + CFRelease(buffer); + CFRelease(array); + return rep; +} + +TStringIRep* TSICTStringCreateWithDictionaryAndFormat(CFDictionaryRef dictionary, TSITStringFormat format) +{ + CFRetain(dictionary); + + CFMutableDataRef buffer = CFDataCreateMutable(kCFAllocatorDefault, 0); + + TStringCollectionCallbackContext cx = {buffer, format}; + CFDictionaryApplyFunction(dictionary, DictionaryBufferAppendCallback, &cx); + + TStringIRep* rep = TSICTStringCreateWithDataOfTypeAndFormat(buffer, kTSITStringTagDict, format); + CFRelease(buffer); + CFRelease(dictionary); + return rep; +} diff --git a/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/TSICTString.h b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/TSICTString.h new file mode 100644 index 00000000..daf085c3 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/TSICTString.h @@ -0,0 +1,74 @@ +// +// TSICTString.h +// TSITString +// +// Created by Travis Tilley on 9/27/11. +// + +#ifndef TSICTString_H +#define TSICTString_H + +#include + + +typedef enum { + kTSITStringTagString = 0, + kTSITStringTagNumber = 1, + kTSITStringTagFloat = 2, + kTSITStringTagBool = 3, + kTSITStringTagNull = 4, + kTSITStringTagDict = 5, + kTSITStringTagList = 6, + kTSITStringTagInvalid = 7, +} TSITStringTag; + +extern const char* const TNetstringTypes; +extern const char* const OTNetstringTypes; +extern const UInt8 TNetstringSeparator; + +typedef enum { + kTSITStringFormatDefault = 0, + kTSITStringFormatOTNetstring = 1, + kTSITStringFormatTNetstring = 2, +} TSITStringFormat; + +extern TSITStringFormat TSITStringDefaultFormat; + +typedef struct TSITStringIntermediate { + CFDataRef data; + char* length; + TSITStringTag type; + TSITStringFormat format; +} TStringIRep; + +typedef struct { + CFMutableDataRef buffer; + TSITStringFormat format; +} TStringCollectionCallbackContext; + + +void Init_TSICTString(void); + +void TSICTStringSetDefaultFormat(TSITStringFormat format); +TSITStringFormat TSICTStringGetDefaultFormat(void); + +void TSICTStringDestroy(TStringIRep* rep); + +CFDataRef TSICTStringCreateRenderedData(TStringIRep* rep); +CFDataRef TSICTStringCreateRenderedDataFromObjectWithFormat(CFTypeRef object, TSITStringFormat format); + +CFStringRef TSICTStringCreateRenderedString(TStringIRep* rep); +CFStringRef TSICTStringCreateRenderedStringFromObjectWithFormat(CFTypeRef object, TSITStringFormat format); + +TStringIRep* TSICTStringCreateWithObjectAndFormat(CFTypeRef object, TSITStringFormat format); +TStringIRep* TSICTStringCreateWithStringAndFormat(CFStringRef string, TSITStringFormat format); +TStringIRep* TSICTStringCreateWithNumberAndFormat(CFNumberRef number, TSITStringFormat format); +TStringIRep* TSICTStringCreateTrueWithFormat(TSITStringFormat format); +TStringIRep* TSICTStringCreateFalseWithFormat(TSITStringFormat format); +TStringIRep* TSICTStringCreateNullWithFormat(TSITStringFormat format); +TStringIRep* TSICTStringCreateInvalidWithFormat(TSITStringFormat format); +TStringIRep* TSICTStringCreateWithArrayAndFormat(CFArrayRef array, TSITStringFormat format); +TStringIRep* TSICTStringCreateWithDictionaryAndFormat(CFDictionaryRef dictionary, TSITStringFormat format); + + +#endif diff --git a/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/cli.c b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/cli.c new file mode 100644 index 00000000..6d36dd13 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/cli.c @@ -0,0 +1,201 @@ +#include +#include "cli.h" + +const char* cli_info_purpose = "A flexible command-line interface for the FSEvents API"; +const char* cli_info_usage = "Usage: fsevent_watch [OPTIONS]... [PATHS]..."; +const char* cli_info_help[] = { + " -h, --help you're looking at it", + " -V, --version print version number and exit", + " -p, --show-plist display the embedded Info.plist values", + " -s, --since-when=EventID fire historical events since ID", + " -l, --latency=seconds latency period (default='0.5')", + " -n, --no-defer enable no-defer latency modifier", + " -r, --watch-root watch for when the root path has changed", + // " -i, --ignore-self ignore current process", + " -F, --file-events provide file level event data", + " -f, --format=name output format (classic, niw, \n" + " tnetstring, otnetstring)", + 0 +}; + +static void default_args (struct cli_info* args_info) +{ + args_info->since_when_arg = kFSEventStreamEventIdSinceNow; + args_info->latency_arg = 0.5; + args_info->no_defer_flag = false; + args_info->watch_root_flag = false; + args_info->ignore_self_flag = false; + args_info->file_events_flag = false; + args_info->mark_self_flag = false; + args_info->format_arg = kFSEventWatchOutputFormatOTNetstring; +} + +static void cli_parser_release (struct cli_info* args_info) +{ + unsigned int i; + + for (i=0; i < args_info->inputs_num; ++i) { + free(args_info->inputs[i]); + } + + if (args_info->inputs_num) { + free(args_info->inputs); + } + + args_info->inputs_num = 0; +} + +void cli_parser_init (struct cli_info* args_info) +{ + default_args(args_info); + + args_info->inputs = 0; + args_info->inputs_num = 0; +} + +void cli_parser_free (struct cli_info* args_info) +{ + cli_parser_release(args_info); +} + +static void cli_print_info_dict (const void *key, + const void *value, + void *context) +{ + CFStringRef entry = CFStringCreateWithFormat(NULL, NULL, + CFSTR("%@:\n %@"), key, value); + if (entry) { + CFShow(entry); + CFRelease(entry); + } +} + +void cli_show_plist (void) +{ + CFBundleRef mainBundle = CFBundleGetMainBundle(); + CFRetain(mainBundle); + CFDictionaryRef mainBundleDict = CFBundleGetInfoDictionary(mainBundle); + if (mainBundleDict) { + CFRetain(mainBundleDict); + printf("Embedded Info.plist metadata:\n\n"); + CFDictionaryApplyFunction(mainBundleDict, cli_print_info_dict, NULL); + CFRelease(mainBundleDict); + } + CFRelease(mainBundle); + printf("\n"); +} + +void cli_print_version (void) +{ + printf("%s %s\n\n", CLI_NAME, CLI_VERSION); +#ifdef COMPILED_AT + printf("Compiled at: %s\n", COMPILED_AT); +#endif +#ifdef COMPILER + printf("Compiled with: %s\n", COMPILER); +#endif +#ifdef TARGET_CPU + printf("Compiled for: %s\n", TARGET_CPU); +#endif + printf("\n"); +} + +void cli_print_help (void) +{ + cli_print_version(); + + printf("\n%s\n", cli_info_purpose); + printf("\n%s\n", cli_info_usage); + printf("\n"); + + int i = 0; + while (cli_info_help[i]) { + printf("%s\n", cli_info_help[i++]); + } +} + +int cli_parser (int argc, const char** argv, struct cli_info* args_info) +{ + static struct option longopts[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "show-plist", no_argument, NULL, 'p' }, + { "since-when", required_argument, NULL, 's' }, + { "latency", required_argument, NULL, 'l' }, + { "no-defer", no_argument, NULL, 'n' }, + { "watch-root", no_argument, NULL, 'r' }, + { "ignore-self", no_argument, NULL, 'i' }, + { "file-events", no_argument, NULL, 'F' }, + { "mark-self", no_argument, NULL, 'm' }, + { "format", required_argument, NULL, 'f' }, + { 0, 0, 0, 0 } + }; + + const char* shortopts = "hVps:l:nriFf:"; + + int c = -1; + + while ((c = getopt_long(argc, (char * const*)argv, shortopts, longopts, NULL)) != -1) { + switch(c) { + case 's': // since-when + args_info->since_when_arg = strtoull(optarg, NULL, 0); + break; + case 'l': // latency + args_info->latency_arg = strtod(optarg, NULL); + break; + case 'n': // no-defer + args_info->no_defer_flag = true; + break; + case 'r': // watch-root + args_info->watch_root_flag = true; + break; + case 'i': // ignore-self + args_info->ignore_self_flag = true; + break; + case 'F': // file-events + args_info->file_events_flag = true; + break; + case 'm': // mark-self + args_info->mark_self_flag = true; + break; + case 'f': // format + if (strcmp(optarg, "classic") == 0) { + args_info->format_arg = kFSEventWatchOutputFormatClassic; + } else if (strcmp(optarg, "niw") == 0) { + args_info->format_arg = kFSEventWatchOutputFormatNIW; + } else if (strcmp(optarg, "tnetstring") == 0) { + args_info->format_arg = kFSEventWatchOutputFormatTNetstring; + } else if (strcmp(optarg, "otnetstring") == 0) { + args_info->format_arg = kFSEventWatchOutputFormatOTNetstring; + } else { + fprintf(stderr, "Unknown output format: %s\n", optarg); + exit(EXIT_FAILURE); + } + break; + case 'V': // version + cli_print_version(); + exit(EXIT_SUCCESS); + case 'p': // show-plist + cli_show_plist(); + exit(EXIT_SUCCESS); + case 'h': // help + case '?': // invalid option + case ':': // missing argument + cli_print_help(); + exit((c == 'h') ? EXIT_SUCCESS : EXIT_FAILURE); + } + } + + if (optind < argc) { + int i = 0; + args_info->inputs_num = (unsigned int)(argc - optind); + args_info->inputs = + (char**)(malloc ((args_info->inputs_num)*sizeof(char*))); + while (optind < argc) + if (argv[optind++] != argv[0]) { + args_info->inputs[i++] = strdup(argv[optind-1]); + } + } + + return EXIT_SUCCESS; +} diff --git a/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/cli.h b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/cli.h new file mode 100644 index 00000000..2164995a --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/cli.h @@ -0,0 +1,45 @@ +#ifndef CLI_H +#define CLI_H + +#include "common.h" + +#ifndef CLI_NAME +#define CLI_NAME "fsevent_watch" +#endif /* CLI_NAME */ + +#ifndef PROJECT_VERSION +#error "PROJECT_VERSION not set" +#endif /* PROJECT_VERSION */ + +#ifndef CLI_VERSION +#define CLI_VERSION _xstr(PROJECT_VERSION) +#endif /* CLI_VERSION */ + + +struct cli_info { + UInt64 since_when_arg; + double latency_arg; + bool no_defer_flag; + bool watch_root_flag; + bool ignore_self_flag; + bool file_events_flag; + bool mark_self_flag; + enum FSEventWatchOutputFormat format_arg; + + char** inputs; + unsigned inputs_num; +}; + +extern const char* cli_info_purpose; +extern const char* cli_info_usage; +extern const char* cli_info_help[]; + +void cli_print_help(void); +void cli_print_version(void); + +int cli_parser (int argc, const char** argv, struct cli_info* args_info); +void cli_parser_init (struct cli_info* args_info); +void cli_parser_free (struct cli_info* args_info); + + +#endif /* CLI_H */ diff --git a/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/common.h b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/common.h new file mode 100644 index 00000000..b2d3e4eb --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/common.h @@ -0,0 +1,22 @@ +#ifndef fsevent_watch_common_h +#define fsevent_watch_common_h + +#include +#ifdef __OBJC__ +#import +#endif + +#include +#include +#include "compat.h" +#include "defines.h" +#include "TSICTString.h" + +enum FSEventWatchOutputFormat { + kFSEventWatchOutputFormatClassic, + kFSEventWatchOutputFormatNIW, + kFSEventWatchOutputFormatTNetstring, + kFSEventWatchOutputFormatOTNetstring +}; + +#endif /* fsevent_watch_common_h */ diff --git a/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/compat.c b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/compat.c new file mode 100644 index 00000000..5f51baff --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/compat.c @@ -0,0 +1,41 @@ +#include "compat.h" + + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_6) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_6_0) +FSEventStreamCreateFlags kFSEventStreamCreateFlagIgnoreSelf = 0x00000008; +#endif + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_7) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_6_0) +FSEventStreamCreateFlags kFSEventStreamCreateFlagFileEvents = 0x00000010; +FSEventStreamEventFlags kFSEventStreamEventFlagItemCreated = 0x00000100; +FSEventStreamEventFlags kFSEventStreamEventFlagItemRemoved = 0x00000200; +FSEventStreamEventFlags kFSEventStreamEventFlagItemInodeMetaMod = 0x00000400; +FSEventStreamEventFlags kFSEventStreamEventFlagItemRenamed = 0x00000800; +FSEventStreamEventFlags kFSEventStreamEventFlagItemModified = 0x00001000; +FSEventStreamEventFlags kFSEventStreamEventFlagItemFinderInfoMod = 0x00002000; +FSEventStreamEventFlags kFSEventStreamEventFlagItemChangeOwner = 0x00004000; +FSEventStreamEventFlags kFSEventStreamEventFlagItemXattrMod = 0x00008000; +FSEventStreamEventFlags kFSEventStreamEventFlagItemIsFile = 0x00010000; +FSEventStreamEventFlags kFSEventStreamEventFlagItemIsDir = 0x00020000; +FSEventStreamEventFlags kFSEventStreamEventFlagItemIsSymlink = 0x00040000; +#endif + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_9) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_7_0) +FSEventStreamCreateFlags kFSEventStreamCreateFlagMarkSelf = 0x00000020; +FSEventStreamEventFlags kFSEventStreamEventFlagOwnEvent = 0x00080000; +#endif + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_10) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_9_0) +FSEventStreamEventFlags kFSEventStreamEventFlagItemIsHardlink = 0x00100000; +FSEventStreamEventFlags kFSEventStreamEventFlagItemIsLastHardlink = 0x00200000; +#endif + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_13) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_11_0) +FSEventStreamCreateFlags kFSEventStreamCreateFlagUseExtendedData = 0x00000040; +FSEventStreamEventFlags kFSEventStreamEventFlagItemCloned = 0x00400000; +#endif diff --git a/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/compat.h b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/compat.h new file mode 100644 index 00000000..757b4135 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/compat.h @@ -0,0 +1,100 @@ +/** + * @headerfile compat.h + * FSEventStream flag compatibility shim + * + * In order to compile a binary against an older SDK yet still support the + * features present in later OS releases, we need to define any missing enum + * constants not present in the older SDK. This allows us to safely defer + * feature detection to runtime (and avoid recompilation). + */ + + +#ifndef listen_fsevents_compat_h +#define listen_fsevents_compat_h + +#ifndef __CORESERVICES__ +#include +#endif // __CORESERVICES__ + +#ifndef __AVAILABILITY__ +#include +#endif // __AVAILABILITY__ + +#ifndef __MAC_10_6 +#define __MAC_10_6 1060 +#endif +#ifndef __MAC_10_7 +#define __MAC_10_7 1070 +#endif +#ifndef __MAC_10_9 +#define __MAC_10_9 1090 +#endif +#ifndef __MAC_10_10 +#define __MAC_10_10 101000 +#endif +#ifndef __MAC_10_13 +#define __MAC_10_13 101300 +#endif +#ifndef __IPHONE_6_0 +#define __IPHONE_6_0 60000 +#endif +#ifndef __IPHONE_7_0 +#define __IPHONE_7_0 70000 +#endif +#ifndef __IPHONE_9_0 +#define __IPHONE_9_0 90000 +#endif +#ifndef __IPHONE_11_0 +#define __IPHONE_11_0 110000 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_6) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_6_0) +extern FSEventStreamCreateFlags kFSEventStreamCreateFlagIgnoreSelf; +#endif + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_7) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_6_0) +extern FSEventStreamCreateFlags kFSEventStreamCreateFlagFileEvents; +extern FSEventStreamEventFlags kFSEventStreamEventFlagItemCreated, + kFSEventStreamEventFlagItemRemoved, + kFSEventStreamEventFlagItemInodeMetaMod, + kFSEventStreamEventFlagItemRenamed, + kFSEventStreamEventFlagItemModified, + kFSEventStreamEventFlagItemFinderInfoMod, + kFSEventStreamEventFlagItemChangeOwner, + kFSEventStreamEventFlagItemXattrMod, + kFSEventStreamEventFlagItemIsFile, + kFSEventStreamEventFlagItemIsDir, + kFSEventStreamEventFlagItemIsSymlink; +#endif + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_9) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_7_0) +extern FSEventStreamCreateFlags kFSEventStreamCreateFlagMarkSelf; +extern FSEventStreamEventFlags kFSEventStreamEventFlagOwnEvent; +#endif + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_10) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_9_0) +extern FSEventStreamEventFlags kFSEventStreamEventFlagItemIsHardlink, + kFSEventStreamEventFlagItemIsLastHardlink; +#endif + +#if (defined(MAC_OS_X_VERSION_MAX_ALLOWED) && MAC_OS_X_VERSION_MAX_ALLOWED < __MAC_10_13) || \ + (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_11_0) +extern FSEventStreamCreateFlags kFSEventStreamCreateFlagUseExtendedData; +extern FSEventStreamEventFlags kFSEventStreamEventFlagItemCloned; +#endif + + +#ifdef __cplusplus +} +#endif + +#endif // listen_fsevents_compat_h diff --git a/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/defines.h b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/defines.h new file mode 100644 index 00000000..6f6e87b0 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/defines.h @@ -0,0 +1,40 @@ +#ifndef fsevent_watch_defines_h +#define fsevent_watch_defines_h + +#define _str(s) #s +#define _xstr(s) _str(s) + +#define COMPILED_AT __DATE__ " " __TIME__ + +#if defined (__clang__) +#define COMPILER "clang " __clang_version__ +#elif defined (__GNUC__) +#define COMPILER "gcc " __VERSION__ +#else +#define COMPILER "unknown" +#endif + +#if defined(__ppc__) +#define TARGET_CPU "ppc" +#elif defined(__ppc64__) +#define TARGET_CPU "ppc64" +#elif defined(__i386__) +#define TARGET_CPU "i386" +#elif defined(__x86_64__) +#define TARGET_CPU "x86_64" +#else +#define TARGET_CPU "unknown" +#endif + +#define FLAG_CHECK(flags, flag) ((flags) & (flag)) + +#define FPRINTF_FLAG_CHECK(flags, flag, msg, fd) \ + do { \ + if (FLAG_CHECK(flags, flag)) { \ + fprintf(fd, "%s", msg "\n"); } } \ + while (0) + +#define FLAG_CHECK_STDERR(flags, flag, msg) \ + FPRINTF_FLAG_CHECK(flags, flag, msg, stderr) + +#endif /* fsevent_watch_defines_h */ diff --git a/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/main.c b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/main.c new file mode 100644 index 00000000..b18596a6 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/main.c @@ -0,0 +1,548 @@ +#include "common.h" +#include "signal_handlers.h" +#include "cli.h" +#include "FSEventsFix.h" + +// TODO: set on fire. cli.{h,c} handle both parsing and defaults, so there's +// no need to set those here. also, in order to scope metadata by path, +// each stream will need its own configuration... so this won't work as +// a global any more. In the end the goal is to make the output format +// able to declare not just that something happened and what flags were +// attached, but what path it was watching that caused those events (so +// that the path itself can be used for routing that information to the +// relevant callback). +// +// Structure for storing metadata parsed from the commandline +static struct { + FSEventStreamEventId sinceWhen; + CFTimeInterval latency; + FSEventStreamCreateFlags flags; + CFMutableArrayRef paths; + enum FSEventWatchOutputFormat format; +} config = { + (UInt64) kFSEventStreamEventIdSinceNow, + (double) 0.3, + (CFOptionFlags) kFSEventStreamCreateFlagNone, + NULL, + kFSEventWatchOutputFormatOTNetstring +}; + +// Prototypes +static void append_path(const char* path); +static inline void parse_cli_settings(int argc, const char* argv[]); +static void callback(FSEventStreamRef streamRef, + void* clientCallBackInfo, + size_t numEvents, + void* eventPaths, + const FSEventStreamEventFlags eventFlags[], + const FSEventStreamEventId eventIds[]); +static bool needs_fsevents_fix = false; + +// Resolve a path and append it to the CLI settings structure +// The FSEvents API will, internally, resolve paths using a similar scheme. +// Performing this ahead of time makes things less confusing, IMHO. +static void append_path(const char* path) +{ +#ifdef DEBUG + fprintf(stderr, "\n"); + fprintf(stderr, "append_path called for: %s\n", path); +#endif + +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 + +#ifdef DEBUG + fprintf(stderr, "compiled against 10.6+, using CFURLCreateFileReferenceURL\n"); +#endif + + CFURLRef url = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8*)path, (CFIndex)strlen(path), false); + CFURLRef placeholder = CFURLCopyAbsoluteURL(url); + CFRelease(url); + + CFMutableArrayRef imaginary = NULL; + + // if we don't have an existing url, spin until we get to a parent that + // does exist, saving any imaginary components for appending back later + while(!CFURLResourceIsReachable(placeholder, NULL)) { +#ifdef DEBUG + fprintf(stderr, "path does not exist\n"); +#endif + + CFStringRef child; + + if (imaginary == NULL) { + imaginary = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); + } + + child = CFURLCopyLastPathComponent(placeholder); + CFArrayInsertValueAtIndex(imaginary, 0, child); + CFRelease(child); + + url = CFURLCreateCopyDeletingLastPathComponent(NULL, placeholder); + CFRelease(placeholder); + placeholder = url; + +#ifdef DEBUG + fprintf(stderr, "parent: "); + CFShow(placeholder); +#endif + } + +#ifdef DEBUG + fprintf(stderr, "path exists\n"); +#endif + + // realpath() doesn't always return the correct case for a path, so this + // is a funky workaround that converts a path into a (volId/inodeId) pair + // and asks what the path should be for that. since it looks at the actual + // inode instead of returning the same case passed in like realpath() + // appears to do for HFS+, it should always be correct. + url = CFURLCreateFileReferenceURL(NULL, placeholder, NULL); + CFRelease(placeholder); + placeholder = CFURLCreateFilePathURL(NULL, url, NULL); + CFRelease(url); + +#ifdef DEBUG + fprintf(stderr, "path resolved to: "); + CFShow(placeholder); +#endif + + // if we stripped off any imaginary path components, append them back on + if (imaginary != NULL) { + CFIndex count = CFArrayGetCount(imaginary); + for (CFIndex i = 0; i= 6)) { + config.flags |= kFSEventStreamCreateFlagIgnoreSelf; + } else { + fprintf(stderr, "MacOSX 10.6 or later is required for --ignore-self\n"); + exit(EXIT_FAILURE); + } + } + + if (args_info.file_events_flag) { + if ((osMajorVersion == 10) & (osMinorVersion >= 7)) { + config.flags |= kFSEventStreamCreateFlagFileEvents; + } else { + fprintf(stderr, "MacOSX 10.7 or later required for --file-events\n"); + exit(EXIT_FAILURE); + } + } + + if (args_info.mark_self_flag) { + if ((osMajorVersion == 10) & (osMinorVersion >= 9)) { + config.flags |= kFSEventStreamCreateFlagMarkSelf; + } else { + fprintf(stderr, "MacOSX 10.9 or later required for --mark-self\n"); + exit(EXIT_FAILURE); + } + } + + if (args_info.inputs_num == 0) { + append_path("."); + } else { + for (unsigned int i=0; i < args_info.inputs_num; ++i) { + append_path(args_info.inputs[i]); + } + } + + cli_parser_free(&args_info); + +#ifdef DEBUG + fprintf(stderr, "config.sinceWhen %llu\n", config.sinceWhen); + fprintf(stderr, "config.latency %f\n", config.latency); + +// STFU clang +#if defined(__LP64__) + fprintf(stderr, "config.flags %#.8x\n", config.flags); +#else + fprintf(stderr, "config.flags %#.8lx\n", config.flags); +#endif + + FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagUseCFTypes, + " Using CF instead of C types"); + FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagNoDefer, + " NoDefer latency modifier enabled"); + FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagWatchRoot, + " WatchRoot notifications enabled"); + FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagIgnoreSelf, + " IgnoreSelf enabled"); + FLAG_CHECK_STDERR(config.flags, kFSEventStreamCreateFlagFileEvents, + " FileEvents enabled"); + + fprintf(stderr, "config.paths\n"); + + long numpaths = CFArrayGetCount(config.paths); + + for (long i = 0; i < numpaths; i++) { + char path[PATH_MAX]; + CFStringGetCString(CFArrayGetValueAtIndex(config.paths, i), + path, + PATH_MAX, + kCFStringEncodingUTF8); + fprintf(stderr, " %s\n", path); + } + + fprintf(stderr, "\n"); +#endif +} + +// original output format for rb-fsevent +static void classic_output_format(size_t numEvents, + char** paths) +{ + for (size_t i = 0; i < numEvents; i++) { + fprintf(stdout, "%s:", paths[i]); + } + fprintf(stdout, "\n"); +} + +// output format used in the Yoshimasa Niwa branch of rb-fsevent +static void niw_output_format(size_t numEvents, + char** paths, + const FSEventStreamEventFlags eventFlags[], + const FSEventStreamEventId eventIds[]) +{ + for (size_t i = 0; i < numEvents; i++) { + fprintf(stdout, "%lu:%llu:%s\n", + (unsigned long)eventFlags[i], + (unsigned long long)eventIds[i], + paths[i]); + } + fprintf(stdout, "\n"); +} + +static void tstring_output_format(size_t numEvents, + char** paths, + const FSEventStreamEventFlags eventFlags[], + const FSEventStreamEventId eventIds[], + TSITStringFormat format) +{ + CFMutableArrayRef events = CFArrayCreateMutable(kCFAllocatorDefault, + 0, &kCFTypeArrayCallBacks); + + for (size_t i = 0; i < numEvents; i++) { + CFMutableDictionaryRef event = CFDictionaryCreateMutable(kCFAllocatorDefault, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + CFStringRef path = CFStringCreateWithBytes(kCFAllocatorDefault, + (const UInt8*)paths[i], + (CFIndex)strlen(paths[i]), + kCFStringEncodingUTF8, + false); + CFDictionarySetValue(event, CFSTR("path"), path); + + CFNumberRef ident = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &eventIds[i]); + CFDictionarySetValue(event, CFSTR("id"), ident); + + CFNumberRef cflags = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &eventFlags[i]); + CFDictionarySetValue(event, CFSTR("cflags"), cflags); + + CFMutableArrayRef flags = CFArrayCreateMutable(kCFAllocatorDefault, + 0, &kCFTypeArrayCallBacks); + +#define FLAG_ADD_NAME(flagsnum, flagnum, flagname, flagarray) \ + do { \ + if (FLAG_CHECK(flagsnum, flagnum)) { \ + CFArrayAppendValue(flagarray, CFSTR(flagname)); } } \ + while(0) + + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagMustScanSubDirs, "MustScanSubDirs", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagUserDropped, "UserDropped", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagKernelDropped, "KernelDropped", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagEventIdsWrapped, "EventIdsWrapped", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagHistoryDone, "HistoryDone", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagRootChanged, "RootChanged", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagMount, "Mount", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagUnmount, "Unmount", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemCreated, "ItemCreated", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemRemoved, "ItemRemoved", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemInodeMetaMod, "ItemInodeMetaMod", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemRenamed, "ItemRenamed", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemModified, "ItemModified", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemFinderInfoMod, "ItemFinderInfoMod", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemChangeOwner, "ItemChangeOwner", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemXattrMod, "ItemXattrMod", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemIsFile, "ItemIsFile", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemIsDir, "ItemIsDir", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemIsSymlink, "ItemIsSymlink", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagOwnEvent, "OwnEvent", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemIsHardlink, "ItemIsHardLink", flags); + FLAG_ADD_NAME(eventFlags[i], kFSEventStreamEventFlagItemIsLastHardlink, "ItemIsLastHardLink", flags); + + CFDictionarySetValue(event, CFSTR("flags"), flags); + + + CFArrayAppendValue(events, event); + + CFRelease(event); + CFRelease(path); + CFRelease(ident); + CFRelease(cflags); + CFRelease(flags); + } + + CFMutableDictionaryRef meta = CFDictionaryCreateMutable(kCFAllocatorDefault, + 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + CFDictionarySetValue(meta, CFSTR("events"), events); + + CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &numEvents); + CFDictionarySetValue(meta, CFSTR("numEvents"), num); + + CFDataRef data = TSICTStringCreateRenderedDataFromObjectWithFormat(meta, format); + fprintf(stdout, "%s", CFDataGetBytePtr(data)); + + CFRelease(events); + CFRelease(num); + CFRelease(meta); + CFRelease(data); +} + +static void callback(__attribute__((unused)) FSEventStreamRef streamRef, + __attribute__((unused)) void* clientCallBackInfo, + size_t numEvents, + void* eventPaths, + const FSEventStreamEventFlags eventFlags[], + const FSEventStreamEventId eventIds[]) +{ + char** paths = eventPaths; + + +#ifdef DEBUG + fprintf(stderr, "\n"); + fprintf(stderr, "FSEventStreamCallback fired!\n"); + fprintf(stderr, " numEvents: %lu\n", numEvents); + + for (size_t i = 0; i < numEvents; i++) { + fprintf(stderr, "\n"); + fprintf(stderr, " event ID: %llu\n", eventIds[i]); + +// STFU clang +#if defined(__LP64__) + fprintf(stderr, " event flags: %#.8x\n", eventFlags[i]); +#else + fprintf(stderr, " event flags: %#.8lx\n", eventFlags[i]); +#endif + + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagMustScanSubDirs, + " Recursive scanning of directory required"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagUserDropped, + " Buffering problem: events dropped user-side"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagKernelDropped, + " Buffering problem: events dropped kernel-side"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagEventIdsWrapped, + " Event IDs have wrapped"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagHistoryDone, + " All historical events have been processed"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagRootChanged, + " Root path has changed"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagMount, + " A new volume was mounted at this path"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagUnmount, + " A volume was unmounted from this path"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemCreated, + " Item created"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemRemoved, + " Item removed"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemInodeMetaMod, + " Item metadata modified"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemRenamed, + " Item renamed"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemModified, + " Item modified"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemFinderInfoMod, + " Item Finder Info modified"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemChangeOwner, + " Item changed ownership"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemXattrMod, + " Item extended attributes modified"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemIsFile, + " Item is a file"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemIsDir, + " Item is a directory"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemIsSymlink, + " Item is a symbolic link"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemIsHardlink, + " Item is a hard link"); + FLAG_CHECK_STDERR(eventFlags[i], kFSEventStreamEventFlagItemIsLastHardlink, + " Item is the last hard link"); + fprintf(stderr, " event path: %s\n", paths[i]); + fprintf(stderr, "\n"); + } + + fprintf(stderr, "\n"); +#endif + + if (config.format == kFSEventWatchOutputFormatClassic) { + classic_output_format(numEvents, paths); + } else if (config.format == kFSEventWatchOutputFormatNIW) { + niw_output_format(numEvents, paths, eventFlags, eventIds); + } else if (config.format == kFSEventWatchOutputFormatTNetstring) { + tstring_output_format(numEvents, paths, eventFlags, eventIds, + kTSITStringFormatTNetstring); + } else if (config.format == kFSEventWatchOutputFormatOTNetstring) { + tstring_output_format(numEvents, paths, eventFlags, eventIds, + kTSITStringFormatOTNetstring); + } + + fflush(stdout); +} + +int main(int argc, const char* argv[]) +{ + install_signal_handlers(); + parse_cli_settings(argc, argv); + + if (needs_fsevents_fix) { + FSEventsFixEnable(); + } + + FSEventStreamContext context = {0, NULL, NULL, NULL, NULL}; + FSEventStreamRef stream; + stream = FSEventStreamCreate(kCFAllocatorDefault, + (FSEventStreamCallback)&callback, + &context, + config.paths, + config.sinceWhen, + config.latency, + config.flags); + +#ifdef DEBUG + FSEventStreamShow(stream); + fprintf(stderr, "\n"); +#endif + + if (needs_fsevents_fix) { + FSEventsFixDisable(); + } + + FSEventStreamScheduleWithRunLoop(stream, + CFRunLoopGetCurrent(), + kCFRunLoopDefaultMode); + FSEventStreamStart(stream); + CFRunLoopRun(); + FSEventStreamFlushSync(stream); + FSEventStreamStop(stream); + + return 0; +} diff --git a/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/signal_handlers.c b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/signal_handlers.c new file mode 100644 index 00000000..b20da3f3 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/signal_handlers.c @@ -0,0 +1,66 @@ +#include "signal_handlers.h" +#include +#include +#include +#include +#include +#include + + +#define PPID_ALARM_INTERVAL 2 // send SIGALRM every this seconds + + +static pid_t orig_ppid; + + +static void signal_handler(int _) { + exit(EXIT_FAILURE); +} + +static void check_ppid(void) { + if (getppid() != orig_ppid) { + exit(EXIT_FAILURE); + } +} + +static void check_stdout_open(void) { + if (fcntl(STDOUT_FILENO, F_GETFD) < 0) { + exit(EXIT_FAILURE); + } +} + +static void alarm_handler(int _) { + check_ppid(); + check_stdout_open(); + alarm(PPID_ALARM_INTERVAL); + signal(SIGALRM, alarm_handler); +} + +static void die(const char *msg) { + fprintf(stderr, "\nFATAL: %s\n", msg); + abort(); +} + +static void install_signal_handler(int sig, void (*handler)(int)) { + if (signal(sig, handler) == SIG_ERR) { + die("Could not install signal handler"); + } +} + +void install_signal_handlers(void) { + // check pipe is still connected + check_stdout_open(); + + // watch getppid() every PPID_ALARM_INTERVAL seconds + orig_ppid = getppid(); + if (orig_ppid <= 1) { + die("prematurely zombied"); + } + install_signal_handler(SIGALRM, alarm_handler); + alarm(PPID_ALARM_INTERVAL); + + // be sure to exit on SIGHUP, SIGPIPE + install_signal_handler(SIGHUP, signal_handler); + install_signal_handler(SIGPIPE, signal_handler); +} + diff --git a/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/signal_handlers.h b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/signal_handlers.h new file mode 100644 index 00000000..c31685d9 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/fsevent_watch/signal_handlers.h @@ -0,0 +1,16 @@ +/** + * @headerfile signal_handlers.h + * Signal handlers to stop the zombie hordes + * + * Catch and handle signals better so that we die faster like a good meat puppet. + */ + + +#ifndef fsevent_watch_signal_handlers_h +#define fsevent_watch_signal_handlers_h + + +void install_signal_handlers(void); + + +#endif // fsevent_watch_signal_handlers_h diff --git a/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/rakefile.rb b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/rakefile.rb new file mode 100644 index 00000000..d7789bdc --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/ext/rakefile.rb @@ -0,0 +1,226 @@ +# -*- encoding: utf-8 -*- +require 'rubygems' unless defined?(Gem) +require 'pathname' +require 'date' +require 'time' +require 'rake/clean' + +raise "unable to find xcodebuild" unless system('which', 'xcodebuild') + + +FSEVENT_WATCH_EXE_VERSION = '0.1.5' + +$this_dir = Pathname.new(__FILE__).dirname.expand_path +$final_exe = $this_dir.parent.join('bin/fsevent_watch') + +$src_dir = $this_dir.join('fsevent_watch') +$obj_dir = $this_dir.join('build') + +SRC = Pathname.glob("#{$src_dir}/*.c") +OBJ = SRC.map {|s| $obj_dir.join("#{s.basename('.c')}.o")} + +$now = DateTime.now.xmlschema rescue Time.now.xmlschema + +$CC = ENV['CC'] || `which clang || which gcc`.strip +$CFLAGS = ENV['CFLAGS'] || '-fconstant-cfstrings -fasm-blocks -fstrict-aliasing -Wall' +$ARCHFLAGS = ENV['ARCHFLAGS'] || '-arch x86_64' +$DEFINES = "-DNS_BUILD_32_LIKE_64 -DNS_BLOCK_ASSERTIONS -DPROJECT_VERSION=#{FSEVENT_WATCH_EXE_VERSION}" + +$GCC_C_LANGUAGE_STANDARD = ENV['GCC_C_LANGUAGE_STANDARD'] || 'gnu11' + +# generic developer id name so it'll match correctly for anyone who has only +# one developer id in their keychain (not that I expect anyone else to bother) +$CODE_SIGN_IDENTITY = 'Developer ID Application' + +$arch = `uname -m`.strip +$os_release = `uname -r`.strip +$BUILD_TRIPLE = "#{$arch}-apple-darwin#{$os_release}" + +$CCVersion = `#{$CC} --version | head -n 1`.strip + + +CLEAN.include OBJ.map(&:to_s) +CLEAN.include $obj_dir.join('Info.plist').to_s +CLEAN.include $obj_dir.join('fsevent_watch').to_s +CLOBBER.include $final_exe.to_s + + +task :sw_vers do + $mac_product_version = `sw_vers -productVersion`.strip + $mac_build_version = `sw_vers -buildVersion`.strip + $MACOSX_DEPLOYMENT_TARGET = ENV['MACOSX_DEPLOYMENT_TARGET'] || $mac_product_version.sub(/\.\d*$/, '') + $CFLAGS = "#{$CFLAGS} -mmacosx-version-min=#{$MACOSX_DEPLOYMENT_TARGET}" +end + +task :get_sdk_info => :sw_vers do + $SDK_INFO = {} + version_info = `xcodebuild -version -sdk macosx#{$MACOSX_DEPLOYMENT_TARGET}` + raise "invalid SDK" unless !!$?.exitstatus + version_info.strip.each_line do |line| + next if line.strip.empty? + next unless line.include?(':') + match = line.match(/([^:]*): (.*)/) + next unless match + $SDK_INFO[match[1]] = match[2] + end +end + +task :debug => :sw_vers do + $DEFINES = "-DDEBUG #{$DEFINES}" + $CFLAGS = "#{$CFLAGS} -O0 -fno-omit-frame-pointer -g" +end + +task :release => :sw_vers do + $DEFINES = "-DNDEBUG #{$DEFINES}" + $CFLAGS = "#{$CFLAGS} -Ofast" +end + +desc 'configure build type depending on whether ENV var FWDEBUG is set' +task :set_build_type => :sw_vers do + if ENV['FWDEBUG'] + Rake::Task[:debug].invoke + else + Rake::Task[:release].invoke + end +end + +desc 'set build arch to ppc' +task :ppc do + $ARCHFLAGS = '-arch ppc' +end + +desc 'set build arch to x86_64' +task :x86_64 do + $ARCHFLAGS = '-arch x86_64' +end + +desc 'set build arch to i386' +task :x86 do + $ARCHFLAGS = '-arch i386' +end + +task :setup_env => [:set_build_type, :sw_vers, :get_sdk_info] + +directory $obj_dir.to_s +file $obj_dir.to_s => :setup_env + +SRC.zip(OBJ).each do |source, object| + file object.to_s => [source.to_s, $obj_dir.to_s] do + cmd = [ + $CC, + $ARCHFLAGS, + "-std=#{$GCC_C_LANGUAGE_STANDARD}", + $CFLAGS, + $DEFINES, + "-I#{$src_dir}", + '-isysroot', + $SDK_INFO['Path'], + '-c', source, + '-o', object + ] + sh(cmd.map {|s| s.to_s}.join(' ')) + end +end + +file $obj_dir.join('Info.plist').to_s => [$obj_dir.to_s, :setup_env] do + File.open($obj_dir.join('Info.plist').to_s, 'w+') do |file| + indentation = '' + indent = lambda {|num| indentation = ' ' * num } + add = lambda {|str| file << "#{indentation}#{str}\n" } + key = lambda {|str| add["#{str}"] } + string = lambda {|str| add["#{str}"] } + + + add[''] + add[''] + add[''] + + indent[2] + add[''] + indent[4] + + key['CFBundleExecutable'] + string['fsevent_watch'] + key['CFBundleIdentifier'] + string['com.teaspoonofinsanity.fsevent_watch'] + key['CFBundleName'] + string['fsevent_watch'] + key['CFBundleDisplayName'] + string['FSEvent Watch CLI'] + key['NSHumanReadableCopyright'] + string['Copyright (C) 2011-2017 Travis Tilley'] + + key['CFBundleVersion'] + string["#{FSEVENT_WATCH_EXE_VERSION}"] + key['LSMinimumSystemVersion'] + string["#{$MACOSX_DEPLOYMENT_TARGET}"] + key['DTSDKBuild'] + string["#{$SDK_INFO['ProductBuildVersion']}"] + key['DTSDKName'] + string["macosx#{$SDK_INFO['SDKVersion']}"] + key['DTSDKPath'] + string["#{$SDK_INFO['Path']}"] + key['BuildMachineOSBuild'] + string["#{$mac_build_version}"] + key['BuildMachineOSVersion'] + string["#{$mac_product_version}"] + key['FSEWCompiledAt'] + string["#{$now}"] + key['FSEWVersionInfoBuilder'] + string["#{`whoami`.strip}"] + key['FSEWBuildTriple'] + string["#{$BUILD_TRIPLE}"] + key['FSEWCC'] + string["#{$CC}"] + key['FSEWCCVersion'] + string["#{$CCVersion}"] + key['FSEWCFLAGS'] + string["#{$CFLAGS}"] + + indent[2] + add[''] + indent[0] + + add[''] + end +end + +desc 'generate an Info.plist used for code signing as well as embedding build settings into the resulting binary' +task :plist => $obj_dir.join('Info.plist').to_s + + +file $obj_dir.join('fsevent_watch').to_s => [$obj_dir.to_s, $obj_dir.join('Info.plist').to_s] + OBJ.map(&:to_s) do + cmd = [ + $CC, + $ARCHFLAGS, + "-std=#{$GCC_C_LANGUAGE_STANDARD}", + $CFLAGS, + $DEFINES, + "-I#{$src_dir}", + '-isysroot', + $SDK_INFO['Path'], + '-framework CoreFoundation -framework CoreServices', + '-sectcreate __TEXT __info_plist', + $obj_dir.join('Info.plist') + ] + OBJ + [ + '-o', $obj_dir.join('fsevent_watch') + ] + sh(cmd.map {|s| s.to_s}.join(' ')) +end + +desc 'compile and link build/fsevent_watch' +task :build => $obj_dir.join('fsevent_watch').to_s + +desc 'codesign build/fsevent_watch binary' +task :codesign => :build do + sh "codesign -s '#{$CODE_SIGN_IDENTITY}' #{$obj_dir.join('fsevent_watch')}" +end + +directory $this_dir.parent.join('bin') + +desc 'replace bundled fsevent_watch binary with build/fsevent_watch' +task :replace_exe => [$this_dir.parent.join('bin'), :build] do + sh "mv #{$obj_dir.join('fsevent_watch')} #{$final_exe}" +end + +task :default => [:replace_exe, :clean] diff --git a/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/lib/otnetstring.rb b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/lib/otnetstring.rb new file mode 100644 index 00000000..cd8de4c2 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/lib/otnetstring.rb @@ -0,0 +1,85 @@ +# Copyright (c) 2011 Konstantin Haase +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + + +require 'stringio' + +module OTNetstring + class Error < StandardError; end + + class << self + def parse(io, encoding = 'internal', fallback_encoding = nil) + fallback_encoding = io.encoding if io.respond_to? :encoding + io = StringIO.new(io) if io.respond_to? :to_str + length, byte = "", nil + + while byte.nil? || byte =~ /\d/ + length << byte if byte + byte = io.read(1) + end + + if length.size > 9 + raise Error, "#{length} is longer than 9 digits" + elsif length !~ /\d+/ + raise Error, "Expected '#{byte}' to be a digit" + end + length = Integer(length) + + case byte + when '#' then Integer io.read(length) + when ',' then with_encoding io.read(length), encoding, fallback_encoding + when '~' then + raise Error, "nil has length of 0, #{length} given" unless length == 0 + when '!' then io.read(length) == 'true' + when '[', '{' + array = [] + start = io.pos + array << parse(io, encoding, fallback_encoding) while io.pos - start < length + raise Error, 'Nested element longer than container' if io.pos - start != length + byte == "{" ? Hash[*array] : array + else + raise Error, "Unknown type '#{byte}'" + end + end + + def encode(obj, string_sep = ',') + case obj + when String then with_encoding "#{obj.bytesize}#{string_sep}#{obj}", "binary" + when Integer then encode(obj.inspect, '#') + when NilClass then "0~" + when Array then encode(obj.map { |e| encode(e) }.join, '[') + when Hash then encode(obj.map { |a,b| encode(a)+encode(b) }.join, '{') + when FalseClass, TrueClass then encode(obj.inspect, '!') + else raise Error, 'cannot encode %p' % obj + end + end + + private + + def with_encoding(str, encoding, fallback = nil) + return str unless str.respond_to? :encode + encoding = Encoding.find encoding if encoding.respond_to? :to_str + encoding ||= fallback + encoding ? str.encode(encoding) : str + rescue EncodingError + str.force_encoding(encoding) + end + end +end diff --git a/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/lib/rb-fsevent.rb b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/lib/rb-fsevent.rb new file mode 100644 index 00000000..1ff68a30 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/lib/rb-fsevent.rb @@ -0,0 +1,3 @@ +# -*- encoding: utf-8 -*- +require 'rb-fsevent/fsevent' +require 'rb-fsevent/version' diff --git a/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/lib/rb-fsevent/fsevent.rb b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/lib/rb-fsevent/fsevent.rb new file mode 100644 index 00000000..23c5aa9e --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/lib/rb-fsevent/fsevent.rb @@ -0,0 +1,157 @@ +# -*- encoding: utf-8 -*- + +require 'otnetstring' + +class FSEvent + class << self + class_eval <<-END + def root_path + "#{File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))}" + end + END + class_eval <<-END + def watcher_path + "#{File.join(FSEvent.root_path, 'bin', 'fsevent_watch')}" + end + END + end + + attr_reader :paths, :callback + + def initialize args = nil, &block + watch(args, &block) unless args.nil? + end + + def watch(watch_paths, options=nil, &block) + @paths = watch_paths.kind_of?(Array) ? watch_paths : [watch_paths] + @callback = block + + if options.kind_of?(Hash) + @options = parse_options(options) + elsif options.kind_of?(Array) + @options = options + else + @options = [] + end + end + + def run + @pipe = open_pipe + @running = true + + # please note the use of IO::select() here, as it is used specifically to + # preserve correct signal handling behavior in ruby 1.8. + while @running && IO::select([@pipe], nil, nil, nil) + # managing the IO ourselves allows us to be careful and never pass an + # incomplete message to OTNetstring.parse() + message = "" + length = "" + byte = nil + + reading_length = true + found_length = false + + while reading_length + byte = @pipe.read_nonblock(1) + if "#{byte}" =~ /\d/ + length << byte + found_length = true + elsif found_length == false + next + else + reading_length = false + end + end + length = Integer(length, 10) + type = byte + + message << "#{length}#{type}" + message << @pipe.read(length) + + decoded = OTNetstring.parse(message) + modified_paths = decoded["events"].map {|event| event["path"]} + # passing the full info as a second block param feels icky, but such is + # the trap of backward compatibility. + case callback.arity + when 1 + callback.call(modified_paths) + when 2 + callback.call(modified_paths, decoded) + end + end + rescue Interrupt, IOError, Errno::EBADF + ensure + stop + end + + def stop + unless @pipe.nil? + Process.kill('KILL', @pipe.pid) if process_running?(@pipe.pid) + @pipe.close + end + rescue IOError + ensure + @running = false + end + + def process_running?(pid) + begin + Process.kill(0, pid) + true + rescue Errno::ESRCH + false + end + end + + if RUBY_VERSION < '1.9' + def open_pipe + IO.popen("'#{self.class.watcher_path}' #{options_string} #{shellescaped_paths}") + end + + private + + def options_string + @options.join(' ') + end + + def shellescaped_paths + @paths.map {|path| shellescape(path)}.join(' ') + end + + # for Ruby 1.8.6 support + def shellescape(str) + # An empty argument will be skipped, so return empty quotes. + return "''" if str.empty? + + str = str.dup + + # Process as a single byte sequence because not all shell + # implementations are multibyte aware. + str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1") + + # A LF cannot be escaped with a backslash because a backslash + LF + # combo is regarded as line continuation and simply ignored. + str.gsub!(/\n/, "'\n'") + + return str + end + else + def open_pipe + IO.popen([self.class.watcher_path] + @options + @paths) + end + end + + private + + def parse_options(options={}) + opts = ['--format=otnetstring'] + opts.concat(['--since-when', options[:since_when]]) if options[:since_when] + opts.concat(['--latency', options[:latency]]) if options[:latency] + opts.push('--no-defer') if options[:no_defer] + opts.push('--watch-root') if options[:watch_root] + opts.push('--file-events') if options[:file_events] + # ruby 1.9's IO.popen(array-of-stuff) syntax requires all items to be strings + opts.map {|opt| "#{opt}"} + end + +end diff --git a/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/lib/rb-fsevent/version.rb b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/lib/rb-fsevent/version.rb new file mode 100644 index 00000000..ced71512 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/lib/rb-fsevent/version.rb @@ -0,0 +1,5 @@ +# -*- encoding: utf-8 -*- + +class FSEvent + VERSION = '0.10.3' +end diff --git a/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/rb-fsevent.gemspec b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/rb-fsevent.gemspec new file mode 100644 index 00000000..a7370dbf --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-fsevent-0.10.3/rb-fsevent.gemspec @@ -0,0 +1,27 @@ +# coding: utf-8 +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'rb-fsevent/version' + +Gem::Specification.new do |s| + s.name = 'rb-fsevent' + s.version = FSEvent::VERSION + s.authors = ['Thibaud Guillaume-Gentil', 'Travis Tilley'] + s.email = ['thibaud@thibaud.gg', 'ttilley@gmail.com'] + s.homepage = 'http://rubygems.org/gems/rb-fsevent' + s.summary = 'Very simple & usable FSEvents API' + s.description = 'FSEvents API with Signals catching (without RubyCocoa)' + s.license = 'MIT' + + s.metadata = { + 'source_code_uri' => 'https://github.com/thibaudgg/rb-fsevent' + } + + s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^spec/}) } + s.require_path = 'lib' + + s.add_development_dependency 'bundler', '~> 1.0' + s.add_development_dependency 'rspec', '~> 3.6' + s.add_development_dependency 'guard-rspec', '~> 4.2' + s.add_development_dependency 'rake', '~> 12.0' +end diff --git a/path/ruby/2.6.0/gems/rb-inotify-0.10.0/.gitignore b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/.gitignore new file mode 100644 index 00000000..79dfcae9 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/.gitignore @@ -0,0 +1,21 @@ +*.gem +*.rbc +.bundle +.config +.yardoc +Gemfile.lock +InstalledFiles +_yardoc +coverage +doc/ +lib/bundler/man +pkg +rdoc +spec/reports +test/tmp +test/version_tmp +tmp +.tags* +.rspec_status +/guard/ +/listen/ diff --git a/path/ruby/2.6.0/gems/rb-inotify-0.10.0/.travis.yml b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/.travis.yml new file mode 100644 index 00000000..72313cbf --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/.travis.yml @@ -0,0 +1,21 @@ +language: ruby +cache: bundler + +matrix: + include: + - rvm: 2.3 + - rvm: 2.4 + - rvm: 2.5 + - rvm: 2.6 + - rvm: jruby + - rvm: truffleruby + - rvm: jruby-head + - rvm: ruby-head + - rvm: rbx-3 + allow_failures: + - rvm: truffleruby + - rvm: jruby + - rvm: ruby-head + - rvm: jruby-head + - rvm: rbx-3 + fast_finish: true diff --git a/path/ruby/2.6.0/gems/rb-inotify-0.10.0/.yardopts b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/.yardopts new file mode 100644 index 00000000..cd347c50 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/.yardopts @@ -0,0 +1,4 @@ +--readme README.md +--markup markdown +--markup-provider maruku +--no-private diff --git a/path/ruby/2.6.0/gems/rb-inotify-0.10.0/Gemfile b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/Gemfile new file mode 100644 index 00000000..9b2ce272 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/Gemfile @@ -0,0 +1,16 @@ +source 'https://rubygems.org' + +# Specify your gem's dependencies in utopia.gemspec +gemspec + +group :development do + gem 'pry' + gem 'pry-coolline' + + gem 'tty-prompt' +end + +group :test do + gem 'simplecov' + gem 'coveralls', require: false +end diff --git a/path/ruby/2.6.0/gems/rb-inotify-0.10.0/LICENSE.md b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/LICENSE.md new file mode 100644 index 00000000..53caf2a4 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/LICENSE.md @@ -0,0 +1,10 @@ +# The MIT License (MIT) + +Copyright, 2009, by [Natalie Weizenbaum](https://github.com/nex3). +Copyright, 2017, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams). + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/path/ruby/2.6.0/gems/rb-inotify-0.10.0/README.md b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/README.md new file mode 100644 index 00000000..032246e4 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/README.md @@ -0,0 +1,103 @@ +# rb-inotify + +This is a simple wrapper over the [inotify](http://en.wikipedia.org/wiki/Inotify) Linux kernel subsystem +for monitoring changes to files and directories. +It uses the [FFI](http://wiki.github.com/ffi/ffi) gem to avoid having to compile a C extension. + +[API documentation is available on rdoc.info](http://rdoc.info/projects/nex3/rb-inotify). + +[![Build Status](https://secure.travis-ci.org/guard/rb-inotify.svg)](http://travis-ci.org/guard/rb-inotify) +[![Code Climate](https://codeclimate.com/github/guard/rb-inotify.svg)](https://codeclimate.com/github/guard/rb-inotify) +[![Coverage Status](https://coveralls.io/repos/guard/rb-inotify/badge.svg)](https://coveralls.io/r/guard/rb-inotify) + +## Basic Usage + +The API is similar to the inotify C API, but with a more Rubyish feel. +First, create a notifier: + + notifier = INotify::Notifier.new + +Then, tell it to watch the paths you're interested in +for the events you care about: + + notifier.watch("path/to/foo.txt", :modify) {puts "foo.txt was modified!"} + notifier.watch("path/to/bar", :moved_to, :create) do |event| + puts "#{event.name} is now in path/to/bar!" + end + +Inotify can watch directories or individual files. +It can pay attention to all sorts of events; +for a full list, see [the inotify man page](http://www.tin.org/bin/man.cgi?section=7&topic=inotify). + +Finally, you get at the events themselves: + + notifier.run + +This will loop infinitely, calling the appropriate callbacks when the files are changed. +If you don't want infinite looping, +you can also block until there are available events, +process them all at once, +and then continue on your merry way: + + notifier.process + +## Advanced Usage + +Sometimes it's necessary to have finer control over the underlying IO operations +than is provided by the simple callback API. +The trick to this is that the \{INotify::Notifier#to_io Notifier#to_io} method +returns a fully-functional IO object, +with a file descriptor and everything. +This means, for example, that it can be passed to `IO#select`: + + # Wait 10 seconds for an event then give up + if IO.select([notifier.to_io], [], [], 10) + notifier.process + end + +It can even be used with EventMachine: + + require 'eventmachine' + + EM.run do + EM.watch notifier.to_io do + notifier.process + end + end + +Unfortunately, this currently doesn't work under JRuby. +JRuby currently doesn't use native file descriptors for the IO object, +so we can't use the notifier's file descriptor as a stand-in. + +## Contributing + +1. Fork it +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create new Pull Request + +## License + +Released under the MIT license. + +Copyright, 2009, by [Natalie Weizenbaum](https://github.com/nex3). +Copyright, 2017, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams). + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/path/ruby/2.6.0/gems/rb-inotify-0.10.0/Rakefile b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/Rakefile new file mode 100644 index 00000000..bc3a3b6b --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/Rakefile @@ -0,0 +1,14 @@ +require "bundler/gem_tasks" +require 'rspec/core/rake_task' + +RSpec::Core::RakeTask.new(:spec) + +desc "Run tests" +task :default => :spec + +task :console do + require 'rb-inotify' + require 'pry' + + binding.pry +end diff --git a/path/ruby/2.6.0/gems/rb-inotify-0.10.0/lib/rb-inotify.rb b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/lib/rb-inotify.rb new file mode 100644 index 00000000..8897aefa --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/lib/rb-inotify.rb @@ -0,0 +1,15 @@ +require 'rb-inotify/version' +require 'rb-inotify/native' +require 'rb-inotify/native/flags' +require 'rb-inotify/notifier' +require 'rb-inotify/watcher' +require 'rb-inotify/event' +require 'rb-inotify/errors' + +# The root module of the library, which is laid out as so: +# +# * {Notifier} -- The main class, where the notifications are set up +# * {Watcher} -- A watcher for a single file or directory +# * {Event} -- An filesystem event notification +module INotify +end diff --git a/path/ruby/2.6.0/gems/rb-inotify-0.10.0/lib/rb-inotify/errors.rb b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/lib/rb-inotify/errors.rb new file mode 100644 index 00000000..afee7099 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/lib/rb-inotify/errors.rb @@ -0,0 +1,3 @@ +module INotify + class QueueOverflowError < RuntimeError; end +end diff --git a/path/ruby/2.6.0/gems/rb-inotify-0.10.0/lib/rb-inotify/event.rb b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/lib/rb-inotify/event.rb new file mode 100644 index 00000000..11701acf --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/lib/rb-inotify/event.rb @@ -0,0 +1,146 @@ +module INotify + # An event caused by a change on the filesystem. + # Each {Watcher} can fire many events, + # which are passed to that watcher's callback. + class Event + # A list of other events that are related to this one. + # Currently, this is only used for files that are moved within the same directory: + # the `:moved_from` and the `:moved_to` events will be related. + # + # @return [Array] + attr_reader :related + + # The name of the file that the event occurred on. + # This is only set for events that occur on files in directories; + # otherwise, it's `""`. + # Similarly, if the event is being fired for the directory itself + # the name will be `""` + # + # This pathname is relative to the enclosing directory. + # For the absolute pathname, use \{#absolute\_name}. + # Note that when the `:recursive` flag is passed to {Notifier#watch}, + # events in nested subdirectories will still have a `#name` field + # relative to their immediately enclosing directory. + # For example, an event on the file `"foo/bar/baz"` + # will have name `"baz"`. + # + # @return [String] + attr_reader :name + + # The {Notifier} that fired this event. + # + # @return [Notifier] + attr_reader :notifier + + # An integer specifying that this event is related to some other event, + # which will have the same cookie. + # + # Currently, this is only used for files that are moved within the same directory. + # Both the `:moved_from` and the `:moved_to` events will have the same cookie. + # + # @private + # @return [Fixnum] + attr_reader :cookie + + # The {Watcher#id id} of the {Watcher} that fired this event. + # + # @private + # @return [Fixnum] + attr_reader :watcher_id + + # Returns the {Watcher} that fired this event. + # + # @return [Watcher] + def watcher + @watcher ||= @notifier.watchers[@watcher_id] + end + + # The absolute path of the file that the event occurred on. + # + # This is actually only as absolute as the path passed to the {Watcher} + # that created this event. + # However, it is relative to the working directory, + # assuming that hasn't changed since the watcher started. + # + # @return [String] + def absolute_name + return watcher.path if name.empty? + return File.join(watcher.path, name) + end + + # Returns the flags that describe this event. + # This is generally similar to the input to {Notifier#watch}, + # except that it won't contain options flags nor `:all_events`, + # and it may contain one or more of the following flags: + # + # `:unmount` + # : The filesystem containing the watched file or directory was unmounted. + # + # `:ignored` + # : The \{#watcher watcher} was closed, or the watched file or directory was deleted. + # + # `:isdir` + # : The subject of this event is a directory. + # + # @return [Array] + def flags + @flags ||= Native::Flags.from_mask(@native[:mask]) + end + + # Constructs an {Event} object from a string of binary data, + # and destructively modifies the string to get rid of the initial segment + # used to construct the Event. + # + # @private + # @param data [String] The string to be modified + # @param notifier [Notifier] The {Notifier} that fired the event + # @return [Event, nil] The event, or `nil` if the string is empty + def self.consume(data, notifier) + return nil if data.empty? + ev = new(data, notifier) + data.replace data[ev.size..-1] + ev + end + + # Creates an event from a string of binary data. + # Differs from {Event.consume} in that it doesn't modify the string. + # + # @private + # @param data [String] The data string + # @param notifier [Notifier] The {Notifier} that fired the event + def initialize(data, notifier) + ptr = FFI::MemoryPointer.from_string(data) + @native = Native::Event.new(ptr) + @related = [] + @cookie = @native[:cookie] + @name = fix_encoding(data[@native.size, @native[:len]].gsub(/\0+$/, '')) + @notifier = notifier + @watcher_id = @native[:wd] + + raise QueueOverflowError.new("inotify event queue has overflowed.") if @native[:mask] & Native::Flags::IN_Q_OVERFLOW != 0 + end + + # Calls the callback of the watcher that fired this event, + # passing in the event itself. + # + # @private + def callback! + watcher && watcher.callback!(self) + end + + # Returns the size of this event object in bytes, + # including the \{#name} string. + # + # @return [Fixnum] + def size + @native.size + @native[:len] + end + + private + + def fix_encoding(name) + name.force_encoding('filesystem') if name.respond_to?(:force_encoding) + name + end + end +end diff --git a/path/ruby/2.6.0/gems/rb-inotify-0.10.0/lib/rb-inotify/native.rb b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/lib/rb-inotify/native.rb new file mode 100644 index 00000000..6da36eb1 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/lib/rb-inotify/native.rb @@ -0,0 +1,33 @@ +require 'ffi' + +module INotify + # This module contains the low-level foreign-function interface code + # for dealing with the inotify C APIs. + # It's an implementation detail, and not meant for users to deal with. + # + # @private + module Native + extend FFI::Library + ffi_lib FFI::Library::LIBC + begin + ffi_lib 'inotify' + rescue LoadError + end + + # The C struct describing an inotify event. + # + # @private + class Event < FFI::Struct + layout( + :wd, :int, + :mask, :uint32, + :cookie, :uint32, + :len, :uint32) + end + + attach_function :inotify_init, [], :int + attach_function :inotify_add_watch, [:int, :string, :uint32], :int + attach_function :inotify_rm_watch, [:int, :uint32], :int + attach_function :fpathconf, [:int, :int], :long + end +end diff --git a/path/ruby/2.6.0/gems/rb-inotify-0.10.0/lib/rb-inotify/native/flags.rb b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/lib/rb-inotify/native/flags.rb new file mode 100644 index 00000000..56401306 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/lib/rb-inotify/native/flags.rb @@ -0,0 +1,94 @@ +module INotify + module Native + # A module containing all the inotify flags + # to be passed to {Notifier#watch}. + # + # @private + module Flags + # File was accessed. + IN_ACCESS = 0x00000001 + # Metadata changed. + IN_ATTRIB = 0x00000004 + # Writtable file was closed. + IN_CLOSE_WRITE = 0x00000008 + # File was modified. + IN_MODIFY = 0x00000002 + # Unwrittable file closed. + IN_CLOSE_NOWRITE = 0x00000010 + # File was opened. + IN_OPEN = 0x00000020 + # File was moved from X. + IN_MOVED_FROM = 0x00000040 + # File was moved to Y. + IN_MOVED_TO = 0x00000080 + # Subfile was created. + IN_CREATE = 0x00000100 + # Subfile was deleted. + IN_DELETE = 0x00000200 + # Self was deleted. + IN_DELETE_SELF = 0x00000400 + # Self was moved. + IN_MOVE_SELF = 0x00000800 + + ## Helper events. + + # Close. + IN_CLOSE = (IN_CLOSE_WRITE | IN_CLOSE_NOWRITE) + # Moves. + IN_MOVE = (IN_MOVED_FROM | IN_MOVED_TO) + # All events which a program can wait on. + IN_ALL_EVENTS = (IN_ACCESS | IN_MODIFY | IN_ATTRIB | IN_CLOSE_WRITE | + IN_CLOSE_NOWRITE | IN_OPEN | IN_MOVED_FROM | IN_MOVED_TO | IN_CREATE | + IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF) + + + ## Special flags. + + # Only watch the path if it is a directory. + IN_ONLYDIR = 0x01000000 + # Do not follow a sym link. + IN_DONT_FOLLOW = 0x02000000 + # Add to the mask of an already existing watch. + IN_MASK_ADD = 0x20000000 + # Only send event once. + IN_ONESHOT = 0x80000000 + + + ## Events sent by the kernel. + + # Backing fs was unmounted. + IN_UNMOUNT = 0x00002000 + # Event queued overflowed. + IN_Q_OVERFLOW = 0x00004000 + # File was ignored. + IN_IGNORED = 0x00008000 + # Event occurred against dir. + IN_ISDIR = 0x40000000 + + ## fpathconf Macros + + # returns the maximum length of a filename in the directory path or fd that the process is allowed to create. The corresponding macro is _POSIX_NAME_MAX. + PC_NAME_MAX = 3 + + # Converts a list of flags to the bitmask that the C API expects. + # + # @param flags [Array] + # @return [Fixnum] + def self.to_mask(flags) + flags.map {|flag| const_get("IN_#{flag.to_s.upcase}")}. + inject(0) {|mask, flag| mask | flag} + end + + # Converts a bitmask from the C API into a list of flags. + # + # @param mask [Fixnum] + # @return [Array] + def self.from_mask(mask) + constants.map {|c| c.to_s}.select do |c| + next false unless c =~ /^IN_/ + const_get(c) & mask != 0 + end.map {|c| c.sub("IN_", "").downcase.to_sym} - [:all_events] + end + end + end +end diff --git a/path/ruby/2.6.0/gems/rb-inotify-0.10.0/lib/rb-inotify/notifier.rb b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/lib/rb-inotify/notifier.rb new file mode 100644 index 00000000..5884e959 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/lib/rb-inotify/notifier.rb @@ -0,0 +1,315 @@ +require 'thread' + +module INotify + # Notifier wraps a single instance of inotify. + # It's possible to have more than one instance, + # but usually unnecessary. + # + # @example + # # Create the notifier + # notifier = INotify::Notifier.new + # + # # Run this callback whenever the file path/to/foo.txt is read + # notifier.watch("path/to/foo.txt", :access) do + # puts "Foo.txt was accessed!" + # end + # + # # Watch for any file in the directory being deleted + # # or moved out of the directory. + # notifier.watch("path/to/directory", :delete, :moved_from) do |event| + # # The #name field of the event object contains the name of the affected file + # puts "#{event.name} is no longer in the directory!" + # end + # + # # Nothing happens until you run the notifier! + # notifier.run + class Notifier + # A list of directories that should never be recursively watched. + # + # * Files in `/dev/fd` sometimes register as directories, but are not enumerable. + RECURSIVE_BLACKLIST = %w[/dev/fd] + + # A hash from {Watcher} ids to the instances themselves. + # + # @private + # @return [{Fixnum => Watcher}] + attr_reader :watchers + + # The underlying file descriptor for this notifier. + # This is a valid OS file descriptor, and can be used as such + # (except under JRuby -- see \{#to\_io}). + # + # @return [Fixnum] + def fd + @handle.fileno + end + + # Creates a new {Notifier}. + # + # @return [Notifier] + # @raise [SystemCallError] if inotify failed to initialize for some reason + def initialize + @running = Mutex.new + @pipe = IO.pipe + + @watchers = {} + + fd = Native.inotify_init + unless fd < 0 + @handle = IO.new(fd) + return + end + + raise SystemCallError.new( + "Failed to initialize inotify" + + case FFI.errno + when Errno::EMFILE::Errno; ": the user limit on the total number of inotify instances has been reached." + when Errno::ENFILE::Errno; ": the system limit on the total number of file descriptors has been reached." + when Errno::ENOMEM::Errno; ": insufficient kernel memory is available." + else; "" + end, + FFI.errno) + end + + # Returns a Ruby IO object wrapping the underlying file descriptor. + # Since this file descriptor is fully functional (except under JRuby), + # this IO object can be used in any way a Ruby-created IO object can. + # This includes passing it to functions like `#select`. + # + # Note that this always returns the same IO object. + # Creating lots of IO objects for the same file descriptor + # can cause some odd problems. + # + # **This is not supported under JRuby**. + # JRuby currently doesn't use native file descriptors for the IO object, + # so we can't use this file descriptor as a stand-in. + # + # @return [IO] An IO object wrapping the file descriptor + # @raise [NotImplementedError] if this is being called in JRuby + def to_io + @handle + end + + # Watches a file or directory for changes, + # calling the callback when there are. + # This is only activated once \{#process} or \{#run} is called. + # + # **Note that by default, this does not recursively watch subdirectories + # of the watched directory**. + # To do so, use the `:recursive` flag. + # + # ## Flags + # + # `:access` + # : A file is accessed (that is, read). + # + # `:attrib` + # : A file's metadata is changed (e.g. permissions, timestamps, etc). + # + # `:close_write` + # : A file that was opened for writing is closed. + # + # `:close_nowrite` + # : A file that was not opened for writing is closed. + # + # `:modify` + # : A file is modified. + # + # `:open` + # : A file is opened. + # + # ### Directory-Specific Flags + # + # These flags only apply when a directory is being watched. + # + # `:moved_from` + # : A file is moved out of the watched directory. + # + # `:moved_to` + # : A file is moved into the watched directory. + # + # `:create` + # : A file is created in the watched directory. + # + # `:delete` + # : A file is deleted in the watched directory. + # + # `:delete_self` + # : The watched file or directory itself is deleted. + # + # `:move_self` + # : The watched file or directory itself is moved. + # + # ### Helper Flags + # + # These flags are just combinations of the flags above. + # + # `:close` + # : Either `:close_write` or `:close_nowrite` is activated. + # + # `:move` + # : Either `:moved_from` or `:moved_to` is activated. + # + # `:all_events` + # : Any event above is activated. + # + # ### Options Flags + # + # These flags don't actually specify events. + # Instead, they specify options for the watcher. + # + # `:onlydir` + # : Only watch the path if it's a directory. + # + # `:dont_follow` + # : Don't follow symlinks. + # + # `:mask_add` + # : Add these flags to the pre-existing flags for this path. + # + # `:oneshot` + # : Only send the event once, then shut down the watcher. + # + # `:recursive` + # : Recursively watch any subdirectories that are created. + # Note that this is a feature of rb-inotify, + # rather than of inotify itself, which can only watch one level of a directory. + # This means that the {Event#name} field + # will contain only the basename of the modified file. + # When using `:recursive`, {Event#absolute_name} should always be used. + # + # @param path [String] The path to the file or directory + # @param flags [Array] Which events to watch for + # @yield [event] A block that will be called + # whenever one of the specified events occur + # @yieldparam event [Event] The Event object containing information + # about the event that occured + # @return [Watcher] A Watcher set up to watch this path for these events + # @raise [SystemCallError] if the file or directory can't be watched, + # e.g. if the file isn't found, read access is denied, + # or the flags don't contain any events + def watch(path, *flags, &callback) + return Watcher.new(self, path, *flags, &callback) unless flags.include?(:recursive) + + dir = Dir.new(path) + + dir.each do |base| + d = File.join(path, base) + binary_d = d.respond_to?(:force_encoding) ? d.dup.force_encoding('BINARY') : d + next if binary_d =~ /\/\.\.?$/ # Current or parent directory + next if RECURSIVE_BLACKLIST.include?(d) + next if flags.include?(:dont_follow) && File.symlink?(d) + next if !File.directory?(d) + + watch(d, *flags, &callback) + end + + dir.close + + rec_flags = [:create, :moved_to] + return watch(path, *((flags - [:recursive]) | rec_flags)) do |event| + callback.call(event) if flags.include?(:all_events) || !(flags & event.flags).empty? + next if (rec_flags & event.flags).empty? || !event.flags.include?(:isdir) + begin + watch(event.absolute_name, *flags, &callback) + rescue Errno::ENOENT + # If the file has been deleted since the glob was run, we don't want to error out. + end + end + end + + # Starts the notifier watching for filesystem events. + # Blocks until \{#stop} is called. + # + # @see #process + def run + @running.synchronize do + @stop = false + + process until @stop + end + end + + # Stop watching for filesystem events. + # That is, if we're in a \{#run} loop, + # exit out as soon as we finish handling the events. + def stop + @stop = true + @pipe.last.write "." + + @running.synchronize do + # no-op: we just needed to wait until the lock was available + end + end + + # Blocks until there are one or more filesystem events + # that this notifier has watchers registered for. + # Once there are events, the appropriate callbacks are called + # and this function returns. + # + # @see #run + def process + read_events.each do |event| + event.callback! + event.flags.include?(:ignored) && event.notifier.watchers.delete(event.watcher_id) + end + end + + # Close the notifier. + # + # @raise [SystemCallError] if closing the underlying file descriptor fails. + def close + stop + @handle.close + @watchers.clear + end + + # Blocks until there are one or more filesystem events that this notifier + # has watchers registered for. Once there are events, returns their {Event} + # objects. + # + # This can return an empty list if the watcher was closed elsewhere. + # + # {#run} or {#process} are ususally preferable to calling this directly. + def read_events + size = Native::Event.size + Native.fpathconf(fd, Native::Flags::PC_NAME_MAX) + 1 + tries = 1 + + begin + data = readpartial(size) + rescue SystemCallError => er + # EINVAL means that there's more data to be read + # than will fit in the buffer size + raise er unless er.errno == Errno::EINVAL::Errno && tries < 5 + size *= 2 + tries += 1 + retry + end + return [] if data.nil? + + events = [] + cookies = {} + while event = Event.consume(data, self) + events << event + next if event.cookie == 0 + cookies[event.cookie] ||= [] + cookies[event.cookie] << event + end + cookies.each {|c, evs| evs.each {|ev| ev.related.replace(evs - [ev]).freeze}} + events + end + + private + + # Same as IO#readpartial, or as close as we need. + def readpartial(size) + readable, = select([@handle, @pipe.first]) + return nil if readable.include?(@pipe.first) + @handle.readpartial(size) + rescue Errno::EBADF + # If the IO has already been closed, reading from it will cause + # Errno::EBADF. + nil + end + end +end diff --git a/path/ruby/2.6.0/gems/rb-inotify-0.10.0/lib/rb-inotify/version.rb b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/lib/rb-inotify/version.rb new file mode 100644 index 00000000..ecb53138 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/lib/rb-inotify/version.rb @@ -0,0 +1,24 @@ +# Copyright, 2012, by Natalie Weizenbaum. +# Copyright, 2017, by Samuel G. D. Williams. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +module INotify + VERSION = '0.10.0' +end diff --git a/path/ruby/2.6.0/gems/rb-inotify-0.10.0/lib/rb-inotify/watcher.rb b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/lib/rb-inotify/watcher.rb new file mode 100644 index 00000000..1205e2de --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/lib/rb-inotify/watcher.rb @@ -0,0 +1,88 @@ +module INotify + # Watchers monitor a single path for changes, + # specified by {INotify::Notifier#watch event flags}. + # A watcher is usually created via \{Notifier#watch}. + # + # One {Notifier} may have many {Watcher}s. + # The Notifier actually takes care of the checking for events, + # via \{Notifier#run #run} or \{Notifier#process #process}. + # The main purpose of having Watcher objects + # is to be able to disable them using \{#close}. + class Watcher + # The {Notifier} that this Watcher belongs to. + # + # @return [Notifier] + attr_reader :notifier + + # The path that this Watcher is watching. + # + # @return [String] + attr_reader :path + + # The {INotify::Notifier#watch flags} + # specifying the events that this Watcher is watching for, + # and potentially some options as well. + # + # @return [Array] + attr_reader :flags + + # The id for this Watcher. + # Used to retrieve this Watcher from {Notifier#watchers}. + # + # @private + # @return [Fixnum] + attr_reader :id + + # Calls this Watcher's callback with the given {Event}. + # + # @private + # @param event [Event] + def callback!(event) + @callback[event] + end + + # Disables this Watcher, so that it doesn't fire any more events. + # + # @raise [SystemCallError] if the watch fails to be disabled for some reason + def close + if Native.inotify_rm_watch(@notifier.fd, @id) == 0 + @notifier.watchers.delete(@id) + return + end + + raise SystemCallError.new("Failed to stop watching #{path.inspect}", + FFI.errno) + end + + # Creates a new {Watcher}. + # + # @private + # @see Notifier#watch + def initialize(notifier, path, *flags, &callback) + @notifier = notifier + @callback = callback || proc {} + @path = path + @flags = flags.freeze + @id = Native.inotify_add_watch(@notifier.fd, path.dup, + Native::Flags.to_mask(flags)) + + unless @id < 0 + @notifier.watchers[@id] = self + return + end + + raise SystemCallError.new( + "Failed to watch #{path.inspect}" + + case FFI.errno + when Errno::EACCES::Errno; ": read access to the given file is not permitted." + when Errno::EBADF::Errno; ": the given file descriptor is not valid." + when Errno::EFAULT::Errno; ": path points outside of the process's accessible address space." + when Errno::EINVAL::Errno; ": the given event mask contains no legal events; or fd is not an inotify file descriptor." + when Errno::ENOMEM::Errno; ": insufficient kernel memory was available." + when Errno::ENOSPC::Errno; ": The user limit on the total number of inotify watches was reached or the kernel failed to allocate a needed resource." + else; "" + end, + FFI.errno) + end + end +end diff --git a/path/ruby/2.6.0/gems/rb-inotify-0.10.0/rb-inotify.gemspec b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/rb-inotify.gemspec new file mode 100644 index 00000000..e83eafeb --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/rb-inotify.gemspec @@ -0,0 +1,28 @@ +# -*- encoding: utf-8 -*- +require_relative 'lib/rb-inotify/version' + +Gem::Specification.new do |spec| + spec.name = 'rb-inotify' + spec.version = INotify::VERSION + spec.platform = Gem::Platform::RUBY + + spec.summary = 'A Ruby wrapper for Linux inotify, using FFI' + spec.authors = ['Natalie Weizenbaum', 'Samuel Williams'] + spec.email = ['nex342@gmail.com', 'samuel.williams@oriontransfer.co.nz'] + spec.homepage = 'https://github.com/guard/rb-inotify' + spec.licenses = ['MIT'] + + spec.files = `git ls-files`.split($/) + spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } + spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) + spec.require_paths = ["lib"] + + spec.required_ruby_version = '>= 2.2' + + spec.add_dependency "ffi", "~> 1.0" + + spec.add_development_dependency "rspec", "~> 3.6" + spec.add_development_dependency "bundler" + spec.add_development_dependency "rake" + spec.add_development_dependency "concurrent-ruby" +end diff --git a/path/ruby/2.6.0/gems/rb-inotify-0.10.0/spec/inotify_spec.rb b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/spec/inotify_spec.rb new file mode 100644 index 00000000..b73ea303 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/spec/inotify_spec.rb @@ -0,0 +1,9 @@ +require 'spec_helper' + +describe INotify do + describe "version" do + it "exists" do + expect(INotify::VERSION).to be_truthy + end + end +end diff --git a/path/ruby/2.6.0/gems/rb-inotify-0.10.0/spec/notifier_spec.rb b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/spec/notifier_spec.rb new file mode 100644 index 00000000..4d7c1d0c --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/spec/notifier_spec.rb @@ -0,0 +1,170 @@ +require 'spec_helper' +require 'tmpdir' +require 'concurrent' + +describe INotify::Notifier do + describe "instance" do + around do |block| + Dir.mktmpdir do |dir| + @root = Pathname.new(dir) + @notifier = INotify::Notifier.new + + begin + block.call + ensure + @notifier.close + end + end + end + + let(:dir) do + @root.join("foo").tap(&:mkdir) + end + + let(:another_dir) do + @root.join("bar").tap(&:mkdir) + end + + it "stops" do + @notifier.stop + end + + describe :process do + it "gets events" do + events = recording(dir, :create) + dir.join("test.txt").write("hello world") + + @notifier.process + + expect(events.size).to eq(1) + expect(events.first.name).to eq("test.txt") + expect(events.first.absolute_name).to eq(dir.join("test.txt").to_s) + end + + it "gets simultaneous events" do + events = recording(dir, :create) + + dir.join("one.txt").write("hello world") + dir.join("two.txt").write("hello world") + + @notifier.process + + expect(events.map(&:name)).to match_array(%w(one.txt two.txt)) + end + + it "separates events between watches" do + bar_events = nil + + foo_events = recording(dir, :create) + bar_events = recording(another_dir, :create) + + dir.join("test.txt").write("hello world") + another_dir.join("test_two.txt").write("hello world") + + @notifier.process + + expect(foo_events.size).to eq(1) + expect(foo_events.first.name).to eq("test.txt") + expect(foo_events.first.absolute_name).to eq(dir.join("test.txt").to_s) + + expect(bar_events.size).to eq(1) + expect(bar_events.first.name).to eq("test_two.txt") + expect(bar_events.first.absolute_name).to eq(another_dir.join("test_two.txt").to_s) + end + end + + describe :run do + it "processes repeatedly until stopped" do + barriers = Array.new(3) { Concurrent::Event.new } + barrier_queue = barriers.dup + events = recording(dir, :create) { barrier_queue.shift.set } + + run_thread = Thread.new { @notifier.run } + + dir.join("one.txt").write("hello world") + barriers.shift.wait(1) or raise "timeout" + + expect(events.map(&:name)).to match_array(%w(one.txt)) + + dir.join("two.txt").write("hello world") + barriers.shift.wait(1) or raise "timeout" + + expect(events.map(&:name)).to match_array(%w(one.txt two.txt)) + + @notifier.stop + + dir.join("three.txt").write("hello world") + barriers.shift.wait(1) + + dir.join("four.txt").write("hello world") + run_thread.join + + expect(events.map(&:name)).to match_array(%w(one.txt two.txt)) + end + end + + describe :fd do + it "returns an integer" do + expect(@notifier.fd).to be_an(Integer) + end + end + + describe :to_io do + it "returns a ruby IO" do + expect(@notifier.to_io).to be_an(::IO) + end + + it "matches the fd" do + expect(@notifier.to_io.fileno).to eq(@notifier.fd) + end + + it "caches its result" do + expect(@notifier.to_io).to be(@notifier.to_io) + end + + it "is selectable" do + events = recording(dir, :create) + expect(select([@notifier.to_io], nil, nil, 0.2)).to be_nil + + dir.join("test.txt").write("hello world") + expect(select([@notifier.to_io], nil, nil, 0.2)).to eq([[@notifier.to_io], [], []]) + + @notifier.process + expect(select([@notifier.to_io], nil, nil, 0.2)).to be_nil + end + end + + private + + def recording(dir, *flags, callback: nil) + events = [] + @notifier.watch(dir.to_s, *flags) do |event| + events << event + yield if block_given? + end + + events + end + end + + describe "mixed instances" do + it "doesn't tangle fds" do + notifiers = Array.new(30) { INotify::Notifier.new } + notifiers.each(&:to_io) + + one = Array.new(10) { IO.pipe.last } + notifiers.each(&:close) + + two = Array.new(10) { IO.pipe.last } + + notifiers = nil + GC.start + + _, writable, _ = select(nil, one, nil, 1) + expect(writable).to match_array(one) + + _, writable, _ = select(nil, two, nil, 1) + expect(writable).to match_array(two) + end + end +end diff --git a/path/ruby/2.6.0/gems/rb-inotify-0.10.0/spec/spec_helper.rb b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/spec/spec_helper.rb new file mode 100644 index 00000000..b62f9cf5 --- /dev/null +++ b/path/ruby/2.6.0/gems/rb-inotify-0.10.0/spec/spec_helper.rb @@ -0,0 +1,29 @@ + +if ENV['COVERAGE'] || ENV['TRAVIS'] + begin + require 'simplecov' + + SimpleCov.start do + add_filter "/spec/" + end + + if ENV['TRAVIS'] + require 'coveralls' + Coveralls.wear! + end + rescue LoadError + warn "Could not load simplecov: #{$!}" + end +end + +require "bundler/setup" +require "rb-inotify" + +RSpec.configure do |config| + # Enable flags like --only-failures and --next-failure + config.example_status_persistence_file_path = ".rspec_status" + + config.expect_with :rspec do |c| + c.syntax = :expect + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/CHANGELOG.md b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/CHANGELOG.md new file mode 100644 index 00000000..88762eda --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/CHANGELOG.md @@ -0,0 +1,337 @@ +## [Unreleased] + +### [1.6.0] - 2019-06-16 - [Janosch Müller](mailto:janosch84@gmail.com) + +### Added + +- Added support for 16 new unicode properties introduced in Ruby 2.6.2 and 2.6.3 + +### [1.5.1] - 2019-05-23 - [Janosch Müller](mailto:janosch84@gmail.com) + +### Fixed + +- Fixed `#options` (and thus `#i?`, `#u?` etc.) not being set for some expressions: + * this affected posix classes as well as alternation, conditional, and intersection branches + * `#options` was already correct for all child expressions of such branches + * this only made an operational difference for posix classes as they respect encoding flags +- Fixed `#options` not respecting all negative options in weird cases like '(?u-m-x)' +- Fixed `Group#option_changes` not accounting for indirectly disabled (overridden) encoding flags +- Fixed `Scanner` allowing negative encoding options if there were no positive options, e.g. '(?-u)' +- Fixed `ScannerError` for some valid meta/control sequences such as '\\C-\\\\' +- Fixed `Expression#match` and `#=~` not working with a single argument + +### [1.5.0] - 2019-05-14 - [Janosch Müller](mailto:janosch84@gmail.com) + +### Added + +- Added `#referenced_expression` for backrefs, subexp calls and conditionals + * returns the `Group` expression that is being referenced via name or number +- Added `Expression#repetitions` + * returns a `Range` of allowed repetitions (`1..1` if there is no quantifier) + * like `#quantity` but with a more uniform interface +- Added `Expression#match_length` + * allows to inspect and iterate over String lengths matched by the Expression + +### Fixed + +- Fixed `Expression#clone` "direction" + * it used to dup ivars onto the callee, leaving only the clone referencing the original objects + * this will affect you if you call `#eql?`/`#equal?` on expressions or use them as Hash keys +- Fixed `#clone` results for `Sequences`, e.g. alternations and conditionals + * the inner `#text` was cloned onto the `Sequence` and thus duplicated + * e.g. `Regexp::Parser.parse(/(a|bc)/).clone.to_s # => (aa|bcbc)` +- Fixed inconsistent `#to_s` output for `Sequences` + * it used to return only the "specific" text, e.g. "|" for an alternation + * now it includes nested expressions as it does for all other `Subexpressions` +- Fixed quantification of codepoint lists with more than one entry (`\u{62 63 64}+`) + * quantifiers apply only to the last entry, so this token is now split up if quantified + +### [1.4.0] - 2019-04-02 - [Janosch Müller](mailto:janosch84@gmail.com) + +### Added + +- Added support for 19 new unicode properties introduced in Ruby 2.6.0 + +### [1.3.0] - 2018-11-14 - [Janosch Müller](mailto:janosch84@gmail.com) + +### Added + +- `Syntax#features` returns a `Hash` of all types and tokens supported by a given `Syntax` + +### Fixed + +- Thanks to [Akira Matsuda](https://github.com/amatsuda) + * eliminated warning "assigned but unused variable - testEof" + +## [1.2.0] - 2018-09-28 - [Janosch Müller](mailto:janosch84@gmail.com) + +### Added + +- `Subexpression` (branch node) includes `Enumerable`, allowing to `#select` children etc. + +### Fixed + +- Fixed missing quantifier in `Conditional::Expression` methods `#to_s`, `#to_re` +- `Conditional::Condition` no longer lives outside the recursive `#expressions` tree + - it used to be the only expression stored in a custom ivar, complicating traversal + - its setter and getter (`#condition=`, `#condition`) still work as before + +## [1.1.0] - 2018-09-17 - [Janosch Müller](mailto:janosch84@gmail.com) + +### Added + +- Added `Quantifier` methods `#greedy?`, `#possessive?`, `#reluctant?`/`#lazy?` +- Added `Group::Options#option_changes` + - shows the options enabled or disabled by the given options group + - as with all other expressions, `#options` shows the overall active options +- Added `Conditional#reference` and `Condition#reference`, indicating the determinative group +- Added `Subexpression#dig`, acts like [`Array#dig`](http://ruby-doc.org/core-2.5.0/Array.html#method-i-dig) + +### Fixed + +- Fixed parsing of quantified conditional expressions (quantifiers were assigned to the wrong expression) +- Fixed scanning and parsing of forward-referring subexpression calls (e.g. `\g<+1>`) +- `Root` and `Sequence` expressions now support the same constructor signature as all other expressions + +## [1.0.0] - 2018-09-01 - [Janosch Müller](mailto:janosch84@gmail.com) + +This release includes several breaking changes, mostly to character sets, #map and properties. + +### Changed + +- Changed handling of sets (a.k.a. character classes or "bracket expressions") + * see PR [#55](https://github.com/ammar/regexp_parser/pull/55) / issue [#47](https://github.com/ammar/regexp_parser/issues/47) for details + * sets are now parsed to expression trees like other nestable expressions + * `#scan` now emits the same tokens as outside sets (no longer `:set, :member`) + * `CharacterSet#members` has been removed + * new `Range` and `Intersection` classes represent corresponding syntax features + * a new `PosixClass` expression class represents e.g. `[[:ascii:]]` + * `PosixClass` instances behave like `Property` ones, e.g. support `#negative?` + * `#scan` emits `:(non)posixclass, :` instead of `:set, :char_(non)` +- Changed `Subexpression#map` to act like regular `Enumerable#map` + * the old behavior is available as `Subexpression#flat_map` + * e.g. `parse(/[a]/).map(&:to_s) == ["[a]"]`; used to be `["[a]", "a"]` +- Changed expression emissions for some escape sequences + * `EscapeSequence::Codepoint`, `CodepointList`, `Hex` and `Octal` are now all used + * they already existed, but were all parsed as `EscapeSequence::Literal` + * e.g. `\x97` is now `EscapeSequence::Hex` instead of `EscapeSequence::Literal` +- Changed naming of many property tokens (emitted for `\p{...}`) + * if you work with these tokens, see PR [#56](https://github.com/ammar/regexp_parser/pull/56) for details + * e.g. `:punct_dash` is now `:dash_punctuation` +- Changed `(?m)` and the likes to emit as `:options_switch` token (@4ade4d1) + * allows differentiating from group-local `:options`, e.g. `(?m:.)` +- Changed name of `Backreference::..NestLevel` to `..RecursionLevel` (@4184339) +- Changed `Backreference::Number#number` from `String` to `Integer` (@40a2231) + +### Added + +- Added support for all previously missing properties (about 250) +- Added `Expression::UnicodeProperty#shortcut` (e.g. returns "m" for `\p{mark}`) +- Added `#char(s)` and `#codepoint(s)` methods to all `EscapeSequence` expressions +- Added `#number`/`#name`/`#recursion_level` to all backref/call expressions (@174bf21) +- Added `#number` and `#number_at_level` to capturing group expressions (@40a2231) + +### Fixed + +- Fixed Ruby version mapping of some properties +- Fixed scanning of some property spellings, e.g. with dashes +- Fixed some incorrect property alias normalizations +- Fixed scanning of codepoint escapes with 6 digits (e.g. `\u{10FFFF}`) +- Fixed scanning of `\R` and `\X` within sets; they act as literals there + +## [0.5.0] - 2018-04-29 - [Janosch Müller](mailto:janosch84@gmail.com) + +### Changed + +- Changed handling of Ruby versions (PR [#53](https://github.com/ammar/regexp_parser/pull/53)) + * New Ruby versions are now supported by default + * Some deep-lying APIs have changed, which should not affect most users: + * `Regexp::Syntax::VERSIONS` is gone + * Syntax version names have changed from `Regexp::Syntax::Ruby::Vnnn` + to `Regexp::Syntax::Vn_n_n` + * Syntax version classes for Ruby versions without regex feature changes + are no longer predefined and are now only created on demand / lazily + * `Regexp::Syntax::supported?` returns true for any argument >= 1.8.6 + +### Fixed + +- Fixed some use cases of Expression methods #strfregexp and #to_h (@e738107) + +### Added + +- Added full signature support to collection methods of Expressions (@aa7c55a) + +## [0.4.13] - 2018-04-04 - [Ammar Ali](mailto:ammarabuali@gmail.com) + +- Added ruby version files for 2.2.10 and 2.3.7 + +## [0.4.12] - 2018-03-30 - [Janosch Müller](mailto:janosch84@gmail.com) + +- Added ruby version files for 2.4.4 and 2.5.1 + +## [0.4.11] - 2018-03-04 - [Janosch Müller](mailto:janosch84@gmail.com) + +- Fixed UnknownSyntaxNameError introduced in v0.4.10 if + the gems parent dir tree included a 'ruby' dir + +## [0.4.10] - 2018-03-04 - [Janosch Müller](mailto:janosch84@gmail.com) + +- Added ruby version file for 2.6.0 +- Added support for Emoji properties (available in Ruby since 2.5.0) +- Added support for XPosixPunct and Regional_Indicator properties +- Fixed parsing of Unicode 6.0 and 7.0 script properties +- Fixed parsing of the special Assigned property +- Fixed scanning of InCyrillic_Supplement property + +## [0.4.9] - 2017-12-25 - [Ammar Ali](mailto:ammarabuali@gmail.com) + +- Added ruby version file for 2.5.0 + +## [0.4.8] - 2017-12-18 - [Janosch Müller](mailto:janosch84@gmail.com) + +- Added ruby version files for 2.2.9, 2.3.6, and 2.4.3 + +## [0.4.7] - 2017-10-15 - [Janosch Müller](mailto:janosch84@gmail.com) + +- Fixed a thread safety issue (issue #45) +- Some public class methods that were only reliable for + internal use are now private instance methods (PR #46) +- Improved the usefulness of Expression#options (issue #43) - + #options and derived methods such as #i?, #m? and #x? are now + defined for all Expressions that are affected by such flags. +- Fixed scanning of whitespace following (?x) (commit 5c94bd2) +- Fixed a Parser bug where the #number attribute of traditional + numerical backreferences was not set correctly (commit 851b620) + +## [0.4.6] - 2017-09-18 - [Janosch Müller](mailto:janosch84@gmail.com) + +- Added Parser support for hex escapes in sets (PR #36) +- Added Parser support for octal escapes (PR #37) +- Added support for cluster types \R and \X (PR #38) +- Added support for more metacontrol notations (PR #39) + +## [0.4.5] - 2017-09-17 - [Ammar Ali](mailto:ammarabuali@gmail.com) + +- Thanks to [Janosch Müller](https://github.com/janosch-x): + * Support ruby 2.2.7 (PR #42) +- Added ruby version files for 2.2.8, 2.3.5, and 2.4.2 + +## [0.4.4] - 2017-07-10 - [Ammar Ali](mailto:ammarabuali@gmail.com) + +- Thanks to [Janosch Müller](https://github.com/janosch-x): + * Add support for new absence operator (PR #33) +- Thanks to [Bartek Bułat](https://github.com/barthez): + * Add support for Ruby 2.3.4 version (PR #40) + +## [0.4.3] - 2017-03-24 - [Ammar Ali](mailto:ammarabuali@gmail.com) + +- Added ruby version file for 2.4.1 + +## [0.4.2] - 2017-01-10 - [Ammar Ali](mailto:ammarabuali@gmail.com) + +- Thanks to [Janosch Müller](https://github.com/janosch-x): + * Support ruby 2.4 (PR #30) + * Improve codepoint handling (PR #27) + +## [0.4.1] - 2016-11-22 - [Ammar Ali](mailto:ammarabuali@gmail.com) + +- Updated ruby version file for 2.3.3 + +## [0.4.0] - 2016-11-20 - [Ammar Ali](mailto:ammarabuali@gmail.com) + +- Added Syntax.supported? method +- Updated ruby versions for latest releases; 2.1.10, 2.2.6, and 2.3.2 + +## [0.3.6] - 2016-06-08 - [Ammar Ali](mailto:ammarabuali@gmail.com) + +- Thanks to [John Backus](https://github.com/backus): + * Remove warnings (PR #26) + +## [0.3.5] - 2016-05-30 - [Ammar Ali](mailto:ammarabuali@gmail.com) + +- Thanks to [John Backus](https://github.com/backus): + * Fix parsing of /\xFF/n (hex:escape) (PR #24) + +## [0.3.4] - 2016-05-25 - [Ammar Ali](mailto:ammarabuali@gmail.com) + +- Thanks to [John Backus](https://github.com/backus): + * Fix warnings (PR #19) +- Thanks to [Dana Scheider](https://github.com/danascheider): + * Correct error in README (PR #20) +- Fixed mistyped \h and \H character types (issue #21) +- Added ancestry syntax files for latest rubies (issue #22) + +## [0.3.3] - 2016-04-26 - [Ammar Ali](mailto:ammarabuali@gmail.com) + +- Thanks to [John Backus](https://github.com/backus): + * Fixed scanning of zero length comments (PR #12) + * Fixed missing escape:codepoint_list syntax token (PR #14) + * Fixed to_s for modified interval quantifiers (PR #17) +- Added a note about MRI implementation quirks to Scanner section + +## [0.3.2] - 2016-01-01 - [Ammar Ali](mailto:ammarabuali@gmail.com) + +- Updated ruby versions for latest releases; 2.1.8, 2.2.4, and 2.3.0 +- Fixed class name for UnknownSyntaxNameError exception +- Added UnicodeBlocks support to the parser. +- Added UnicodeBlocks support to the scanner. +- Added expand_members method to CharacterSet, returns traditional + or unicode property forms of shothands (\d, \W, \s, etc.) +- Improved meaning and output of %t and %T in strfregexp. +- Added syntax versions for ruby 2.1.4 and 2.1.5 and updated + latest 2.1 version. +- Added to_h methods to Expression, Subexpression, and Quantifier. +- Added traversal methods; traverse, each_expression, and map. +- Added token/type test methods; type?, is?, and one_of? +- Added printing method strfregexp, inspired by strftime. +- Added scanning and parsing of free spacing (x mode) expressions. +- Improved handling of inline options (?mixdau:...) +- Added conditional expressions. Ruby 2.0. +- Added keep (\K) markers. Ruby 2.0. +- Added d, a, and u options. Ruby 2.0. +- Added missing meta sequences to the parser. They were supported by the scanner only. +- Renamed Lexer's method to lex, added an alias to the old name (scan) +- Use #map instead of #each to run the block in Lexer.lex. +- Replaced VERSION.yml file with a constant. +- Updated README +- Update tokens and scanner with new additions in Unicode 7.0. + +## [0.1.6] - 2014-10-06 - [Ammar Ali](mailto:ammarabuali@gmail.com) + +- Fixed test and gem building rake tasks and extracted the gem + specification from the Rakefile into a .gemspec file. +- Added syntax files for missing ruby 2.x versions. These do not add + extra syntax support, they just make the gem work with the newer + ruby versions. +- Added .travis.yml to project root. +- README: + - Removed note purporting runtime support for ruby 1.8.6. + - Added a section identifying the main unsupported syntax features. + - Added sections for Testing and Building + - Added badges for gem version, Travis CI, and code climate. +- Updated README, fixing broken examples, and converting it from a rdoc file to Github's flavor of Markdown. +- Fixed a parser bug where an alternation sequence that contained nested expressions was incorrectly being appended to the parent expression when the nesting was exited. e.g. in /a|(b)c/, c was appended to the root. + +- Fixed a bug where character types were not being correctly scanned within character sets. e.g. in [\d], two tokens were scanned; one for the backslash '\' and one for the 'd' + +## [0.1.5] - 2014-01-14 - [Ammar Ali](mailto:ammarabuali@gmail.com) + +- Correct ChangeLog. +- Added syntax stubs for ruby versions 2.0 and 2.1 +- Added clone methods for deep copying expressions. +- Added optional format argument for to_s on expressions to return the text of the expression with (:full, the default) or without (:base) its quantifier. +- Renamed the :beginning_of_line and :end_of_line tokens to :bol and :eol. +- Fixed a bug where alternations with more than two alternatives and one of them ending in a group were being incorrectly nested. +- Improved EOF handling in general and especially from sequences like hex and control escapes. +- Fixed a bug where named groups with an empty name would return a blank token []. +- Fixed a bug where member of a parent set where being added to its last subset. +- Various code cleanups in scanner.rl +- Fixed a few mutable string bugs by calling dup on the originals. +- Made ruby 1.8.6 the base for all 1.8 syntax, and the 1.8 name a pointer to the latest (1.8.7 at this time) +- Removed look-behind assertions (positive and negative) from 1.8 syntax +- Added control (\cc and \C-c) and meta (\M-c) escapes to 1.8 syntax +- The default syntax is now the one of the running ruby version in both the lexer and the parser. + +## [0.1.0] - 2010-11-21 - [Ammar Ali](mailto:ammarabuali@gmail.com) + +- Initial release diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/Gemfile b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/Gemfile new file mode 100644 index 00000000..7be3340f --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/Gemfile @@ -0,0 +1,9 @@ +source 'https://rubygems.org' + +gemspec + +group :development, :test do + gem 'rake', '~> 12.2' + gem 'regexp_property_values', '~> 1.0' + gem 'rspec', '~> 3.8' +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/LICENSE b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/LICENSE new file mode 100644 index 00000000..e2378bc0 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2010, 2012-2015, Ammar Ali + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/README.md b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/README.md new file mode 100644 index 00000000..982f518d --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/README.md @@ -0,0 +1,470 @@ +# Regexp::Parser + +[![Gem Version](https://badge.fury.io/rb/regexp_parser.svg)](http://badge.fury.io/rb/regexp_parser) [![Build Status](https://secure.travis-ci.org/ammar/regexp_parser.svg?branch=master)](http://travis-ci.org/ammar/regexp_parser) [![Code Climate](https://codeclimate.com/github/ammar/regexp_parser.svg)](https://codeclimate.com/github/ammar/regexp_parser/badges) + +A Ruby gem for tokenizing, parsing, and transforming regular expressions. + +* Multilayered + * A scanner/tokenizer based on [Ragel](http://www.colm.net/open-source/ragel/) + * A lexer that produces a "stream" of token objects. + * A parser that produces a "tree" of Expression objects (OO API) +* Runs on Ruby 1.9, 2.x, and JRuby (1.9 mode) runtimes. +* Recognizes Ruby 1.8, 1.9, and 2.x regular expressions [See Supported Syntax](#supported-syntax) + + +_For examples of regexp_parser in use, see [Example Projects](#example-projects)._ + + +--- +## Requirements + +* Ruby >= 1.9 +* Ragel >= 6.0, but only if you want to build the gem or work on the scanner. + + +_Note: See the .travis.yml file for covered versions._ + + +--- +## Install + +Install the gem with: + + `gem install regexp_parser` + +Or, add it to your project's `Gemfile`: + +```gem 'regexp_parser', '~> X.Y.Z'``` + +See rubygems for the the [latest version number](https://rubygems.org/gems/regexp_parser) + + +--- +## Usage + +The three main modules are **Scanner**, **Lexer**, and **Parser**. Each of them +provides a single method that takes a regular expression (as a RegExp object or +a string) and returns its results. The **Lexer** and the **Parser** accept an +optional second argument that specifies the syntax version, like 'ruby/2.0', +which defaults to the host Ruby version (using RUBY_VERSION). + +Here are the basic usage examples: + +```ruby +require 'regexp_parser' + +Regexp::Scanner.scan(regexp) + +Regexp::Lexer.lex(regexp) + +Regexp::Parser.parse(regexp) +``` + +All three methods accept a block as the last argument, which, if given, gets +called with the results as follows: + +* **Scanner**: the block gets passed the results as they are scanned. See the + example in the next section for details. + +* **Lexer**: after completion, the block gets passed the tokens one by one. + _The result of the block is returned._ + +* **Parser**: after completion, the block gets passed the root expression. + _The result of the block is returned._ + + +--- +## Components + +### Scanner +A Ragel-generated scanner that recognizes the cumulative syntax of all +supported syntax versions. It breaks a given expression's text into the +smallest parts, and identifies their type, token, text, and start/end +offsets within the pattern. + + +#### Example +The following scans the given pattern and prints out the type, token, text and +start/end offsets for each token found. + +```ruby +require 'regexp_parser' + +Regexp::Scanner.scan /(ab?(cd)*[e-h]+)/ do |type, token, text, ts, te| + puts "type: #{type}, token: #{token}, text: '#{text}' [#{ts}..#{te}]" +end + +# output +# type: group, token: capture, text: '(' [0..1] +# type: literal, token: literal, text: 'ab' [1..3] +# type: quantifier, token: zero_or_one, text: '?' [3..4] +# type: group, token: capture, text: '(' [4..5] +# type: literal, token: literal, text: 'cd' [5..7] +# type: group, token: close, text: ')' [7..8] +# type: quantifier, token: zero_or_more, text: '*' [8..9] +# type: set, token: open, text: '[' [9..10] +# type: set, token: range, text: 'e-h' [10..13] +# type: set, token: close, text: ']' [13..14] +# type: quantifier, token: one_or_more, text: '+' [14..15] +# type: group, token: close, text: ')' [15..16] +``` + +A one-liner that uses map on the result of the scan to return the textual +parts of the pattern: + +```ruby +Regexp::Scanner.scan( /(cat?([bhm]at)){3,5}/ ).map {|token| token[2]} +#=> ["(", "cat", "?", "(", "[", "b", "h", "m", "]", "at", ")", ")", "{3,5}"] +``` + + +#### Notes + * The scanner performs basic syntax error checking, like detecting missing + balancing punctuation and premature end of pattern. Flavor validity checks + are performed in the lexer, which uses a syntax object. + + * If the input is a Ruby **Regexp** object, the scanner calls #source on it to + get its string representation. #source does not include the options of + the expression (m, i, and x). To include the options in the scan, #to_s + should be called on the **Regexp** before passing it to the scanner or the + lexer. For the parser, however, this is not necessary. It automatically + exposes the options of a passed **Regexp** in the returned root expression. + + * To keep the scanner simple(r) and fairly reusable for other purposes, it + does not perform lexical analysis on the tokens, sticking to the task + of identifying the smallest possible tokens and leaving lexical analysis + to the lexer. + + * The MRI implementation may accept expressions that either conflict with + the documentation or are undocumented. The scanner does not support such + implementation quirks. + _(See issues [#3](https://github.com/ammar/regexp_parser/issues/3) and + [#15](https://github.com/ammar/regexp_parser/issues/15) for examples)_ + + +--- +### Syntax +Defines the supported tokens for a specific engine implementation (aka a +flavor). Syntax classes act as lookup tables, and are layered to create +flavor variations. Syntax only comes into play in the lexer. + +#### Example +The following instantiates syntax objects for Ruby 2.0, 1.9, 1.8, and +checks a few of their implementation features. + +```ruby +require 'regexp_parser' + +ruby_20 = Regexp::Syntax.new 'ruby/2.0' +ruby_20.implements? :quantifier, :zero_or_one # => true +ruby_20.implements? :quantifier, :zero_or_one_reluctant # => true +ruby_20.implements? :quantifier, :zero_or_one_possessive # => true +ruby_20.implements? :conditional, :condition # => true + +ruby_19 = Regexp::Syntax.new 'ruby/1.9' +ruby_19.implements? :quantifier, :zero_or_one # => true +ruby_19.implements? :quantifier, :zero_or_one_reluctant # => true +ruby_19.implements? :quantifier, :zero_or_one_possessive # => true +ruby_19.implements? :conditional, :condition # => false + +ruby_18 = Regexp::Syntax.new 'ruby/1.8' +ruby_18.implements? :quantifier, :zero_or_one # => true +ruby_18.implements? :quantifier, :zero_or_one_reluctant # => true +ruby_18.implements? :quantifier, :zero_or_one_possessive # => false +ruby_18.implements? :conditional, :condition # => false +``` + + +#### Notes + * Variations on a token, for example a named group with angle brackets (< and >) + vs one with a pair of single quotes, are specified with an underscore followed + by two characters appended to the base token. In the previous named group example, + the tokens would be :named_ab (angle brackets) and :named_sq (single quotes). + These variations are normalized by the syntax to :named. + + +--- +### Lexer +Sits on top of the scanner and performs lexical analysis on the tokens that +it emits. Among its tasks are; breaking quantified literal runs, collecting the +emitted token attributes into Token objects, calculating their nesting depth, +normalizing tokens for the parser, and checking if the tokens are implemented by +the given syntax version. + +See the [Token Objects](https://github.com/ammar/regexp_parser/wiki/Token-Objects) +wiki page for more information on Token objects. + + +#### Example +The following example lexes the given pattern, checks it against the Ruby 1.9 +syntax, and prints the token objects' text indented to their level. + +```ruby +require 'regexp_parser' + +Regexp::Lexer.lex /a?(b(c))*[d]+/, 'ruby/1.9' do |token| + puts "#{' ' * token.level}#{token.text}" +end + +# output +# a +# ? +# ( +# b +# ( +# c +# ) +# ) +# * +# [ +# d +# ] +# + +``` + +A one-liner that returns an array of the textual parts of the given pattern. +Compare the output with that of the one-liner example of the **Scanner**; notably +how the sequence 'cat' is treated. The 't' is separated because it's followed +by a quantifier that only applies to it. + +```ruby +Regexp::Lexer.scan( /(cat?([b]at)){3,5}/ ).map {|token| token.text} +#=> ["(", "ca", "t", "?", "(", "[", "b", "]", "at", ")", ")", "{3,5}"] +``` + +#### Notes + * The syntax argument is optional. It defaults to the version of the Ruby + interpreter in use, as returned by RUBY_VERSION. + + * The lexer normalizes some tokens, as noted in the Syntax section above. + + +--- +### Parser +Sits on top of the lexer and transforms the "stream" of Token objects emitted +by it into a tree of Expression objects represented by an instance of the +Expression::Root class. + +See the [Expression Objects](https://github.com/ammar/regexp_parser/wiki/Expression-Objects) +wiki page for attributes and methods. + + +#### Example + +```ruby +require 'regexp_parser' + +regex = /a?(b+(c)d)*(?[0-9]+)/ + +tree = Regexp::Parser.parse( regex, 'ruby/2.1' ) + +tree.traverse do |event, exp| + puts "#{event}: #{exp.type} `#{exp.to_s}`" +end + +# Output +# visit: literal `a?` +# enter: group `(b+(c)d)*` +# visit: literal `b+` +# enter: group `(c)` +# visit: literal `c` +# exit: group `(c)` +# visit: literal `d` +# exit: group `(b+(c)d)*` +# enter: group `(?[0-9]+)` +# visit: set `[0-9]+` +# exit: group `(?[0-9]+)` +``` + +Another example, using each_expression and strfregexp to print the object tree. +_See the traverse.rb and strfregexp.rb files under `lib/regexp_parser/expression/methods` +for more information on these methods._ + +```ruby +include_root = true +indent_offset = include_root ? 1 : 0 + +tree.each_expression(include_root) do |exp, level_index| + puts exp.strfregexp("%>> %c", indent_offset) +end + +# Output +# > Regexp::Expression::Root +# > Regexp::Expression::Literal +# > Regexp::Expression::Group::Capture +# > Regexp::Expression::Literal +# > Regexp::Expression::Group::Capture +# > Regexp::Expression::Literal +# > Regexp::Expression::Literal +# > Regexp::Expression::Group::Named +# > Regexp::Expression::CharacterSet +``` + +_Note: quantifiers do not appear in the output because they are members of the +Expression class. See the next section for details._ + + +--- + + +## Supported Syntax +The three modules support all the regular expression syntax features of Ruby 1.8, +1.9, and 2.x: + +_Note that not all of these are available in all versions of Ruby_ + + +| Syntax Feature | Examples | ⋯ | +| ------------------------------------- | ------------------------------------------------------- |:--------:| +| **Alternation** | `a\|b\|c` | ✓ | +| **Anchors** | `\A`, `^`, `\b` | ✓ | +| **Character Classes** | `[abc]`, `[^\\]`, `[a-d&&aeiou]`, `[a=e=b]` | ✓ | +| **Character Types** | `\d`, `\H`, `\s` | ✓ | +| **Cluster Types** | `\R`, `\X` | ✓ | +| **Conditional Exps.** | `(?(cond)yes-subexp)`, `(?(cond)yes-subexp\|no-subexp)` | ✓ | +| **Escape Sequences** | `\t`, `\\+`, `\?` | ✓ | +| **Free Space** | whitespace and `# Comments` _(x modifier)_ | ✓ | +| **Grouped Exps.** | | ⋱ | +|   _**Assertions**_ | | ⋱ | +|   _Lookahead_ | `(?=abc)` | ✓ | +|   _Negative Lookahead_ | `(?!abc)` | ✓ | +|   _Lookbehind_ | `(?<=abc)` | ✓ | +|   _Negative Lookbehind_ | `(?abc)` | ✓ | +|   _**Absence**_ | `(?~abc)` | ✓ | +|   _**Back-references**_ | | ⋱ | +|   _Named_ | `\k` | ✓ | +|   _Nest Level_ | `\k` | ✓ | +|   _Numbered_ | `\k<1>` | ✓ | +|   _Relative_ | `\k<-2>` | ✓ | +|   _Traditional_ | `\1` thru `\9` | ✓ | +|   _**Capturing**_ | `(abc)` | ✓ | +|   _**Comments**_ | `(?# comment text)` | ✓ | +|   _**Named**_ | `(?abc)`, `(?'name'abc)` | ✓ | +|   _**Options**_ | `(?mi-x:abc)`, `(?a:\s\w+)`, `(?i)` | ✓ | +|   _**Passive**_ | `(?:abc)` | ✓ | +|   _**Subexp. Calls**_ | `\g`, `\g<1>` | ✓ | +| **Keep** | `\K`, `(ab\Kc\|d\Ke)f` | ✓ | +| **Literals** _(utf-8)_ | `Ruby`, `ルビー`, `روبي` | ✓ | +| **POSIX Classes** | `[:alpha:]`, `[:^digit:]` | ✓ | +| **Quantifiers** | | ⋱ | +|   _**Greedy**_ | `?`, `*`, `+`, `{m,M}` | ✓ | +|   _**Reluctant** (Lazy)_ | `??`, `*?`, `+?`, `{m,M}?` | ✓ | +|   _**Possessive**_ | `?+`, `*+`, `++`, `{m,M}+` | ✓ | +| **String Escapes** | | ⋱ | +|   _**Control**_ | `\C-C`, `\cD` | ✓ | +|   _**Hex**_ | `\x20`, `\x{701230}` | ✓ | +|   _**Meta**_ | `\M-c`, `\M-\C-C`, `\M-\cC`, `\C-\M-C`, `\c\M-C` | ✓ | +|   _**Octal**_ | `\0`, `\01`, `\012` | ✓ | +|   _**Unicode**_ | `\uHHHH`, `\u{H+ H+}` | ✓ | +| **Unicode Properties** | _([Unicode 11.0.0](http://www.unicode.org/versions/Unicode11.0.0/))_ | ⋱ | +|   _**Age**_ | `\p{Age=5.2}`, `\P{age=7.0}`, `\p{^age=8.0}` | ✓ | +|   _**Blocks**_ | `\p{InArmenian}`, `\P{InKhmer}`, `\p{^InThai}` | ✓ | +|   _**Classes**_ | `\p{Alpha}`, `\P{Space}`, `\p{^Alnum}` | ✓ | +|   _**Derived**_ | `\p{Math}`, `\P{Lowercase}`, `\p{^Cased}` | ✓ | +|   _**General Categories**_ | `\p{Lu}`, `\P{Cs}`, `\p{^sc}` | ✓ | +|   _**Scripts**_ | `\p{Arabic}`, `\P{Hiragana}`, `\p{^Greek}` | ✓ | +|   _**Simple**_ | `\p{Dash}`, `\p{Extender}`, `\p{^Hyphen}` | ✓ | + +##### Inapplicable Features + +Some modifiers, like `o` and `s`, apply to the **Regexp** object itself and do not +appear in its source. Other such modifiers include the encoding modifiers `e` and `n` +[See](http://www.ruby-doc.org/core-2.5.0/Regexp.html#class-Regexp-label-Encoding). +These are not seen by the scanner. + +The following features are not currently enabled for Ruby by its regular +expressions library (Onigmo). They are not supported by the scanner. + + - **Quotes**: `\Q...\E` _[[See]](https://github.com/k-takata/Onigmo/blob/7911409/doc/RE#L499)_ + - **Capture History**: `(?@...)`, `(?@...)` _[[See]](https://github.com/k-takata/Onigmo/blob/7911409/doc/RE#L550)_ + + +See something missing? Please submit an [issue](https://github.com/ammar/regexp_parser/issues) + +_**Note**: Attempting to process expressions with unsupported syntax features can raise an error, +or incorrectly return tokens/objects as literals._ + + +## Testing +To run the tests simply run rake from the root directory, as 'test' is the default task. + +It generates the scanner's code from the Ragel source files and runs all the tests, thus it requires Ragel to be installed. + +The tests use RSpec. They can also be run with the test runner that whitelists some warnings: + +``` +bin/test +``` + +You can run a specific test like so: + +``` +bin/test spec/scanner/properties_spec.rb +``` + +Note that changes to Ragel files will not be reflected when running `rspec` or `bin/test`, so you might want to run: + +``` +rake ragel:rb && bin/test spec/scanner/properties_spec.rb +``` + +## Building +Building the scanner and the gem requires [Ragel](http://www.colm.net/open-source/ragel/) to be +installed. The build tasks will automatically invoke the 'ragel:rb' task to generate the +Ruby scanner code. + + +The project uses the standard rubygems package tasks, so: + + +To build the gem, run: +``` +rake build +``` + +To install the gem from the cloned project, run: +``` +rake install +``` + + +## Example Projects +Projects using regexp_parser. + +- [meta_re](https://github.com/ammar/meta_re) is a regular expression preprocessor with alias support. + +- [mutant](https://github.com/mbj/mutant) (before v0.9.0) manipulates your regular expressions (amongst others) to see if your tests cover their behavior. + +- [twitter-cldr-rb](https://github.com/twitter/twitter-cldr-rb) uses regexp_parser to generate examples of postal codes. + +- [js_regex](https://github.com/janosch-x/js_regex) converts Ruby regular expressions to JavaScript-compatible regular expressions. + + +## References +Documentation and books used while working on this project. + + +#### Ruby Flavors +* Oniguruma Regular Expressions (Ruby 1.9.x) [link](https://github.com/kkos/oniguruma/blob/master/doc/RE) +* Onigmo Regular Expressions (Ruby >= 2.0) [link](https://github.com/k-takata/Onigmo/blob/master/doc/RE) + + +#### Regular Expressions +* Mastering Regular Expressions, By Jeffrey E.F. Friedl (2nd Edition) [book](http://oreilly.com/catalog/9781565922570/) +* Regular Expression Flavor Comparison [link](http://www.regular-expressions.info/refflavors.html) +* Enumerating the strings of regular languages [link](http://www.cs.dartmouth.edu/~doug/nfa.ps.gz) +* Stack Overflow Regular Expressions FAQ [link](http://stackoverflow.com/questions/22937618/reference-what-does-this-regex-mean/22944075#22944075) + + +#### Unicode +* Unicode Explained, By Jukka K. Korpela. [book](http://oreilly.com/catalog/9780596101213) +* Unicode Derived Properties [link](http://www.unicode.org/Public/UNIDATA/DerivedCoreProperties.txt) +* Unicode Property Aliases [link](http://www.unicode.org/Public/UNIDATA/PropertyAliases.txt) +* Unicode Regular Expressions [link](http://www.unicode.org/reports/tr18/) +* Unicode Standard Annex #44 [link](http://www.unicode.org/reports/tr44/) + + +--- +##### Copyright +_Copyright (c) 2010-2019 Ammar Ali. See LICENSE file for details._ diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/Rakefile b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/Rakefile new file mode 100644 index 00000000..f61008bc --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/Rakefile @@ -0,0 +1,87 @@ +require 'rubygems' + +require 'rake' +require 'rake/testtask' + +require 'bundler' +require 'rubygems/package_task' + + +RAGEL_SOURCE_DIR = File.expand_path '../lib/regexp_parser/scanner', __FILE__ +RAGEL_OUTPUT_DIR = File.expand_path '../lib/regexp_parser', __FILE__ +RAGEL_SOURCE_FILES = %w{scanner} # scanner.rl includes property.rl + + +Bundler::GemHelper.install_tasks + + +task :default => [:'test:full'] + +namespace :test do + task full: :'ragel:rb' do + sh 'bin/test' + end +end + +namespace :ragel do + desc "Process the ragel source files and output ruby code" + task :rb do |t| + RAGEL_SOURCE_FILES.each do |file| + output_file = "#{RAGEL_OUTPUT_DIR}/#{file}.rb" + # using faster flat table driven FSM, about 25% larger code, but about 30% faster + sh "ragel -F1 -R #{RAGEL_SOURCE_DIR}/#{file}.rl -o #{output_file}" + + contents = File.read(output_file) + + File.open(output_file, 'r+') do |file| + contents = "# -*- warn-indent:false; -*-\n" + contents + + file.write(contents) + end + end + end + + desc "Delete the ragel generated source file(s)" + task :clean do |t| + RAGEL_SOURCE_FILES.each do |file| + sh "rm -f #{RAGEL_OUTPUT_DIR}/#{file}.rb" + end + end +end + + +# Add ragel task as a prerequisite for building the gem to ensure that the +# latest scanner code is generated and included in the build. +desc "Runs ragel:rb before building the gem" +task :build => ['ragel:rb'] + + +namespace :props do + desc 'Write new property value hashes for the properties scanner' + task :update do + require 'regexp_property_values' + RegexpPropertyValues.update + dir = File.expand_path('../lib/regexp_parser/scanner/properties', __FILE__) + + require 'psych' + write_hash_to_file = ->(hash, path) do + File.open(path, 'w') do |f| + f.puts '#', + "# THIS FILE IS AUTO-GENERATED BY `rake props:update`, DO NOT EDIT", + '#', + hash.sort.to_h.to_yaml + end + puts "Wrote #{hash.count} aliases to `#{path}`" + end + + long_names_to_tokens = RegexpPropertyValues.all.map do |val| + [val.identifier, val.full_name.downcase] + end + write_hash_to_file.call(long_names_to_tokens, "#{dir}/long.yml") + + short_names_to_tokens = RegexpPropertyValues.alias_hash.map do |k, v| + [k.identifier, v.full_name.downcase] + end + write_hash_to_file.call(short_names_to_tokens, "#{dir}/short.yml") + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser.rb new file mode 100644 index 00000000..9569e3b1 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser.rb @@ -0,0 +1,8 @@ +# encoding: utf-8 + +require 'regexp_parser/version' +require 'regexp_parser/token' +require 'regexp_parser/scanner' +require 'regexp_parser/syntax' +require 'regexp_parser/lexer' +require 'regexp_parser/parser' diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression.rb new file mode 100644 index 00000000..2d67e8ff --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression.rb @@ -0,0 +1,163 @@ +module Regexp::Expression + + class Base + attr_accessor :type, :token + attr_accessor :text, :ts + attr_accessor :level, :set_level, :conditional_level, :nesting_level + + attr_accessor :quantifier + attr_accessor :options + + def initialize(token, options = {}) + self.type = token.type + self.token = token.token + self.text = token.text + self.ts = token.ts + self.level = token.level + self.set_level = token.set_level + self.conditional_level = token.conditional_level + self.nesting_level = 0 + self.quantifier = nil + self.options = options + end + + def initialize_clone(orig) + self.text = (orig.text ? orig.text.dup : nil) + self.options = (orig.options ? orig.options.dup : nil) + self.quantifier = (orig.quantifier ? orig.quantifier.clone : nil) + super + end + + def to_re(format = :full) + ::Regexp.new(to_s(format)) + end + + alias :starts_at :ts + + def full_length + to_s.length + end + + def offset + [starts_at, full_length] + end + + def coded_offset + '@%d+%d' % offset + end + + def to_s(format = :full) + "#{text}#{quantifier_affix(format)}" + end + + def quantifier_affix(expression_format) + quantifier.to_s if quantified? && expression_format != :base + end + + def terminal? + !respond_to?(:expressions) + end + + def quantify(token, text, min = nil, max = nil, mode = :greedy) + self.quantifier = Quantifier.new(token, text, min, max, mode) + end + + def unquantified_clone + clone.tap { |exp| exp.quantifier = nil } + end + + def quantified? + !quantifier.nil? + end + + # Deprecated. Prefer `#repetitions` which has a more uniform interface. + def quantity + return [nil,nil] unless quantified? + [quantifier.min, quantifier.max] + end + + def repetitions + return 1..1 unless quantified? + min = quantifier.min + max = quantifier.max < 0 ? Float::INFINITY : quantifier.max + # fix Range#minmax - https://bugs.ruby-lang.org/issues/15807 + (min..max).tap { |r| r.define_singleton_method(:minmax) { [min, max] } } + end + + def greedy? + quantified? and quantifier.greedy? + end + + def reluctant? + quantified? and quantifier.reluctant? + end + alias :lazy? :reluctant? + + def possessive? + quantified? and quantifier.possessive? + end + + def attributes + { + type: type, + token: token, + text: to_s(:base), + starts_at: ts, + length: full_length, + level: level, + set_level: set_level, + conditional_level: conditional_level, + options: options, + quantifier: quantified? ? quantifier.to_h : nil, + } + end + alias :to_h :attributes + end + + def self.parsed(exp) + warn('WARNING: Regexp::Expression::Base.parsed is buggy and '\ + 'will be removed in 2.0.0. Use Regexp::Parser.parse instead.') + case exp + when String + Regexp::Parser.parse(exp) + when Regexp + Regexp::Parser.parse(exp.source) # <- causes loss of root options + when Regexp::Expression # <- never triggers + exp + else + raise ArgumentError, 'Expression.parsed accepts a String, Regexp, or '\ + 'a Regexp::Expression as a value for exp, but it '\ + "was given #{exp.class.name}." + end + end + +end # module Regexp::Expression + +require 'regexp_parser/expression/quantifier' +require 'regexp_parser/expression/subexpression' +require 'regexp_parser/expression/sequence' +require 'regexp_parser/expression/sequence_operation' + +require 'regexp_parser/expression/classes/alternation' +require 'regexp_parser/expression/classes/anchor' +require 'regexp_parser/expression/classes/backref' +require 'regexp_parser/expression/classes/conditional' +require 'regexp_parser/expression/classes/escape' +require 'regexp_parser/expression/classes/free_space' +require 'regexp_parser/expression/classes/group' +require 'regexp_parser/expression/classes/keep' +require 'regexp_parser/expression/classes/literal' +require 'regexp_parser/expression/classes/posix_class' +require 'regexp_parser/expression/classes/property' +require 'regexp_parser/expression/classes/root' +require 'regexp_parser/expression/classes/set' +require 'regexp_parser/expression/classes/set/intersection' +require 'regexp_parser/expression/classes/set/range' +require 'regexp_parser/expression/classes/type' + +require 'regexp_parser/expression/methods/match' +require 'regexp_parser/expression/methods/match_length' +require 'regexp_parser/expression/methods/options' +require 'regexp_parser/expression/methods/strfregexp' +require 'regexp_parser/expression/methods/tests' +require 'regexp_parser/expression/methods/traverse' diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/alternation.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/alternation.rb new file mode 100644 index 00000000..11bfafea --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/alternation.rb @@ -0,0 +1,10 @@ +module Regexp::Expression + # A sequence of expressions, used by Alternation as one of its alternative. + class Alternative < Regexp::Expression::Sequence; end + + class Alternation < Regexp::Expression::SequenceOperation + OPERAND = Alternative + + alias :alternatives :expressions + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/anchor.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/anchor.rb new file mode 100644 index 00000000..f67b1caf --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/anchor.rb @@ -0,0 +1,26 @@ +module Regexp::Expression + + module Anchor + class Base < Regexp::Expression::Base; end + + class BeginningOfLine < Anchor::Base; end + class EndOfLine < Anchor::Base; end + + class BeginningOfString < Anchor::Base; end + class EndOfString < Anchor::Base; end + + class EndOfStringOrBeforeEndOfLine < Anchor::Base; end + + class WordBoundary < Anchor::Base; end + class NonWordBoundary < Anchor::Base; end + + class MatchStart < Anchor::Base; end + + BOL = BeginningOfLine + EOL = EndOfLine + BOS = BeginningOfString + EOS = EndOfString + EOSobEOL = EndOfStringOrBeforeEndOfLine + end + +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/backref.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/backref.rb new file mode 100644 index 00000000..6716b3a5 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/backref.rb @@ -0,0 +1,55 @@ +module Regexp::Expression + module Backreference + class Base < Regexp::Expression::Base + attr_accessor :referenced_expression + end + + class Number < Backreference::Base + attr_reader :number + alias reference number + + def initialize(token, options = {}) + @number = token.text[token.token.equal?(:number) ? 1..-1 : 3..-2].to_i + super + end + end + + class Name < Backreference::Base + attr_reader :name + alias reference name + + def initialize(token, options = {}) + @name = token.text[3..-2] + super + end + end + + class NumberRelative < Backreference::Number + attr_accessor :effective_number + alias reference effective_number + end + + class NumberCall < Backreference::Number; end + class NameCall < Backreference::Name; end + class NumberCallRelative < Backreference::NumberRelative; end + + class NumberRecursionLevel < Backreference::Number + attr_reader :recursion_level + + def initialize(token, options = {}) + super + @number, @recursion_level = token.text[3..-2].split(/(?=[+-])/).map(&:to_i) + end + end + + class NameRecursionLevel < Backreference::Name + attr_reader :recursion_level + + def initialize(token, options = {}) + super + @name, recursion_level = token.text[3..-2].split(/(?=[+-])/) + @recursion_level = recursion_level.to_i + end + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/conditional.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/conditional.rb new file mode 100644 index 00000000..262e28ea --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/conditional.rb @@ -0,0 +1,58 @@ +module Regexp::Expression + module Conditional + class TooManyBranches < StandardError + def initialize + super('The conditional expression has more than 2 branches') + end + end + + class Condition < Regexp::Expression::Base + attr_accessor :referenced_expression + + # Name or number of the referenced capturing group that determines state. + # Returns a String if reference is by name, Integer if by number. + def reference + ref = text.tr("'<>()", "") + ref =~ /\D/ ? ref : Integer(ref) + end + end + + class Branch < Regexp::Expression::Sequence; end + + class Expression < Regexp::Expression::Subexpression + attr_accessor :referenced_expression + + def <<(exp) + expressions.last << exp + end + + def add_sequence(active_opts = {}) + raise TooManyBranches.new if branches.length == 2 + params = { conditional_level: conditional_level + 1 } + Branch.add_to(self, params, active_opts) + end + alias :branch :add_sequence + + def condition=(exp) + expressions.delete(condition) + expressions.unshift(exp) + end + + def condition + find { |subexp| subexp.is_a?(Condition) } + end + + def branches + select { |subexp| subexp.is_a?(Sequence) } + end + + def reference + condition.reference + end + + def to_s(format = :full) + "#{text}#{condition}#{branches.join('|')})#{quantifier_affix(format)}" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/escape.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/escape.rb new file mode 100644 index 00000000..0d739ad4 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/escape.rb @@ -0,0 +1,94 @@ +module Regexp::Expression + module EscapeSequence + class Base < Regexp::Expression::Base + require 'yaml' + + def char + # poor man's unescape without using eval + YAML.load(%Q(---\n"#{text}"\n)) + end + + def codepoint + char.ord + end + end + + class Literal < EscapeSequence::Base + def char + text[1..-1] + end + end + + class AsciiEscape < EscapeSequence::Base; end + class Backspace < EscapeSequence::Base; end + class Bell < EscapeSequence::Base; end + class FormFeed < EscapeSequence::Base; end + class Newline < EscapeSequence::Base; end + class Return < EscapeSequence::Base; end + class Tab < EscapeSequence::Base; end + class VerticalTab < EscapeSequence::Base; end + + class Hex < EscapeSequence::Base; end + class Codepoint < EscapeSequence::Base; end + + class CodepointList < EscapeSequence::Base + def char + raise NoMethodError, 'CodepointList responds only to #chars' + end + + def codepoint + raise NoMethodError, 'CodepointList responds only to #codepoints' + end + + def chars + codepoints.map { |cp| cp.chr('utf-8') } + end + + def codepoints + text.scan(/\h+/).map(&:hex) + end + end + + class Octal < EscapeSequence::Base + def char + text[1..-1].to_i(8).chr('utf-8') + end + end + + class AbstractMetaControlSequence < EscapeSequence::Base + def char + codepoint.chr('utf-8') + end + + private + + def control_sequence_to_s(control_sequence) + five_lsb = control_sequence.unpack('B*').first[-5..-1] + ["000#{five_lsb}"].pack('B*') + end + + def meta_char_to_codepoint(meta_char) + byte_value = meta_char.ord + byte_value < 128 ? byte_value + 128 : byte_value + end + end + + class Control < AbstractMetaControlSequence + def codepoint + control_sequence_to_s(text).ord + end + end + + class Meta < AbstractMetaControlSequence + def codepoint + meta_char_to_codepoint(text[-1]) + end + end + + class MetaControl < AbstractMetaControlSequence + def codepoint + meta_char_to_codepoint(control_sequence_to_s(text)) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/free_space.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/free_space.rb new file mode 100644 index 00000000..e022b0bd --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/free_space.rb @@ -0,0 +1,17 @@ +module Regexp::Expression + + class FreeSpace < Regexp::Expression::Base + def quantify(token, text, min = nil, max = nil, mode = :greedy) + raise "Can not quantify a free space object" + end + end + + class Comment < Regexp::Expression::FreeSpace; end + + class WhiteSpace < Regexp::Expression::FreeSpace + def merge(exp) + text << exp.text + end + end + +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/group.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/group.rb new file mode 100644 index 00000000..63e8275a --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/group.rb @@ -0,0 +1,60 @@ +module Regexp::Expression + module Group + class Base < Regexp::Expression::Subexpression + def to_s(format = :full) + "#{text}#{expressions.join})#{quantifier_affix(format)}" + end + + def capturing?; false end + + def comment?; false end + end + + class Atomic < Group::Base; end + class Passive < Group::Base; end + class Absence < Group::Base; end + class Options < Group::Base + attr_accessor :option_changes + end + + class Capture < Group::Base + attr_accessor :number, :number_at_level + alias identifier number + + def capturing?; true end + end + + class Named < Group::Capture + attr_reader :name + alias identifier name + + def initialize(token, options = {}) + @name = token.text[3..-2] + super + end + + def initialize_clone(orig) + @name = orig.name.dup + super + end + end + + class Comment < Group::Base + def to_s(_format = :full) + text.dup + end + + def comment?; true end + end + end + + module Assertion + class Base < Regexp::Expression::Group::Base; end + + class Lookahead < Assertion::Base; end + class NegativeLookahead < Assertion::Base; end + + class Lookbehind < Assertion::Base; end + class NegativeLookbehind < Assertion::Base; end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/keep.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/keep.rb new file mode 100644 index 00000000..50135884 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/keep.rb @@ -0,0 +1,5 @@ +module Regexp::Expression + module Keep + class Mark < Regexp::Expression::Base; end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/literal.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/literal.rb new file mode 100644 index 00000000..e4120eaf --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/literal.rb @@ -0,0 +1,7 @@ +module Regexp::Expression + + class Literal < Regexp::Expression::Base + # Obviously nothing special here, yet. + end + +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/posix_class.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/posix_class.rb new file mode 100644 index 00000000..3ab9f5c8 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/posix_class.rb @@ -0,0 +1,11 @@ +module Regexp::Expression + class PosixClass < Regexp::Expression::Base + def negative? + type == :nonposixclass + end + + def name + token.to_s + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/property.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/property.rb new file mode 100644 index 00000000..cee19ac2 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/property.rb @@ -0,0 +1,120 @@ +module Regexp::Expression + + module UnicodeProperty + class Base < Regexp::Expression::Base + def negative? + type == :nonproperty + end + + def name + text =~ /\A\\[pP]\{([^}]+)\}\z/; $1 + end + + def shortcut + (Regexp::Scanner.short_prop_map.rassoc(token.to_s) || []).first + end + end + + class Alnum < Base; end + class Alpha < Base; end + class Ascii < Base; end + class Blank < Base; end + class Cntrl < Base; end + class Digit < Base; end + class Graph < Base; end + class Lower < Base; end + class Print < Base; end + class Punct < Base; end + class Space < Base; end + class Upper < Base; end + class Word < Base; end + class Xdigit < Base; end + class XPosixPunct < Base; end + + class Newline < Base; end + + class Any < Base; end + class Assigned < Base; end + + module Letter + class Base < UnicodeProperty::Base; end + + class Any < Letter::Base; end + class Cased < Letter::Base; end + class Uppercase < Letter::Base; end + class Lowercase < Letter::Base; end + class Titlecase < Letter::Base; end + class Modifier < Letter::Base; end + class Other < Letter::Base; end + end + + module Mark + class Base < UnicodeProperty::Base; end + + class Any < Mark::Base; end + class Combining < Mark::Base; end + class Nonspacing < Mark::Base; end + class Spacing < Mark::Base; end + class Enclosing < Mark::Base; end + end + + module Number + class Base < UnicodeProperty::Base; end + + class Any < Number::Base; end + class Decimal < Number::Base; end + class Letter < Number::Base; end + class Other < Number::Base; end + end + + module Punctuation + class Base < UnicodeProperty::Base; end + + class Any < Punctuation::Base; end + class Connector < Punctuation::Base; end + class Dash < Punctuation::Base; end + class Open < Punctuation::Base; end + class Close < Punctuation::Base; end + class Initial < Punctuation::Base; end + class Final < Punctuation::Base; end + class Other < Punctuation::Base; end + end + + module Separator + class Base < UnicodeProperty::Base; end + + class Any < Separator::Base; end + class Space < Separator::Base; end + class Line < Separator::Base; end + class Paragraph < Separator::Base; end + end + + module Symbol + class Base < UnicodeProperty::Base; end + + class Any < Symbol::Base; end + class Math < Symbol::Base; end + class Currency < Symbol::Base; end + class Modifier < Symbol::Base; end + class Other < Symbol::Base; end + end + + module Codepoint + class Base < UnicodeProperty::Base; end + + class Any < Codepoint::Base; end + class Control < Codepoint::Base; end + class Format < Codepoint::Base; end + class Surrogate < Codepoint::Base; end + class PrivateUse < Codepoint::Base; end + class Unassigned < Codepoint::Base; end + end + + class Age < UnicodeProperty::Base; end + class Derived < UnicodeProperty::Base; end + class Emoji < UnicodeProperty::Base; end + class Script < UnicodeProperty::Base; end + class Block < UnicodeProperty::Base; end + end + +end # module Regexp::Expression diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/root.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/root.rb new file mode 100644 index 00000000..736f5613 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/root.rb @@ -0,0 +1,24 @@ +module Regexp::Expression + + class Root < Regexp::Expression::Subexpression + # TODO: this override is here for backwards compatibility, remove in 2.0.0 + def initialize(*args) + unless args.first.is_a?(Regexp::Token) + warn('WARNING: Root.new without a Token argument is deprecated and '\ + 'will be removed in 2.0.0. Use Root.build for the old behavior.') + return super(self.class.build_token, *args) + end + super + end + + class << self + def build(options = {}) + new(build_token, options) + end + + def build_token + Regexp::Token.new(:expression, :root, '', 0) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/set.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/set.rb new file mode 100644 index 00000000..3cf8fb6d --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/set.rb @@ -0,0 +1,27 @@ +module Regexp::Expression + class CharacterSet < Regexp::Expression::Subexpression + attr_accessor :closed, :negative + + alias :negative? :negative + alias :negated? :negative + alias :closed? :closed + + def initialize(token, options = {}) + self.negative = false + self.closed = false + super + end + + def negate + self.negative = true + end + + def close + self.closed = true + end + + def to_s(format = :full) + "#{text}#{'^' if negated?}#{expressions.join}]#{quantifier_affix(format)}" + end + end +end # module Regexp::Expression diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/set/intersection.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/set/intersection.rb new file mode 100644 index 00000000..bc119df8 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/set/intersection.rb @@ -0,0 +1,9 @@ +module Regexp::Expression + class CharacterSet < Regexp::Expression::Subexpression + class IntersectedSequence < Regexp::Expression::Sequence; end + + class Intersection < Regexp::Expression::SequenceOperation + OPERAND = IntersectedSequence + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/set/range.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/set/range.rb new file mode 100644 index 00000000..f238466a --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/set/range.rb @@ -0,0 +1,23 @@ +module Regexp::Expression + class CharacterSet < Regexp::Expression::Subexpression + class Range < Regexp::Expression::Subexpression + def starts_at + expressions.first.starts_at + end + alias :ts :starts_at + + def <<(exp) + complete? && raise("Can't add more than 2 expressions to a Range") + super + end + + def complete? + count == 2 + end + + def to_s(_format = :full) + expressions.join(text) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/type.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/type.rb new file mode 100644 index 00000000..b716b559 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/classes/type.rb @@ -0,0 +1,19 @@ +module Regexp::Expression + + module CharacterType + class Base < Regexp::Expression::Base; end + + class Any < CharacterType::Base; end + class Digit < CharacterType::Base; end + class NonDigit < CharacterType::Base; end + class Hex < CharacterType::Base; end + class NonHex < CharacterType::Base; end + class Word < CharacterType::Base; end + class NonWord < CharacterType::Base; end + class Space < CharacterType::Base; end + class NonSpace < CharacterType::Base; end + class Linebreak < CharacterType::Base; end + class ExtendedGrapheme < CharacterType::Base; end + end + +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/methods/match.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/methods/match.rb new file mode 100644 index 00000000..076e7956 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/methods/match.rb @@ -0,0 +1,13 @@ +module Regexp::Expression + class Base + def match?(string) + !!match(string) + end + alias :matches? :match? + + def match(string, offset = 0) + Regexp.new(to_s).match(string, offset) + end + alias :=~ :match + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/methods/match_length.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/methods/match_length.rb new file mode 100644 index 00000000..dc1a9ce4 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/methods/match_length.rb @@ -0,0 +1,172 @@ +class Regexp::MatchLength + include Enumerable + + def self.of(obj) + exp = obj.is_a?(Regexp::Expression::Base) ? obj : Regexp::Parser.parse(obj) + exp.match_length + end + + def initialize(exp, opts = {}) + self.exp_class = exp.class + self.min_rep = exp.repetitions.min + self.max_rep = exp.repetitions.max + if base = opts[:base] + self.base_min = base + self.base_max = base + self.reify = ->{ '.' * base } + else + self.base_min = opts.fetch(:base_min) + self.base_max = opts.fetch(:base_max) + self.reify = opts.fetch(:reify) + end + end + + def each(opts = {}) + return enum_for(__method__) unless block_given? + limit = opts[:limit] || 1000 + yielded = 0 + (min..max).each do |num| + next unless include?(num) + yield(num) + break if (yielded += 1) >= limit + end + end + + def endless_each(&block) + return enum_for(__method__) unless block_given? + (min..max).each { |num| yield(num) if include?(num) } + end + + def include?(length) + test_regexp.match?('X' * length) + end + + def fixed? + min == max + end + + def min + min_rep * base_min + end + + def max + max_rep * base_max + end + + def minmax + [min, max] + end + + def inspect + type = exp_class.name.sub('Regexp::Expression::', '') + "#<#{self.class}<#{type}> min=#{min} max=#{max}>" + end + + def to_re + "(?:#{reify.call}){#{min_rep},#{max_rep unless max_rep == Float::INFINITY}}" + end + + private + + attr_accessor :base_min, :base_max, :min_rep, :max_rep, :exp_class, :reify + + def test_regexp + @test_regexp ||= Regexp.new("^#{to_re}$").tap do |regexp| + regexp.respond_to?(:match?) || def regexp.match?(str); !!match(str) end + end + end +end + +module Regexp::Expression + MatchLength = Regexp::MatchLength + + [ + CharacterSet, + CharacterSet::Intersection, + CharacterSet::IntersectedSequence, + CharacterSet::Range, + CharacterType::Base, + EscapeSequence::Base, + PosixClass, + UnicodeProperty::Base, + ].each do |klass| + klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def match_length + MatchLength.new(self, base: 1) + end + RUBY + end + + class Literal + def match_length + MatchLength.new(self, base: text.length) + end + end + + class Subexpression + def match_length + MatchLength.new(self, + base_min: map { |exp| exp.match_length.min }.inject(0, :+), + base_max: map { |exp| exp.match_length.max }.inject(0, :+), + reify: ->{ map { |exp| exp.match_length.to_re }.join }) + end + + def inner_match_length + dummy = Regexp::Expression::Root.build + dummy.expressions = expressions.map(&:clone) + dummy.quantifier = quantifier && quantifier.clone + dummy.match_length + end + end + + [ + Alternation, + Conditional::Expression, + ].each do |klass| + klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def match_length + MatchLength.new(self, + base_min: map { |exp| exp.match_length.min }.min, + base_max: map { |exp| exp.match_length.max }.max, + reify: ->{ map { |exp| exp.match_length.to_re }.join('|') }) + end + RUBY + end + + [ + Anchor::Base, + Assertion::Base, + Conditional::Condition, + FreeSpace, + Keep::Mark, + ].each do |klass| + klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def match_length + MatchLength.new(self, base: 0) + end + RUBY + end + + class Backreference::Base + def match_length + if referenced_expression.nil? + raise ArgumentError, 'Missing referenced_expression - not parsed?' + end + referenced_expression.unquantified_clone.match_length + end + end + + class EscapeSequence::CodepointList + def match_length + MatchLength.new(self, base: codepoints.count) + end + end + + # Special case. Absence group can match 0.. chars, irrespective of content. + # TODO: in theory, they *can* exclude match lengths with `.`: `(?~.{3})` + class Group::Absence + def match_length + MatchLength.new(self, base_min: 0, base_max: Float::INFINITY, reify: ->{ '.*' }) + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/methods/options.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/methods/options.rb new file mode 100644 index 00000000..3ac9e21b --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/methods/options.rb @@ -0,0 +1,35 @@ +module Regexp::Expression + class Base + def multiline? + options[:m] == true + end + alias :m? :multiline? + + def case_insensitive? + options[:i] == true + end + alias :i? :case_insensitive? + alias :ignore_case? :case_insensitive? + + def free_spacing? + options[:x] == true + end + alias :x? :free_spacing? + alias :extended? :free_spacing? + + def default_classes? + options[:d] == true + end + alias :d? :default_classes? + + def ascii_classes? + options[:a] == true + end + alias :a? :ascii_classes? + + def unicode_classes? + options[:u] == true + end + alias :u? :unicode_classes? + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/methods/strfregexp.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/methods/strfregexp.rb new file mode 100644 index 00000000..f0c66fd4 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/methods/strfregexp.rb @@ -0,0 +1,114 @@ +module Regexp::Expression + class Base + + # %l Level (depth) of the expression. Returns 'root' for the root + # expression, returns zero or higher for all others. + # + # %> Indentation at expression's level. + # + # %x Index of the expression at its depth. Available when using + # the sprintf_tree method only. + # + # %s Start offset within the whole expression. + # %e End offset within the whole expression. + # %S Length of expression. + # + # %o Coded offset and length, same as '@%s+%S' + # + # %y Type of expression. + # %k Token of expression. + # %i ID, same as '%y:%k' + # %c Class name + # + # %q Quantifier info, as {m[,M]} + # %Q Quantifier text + # + # %z Quantifier min + # %Z Quantifier max + # + # %t Base text of the expression (excludes quantifier, if any) + # %~t Full text if the expression is terminal, otherwise %i + # %T Full text of the expression (includes quantifier, if any) + # + # %b Basic info, same as '%o %i' + # %m Most info, same as '%b %q' + # %a All info, same as '%m %t' + # + def strfregexp(format = '%a', indent_offset = 0, index = nil) + have_index = index ? true : false + + part = {} + + print_level = nesting_level > 0 ? nesting_level - 1 : nil + + # Order is important! Fields that use other fields in their + # definition must appear before the fields they use. + part_keys = %w{a m b o i l x s e S y k c q Q z Z t ~t T >} + part.keys.each {|k| part[k] = ""} + + part['>'] = print_level ? (' ' * (print_level + indent_offset)) : '' + + part['l'] = print_level ? "#{'%d' % print_level}" : 'root' + part['x'] = "#{'%d' % index}" if have_index + + part['s'] = starts_at + part['S'] = full_length + part['e'] = starts_at + full_length + part['o'] = coded_offset + + part['k'] = token + part['y'] = type + part['i'] = '%y:%k' + part['c'] = self.class.name + + if quantified? + if quantifier.max == -1 + part['q'] = "{#{quantifier.min}, or-more}" + else + part['q'] = "{#{quantifier.min}, #{quantifier.max}}" + end + + part['Q'] = quantifier.text + part['z'] = quantifier.min + part['Z'] = quantifier.max + else + part['q'] = '{1}' + part['Q'] = '' + part['z'] = '1' + part['Z'] = '1' + end + + part['t'] = to_s(:base) + part['~t'] = terminal? ? to_s : "#{type}:#{token}" + part['T'] = to_s(:full) + + part['b'] = '%o %i' + part['m'] = '%b %q' + part['a'] = '%m %t' + + out = format.dup + + part_keys.each do |k| + out.gsub!(/%#{k}/, part[k].to_s) + end + + out + end + + alias :strfre :strfregexp + end + + class Subexpression < Regexp::Expression::Base + def strfregexp_tree(format = '%a', include_self = true, separator = "\n") + output = include_self ? [self.strfregexp(format)] : [] + + output += flat_map do |exp, index| + exp.strfregexp(format, (include_self ? 1 : 0), index) + end + + output.join(separator) + end + + alias :strfre_tree :strfregexp_tree + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/methods/tests.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/methods/tests.rb new file mode 100644 index 00000000..16b0ee31 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/methods/tests.rb @@ -0,0 +1,97 @@ +module Regexp::Expression + class Base + + # Test if this expression has the given test_type, which can be either + # a symbol or an array of symbols to check against the expression's type. + # + # # is it a :group expression + # exp.type? :group + # + # # is it a :set, or :meta + # exp.type? [:set, :meta] + # + def type?(test_type) + test_types = Array(test_type).map(&:to_sym) + test_types.include?(:*) || test_types.include?(type) + end + + # Test if this expression has the given test_token, and optionally a given + # test_type. + # + # # Any expressions + # exp.is? :* # always returns true + # + # # is it a :capture + # exp.is? :capture + # + # # is it a :character and a :set + # exp.is? :character, :set + # + # # is it a :meta :dot + # exp.is? :dot, :meta + # + # # is it a :meta or :escape :dot + # exp.is? :dot, [:meta, :escape] + # + def is?(test_token, test_type = nil) + return true if test_token === :* + token == test_token and (test_type ? type?(test_type) : true) + end + + # Test if this expression matches an entry in the given scope spec. + # + # A scope spec can be one of: + # + # . An array: Interpreted as a set of tokens, tested for inclusion + # of the expression's token. + # + # . A hash: Where the key is interpreted as the expression type + # and the value is either a symbol or an array. In this + # case, when the scope is a hash, one_of? calls itself to + # evaluate the key's value. + # + # . A symbol: matches the expression's token or type, depending on + # the level of the call. If one_of? is called directly with + # a symbol then it will always be checked against the + # type of the expression. If it's being called for a value + # from a hash, it will be checked against the token of the + # expression. + # + # # any expression + # exp.one_of?(:*) # always true + # + # # like exp.type?(:group) + # exp.one_of?(:group) + # + # # any expression of type meta + # exp.one_of?(:meta => :*) + # + # # meta dots and alternations + # exp.one_of?(:meta => [:dot, :alternation]) + # + # # meta dots and any set tokens + # exp.one_of?({meta: [:dot], set: :*}) + # + def one_of?(scope, top = true) + case scope + when Array + scope.include?(:*) || scope.include?(token) + + when Hash + if scope.has_key?(:*) + test_type = scope.has_key?(type) ? type : :* + one_of?(scope[test_type], false) + else + scope.has_key?(type) && one_of?(scope[type], false) + end + + when Symbol + scope.equal?(:*) || (top ? type?(scope) : is?(scope)) + + else + raise ArgumentError, + "Array, Hash, or Symbol expected, #{scope.class.name} given" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/methods/traverse.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/methods/traverse.rb new file mode 100644 index 00000000..3084c4e5 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/methods/traverse.rb @@ -0,0 +1,62 @@ +module Regexp::Expression + class Subexpression < Regexp::Expression::Base + + # Traverses the subexpression (depth-first, pre-order) and calls the given + # block for each expression with three arguments; the traversal event, + # the expression, and the index of the expression within its parent. + # + # The event argument is passed as follows: + # + # - For subexpressions, :enter upon entering the subexpression, and + # :exit upon exiting it. + # + # - For terminal expressions, :visit is called once. + # + # Returns self. + def traverse(include_self = false, &block) + raise 'traverse requires a block' unless block_given? + + block.call(:enter, self, 0) if include_self + + each_with_index do |exp, index| + if exp.terminal? + block.call(:visit, exp, index) + else + block.call(:enter, exp, index) + exp.traverse(&block) + block.call(:exit, exp, index) + end + end + + block.call(:exit, self, 0) if include_self + + self + end + alias :walk :traverse + + # Iterates over the expressions of this expression as an array, passing + # the expression and its index within its parent to the given block. + def each_expression(include_self = false, &block) + traverse(include_self) do |event, exp, index| + yield(exp, index) unless event == :exit + end + end + + # Returns a new array with the results of calling the given block once + # for every expression. If a block is not given, returns an array with + # each expression and its level index as an array. + def flat_map(include_self = false, &block) + result = [] + + each_expression(include_self) do |exp, index| + if block_given? + result << yield(exp, index) + else + result << [exp, index] + end + end + + result + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/quantifier.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/quantifier.rb new file mode 100644 index 00000000..da5dcd32 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/quantifier.rb @@ -0,0 +1,44 @@ +module Regexp::Expression + class Quantifier + MODES = [:greedy, :possessive, :reluctant] + + attr_reader :token, :text, :min, :max, :mode + + def initialize(token, text, min, max, mode) + @token = token + @text = text + @mode = mode + @min = min + @max = max + end + + def initialize_clone(orig) + @text = orig.text.dup + super + end + + def to_s + text.dup + end + alias :to_str :to_s + + def to_h + { + token: token, + text: text, + mode: mode, + min: min, + max: max, + } + end + + MODES.each do |mode| + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{mode}? + mode.equal?(:#{mode}) + end + RUBY + end + alias :lazy? :reluctant? + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/sequence.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/sequence.rb new file mode 100644 index 00000000..7b7f20c5 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/sequence.rb @@ -0,0 +1,67 @@ +module Regexp::Expression + + # A sequence of expressions. Differs from a Subexpressions by how it handles + # quantifiers, as it applies them to its last element instead of itself as + # a whole subexpression. + # + # Used as the base class for the Alternation alternatives, Conditional + # branches, and CharacterSet::Intersection intersected sequences. + class Sequence < Regexp::Expression::Subexpression + # TODO: this override is here for backwards compatibility, remove in 2.0.0 + def initialize(*args) + if args.count == 3 + warn('WARNING: Sequence.new without a Regexp::Token argument is '\ + 'deprecated and will be removed in 2.0.0.') + return self.class.at_levels(*args) + end + super + end + + class << self + def add_to(subexpression, params = {}, active_opts = {}) + sequence = at_levels( + subexpression.level, + subexpression.set_level, + params[:conditional_level] || subexpression.conditional_level + ) + sequence.nesting_level = subexpression.nesting_level + 1 + sequence.options = active_opts + subexpression.expressions << sequence + sequence + end + + def at_levels(level, set_level, conditional_level) + token = Regexp::Token.new( + :expression, + :sequence, + '', + nil, # ts + nil, # te + level, + set_level, + conditional_level + ) + new(token) + end + end + + def starts_at + expressions.first.starts_at + end + alias :ts :starts_at + + def quantify(token, text, min = nil, max = nil, mode = :greedy) + offset = -1 + target = expressions[offset] + while target.is_a?(FreeSpace) + target = expressions[offset -= 1] + end + + target || raise(ArgumentError, "No valid target found for '#{text}' "\ + 'quantifier') + + target.quantify(token, text, min, max, mode) + end + end + +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/sequence_operation.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/sequence_operation.rb new file mode 100644 index 00000000..a6060078 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/sequence_operation.rb @@ -0,0 +1,25 @@ +module Regexp::Expression + # abstract class + class SequenceOperation < Regexp::Expression::Subexpression + alias :sequences :expressions + alias :operands :expressions + alias :operator :text + + def starts_at + expressions.first.starts_at + end + alias :ts :starts_at + + def <<(exp) + expressions.last << exp + end + + def add_sequence(active_opts = {}) + self.class::OPERAND.add_to(self, {}, active_opts) + end + + def to_s(format = :full) + sequences.map { |e| e.to_s(format) }.join(text) + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/subexpression.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/subexpression.rb new file mode 100644 index 00000000..ea4e59d8 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/expression/subexpression.rb @@ -0,0 +1,59 @@ +module Regexp::Expression + + class Subexpression < Regexp::Expression::Base + include Enumerable + + attr_accessor :expressions + + def initialize(token, options = {}) + super + + self.expressions = [] + end + + # Override base method to clone the expressions as well. + def initialize_clone(orig) + self.expressions = orig.expressions.map(&:clone) + super + end + + def <<(exp) + if exp.is_a?(WhiteSpace) && last && last.is_a?(WhiteSpace) + last.merge(exp) + else + exp.nesting_level = nesting_level + 1 + expressions << exp + end + end + + %w[[] at each empty? fetch index join last length values_at].each do |method| + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{method}(*args, &block) + expressions.#{method}(*args, &block) + end + RUBY + end + + def dig(*indices) + exp = self + indices.each { |idx| exp = exp.nil? || exp.terminal? ? nil : exp[idx] } + exp + end + + def te + ts + to_s.length + end + + def to_s(format = :full) + # Note: the format does not get passed down to subexpressions. + "#{expressions.join}#{quantifier_affix(format)}" + end + + def to_h + attributes.merge({ + text: to_s(:base), + expressions: expressions.map(&:to_h) + }) + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/lexer.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/lexer.rb new file mode 100644 index 00000000..29468042 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/lexer.rb @@ -0,0 +1,127 @@ +# A very thin wrapper around the scanner that breaks quantified literal runs, +# collects emitted tokens into an array, calculates their nesting depth, and +# normalizes tokens for the parser, and checks if they are implemented by the +# given syntax flavor. +class Regexp::Lexer + + OPENING_TOKENS = [ + :capture, :passive, :lookahead, :nlookahead, :lookbehind, :nlookbehind, + :atomic, :options, :options_switch, :named, :absence + ].freeze + + CLOSING_TOKENS = [:close].freeze + + def self.lex(input, syntax = "ruby/#{RUBY_VERSION}", &block) + new.lex(input, syntax, &block) + end + + def lex(input, syntax = "ruby/#{RUBY_VERSION}", &block) + syntax = Regexp::Syntax.new(syntax) + + self.tokens = [] + self.nesting = 0 + self.set_nesting = 0 + self.conditional_nesting = 0 + self.shift = 0 + + last = nil + Regexp::Scanner.scan(input) do |type, token, text, ts, te| + type, token = *syntax.normalize(type, token) + syntax.check! type, token + + ascend(type, token) + + if type == :quantifier and last + break_literal(last) if last.type == :literal + break_codepoint_list(last) if last.token == :codepoint_list + end + + current = Regexp::Token.new(type, token, text, ts + shift, te + shift, + nesting, set_nesting, conditional_nesting) + + current = merge_condition(current) if type == :conditional and + [:condition, :condition_close].include?(token) + + last.next = current if last + current.previous = last if last + + tokens << current + last = current + + descend(type, token) + end + + if block_given? + tokens.map { |t| block.call(t) } + else + tokens + end + end + + class << self + alias :scan :lex + end + + private + + attr_accessor :tokens, :nesting, :set_nesting, :conditional_nesting, :shift + + def ascend(type, token) + case type + when :group, :assertion + self.nesting = nesting - 1 if CLOSING_TOKENS.include?(token) + when :set + self.set_nesting = set_nesting - 1 if token == :close + when :conditional + self.conditional_nesting = conditional_nesting - 1 if token == :close + end + end + + def descend(type, token) + case type + when :group, :assertion + self.nesting = nesting + 1 if OPENING_TOKENS.include?(token) + when :set + self.set_nesting = set_nesting + 1 if token == :open + when :conditional + self.conditional_nesting = conditional_nesting + 1 if token == :open + end + end + + # called by scan to break a literal run that is longer than one character + # into two separate tokens when it is followed by a quantifier + def break_literal(token) + lead, last, _ = token.text.partition(/.\z/mu) + return if lead.empty? + + tokens.pop + tokens << Regexp::Token.new(:literal, :literal, lead, + token.ts, (token.te - last.bytesize), + nesting, set_nesting, conditional_nesting) + tokens << Regexp::Token.new(:literal, :literal, last, + (token.ts + lead.bytesize), token.te, + nesting, set_nesting, conditional_nesting) + end + + def break_codepoint_list(token) + lead, _, tail = token.text.rpartition(' ') + return if lead.empty? + + tokens.pop + tokens << Regexp::Token.new(:escape, :codepoint_list, lead + '}', + token.ts, (token.te - tail.length), + nesting, set_nesting, conditional_nesting) + tokens << Regexp::Token.new(:escape, :codepoint_list, '\u{' + tail, + (token.ts + lead.length + 1), (token.te + 3), + nesting, set_nesting, conditional_nesting) + + self.shift = shift + 3 # one space less, but extra \, u, {, and } + end + + def merge_condition(current) + last = tokens.pop + Regexp::Token.new(:conditional, :condition, last.text + current.text, + last.ts, current.te, nesting, set_nesting, conditional_nesting) + end + +end # module Regexp::Lexer diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/parser.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/parser.rb new file mode 100644 index 00000000..2ac42f12 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/parser.rb @@ -0,0 +1,653 @@ +require 'regexp_parser/expression' + +class Regexp::Parser + include Regexp::Expression + include Regexp::Syntax + + class ParserError < StandardError; end + + class UnknownTokenTypeError < ParserError + def initialize(type, token) + super "Unknown token type #{type} #{token.inspect}" + end + end + + class UnknownTokenError < ParserError + def initialize(type, token) + super "Unknown #{type} token #{token.token}" + end + end + + def self.parse(input, syntax = "ruby/#{RUBY_VERSION}", &block) + new.parse(input, syntax, &block) + end + + def parse(input, syntax = "ruby/#{RUBY_VERSION}", &block) + root = Root.build(options_from_input(input)) + + self.root = root + self.node = root + self.nesting = [root] + + self.options_stack = [root.options] + self.switching_options = false + self.conditional_nesting = [] + + self.captured_group_counts = Hash.new(0) + + Regexp::Lexer.scan(input, syntax) do |token| + parse_token(token) + end + + assign_referenced_expressions + + if block_given? + block.call(root) + else + root + end + end + + private + + attr_accessor :root, :node, :nesting, + :options_stack, :switching_options, :conditional_nesting, + :captured_group_counts + + def options_from_input(input) + return {} unless input.is_a?(::Regexp) + + options = {} + options[:i] = true if input.options & ::Regexp::IGNORECASE != 0 + options[:m] = true if input.options & ::Regexp::MULTILINE != 0 + options[:x] = true if input.options & ::Regexp::EXTENDED != 0 + options + end + + def nest(exp) + nesting.push(exp) + node << exp + update_transplanted_subtree(exp, node) + self.node = exp + end + + # subtrees are transplanted to build Alternations, Intersections, Ranges + def update_transplanted_subtree(exp, new_parent) + exp.nesting_level = new_parent.nesting_level + 1 + exp.respond_to?(:each) && + exp.each { |subexp| update_transplanted_subtree(subexp, exp) } + end + + def decrease_nesting + while nesting.last.is_a?(SequenceOperation) + nesting.pop + self.node = nesting.last + end + nesting.pop + yield(node) if block_given? + self.node = nesting.last + self.node = node.last if node.last.is_a?(SequenceOperation) + end + + def nest_conditional(exp) + conditional_nesting.push(exp) + nest(exp) + end + + def parse_token(token) + close_completed_character_set_range + + case token.type + when :meta; meta(token) + when :quantifier; quantifier(token) + when :anchor; anchor(token) + when :escape; escape(token) + when :group; group(token) + when :assertion; group(token) + when :set; set(token) + when :type; type(token) + when :backref; backref(token) + when :conditional; conditional(token) + when :keep; keep(token) + + when :posixclass, :nonposixclass + posixclass(token) + when :property, :nonproperty + property(token) + + when :literal + node << Literal.new(token, active_opts) + when :free_space + free_space(token) + + else + raise UnknownTokenTypeError.new(token.type, token) + end + end + + def set(token) + case token.token + when :open + open_set(token) + when :close + close_set + when :negate + negate_set + when :range + range(token) + when :intersection + intersection(token) + when :collation, :equivalent + node << Literal.new(token, active_opts) + else + raise UnknownTokenError.new('CharacterSet', token) + end + end + + def meta(token) + case token.token + when :dot + node << CharacterType::Any.new(token, active_opts) + when :alternation + sequence_operation(Alternation, token) + else + raise UnknownTokenError.new('Meta', token) + end + end + + def backref(token) + case token.token + when :name_ref + node << Backreference::Name.new(token, active_opts) + when :name_recursion_ref + node << Backreference::NameRecursionLevel.new(token, active_opts) + when :name_call + node << Backreference::NameCall.new(token, active_opts) + when :number, :number_ref + node << Backreference::Number.new(token, active_opts) + when :number_recursion_ref + node << Backreference::NumberRecursionLevel.new(token, active_opts) + when :number_call + node << Backreference::NumberCall.new(token, active_opts) + when :number_rel_ref + node << Backreference::NumberRelative.new(token, active_opts).tap do |exp| + assign_effective_number(exp) + end + when :number_rel_call + node << Backreference::NumberCallRelative.new(token, active_opts).tap do |exp| + assign_effective_number(exp) + end + else + raise UnknownTokenError.new('Backreference', token) + end + end + + def type(token) + case token.token + when :digit + node << CharacterType::Digit.new(token, active_opts) + when :nondigit + node << CharacterType::NonDigit.new(token, active_opts) + when :hex + node << CharacterType::Hex.new(token, active_opts) + when :nonhex + node << CharacterType::NonHex.new(token, active_opts) + when :space + node << CharacterType::Space.new(token, active_opts) + when :nonspace + node << CharacterType::NonSpace.new(token, active_opts) + when :word + node << CharacterType::Word.new(token, active_opts) + when :nonword + node << CharacterType::NonWord.new(token, active_opts) + when :linebreak + node << CharacterType::Linebreak.new(token, active_opts) + when :xgrapheme + node << CharacterType::ExtendedGrapheme.new(token, active_opts) + else + raise UnknownTokenError.new('CharacterType', token) + end + end + + def conditional(token) + case token.token + when :open + nest_conditional(Conditional::Expression.new(token, active_opts)) + when :condition + conditional_nesting.last.condition = Conditional::Condition.new(token, active_opts) + conditional_nesting.last.add_sequence(active_opts) + when :separator + conditional_nesting.last.add_sequence(active_opts) + self.node = conditional_nesting.last.branches.last + when :close + conditional_nesting.pop + decrease_nesting + + self.node = + if conditional_nesting.empty? + nesting.last + else + conditional_nesting.last + end + else + raise UnknownTokenError.new('Conditional', token) + end + end + + def posixclass(token) + node << PosixClass.new(token, active_opts) + end + + include Regexp::Expression::UnicodeProperty + + def property(token) + case token.token + when :alnum; node << Alnum.new(token, active_opts) + when :alpha; node << Alpha.new(token, active_opts) + when :ascii; node << Ascii.new(token, active_opts) + when :blank; node << Blank.new(token, active_opts) + when :cntrl; node << Cntrl.new(token, active_opts) + when :digit; node << Digit.new(token, active_opts) + when :graph; node << Graph.new(token, active_opts) + when :lower; node << Lower.new(token, active_opts) + when :print; node << Print.new(token, active_opts) + when :punct; node << Punct.new(token, active_opts) + when :space; node << Space.new(token, active_opts) + when :upper; node << Upper.new(token, active_opts) + when :word; node << Word.new(token, active_opts) + when :xdigit; node << Xdigit.new(token, active_opts) + when :xposixpunct; node << XPosixPunct.new(token, active_opts) + + # only in Oniguruma (old rubies) + when :newline; node << Newline.new(token, active_opts) + + when :any; node << Any.new(token, active_opts) + when :assigned; node << Assigned.new(token, active_opts) + + when :letter; node << Letter::Any.new(token, active_opts) + when :cased_letter; node << Letter::Cased.new(token, active_opts) + when :uppercase_letter; node << Letter::Uppercase.new(token, active_opts) + when :lowercase_letter; node << Letter::Lowercase.new(token, active_opts) + when :titlecase_letter; node << Letter::Titlecase.new(token, active_opts) + when :modifier_letter; node << Letter::Modifier.new(token, active_opts) + when :other_letter; node << Letter::Other.new(token, active_opts) + + when :mark; node << Mark::Any.new(token, active_opts) + when :combining_mark; node << Mark::Combining.new(token, active_opts) + when :nonspacing_mark; node << Mark::Nonspacing.new(token, active_opts) + when :spacing_mark; node << Mark::Spacing.new(token, active_opts) + when :enclosing_mark; node << Mark::Enclosing.new(token, active_opts) + + when :number; node << Number::Any.new(token, active_opts) + when :decimal_number; node << Number::Decimal.new(token, active_opts) + when :letter_number; node << Number::Letter.new(token, active_opts) + when :other_number; node << Number::Other.new(token, active_opts) + + when :punctuation; node << Punctuation::Any.new(token, active_opts) + when :connector_punctuation; node << Punctuation::Connector.new(token, active_opts) + when :dash_punctuation; node << Punctuation::Dash.new(token, active_opts) + when :open_punctuation; node << Punctuation::Open.new(token, active_opts) + when :close_punctuation; node << Punctuation::Close.new(token, active_opts) + when :initial_punctuation; node << Punctuation::Initial.new(token, active_opts) + when :final_punctuation; node << Punctuation::Final.new(token, active_opts) + when :other_punctuation; node << Punctuation::Other.new(token, active_opts) + + when :separator; node << Separator::Any.new(token, active_opts) + when :space_separator; node << Separator::Space.new(token, active_opts) + when :line_separator; node << Separator::Line.new(token, active_opts) + when :paragraph_separator; node << Separator::Paragraph.new(token, active_opts) + + when :symbol; node << Symbol::Any.new(token, active_opts) + when :math_symbol; node << Symbol::Math.new(token, active_opts) + when :currency_symbol; node << Symbol::Currency.new(token, active_opts) + when :modifier_symbol; node << Symbol::Modifier.new(token, active_opts) + when :other_symbol; node << Symbol::Other.new(token, active_opts) + + when :other; node << Codepoint::Any.new(token, active_opts) + when :control; node << Codepoint::Control.new(token, active_opts) + when :format; node << Codepoint::Format.new(token, active_opts) + when :surrogate; node << Codepoint::Surrogate.new(token, active_opts) + when :private_use; node << Codepoint::PrivateUse.new(token, active_opts) + when :unassigned; node << Codepoint::Unassigned.new(token, active_opts) + + when *Token::UnicodeProperty::Age + node << Age.new(token, active_opts) + + when *Token::UnicodeProperty::Derived + node << Derived.new(token, active_opts) + + when *Token::UnicodeProperty::Emoji + node << Emoji.new(token, active_opts) + + when *Token::UnicodeProperty::Script + node << Script.new(token, active_opts) + + when *Token::UnicodeProperty::UnicodeBlock + node << Block.new(token, active_opts) + + else + raise UnknownTokenError.new('UnicodeProperty', token) + end + end + + def anchor(token) + case token.token + when :bol + node << Anchor::BeginningOfLine.new(token, active_opts) + when :eol + node << Anchor::EndOfLine.new(token, active_opts) + when :bos + node << Anchor::BOS.new(token, active_opts) + when :eos + node << Anchor::EOS.new(token, active_opts) + when :eos_ob_eol + node << Anchor::EOSobEOL.new(token, active_opts) + when :word_boundary + node << Anchor::WordBoundary.new(token, active_opts) + when :nonword_boundary + node << Anchor::NonWordBoundary.new(token, active_opts) + when :match_start + node << Anchor::MatchStart.new(token, active_opts) + else + raise UnknownTokenError.new('Anchor', token) + end + end + + def escape(token) + case token.token + + when :backspace + node << EscapeSequence::Backspace.new(token, active_opts) + + when :escape + node << EscapeSequence::AsciiEscape.new(token, active_opts) + when :bell + node << EscapeSequence::Bell.new(token, active_opts) + when :form_feed + node << EscapeSequence::FormFeed.new(token, active_opts) + when :newline + node << EscapeSequence::Newline.new(token, active_opts) + when :carriage + node << EscapeSequence::Return.new(token, active_opts) + when :tab + node << EscapeSequence::Tab.new(token, active_opts) + when :vertical_tab + node << EscapeSequence::VerticalTab.new(token, active_opts) + + when :hex + node << EscapeSequence::Hex.new(token, active_opts) + when :octal + node << EscapeSequence::Octal.new(token, active_opts) + when :codepoint + node << EscapeSequence::Codepoint.new(token, active_opts) + when :codepoint_list + node << EscapeSequence::CodepointList.new(token, active_opts) + + when :control + if token.text =~ /\A(?:\\C-\\M|\\c\\M)/ + node << EscapeSequence::MetaControl.new(token, active_opts) + else + node << EscapeSequence::Control.new(token, active_opts) + end + + when :meta_sequence + if token.text =~ /\A\\M-\\[Cc]/ + node << EscapeSequence::MetaControl.new(token, active_opts) + else + node << EscapeSequence::Meta.new(token, active_opts) + end + + else + # treating everything else as a literal + node << EscapeSequence::Literal.new(token, active_opts) + end + end + + def keep(token) + node << Keep::Mark.new(token, active_opts) + end + + def free_space(token) + case token.token + when :comment + node << Comment.new(token, active_opts) + when :whitespace + if node.last.is_a?(WhiteSpace) + node.last.merge(WhiteSpace.new(token, active_opts)) + else + node << WhiteSpace.new(token, active_opts) + end + else + raise UnknownTokenError.new('FreeSpace', token) + end + end + + def quantifier(token) + offset = -1 + target_node = node.expressions[offset] + while target_node.is_a?(FreeSpace) + target_node = node.expressions[offset -= 1] + end + + target_node || raise(ArgumentError, 'No valid target found for '\ + "'#{token.text}' ") + + case token.token + when :zero_or_one + target_node.quantify(:zero_or_one, token.text, 0, 1, :greedy) + when :zero_or_one_reluctant + target_node.quantify(:zero_or_one, token.text, 0, 1, :reluctant) + when :zero_or_one_possessive + target_node.quantify(:zero_or_one, token.text, 0, 1, :possessive) + + when :zero_or_more + target_node.quantify(:zero_or_more, token.text, 0, -1, :greedy) + when :zero_or_more_reluctant + target_node.quantify(:zero_or_more, token.text, 0, -1, :reluctant) + when :zero_or_more_possessive + target_node.quantify(:zero_or_more, token.text, 0, -1, :possessive) + + when :one_or_more + target_node.quantify(:one_or_more, token.text, 1, -1, :greedy) + when :one_or_more_reluctant + target_node.quantify(:one_or_more, token.text, 1, -1, :reluctant) + when :one_or_more_possessive + target_node.quantify(:one_or_more, token.text, 1, -1, :possessive) + + when :interval + interval(target_node, token) + + else + raise UnknownTokenError.new('Quantifier', token) + end + end + + def interval(target_node, token) + text = token.text + mchr = text[text.length-1].chr =~ /[?+]/ ? text[text.length-1].chr : nil + case mchr + when '?' + range_text = text[0...-1] + mode = :reluctant + when '+' + range_text = text[0...-1] + mode = :possessive + else + range_text = text + mode = :greedy + end + + range = range_text.gsub(/\{|\}/, '').split(',', 2) + min = range[0].empty? ? 0 : range[0] + max = range[1] ? (range[1].empty? ? -1 : range[1]) : min + + target_node.quantify(:interval, text, min.to_i, max.to_i, mode) + end + + def group(token) + case token.token + when :options, :options_switch + options_group(token) + when :close + close_group + when :comment + node << Group::Comment.new(token, active_opts) + else + open_group(token) + end + end + + MOD_FLAGS = %w[i m x].map(&:to_sym) + ENC_FLAGS = %w[a d u].map(&:to_sym) + + def options_group(token) + positive, negative = token.text.split('-', 2) + negative ||= '' + self.switching_options = token.token.equal?(:options_switch) + + opt_changes = {} + new_active_opts = active_opts.dup + + MOD_FLAGS.each do |flag| + if positive.include?(flag.to_s) + opt_changes[flag] = new_active_opts[flag] = true + end + if negative.include?(flag.to_s) + opt_changes[flag] = false + new_active_opts.delete(flag) + end + end + + if (enc_flag = positive.reverse[/[adu]/]) + enc_flag = enc_flag.to_sym + (ENC_FLAGS - [enc_flag]).each do |other| + opt_changes[other] = false if new_active_opts[other] + new_active_opts.delete(other) + end + opt_changes[enc_flag] = new_active_opts[enc_flag] = true + end + + options_stack << new_active_opts + + options_group = Group::Options.new(token, active_opts) + options_group.option_changes = opt_changes + + nest(options_group) + end + + def open_group(token) + case token.token + when :passive + exp = Group::Passive.new(token, active_opts) + when :atomic + exp = Group::Atomic.new(token, active_opts) + when :named + exp = Group::Named.new(token, active_opts) + when :capture + exp = Group::Capture.new(token, active_opts) + when :absence + exp = Group::Absence.new(token, active_opts) + + when :lookahead + exp = Assertion::Lookahead.new(token, active_opts) + when :nlookahead + exp = Assertion::NegativeLookahead.new(token, active_opts) + when :lookbehind + exp = Assertion::Lookbehind.new(token, active_opts) + when :nlookbehind + exp = Assertion::NegativeLookbehind.new(token, active_opts) + + else + raise UnknownTokenError.new('Group type open', token) + end + + if exp.capturing? + exp.number = total_captured_group_count + 1 + exp.number_at_level = captured_group_count_at_level + 1 + count_captured_group + end + + # Push the active options to the stack again. This way we can simply pop the + # stack for any group we close, no matter if it had its own options or not. + options_stack << active_opts + + nest(exp) + end + + def close_group + options_stack.pop unless switching_options + self.switching_options = false + decrease_nesting + end + + def open_set(token) + token.token = :character + nest(CharacterSet.new(token, active_opts)) + end + + def negate_set + node.negate + end + + def close_set + decrease_nesting(&:close) + end + + def range(token) + exp = CharacterSet::Range.new(token, active_opts) + scope = node.last.is_a?(CharacterSet::IntersectedSequence) ? node.last : node + exp << scope.expressions.pop + nest(exp) + end + + def close_completed_character_set_range + decrease_nesting if node.is_a?(CharacterSet::Range) && node.complete? + end + + def intersection(token) + sequence_operation(CharacterSet::Intersection, token) + end + + def sequence_operation(klass, token) + unless node.is_a?(klass) + operator = klass.new(token, active_opts) + sequence = operator.add_sequence(active_opts) + sequence.expressions = node.expressions + node.expressions = [] + nest(operator) + end + node.add_sequence(active_opts) + end + + def active_opts + options_stack.last + end + + def total_captured_group_count + captured_group_counts.values.reduce(0, :+) + end + + def captured_group_count_at_level + captured_group_counts[node.level] + end + + def count_captured_group + captured_group_counts[node.level] += 1 + end + + def assign_effective_number(exp) + exp.effective_number = + exp.number + total_captured_group_count + (exp.number < 0 ? 1 : 0) + end + + def assign_referenced_expressions + targets = {} + root.each_expression do |exp| + exp.is_a?(Group::Capture) && targets[exp.identifier] = exp + end + root.each_expression do |exp| + exp.respond_to?(:reference) && + exp.referenced_expression = targets[exp.reference] + end + end +end # module Regexp::Parser diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/scanner.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/scanner.rb new file mode 100644 index 00000000..ee01fbe5 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/scanner.rb @@ -0,0 +1,2765 @@ +# -*- warn-indent:false; -*- + +# line 1 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + +# line 661 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + + +# THIS IS A GENERATED FILE, DO NOT EDIT DIRECTLY +# This file was generated from lib/regexp_parser/scanner/scanner.rl + +class Regexp::Scanner + # General scanner error (catch all) + class ScannerError < StandardError; end + + # Base for all scanner validation errors + class ValidationError < StandardError + def initialize(reason) + super reason + end + end + + # Unexpected end of pattern + class PrematureEndError < ScannerError + def initialize(where = '') + super "Premature end of pattern at #{where}" + end + end + + # Invalid sequence format. Used for escape sequences, mainly. + class InvalidSequenceError < ValidationError + def initialize(what = 'sequence', where = '') + super "Invalid #{what} at #{where}" + end + end + + # Invalid group. Used for named groups. + class InvalidGroupError < ValidationError + def initialize(what, reason) + super "Invalid #{what}, #{reason}." + end + end + + # Invalid groupOption. Used for inline options. + class InvalidGroupOption < ValidationError + def initialize(option, text) + super "Invalid group option #{option} in #{text}" + end + end + + # Invalid back reference. Used for name a number refs/calls. + class InvalidBackrefError < ValidationError + def initialize(what, reason) + super "Invalid back reference #{what}, #{reason}" + end + end + + # The property name was not recognized by the scanner. + class UnknownUnicodePropertyError < ValidationError + def initialize(name) + super "Unknown unicode character property name #{name}" + end + end + + # Scans the given regular expression text, or Regexp object and collects the + # emitted token into an array that gets returned at the end. If a block is + # given, it gets called for each emitted token. + # + # This method may raise errors if a syntax error is encountered. + # -------------------------------------------------------------------------- + def self.scan(input_object, &block) + new.scan(input_object, &block) + end + + def scan(input_object, &block) + self.literal = nil + stack = [] + + if input_object.is_a?(Regexp) + input = input_object.source + self.free_spacing = (input_object.options & Regexp::EXTENDED != 0) + else + input = input_object + self.free_spacing = false + end + self.spacing_stack = [{:free_spacing => free_spacing, :depth => 0}] + + data = input.unpack("c*") if input.is_a?(String) + eof = data.length + + self.tokens = [] + self.block = block_given? ? block : nil + + self.set_depth = 0 + self.group_depth = 0 + self.conditional_stack = [] + + +# line 98 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner.rb" +class << self + attr_accessor :_re_scanner_trans_keys + private :_re_scanner_trans_keys, :_re_scanner_trans_keys= +end +self._re_scanner_trans_keys = [ + 0, 0, -128, -65, -128, -65, + -128, -65, -128, -65, -128, + -65, -128, -65, 10, 10, + 41, 41, 39, 122, 33, 122, + 48, 122, 39, 60, 39, + 122, 48, 57, 39, 57, + 48, 57, 39, 57, 39, 122, + 43, 122, 48, 57, 48, + 62, 48, 57, 43, 62, + 43, 122, 44, 125, 48, 125, + 123, 123, 9, 122, 9, + 125, 9, 122, -128, -65, + -128, -65, 38, 38, 45, 122, + 45, 122, 93, 93, 94, + 120, 97, 120, 108, 115, + 110, 112, 117, 117, 109, 109, + 58, 58, 93, 93, 104, + 104, 97, 97, 99, 99, + 105, 105, 105, 105, 108, 108, + 97, 97, 110, 110, 107, + 107, 110, 110, 116, 116, + 114, 114, 108, 108, 105, 105, + 103, 103, 105, 105, 116, + 116, 114, 114, 97, 97, + 112, 112, 104, 104, 111, 111, + 119, 119, 101, 101, 114, + 114, 114, 117, 105, 105, + 110, 110, 110, 110, 99, 99, + 112, 112, 97, 97, 99, + 99, 101, 101, 112, 112, + 112, 112, 111, 111, 114, 114, + 100, 100, 100, 100, 65, + 122, 61, 61, 93, 93, + 45, 45, 92, 92, 92, 92, + 45, 45, 92, 92, 92, + 92, 48, 123, 48, 102, + 48, 102, 48, 102, 48, 102, + 9, 125, 9, 125, 9, + 125, 9, 125, 9, 125, + 9, 125, 48, 123, 41, 41, + 39, 122, 41, 57, 48, + 122, -62, 127, -62, -33, + -32, -17, -16, -12, 1, 127, + 1, 127, 9, 32, 33, + 126, 10, 126, 63, 63, + 33, 126, 33, 126, 43, 63, + 43, 63, 43, 63, 65, + 122, 43, 63, 68, 119, + 80, 112, -62, 125, -128, -65, + -128, -65, -128, -65, 38, + 38, 38, 93, 46, 61, + 48, 122, 36, 125, 48, 55, + 48, 55, 77, 77, 45, + 45, 0, 0, 67, 99, + 45, 45, 0, 0, 92, 92, + 48, 102, 39, 60, 39, + 122, 49, 57, 41, 57, + 48, 122, 0 +] + +class << self + attr_accessor :_re_scanner_key_spans + private :_re_scanner_key_spans, :_re_scanner_key_spans= +end +self._re_scanner_key_spans = [ + 0, 64, 64, 64, 64, 64, 64, 1, + 1, 84, 90, 75, 22, 84, 10, 19, + 10, 19, 84, 80, 10, 15, 10, 20, + 80, 82, 78, 1, 114, 117, 114, 64, + 64, 1, 78, 78, 1, 27, 24, 8, + 3, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 4, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 58, 1, 1, + 1, 1, 1, 1, 1, 1, 76, 55, + 55, 55, 55, 117, 117, 117, 117, 117, + 117, 76, 1, 84, 17, 75, 190, 30, + 16, 5, 127, 127, 24, 94, 117, 1, + 94, 94, 21, 21, 21, 58, 21, 52, + 33, 188, 64, 64, 64, 1, 56, 16, + 75, 90, 8, 8, 1, 1, 0, 33, + 1, 0, 1, 55, 22, 84, 9, 17, + 75 +] + +class << self + attr_accessor :_re_scanner_index_offsets + private :_re_scanner_index_offsets, :_re_scanner_index_offsets= +end +self._re_scanner_index_offsets = [ + 0, 0, 65, 130, 195, 260, 325, 390, + 392, 394, 479, 570, 646, 669, 754, 765, + 785, 796, 816, 901, 982, 993, 1009, 1020, + 1041, 1122, 1205, 1284, 1286, 1401, 1519, 1634, + 1699, 1764, 1766, 1845, 1924, 1926, 1954, 1979, + 1988, 1992, 1994, 1996, 1998, 2000, 2002, 2004, + 2006, 2008, 2010, 2012, 2014, 2016, 2018, 2020, + 2022, 2024, 2026, 2028, 2030, 2032, 2034, 2036, + 2038, 2040, 2042, 2044, 2046, 2048, 2050, 2055, + 2057, 2059, 2061, 2063, 2065, 2067, 2069, 2071, + 2073, 2075, 2077, 2079, 2081, 2083, 2142, 2144, + 2146, 2148, 2150, 2152, 2154, 2156, 2158, 2235, + 2291, 2347, 2403, 2459, 2577, 2695, 2813, 2931, + 3049, 3167, 3244, 3246, 3331, 3349, 3425, 3616, + 3647, 3664, 3670, 3798, 3926, 3951, 4046, 4164, + 4166, 4261, 4356, 4378, 4400, 4422, 4481, 4503, + 4556, 4590, 4779, 4844, 4909, 4974, 4976, 5033, + 5050, 5126, 5217, 5226, 5235, 5237, 5239, 5240, + 5274, 5276, 5277, 5279, 5335, 5358, 5443, 5453, + 5471 +] + +class << self + attr_accessor :_re_scanner_indicies + private :_re_scanner_indicies, :_re_scanner_indicies= +end +self._re_scanner_indicies = [ + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 0, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 0, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 0, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 0, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 0, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 0, 9, 8, + 12, 11, 13, 10, 10, 10, 10, 10, + 10, 10, 10, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 10, 10, 10, + 10, 10, 10, 10, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 10, 10, + 10, 10, 14, 10, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 10, 15, + 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 10, 10, 10, 15, 13, 10, 10, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 10, 10, 10, 10, 16, 10, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 10, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 10, 10, 10, 10, + 13, 10, 10, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 10, 10, 10, + 10, 16, 10, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 10, 18, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 19, 17, 20, 17, 17, + 17, 21, 17, 22, 17, 17, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, + 17, 17, 17, 17, 17, 17, 17, 23, + 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, + 23, 17, 17, 17, 17, 23, 17, 23, + 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, + 23, 17, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 17, 20, 17, 17, + 17, 17, 17, 17, 17, 17, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, + 17, 24, 25, 25, 25, 25, 25, 25, + 25, 25, 25, 17, 20, 17, 17, 17, + 21, 17, 21, 17, 17, 25, 25, 25, + 25, 25, 25, 25, 25, 25, 25, 17, + 20, 17, 17, 17, 21, 17, 21, 17, + 17, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 17, 17, 17, 17, 17, + 17, 17, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 17, 17, 17, 17, + 23, 17, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 17, 26, 17, 27, + 17, 17, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 17, 17, 17, 17, + 20, 17, 17, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 17, 17, 17, + 17, 28, 17, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 17, 29, 29, + 29, 29, 29, 29, 29, 29, 29, 29, + 17, 29, 29, 29, 29, 29, 29, 29, + 29, 29, 29, 17, 17, 17, 17, 20, + 17, 29, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 17, 26, 17, 26, 17, + 17, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 17, 17, 17, 17, 20, + 17, 26, 17, 26, 17, 17, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, + 17, 17, 17, 17, 20, 17, 17, 28, + 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, + 28, 17, 17, 17, 17, 28, 17, 28, + 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, + 28, 17, 32, 31, 31, 31, 33, 33, + 33, 33, 33, 33, 33, 33, 33, 33, + 31, 31, 31, 31, 31, 31, 31, 31, + 31, 31, 31, 31, 31, 31, 31, 31, + 31, 31, 31, 31, 31, 31, 31, 31, + 31, 31, 31, 31, 31, 31, 31, 31, + 31, 31, 31, 31, 31, 31, 31, 31, + 31, 31, 31, 31, 31, 31, 31, 31, + 31, 31, 31, 31, 31, 31, 31, 31, + 31, 31, 31, 31, 31, 31, 31, 31, + 31, 31, 31, 34, 31, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 31, + 31, 31, 31, 31, 31, 31, 31, 31, + 31, 31, 31, 31, 31, 31, 31, 31, + 31, 31, 31, 31, 31, 31, 31, 31, + 31, 31, 31, 31, 31, 31, 31, 31, + 31, 31, 31, 31, 31, 31, 31, 31, + 31, 31, 31, 31, 31, 31, 31, 31, + 31, 31, 31, 31, 31, 31, 31, 31, + 31, 31, 31, 31, 31, 31, 31, 31, + 31, 31, 34, 31, 35, 36, 37, 37, + 37, 37, 37, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 37, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 37, 37, 36, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 36, + 36, 36, 37, 36, 36, 36, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, + 36, 36, 36, 38, 37, 36, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, + 36, 37, 37, 37, 37, 37, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 37, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 37, 37, 36, + 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 36, 36, 36, 37, 36, 36, + 36, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 36, 36, 36, 36, 37, + 36, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 36, 36, 39, 36, 37, + 37, 37, 37, 37, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 37, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 37, 37, 36, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, + 36, 36, 36, 37, 36, 36, 36, 37, + 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, + 37, 36, 36, 36, 36, 37, 36, 37, + 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 37, 37, 37, 37, 37, 37, + 37, 36, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 40, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 40, 44, 43, 47, 46, + 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 46, 46, 46, 46, + 46, 46, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 46, 47, 48, 46, + 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, + 46, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 46, 46, 46, 46, 46, + 46, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 46, 49, 46, 50, 46, + 46, 51, 52, 53, 54, 46, 46, 55, + 46, 46, 46, 46, 56, 46, 46, 46, + 57, 46, 46, 58, 46, 59, 46, 60, + 61, 46, 51, 52, 53, 54, 46, 46, + 55, 46, 46, 46, 46, 56, 46, 46, + 46, 57, 46, 46, 58, 46, 59, 46, + 60, 61, 46, 62, 46, 46, 46, 46, + 46, 46, 63, 46, 64, 46, 65, 46, + 66, 46, 67, 46, 68, 46, 69, 46, + 70, 46, 67, 46, 71, 46, 72, 46, + 67, 46, 73, 46, 74, 46, 75, 46, + 67, 46, 76, 46, 77, 46, 78, 46, + 67, 46, 79, 46, 80, 46, 81, 46, + 67, 46, 82, 46, 83, 46, 84, 46, + 67, 46, 85, 46, 86, 46, 87, 46, + 67, 46, 88, 46, 46, 89, 46, 90, + 46, 81, 46, 91, 46, 81, 46, 92, + 46, 93, 46, 94, 46, 67, 46, 95, + 46, 86, 46, 96, 46, 97, 46, 67, + 46, 54, 46, 98, 98, 98, 98, 98, + 98, 98, 98, 98, 98, 98, 98, 98, + 98, 98, 98, 98, 98, 98, 98, 98, + 98, 98, 98, 98, 98, 46, 46, 46, + 46, 46, 46, 98, 98, 98, 98, 98, + 98, 98, 98, 98, 98, 98, 98, 98, + 98, 98, 98, 98, 98, 98, 98, 98, + 98, 98, 98, 98, 98, 46, 99, 46, + 100, 46, 101, 36, 103, 102, 105, 102, + 106, 36, 108, 107, 110, 107, 111, 111, + 111, 111, 111, 111, 111, 111, 111, 111, + 36, 36, 36, 36, 36, 36, 36, 111, + 111, 111, 111, 111, 111, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 111, + 111, 111, 111, 111, 111, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 112, 36, 113, 113, 113, 113, 113, + 113, 113, 113, 113, 113, 36, 36, 36, + 36, 36, 36, 36, 113, 113, 113, 113, + 113, 113, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 113, 113, 113, 113, + 113, 113, 36, 114, 114, 114, 114, 114, + 114, 114, 114, 114, 114, 36, 36, 36, + 36, 36, 36, 36, 114, 114, 114, 114, + 114, 114, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 114, 114, 114, 114, + 114, 114, 36, 115, 115, 115, 115, 115, + 115, 115, 115, 115, 115, 36, 36, 36, + 36, 36, 36, 36, 115, 115, 115, 115, + 115, 115, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 115, 115, 115, 115, + 115, 115, 36, 116, 116, 116, 116, 116, + 116, 116, 116, 116, 116, 36, 36, 36, + 36, 36, 36, 36, 116, 116, 116, 116, + 116, 116, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 116, 116, 116, 116, + 116, 116, 36, 112, 112, 112, 112, 112, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 112, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 117, 117, 117, 117, 117, 117, + 117, 117, 117, 117, 36, 36, 36, 36, + 36, 36, 36, 117, 117, 117, 117, 117, + 117, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 117, 117, 117, 117, 117, + 117, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 115, + 36, 112, 112, 112, 112, 112, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 112, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 118, 118, 118, 118, 118, 118, 118, 118, + 118, 118, 36, 36, 36, 36, 36, 36, + 36, 118, 118, 118, 118, 118, 118, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 118, 118, 118, 118, 118, 118, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 115, 36, 112, + 112, 112, 112, 112, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 112, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 119, 119, + 119, 119, 119, 119, 119, 119, 119, 119, + 36, 36, 36, 36, 36, 36, 36, 119, + 119, 119, 119, 119, 119, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 119, + 119, 119, 119, 119, 119, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 115, 36, 112, 112, 112, + 112, 112, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 112, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 120, 120, 120, 120, + 120, 120, 120, 120, 120, 120, 36, 36, + 36, 36, 36, 36, 36, 120, 120, 120, + 120, 120, 120, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 120, 120, 120, + 120, 120, 120, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 115, 36, 112, 112, 112, 112, 112, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 112, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 121, 121, 121, 121, 121, 121, + 121, 121, 121, 121, 36, 36, 36, 36, + 36, 36, 36, 121, 121, 121, 121, 121, + 121, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 121, 121, 121, 121, 121, + 121, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 115, + 36, 112, 112, 112, 112, 112, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 112, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 115, 36, 123, + 123, 123, 123, 123, 123, 123, 123, 123, + 123, 122, 122, 122, 122, 122, 122, 122, + 123, 123, 123, 123, 123, 123, 122, 122, + 122, 122, 122, 122, 122, 122, 122, 122, + 122, 122, 122, 122, 122, 122, 122, 122, + 122, 122, 122, 122, 122, 122, 122, 122, + 123, 123, 123, 123, 123, 123, 122, 122, + 122, 122, 122, 122, 122, 122, 122, 122, + 122, 122, 122, 122, 122, 122, 122, 122, + 122, 122, 36, 122, 125, 124, 126, 124, + 124, 124, 124, 124, 124, 124, 124, 127, + 127, 127, 127, 127, 127, 127, 127, 127, + 127, 124, 124, 124, 124, 124, 124, 124, + 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 124, 124, 124, 124, 127, 124, + 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 124, 125, 124, 124, 124, 124, + 124, 124, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 124, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 124, + 124, 124, 124, 126, 124, 124, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, + 124, 124, 124, 124, 129, 124, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, + 124, 130, 130, 130, 130, 130, 130, 130, + 130, 130, 130, 130, 130, 130, 130, 130, + 130, 130, 130, 130, 130, 130, 130, 130, + 130, 130, 130, 130, 130, 130, 130, 131, + 131, 131, 131, 131, 131, 131, 131, 131, + 131, 131, 131, 131, 131, 131, 131, 132, + 132, 132, 132, 132, 31, 31, 31, 31, + 31, 31, 31, 31, 31, 31, 31, 31, + 133, 133, 133, 133, 133, 133, 133, 133, + 134, 134, 134, 134, 134, 133, 133, 133, + 133, 133, 133, 133, 133, 133, 133, 133, + 133, 133, 133, 133, 133, 133, 133, 135, + 136, 136, 137, 138, 136, 136, 136, 139, + 140, 141, 142, 136, 136, 143, 136, 136, + 136, 136, 136, 136, 136, 136, 136, 136, + 136, 136, 136, 136, 136, 136, 144, 136, + 136, 136, 136, 136, 136, 136, 136, 136, + 136, 136, 136, 136, 136, 136, 136, 136, + 136, 136, 136, 136, 136, 136, 136, 136, + 136, 136, 145, 146, 31, 147, 136, 136, + 136, 136, 136, 136, 136, 136, 136, 136, + 136, 136, 136, 136, 136, 136, 136, 136, + 136, 136, 136, 136, 136, 136, 136, 136, + 136, 136, 33, 148, 31, 136, 133, 31, + 130, 130, 130, 130, 130, 130, 130, 130, + 130, 130, 130, 130, 130, 130, 130, 130, + 130, 130, 130, 130, 130, 130, 130, 130, + 130, 130, 130, 130, 130, 130, 149, 131, + 131, 131, 131, 131, 131, 131, 131, 131, + 131, 131, 131, 131, 131, 131, 131, 149, + 132, 132, 132, 132, 132, 149, 133, 133, + 133, 133, 133, 133, 133, 133, 133, 133, + 133, 133, 133, 133, 133, 133, 133, 133, + 133, 133, 133, 133, 133, 133, 133, 133, + 133, 133, 133, 133, 133, 149, 149, 149, + 149, 149, 149, 149, 149, 149, 149, 149, + 149, 149, 149, 149, 149, 149, 149, 149, + 149, 149, 149, 149, 149, 149, 149, 149, + 149, 149, 149, 149, 149, 149, 149, 149, + 149, 149, 149, 149, 149, 149, 149, 149, + 149, 149, 149, 149, 149, 149, 149, 149, + 149, 149, 149, 149, 149, 149, 149, 149, + 149, 149, 149, 149, 149, 149, 149, 149, + 149, 149, 149, 149, 149, 149, 149, 149, + 149, 149, 149, 149, 149, 149, 149, 149, + 149, 149, 149, 149, 149, 149, 149, 149, + 149, 149, 149, 149, 133, 149, 133, 133, + 133, 133, 133, 133, 133, 133, 134, 134, + 134, 134, 134, 133, 133, 133, 133, 133, + 133, 133, 133, 133, 133, 133, 133, 133, + 133, 133, 133, 133, 133, 135, 150, 150, + 150, 150, 150, 150, 150, 150, 150, 150, + 150, 150, 150, 150, 150, 150, 150, 150, + 150, 150, 150, 150, 150, 150, 150, 150, + 150, 150, 150, 150, 150, 150, 150, 150, + 150, 150, 150, 150, 150, 150, 150, 150, + 150, 150, 150, 150, 150, 150, 150, 150, + 150, 150, 150, 150, 150, 150, 150, 150, + 150, 150, 150, 150, 150, 150, 150, 150, + 150, 150, 150, 150, 150, 150, 150, 150, + 150, 150, 150, 150, 150, 150, 150, 150, + 150, 150, 150, 150, 150, 150, 150, 150, + 150, 150, 150, 150, 133, 150, 135, 135, + 135, 135, 135, 150, 150, 150, 150, 150, + 150, 150, 150, 150, 150, 150, 150, 150, + 150, 150, 150, 150, 150, 135, 150, 136, + 136, 136, 149, 136, 136, 136, 149, 149, + 149, 149, 136, 136, 149, 136, 136, 136, + 136, 136, 136, 136, 136, 136, 136, 136, + 136, 136, 136, 136, 136, 149, 136, 136, + 136, 136, 136, 136, 136, 136, 136, 136, + 136, 136, 136, 136, 136, 136, 136, 136, + 136, 136, 136, 136, 136, 136, 136, 136, + 136, 149, 149, 149, 149, 136, 136, 136, + 136, 136, 136, 136, 136, 136, 136, 136, + 136, 136, 136, 136, 136, 136, 136, 136, + 136, 136, 136, 136, 136, 136, 136, 136, + 136, 149, 149, 149, 136, 149, 9, 8, + 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 137, 137, 137, + 8, 137, 137, 137, 8, 8, 8, 8, + 137, 137, 8, 137, 137, 137, 137, 137, + 137, 137, 137, 137, 137, 137, 137, 137, + 137, 137, 137, 8, 137, 137, 137, 137, + 137, 137, 137, 137, 137, 137, 137, 137, + 137, 137, 137, 137, 137, 137, 137, 137, + 137, 137, 137, 137, 137, 137, 137, 8, + 8, 8, 8, 137, 137, 137, 137, 137, + 137, 137, 137, 137, 137, 137, 137, 137, + 137, 137, 137, 137, 137, 137, 137, 137, + 137, 137, 137, 137, 137, 137, 137, 8, + 8, 8, 137, 8, 152, 151, 15, 154, + 11, 154, 154, 154, 14, 155, 153, 154, + 154, 154, 154, 154, 154, 154, 154, 154, + 154, 154, 154, 154, 154, 154, 154, 13, + 154, 156, 15, 13, 154, 154, 154, 154, + 154, 154, 154, 154, 154, 154, 154, 154, + 154, 154, 154, 154, 154, 154, 154, 154, + 154, 154, 154, 154, 154, 154, 154, 154, + 154, 154, 154, 154, 154, 154, 154, 154, + 154, 154, 154, 154, 154, 154, 154, 154, + 154, 154, 154, 154, 154, 154, 154, 154, + 154, 154, 154, 154, 154, 154, 154, 154, + 154, 154, 154, 13, 154, 153, 154, 153, + 154, 154, 154, 153, 153, 153, 154, 154, + 154, 154, 154, 154, 154, 154, 154, 154, + 154, 154, 154, 154, 154, 154, 157, 154, + 153, 153, 153, 154, 154, 154, 154, 154, + 154, 154, 154, 154, 154, 154, 154, 154, + 154, 154, 154, 154, 154, 154, 154, 154, + 154, 154, 154, 154, 154, 154, 154, 154, + 154, 154, 154, 154, 154, 154, 154, 154, + 154, 154, 154, 154, 154, 154, 154, 154, + 154, 154, 154, 154, 154, 154, 154, 154, + 154, 154, 154, 154, 154, 154, 154, 154, + 154, 154, 153, 154, 159, 158, 158, 158, + 158, 158, 158, 158, 158, 158, 158, 158, + 158, 158, 158, 158, 158, 158, 158, 158, + 159, 158, 161, 160, 160, 160, 160, 160, + 160, 160, 160, 160, 160, 160, 160, 160, + 160, 160, 160, 160, 160, 160, 161, 160, + 163, 162, 162, 162, 162, 162, 162, 162, + 162, 162, 162, 162, 162, 162, 162, 162, + 162, 162, 162, 162, 163, 162, 165, 165, + 164, 164, 164, 164, 165, 164, 164, 164, + 166, 164, 164, 164, 164, 164, 164, 164, + 164, 164, 164, 164, 164, 164, 164, 165, + 164, 164, 164, 164, 164, 164, 164, 165, + 164, 164, 164, 164, 167, 164, 164, 164, + 167, 164, 164, 164, 164, 164, 164, 164, + 164, 164, 164, 164, 164, 164, 164, 165, + 164, 169, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 168, 168, 168, + 168, 168, 168, 168, 168, 169, 168, 170, + 36, 36, 36, 170, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 170, 170, 36, + 36, 36, 170, 170, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 170, + 36, 36, 36, 170, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 170, 36, + 36, 36, 170, 36, 171, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 171, 36, 172, 172, + 172, 172, 172, 172, 172, 172, 172, 172, + 172, 172, 172, 172, 172, 172, 172, 172, + 172, 172, 172, 172, 172, 172, 172, 172, + 172, 172, 172, 172, 173, 173, 173, 173, + 173, 173, 173, 173, 173, 173, 173, 173, + 173, 173, 173, 173, 174, 174, 174, 174, + 174, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, + 175, 41, 176, 41, 175, 175, 175, 175, + 41, 177, 175, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 175, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 178, + 179, 180, 181, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 175, + 175, 175, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 41, 41, 41, 41, 41, + 41, 41, 41, 182, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 182, 183, 183, 183, + 183, 183, 183, 183, 183, 183, 183, 183, + 183, 183, 183, 183, 183, 183, 183, 183, + 183, 183, 183, 183, 183, 183, 183, 183, + 183, 183, 183, 183, 183, 183, 183, 183, + 183, 183, 183, 183, 183, 183, 183, 183, + 183, 183, 183, 183, 183, 183, 183, 183, + 183, 183, 183, 183, 183, 183, 183, 183, + 183, 183, 183, 183, 183, 182, 184, 182, + 186, 185, 185, 185, 185, 185, 185, 185, + 185, 185, 185, 185, 185, 185, 185, 185, + 185, 185, 185, 185, 185, 185, 185, 185, + 185, 185, 185, 185, 185, 185, 185, 185, + 185, 185, 185, 185, 185, 185, 185, 185, + 185, 185, 185, 185, 185, 185, 185, 185, + 185, 185, 185, 185, 185, 185, 185, 187, + 185, 190, 189, 189, 189, 189, 189, 189, + 189, 189, 189, 189, 189, 191, 189, 189, + 192, 189, 194, 194, 194, 194, 194, 194, + 194, 194, 194, 194, 193, 193, 193, 193, + 193, 193, 193, 194, 194, 194, 193, 193, + 193, 194, 193, 193, 193, 194, 193, 194, + 193, 193, 193, 193, 194, 193, 193, 193, + 193, 193, 194, 193, 194, 193, 193, 193, + 193, 193, 193, 193, 193, 194, 193, 193, + 193, 194, 193, 193, 193, 194, 193, 193, + 193, 193, 193, 193, 193, 193, 193, 193, + 193, 193, 193, 193, 194, 193, 196, 195, + 195, 195, 196, 196, 196, 196, 195, 195, + 196, 195, 197, 198, 198, 198, 198, 198, + 198, 198, 199, 199, 195, 195, 195, 195, + 195, 196, 195, 36, 36, 200, 201, 195, + 195, 36, 201, 195, 195, 36, 195, 202, + 195, 195, 203, 195, 201, 201, 195, 195, + 195, 201, 201, 195, 36, 196, 196, 196, + 196, 195, 195, 204, 204, 101, 201, 204, + 204, 36, 201, 195, 195, 36, 195, 195, + 204, 195, 203, 195, 204, 201, 204, 205, + 204, 201, 206, 195, 36, 196, 196, 196, + 195, 208, 208, 208, 208, 208, 208, 208, + 208, 207, 210, 210, 210, 210, 210, 210, + 210, 210, 209, 212, 102, 214, 213, 102, + 216, 107, 107, 107, 107, 107, 107, 107, + 107, 107, 107, 107, 107, 107, 107, 107, + 107, 107, 107, 107, 107, 107, 107, 107, + 107, 107, 107, 107, 107, 107, 107, 107, + 217, 107, 219, 218, 107, 110, 107, 222, + 222, 222, 222, 222, 222, 222, 222, 222, + 222, 221, 221, 221, 221, 221, 221, 221, + 222, 222, 222, 222, 222, 222, 221, 221, + 221, 221, 221, 221, 221, 221, 221, 221, + 221, 221, 221, 221, 221, 221, 221, 221, + 221, 221, 221, 221, 221, 221, 221, 221, + 222, 222, 222, 222, 222, 222, 221, 224, + 223, 223, 223, 223, 223, 225, 223, 223, + 223, 226, 226, 226, 226, 226, 226, 226, + 226, 226, 223, 223, 227, 223, 126, 228, + 228, 228, 228, 228, 228, 228, 228, 127, + 127, 127, 127, 127, 127, 127, 127, 127, + 127, 228, 228, 228, 228, 228, 228, 228, + 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 228, 228, 228, 228, 127, 228, + 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 228, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 228, 125, 228, 228, + 228, 228, 228, 228, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 228, 129, + 129, 129, 129, 129, 129, 129, 129, 129, + 129, 228, 228, 228, 228, 126, 228, 228, + 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 228, 228, 228, 228, 129, 228, + 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 228, 0 +] + +class << self + attr_accessor :_re_scanner_trans_targs + private :_re_scanner_trans_targs, :_re_scanner_trans_targs= +end +self._re_scanner_trans_targs = [ + 110, 111, 3, 112, 5, 6, 113, 110, + 7, 110, 110, 8, 110, 110, 9, 110, + 11, 110, 13, 19, 110, 14, 16, 18, + 15, 17, 20, 22, 24, 21, 23, 0, + 26, 25, 126, 28, 0, 29, 30, 128, + 129, 129, 31, 129, 129, 129, 129, 35, + 36, 129, 38, 39, 50, 54, 58, 62, + 66, 70, 75, 79, 81, 84, 40, 47, + 41, 45, 42, 43, 44, 129, 46, 48, + 49, 51, 52, 53, 55, 56, 57, 59, + 60, 61, 63, 64, 65, 67, 68, 69, + 71, 73, 72, 74, 76, 77, 78, 80, + 82, 83, 86, 87, 129, 89, 137, 140, + 137, 142, 92, 137, 143, 137, 145, 95, + 98, 96, 97, 137, 99, 100, 101, 102, + 103, 104, 137, 147, 148, 148, 106, 107, + 108, 109, 1, 2, 4, 114, 115, 116, + 117, 118, 110, 119, 110, 122, 123, 110, + 124, 110, 125, 110, 110, 110, 110, 110, + 120, 110, 121, 110, 10, 110, 110, 110, + 110, 110, 110, 110, 110, 110, 110, 12, + 110, 110, 127, 27, 130, 131, 132, 129, + 133, 134, 135, 129, 129, 129, 129, 32, + 129, 129, 33, 129, 129, 129, 34, 37, + 85, 136, 136, 137, 137, 138, 138, 137, + 88, 137, 91, 137, 137, 94, 105, 137, + 139, 137, 137, 137, 141, 137, 90, 137, + 144, 146, 137, 93, 137, 137, 137, 148, + 149, 150, 151, 152, 148 +] + +class << self + attr_accessor :_re_scanner_trans_actions + private :_re_scanner_trans_actions, :_re_scanner_trans_actions= +end +self._re_scanner_trans_actions = [ + 1, 2, 0, 2, 0, 0, 2, 3, + 0, 4, 5, 6, 7, 8, 0, 9, + 0, 10, 0, 0, 11, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 12, + 0, 0, 0, 0, 0, 0, 0, 14, + 15, 16, 0, 17, 18, 19, 20, 0, + 0, 21, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 22, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 23, 0, 24, 0, + 25, 0, 0, 26, 0, 27, 0, 0, + 0, 0, 0, 28, 0, 0, 0, 0, + 0, 0, 29, 0, 30, 31, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 34, 35, 36, 37, 0, 0, 38, + 0, 39, 34, 40, 41, 42, 43, 44, + 45, 46, 0, 47, 0, 48, 49, 50, + 51, 52, 53, 54, 55, 56, 57, 0, + 58, 59, 61, 0, 0, 34, 34, 62, + 0, 34, 63, 64, 65, 66, 67, 0, + 68, 69, 0, 70, 71, 72, 0, 0, + 0, 73, 74, 75, 76, 77, 78, 79, + 0, 80, 0, 81, 82, 0, 0, 83, + 0, 84, 85, 86, 34, 87, 0, 88, + 34, 0, 89, 0, 90, 91, 92, 93, + 34, 34, 34, 34, 94 +] + +class << self + attr_accessor :_re_scanner_to_state_actions + private :_re_scanner_to_state_actions, :_re_scanner_to_state_actions= +end +self._re_scanner_to_state_actions = [ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 32, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 60, + 60, 60, 0, 0, 0, 0, 0, 0, + 60, 60, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 60, 0, 0, 0, + 0 +] + +class << self + attr_accessor :_re_scanner_from_state_actions + private :_re_scanner_from_state_actions, :_re_scanner_from_state_actions= +end +self._re_scanner_from_state_actions = [ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 33, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 33, + 33, 33, 0, 0, 0, 0, 0, 0, + 33, 33, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 33, 0, 0, 0, + 0 +] + +class << self + attr_accessor :_re_scanner_eof_actions + private :_re_scanner_eof_actions, :_re_scanner_eof_actions= +end +self._re_scanner_eof_actions = [ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 12, 12, 13, 13, 13, 13, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 12, 12, 0, 12, 12, 0, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 12, 0, 0, 0, 0, 0, 0, + 0, 12, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0 +] + +class << self + attr_accessor :_re_scanner_eof_trans + private :_re_scanner_eof_trans, :_re_scanner_eof_trans= +end +self._re_scanner_eof_trans = [ + 0, 1, 1, 1, 1, 1, 1, 8, + 11, 11, 11, 11, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, + 18, 0, 0, 0, 0, 0, 0, 41, + 41, 44, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, + 46, 46, 46, 46, 46, 46, 46, 46, + 0, 0, 105, 0, 0, 110, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 125, 125, 125, 125, 0, 150, + 150, 150, 150, 151, 151, 150, 150, 152, + 154, 154, 159, 161, 163, 165, 169, 0, + 0, 0, 183, 183, 183, 183, 186, 189, + 0, 0, 208, 210, 212, 212, 212, 216, + 216, 216, 216, 221, 0, 229, 229, 229, + 229 +] + +class << self + attr_accessor :re_scanner_start +end +self.re_scanner_start = 110; +class << self + attr_accessor :re_scanner_first_final +end +self.re_scanner_first_final = 110; +class << self + attr_accessor :re_scanner_error +end +self.re_scanner_error = 0; + +class << self + attr_accessor :re_scanner_en_char_type +end +self.re_scanner_en_char_type = 127; +class << self + attr_accessor :re_scanner_en_unicode_property +end +self.re_scanner_en_unicode_property = 128; +class << self + attr_accessor :re_scanner_en_character_set +end +self.re_scanner_en_character_set = 129; +class << self + attr_accessor :re_scanner_en_set_escape_sequence +end +self.re_scanner_en_set_escape_sequence = 136; +class << self + attr_accessor :re_scanner_en_escape_sequence +end +self.re_scanner_en_escape_sequence = 137; +class << self + attr_accessor :re_scanner_en_conditional_expression +end +self.re_scanner_en_conditional_expression = 148; +class << self + attr_accessor :re_scanner_en_main +end +self.re_scanner_en_main = 110; + + +# line 753 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + +# line 1144 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner.rb" +begin + p ||= 0 + pe ||= data.length + cs = re_scanner_start + top = 0 + ts = nil + te = nil + act = 0 +end + +# line 754 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + +# line 1157 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner.rb" +begin + testEof = false + _slen, _trans, _keys, _inds, _acts, _nacts = nil + _goto_level = 0 + _resume = 10 + _eof_trans = 15 + _again = 20 + _test_eof = 30 + _out = 40 + while true + if _goto_level <= 0 + if p == pe + _goto_level = _test_eof + next + end + if cs == 0 + _goto_level = _out + next + end + end + if _goto_level <= _resume + case _re_scanner_from_state_actions[cs] + when 33 then +# line 1 "NONE" + begin +ts = p + end +# line 1185 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner.rb" + end + _keys = cs << 1 + _inds = _re_scanner_index_offsets[cs] + _slen = _re_scanner_key_spans[cs] + _wide = data[p].ord + _trans = if ( _slen > 0 && + _re_scanner_trans_keys[_keys] <= _wide && + _wide <= _re_scanner_trans_keys[_keys + 1] + ) then + _re_scanner_indicies[ _inds + _wide - _re_scanner_trans_keys[_keys] ] + else + _re_scanner_indicies[ _inds + _slen ] + end + end + if _goto_level <= _eof_trans + cs = _re_scanner_trans_targs[_trans] + if _re_scanner_trans_actions[_trans] != 0 + case _re_scanner_trans_actions[_trans] + when 12 then +# line 131 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + + text = ts ? copy(data, ts-1..-1) : data.pack('c*') + raise PrematureEndError.new( text ) + end + when 36 then +# line 143 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + self.group_depth = group_depth + 1 end + when 6 then +# line 144 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + self.group_depth = group_depth - 1 end + when 34 then +# line 1 "NONE" + begin +te = p+1 + end + when 61 then +# line 12 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/char_type.rl" + begin +te = p+1 + begin + case text = text(data, ts, te, 1).first + when '\d'; emit(:type, :digit, text, ts - 1, te) + when '\D'; emit(:type, :nondigit, text, ts - 1, te) + when '\h'; emit(:type, :hex, text, ts - 1, te) + when '\H'; emit(:type, :nonhex, text, ts - 1, te) + when '\s'; emit(:type, :space, text, ts - 1, te) + when '\S'; emit(:type, :nonspace, text, ts - 1, te) + when '\w'; emit(:type, :word, text, ts - 1, te) + when '\W'; emit(:type, :nonword, text, ts - 1, te) + when '\R'; emit(:type, :linebreak, text, ts - 1, te) + when '\X'; emit(:type, :xgrapheme, text, ts - 1, te) + end + begin + top -= 1 + cs = stack[top] + _goto_level = _again + next + end + + end + end + when 14 then +# line 16 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/property.rl" + begin +te = p+1 + begin + text = text(data, ts, te, 1).first + type = (text[1] == 'P') ^ (text[3] == '^') ? :nonproperty : :property + + name = data[ts+2..te-2].pack('c*').gsub(/[\^\s_\-]/, '').downcase + + token = self.class.short_prop_map[name] || self.class.long_prop_map[name] + raise UnknownUnicodePropertyError.new(name) unless token + + self.emit(type, token.to_sym, text, ts-1, te) + + begin + top -= 1 + cs = stack[top] + _goto_level = _again + next + end + + end + end + when 18 then +# line 171 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin # special case, emits two tokens + emit(:literal, :literal, '-', ts, te) + emit(:set, :intersection, '&&', ts, te) + end + end + when 66 then +# line 176 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + text = text(data, ts, te).first + if tokens.last[1] == :open + emit(:set, :negate, text, ts, te) + else + emit(:literal, :literal, text, ts, te) + end + end + end + when 68 then +# line 197 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + emit(:set, :intersection, *text(data, ts, te)) + end + end + when 64 then +# line 201 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + begin + stack[top] = cs + top+= 1 + cs = 136 + _goto_level = _again + next + end + + end + end + when 62 then +# line 231 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + emit(:literal, :literal, *text(data, ts, te)) + end + end + when 16 then +# line 239 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + char, *rest = *text(data, ts, te) + char.force_encoding('utf-8') if char.respond_to?(:force_encoding) + emit(:literal, :literal, char, *rest) + end + end + when 69 then +# line 185 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p +p = p - 1; begin + text = text(data, ts, te).first + # ranges cant start with a subset or intersection/negation/range operator + if tokens.last[0] == :set + emit(:literal, :literal, text, ts, te) + else + emit(:set, :range, text, ts, te) + end + end + end + when 72 then +# line 205 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p +p = p - 1; begin + emit(:set, :open, *text(data, ts, te)) + begin + stack[top] = cs + top+= 1 + cs = 129 + _goto_level = _again + next + end + + end + end + when 67 then +# line 239 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p +p = p - 1; begin + char, *rest = *text(data, ts, te) + char.force_encoding('utf-8') if char.respond_to?(:force_encoding) + emit(:literal, :literal, char, *rest) + end + end + when 17 then +# line 185 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + begin p = ((te))-1; end + begin + text = text(data, ts, te).first + # ranges cant start with a subset or intersection/negation/range operator + if tokens.last[0] == :set + emit(:literal, :literal, text, ts, te) + else + emit(:set, :range, text, ts, te) + end + end + end + when 20 then +# line 205 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + begin p = ((te))-1; end + begin + emit(:set, :open, *text(data, ts, te)) + begin + stack[top] = cs + top+= 1 + cs = 129 + _goto_level = _again + next + end + + end + end + when 15 then +# line 239 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + begin p = ((te))-1; end + begin + char, *rest = *text(data, ts, te) + char.force_encoding('utf-8') if char.respond_to?(:force_encoding) + emit(:literal, :literal, char, *rest) + end + end + when 74 then +# line 249 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + emit(:escape, :literal, *text(data, ts, te, 1)) + begin + top -= 1 + cs = stack[top] + _goto_level = _again + next + end + + end + end + when 73 then +# line 254 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + p = p - 1; + cs = 129; + begin + stack[top] = cs + top+= 1 + cs = 137 + _goto_level = _again + next + end + + end + end + when 79 then +# line 265 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + text = text(data, ts, te, 1).first + emit(:backref, :number, text, ts-1, te) + begin + top -= 1 + cs = stack[top] + _goto_level = _again + next + end + + end + end + when 85 then +# line 271 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + emit(:escape, :octal, *text(data, ts, te, 1)) + begin + top -= 1 + cs = stack[top] + _goto_level = _again + next + end + + end + end + when 76 then +# line 276 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + case text = text(data, ts, te, 1).first + when '\.'; emit(:escape, :dot, text, ts-1, te) + when '\|'; emit(:escape, :alternation, text, ts-1, te) + when '\^'; emit(:escape, :bol, text, ts-1, te) + when '\$'; emit(:escape, :eol, text, ts-1, te) + when '\?'; emit(:escape, :zero_or_one, text, ts-1, te) + when '\*'; emit(:escape, :zero_or_more, text, ts-1, te) + when '\+'; emit(:escape, :one_or_more, text, ts-1, te) + when '\('; emit(:escape, :group_open, text, ts-1, te) + when '\)'; emit(:escape, :group_close, text, ts-1, te) + when '\{'; emit(:escape, :interval_open, text, ts-1, te) + when '\}'; emit(:escape, :interval_close, text, ts-1, te) + when '\['; emit(:escape, :set_open, text, ts-1, te) + when '\]'; emit(:escape, :set_close, text, ts-1, te) + when "\\\\"; + emit(:escape, :backslash, text, ts-1, te) + end + begin + top -= 1 + cs = stack[top] + _goto_level = _again + next + end + + end + end + when 82 then +# line 297 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + # \b is emitted as backspace only when inside a character set, otherwise + # it is a word boundary anchor. A syntax might "normalize" it if needed. + case text = text(data, ts, te, 1).first + when '\a'; emit(:escape, :bell, text, ts-1, te) + when '\b'; emit(:escape, :backspace, text, ts-1, te) + when '\e'; emit(:escape, :escape, text, ts-1, te) + when '\f'; emit(:escape, :form_feed, text, ts-1, te) + when '\n'; emit(:escape, :newline, text, ts-1, te) + when '\r'; emit(:escape, :carriage, text, ts-1, te) + when '\t'; emit(:escape, :tab, text, ts-1, te) + when '\v'; emit(:escape, :vertical_tab, text, ts-1, te) + end + begin + top -= 1 + cs = stack[top] + _goto_level = _again + next + end + + end + end + when 28 then +# line 313 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + text = text(data, ts, te, 1).first + if text[2].chr == '{' + emit(:escape, :codepoint_list, text, ts-1, te) + else + emit(:escape, :codepoint, text, ts-1, te) + end + begin + top -= 1 + cs = stack[top] + _goto_level = _again + next + end + + end + end + when 92 then +# line 323 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + emit(:escape, :hex, *text(data, ts, te, 1)) + begin + top -= 1 + cs = stack[top] + _goto_level = _again + next + end + + end + end + when 24 then +# line 332 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + emit_meta_control_sequence(data, ts, te, :control) + begin + top -= 1 + cs = stack[top] + _goto_level = _again + next + end + + end + end + when 26 then +# line 337 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + emit_meta_control_sequence(data, ts, te, :meta_sequence) + begin + top -= 1 + cs = stack[top] + _goto_level = _again + next + end + + end + end + when 80 then +# line 342 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + p = p - 1; + cs = ((in_set? ? 129 : 110)); + begin + stack[top] = cs + top+= 1 + cs = 127 + _goto_level = _again + next + end + + end + end + when 81 then +# line 348 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + p = p - 1; + cs = ((in_set? ? 129 : 110)); + begin + stack[top] = cs + top+= 1 + cs = 128 + _goto_level = _again + next + end + + end + end + when 75 then +# line 354 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + emit(:escape, :literal, *text(data, ts, te, 1)) + begin + top -= 1 + cs = stack[top] + _goto_level = _again + next + end + + end + end + when 84 then +# line 271 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p +p = p - 1; begin + emit(:escape, :octal, *text(data, ts, te, 1)) + begin + top -= 1 + cs = stack[top] + _goto_level = _again + next + end + + end + end + when 91 then +# line 323 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p +p = p - 1; begin + emit(:escape, :hex, *text(data, ts, te, 1)) + begin + top -= 1 + cs = stack[top] + _goto_level = _again + next + end + + end + end + when 87 then +# line 332 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p +p = p - 1; begin + emit_meta_control_sequence(data, ts, te, :control) + begin + top -= 1 + cs = stack[top] + _goto_level = _again + next + end + + end + end + when 89 then +# line 337 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p +p = p - 1; begin + emit_meta_control_sequence(data, ts, te, :meta_sequence) + begin + top -= 1 + cs = stack[top] + _goto_level = _again + next + end + + end + end + when 83 then +# line 1 "NONE" + begin + case act + when 18 then + begin begin p = ((te))-1; end + + text = text(data, ts, te, 1).first + emit(:backref, :number, text, ts-1, te) + begin + top -= 1 + cs = stack[top] + _goto_level = _again + next + end + + end + when 19 then + begin begin p = ((te))-1; end + + emit(:escape, :octal, *text(data, ts, te, 1)) + begin + top -= 1 + cs = stack[top] + _goto_level = _again + next + end + + end +end + end + when 31 then +# line 364 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + text = text(data, ts, te-1).first + emit(:conditional, :condition, text, ts, te-1) + emit(:conditional, :condition_close, ')', te-1, te) + end + end + when 93 then +# line 370 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + p = p - 1; + begin + stack[top] = cs + top+= 1 + cs = 110 + _goto_level = _again + next + end + + end + end + when 94 then +# line 370 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p +p = p - 1; begin + p = p - 1; + begin + stack[top] = cs + top+= 1 + cs = 110 + _goto_level = _again + next + end + + end + end + when 30 then +# line 370 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + begin p = ((te))-1; end + begin + p = p - 1; + begin + stack[top] = cs + top+= 1 + cs = 110 + _goto_level = _again + next + end + + end + end + when 38 then +# line 383 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + emit(:meta, :dot, *text(data, ts, te)) + end + end + when 41 then +# line 387 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + if conditional_stack.last == group_depth + emit(:conditional, :separator, *text(data, ts, te)) + else + emit(:meta, :alternation, *text(data, ts, te)) + end + end + end + when 40 then +# line 397 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + emit(:anchor, :bol, *text(data, ts, te)) + end + end + when 35 then +# line 401 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + emit(:anchor, :eol, *text(data, ts, te)) + end + end + when 57 then +# line 405 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + emit(:keep, :mark, *text(data, ts, te)) + end + end + when 56 then +# line 409 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + case text = text(data, ts, te).first + when '\\A'; emit(:anchor, :bos, text, ts, te) + when '\\z'; emit(:anchor, :eos, text, ts, te) + when '\\Z'; emit(:anchor, :eos_ob_eol, text, ts, te) + when '\\b'; emit(:anchor, :word_boundary, text, ts, te) + when '\\B'; emit(:anchor, :nonword_boundary, text, ts, te) + when '\\G'; emit(:anchor, :match_start, text, ts, te) + end + end + end + when 47 then +# line 431 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + text = text(data, ts, te).first + + conditional_stack << group_depth + + emit(:conditional, :open, text[0..-2], ts, te-1) + emit(:conditional, :condition_open, '(', te-1, te) + begin + stack[top] = cs + top+= 1 + cs = 148 + _goto_level = _again + next + end + + end + end + when 48 then +# line 462 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + text = text(data, ts, te).first + if text[2..-1] =~ /([^\-mixdau:]|^$)|-.*([dau])/ + raise InvalidGroupOption.new($1 || "-#{$2}", text) + end + emit_options(text, ts, te) + end + end + when 9 then +# line 476 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + case text = text(data, ts, te).first + when '(?='; emit(:assertion, :lookahead, text, ts, te) + when '(?!'; emit(:assertion, :nlookahead, text, ts, te) + when '(?<='; emit(:assertion, :lookbehind, text, ts, te) + when '(?'; emit(:group, :atomic, text, ts, te) + when '(?~'; emit(:group, :absence, text, ts, te) + + when /^\(\?(?:<>|'')/ + validation_error(:group, 'named group', 'name is empty') + + when /^\(\?<\w*>/ + emit(:group, :named_ab, text, ts, te) + + when /^\(\?'\w*'/ + emit(:group, :named_sq, text, ts, te) + + end + end + end + when 11 then +# line 534 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + case text = text(data, ts, te).first + when /^\\([gk])(<>|'')/ # angle brackets + validation_error(:backref, 'ref/call', 'ref ID is empty') + + when /^\\([gk])<[^\d+-]\w*>/ # angle-brackets + if $1 == 'k' + emit(:backref, :name_ref_ab, text, ts, te) + else + emit(:backref, :name_call_ab, text, ts, te) + end + + when /^\\([gk])'[^\d+-]\w*'/ #single quotes + if $1 == 'k' + emit(:backref, :name_ref_sq, text, ts, te) + else + emit(:backref, :name_call_sq, text, ts, te) + end + + when /^\\([gk])<\d+>/ # angle-brackets + if $1 == 'k' + emit(:backref, :number_ref_ab, text, ts, te) + else + emit(:backref, :number_call_ab, text, ts, te) + end + + when /^\\([gk])'\d+'/ # single quotes + if $1 == 'k' + emit(:backref, :number_ref_sq, text, ts, te) + else + emit(:backref, :number_call_sq, text, ts, te) + end + + when /^\\(?:g<\+|g<-|(k)<-)\d+>/ # angle-brackets + if $1 == 'k' + emit(:backref, :number_rel_ref_ab, text, ts, te) + else + emit(:backref, :number_rel_call_ab, text, ts, te) + end + + when /^\\(?:g'\+|g'-|(k)'-)\d+'/ # single quotes + if $1 == 'k' + emit(:backref, :number_rel_ref_sq, text, ts, te) + else + emit(:backref, :number_rel_call_sq, text, ts, te) + end + + when /^\\k<[^\d+\-]\w*[+\-]\d+>/ # angle-brackets + emit(:backref, :name_recursion_ref_ab, text, ts, te) + + when /^\\k'[^\d+\-]\w*[+\-]\d+'/ # single-quotes + emit(:backref, :name_recursion_ref_sq, text, ts, te) + + when /^\\([gk])<[+\-]?\d+[+\-]\d+>/ # angle-brackets + emit(:backref, :number_recursion_ref_ab, text, ts, te) + + when /^\\([gk])'[+\-]?\d+[+\-]\d+'/ # single-quotes + emit(:backref, :number_recursion_ref_sq, text, ts, te) + + end + end + end + when 54 then +# line 599 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + case text = text(data, ts, te).first + when '?' ; emit(:quantifier, :zero_or_one, text, ts, te) + when '??'; emit(:quantifier, :zero_or_one_reluctant, text, ts, te) + when '?+'; emit(:quantifier, :zero_or_one_possessive, text, ts, te) + end + end + end + when 50 then +# line 607 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + case text = text(data, ts, te).first + when '*' ; emit(:quantifier, :zero_or_more, text, ts, te) + when '*?'; emit(:quantifier, :zero_or_more_reluctant, text, ts, te) + when '*+'; emit(:quantifier, :zero_or_more_possessive, text, ts, te) + end + end + end + when 52 then +# line 615 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + case text = text(data, ts, te).first + when '+' ; emit(:quantifier, :one_or_more, text, ts, te) + when '+?'; emit(:quantifier, :one_or_more_reluctant, text, ts, te) + when '++'; emit(:quantifier, :one_or_more_possessive, text, ts, te) + end + end + end + when 59 then +# line 623 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + emit(:quantifier, :interval, *text(data, ts, te)) + end + end + when 4 then +# line 633 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + if free_spacing + emit(:free_space, :comment, *text(data, ts, te)) + else + append_literal(data, ts, te) + end + end + end + when 46 then +# line 462 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p +p = p - 1; begin + text = text(data, ts, te).first + if text[2..-1] =~ /([^\-mixdau:]|^$)|-.*([dau])/ + raise InvalidGroupOption.new($1 || "-#{$2}", text) + end + emit_options(text, ts, te) + end + end + when 44 then +# line 511 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p +p = p - 1; begin + text = text(data, ts, te).first + emit(:group, :capture, text, ts, te) + end + end + when 53 then +# line 599 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p +p = p - 1; begin + case text = text(data, ts, te).first + when '?' ; emit(:quantifier, :zero_or_one, text, ts, te) + when '??'; emit(:quantifier, :zero_or_one_reluctant, text, ts, te) + when '?+'; emit(:quantifier, :zero_or_one_possessive, text, ts, te) + end + end + end + when 49 then +# line 607 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p +p = p - 1; begin + case text = text(data, ts, te).first + when '*' ; emit(:quantifier, :zero_or_more, text, ts, te) + when '*?'; emit(:quantifier, :zero_or_more_reluctant, text, ts, te) + when '*+'; emit(:quantifier, :zero_or_more_possessive, text, ts, te) + end + end + end + when 51 then +# line 615 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p +p = p - 1; begin + case text = text(data, ts, te).first + when '+' ; emit(:quantifier, :one_or_more, text, ts, te) + when '+?'; emit(:quantifier, :one_or_more_reluctant, text, ts, te) + when '++'; emit(:quantifier, :one_or_more_possessive, text, ts, te) + end + end + end + when 58 then +# line 623 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p +p = p - 1; begin + emit(:quantifier, :interval, *text(data, ts, te)) + end + end + when 55 then +# line 629 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p +p = p - 1; begin + begin + stack[top] = cs + top+= 1 + cs = 137 + _goto_level = _again + next + end + + end + end + when 43 then +# line 641 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p +p = p - 1; begin + if free_spacing + emit(:free_space, :whitespace, *text(data, ts, te)) + else + append_literal(data, ts, te) + end + end + end + when 42 then +# line 656 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p +p = p - 1; begin + append_literal(data, ts, te) + end + end + when 5 then +# line 462 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + begin p = ((te))-1; end + begin + text = text(data, ts, te).first + if text[2..-1] =~ /([^\-mixdau:]|^$)|-.*([dau])/ + raise InvalidGroupOption.new($1 || "-#{$2}", text) + end + emit_options(text, ts, te) + end + end + when 10 then +# line 629 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + begin p = ((te))-1; end + begin + begin + stack[top] = cs + top+= 1 + cs = 137 + _goto_level = _again + next + end + + end + end + when 3 then +# line 656 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + begin p = ((te))-1; end + begin + append_literal(data, ts, te) + end + end + when 1 then +# line 1 "NONE" + begin + case act + when 0 then + begin begin + cs = 0 + _goto_level = _again + next + end +end + when 54 then + begin begin p = ((te))-1; end + + append_literal(data, ts, te) + end +end + end + when 71 then +# line 131 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + + text = ts ? copy(data, ts-1..-1) : data.pack('c*') + raise PrematureEndError.new( text ) + end +# line 205 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p +p = p - 1; begin + emit(:set, :open, *text(data, ts, te)) + begin + stack[top] = cs + top+= 1 + cs = 129 + _goto_level = _again + next + end + + end + end + when 19 then +# line 131 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + + text = ts ? copy(data, ts-1..-1) : data.pack('c*') + raise PrematureEndError.new( text ) + end +# line 205 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + begin p = ((te))-1; end + begin + emit(:set, :open, *text(data, ts, te)) + begin + stack[top] = cs + top+= 1 + cs = 129 + _goto_level = _again + next + end + + end + end + when 90 then +# line 131 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + + text = ts ? copy(data, ts-1..-1) : data.pack('c*') + raise PrematureEndError.new( text ) + end +# line 323 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p +p = p - 1; begin + emit(:escape, :hex, *text(data, ts, te, 1)) + begin + top -= 1 + cs = stack[top] + _goto_level = _again + next + end + + end + end + when 86 then +# line 131 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + + text = ts ? copy(data, ts-1..-1) : data.pack('c*') + raise PrematureEndError.new( text ) + end +# line 332 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p +p = p - 1; begin + emit_meta_control_sequence(data, ts, te, :control) + begin + top -= 1 + cs = stack[top] + _goto_level = _again + next + end + + end + end + when 88 then +# line 131 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + + text = ts ? copy(data, ts-1..-1) : data.pack('c*') + raise PrematureEndError.new( text ) + end +# line 337 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p +p = p - 1; begin + emit_meta_control_sequence(data, ts, te, :meta_sequence) + begin + top -= 1 + cs = stack[top] + _goto_level = _again + next + end + + end + end + when 25 then +# line 131 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + + text = ts ? copy(data, ts-1..-1) : data.pack('c*') + raise PrematureEndError.new( text ) + end +# line 332 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + begin p = ((te))-1; end + begin + emit_meta_control_sequence(data, ts, te, :control) + begin + top -= 1 + cs = stack[top] + _goto_level = _again + next + end + + end + end + when 27 then +# line 131 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + + text = ts ? copy(data, ts-1..-1) : data.pack('c*') + raise PrematureEndError.new( text ) + end +# line 337 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + begin p = ((te))-1; end + begin + emit_meta_control_sequence(data, ts, te, :meta_sequence) + begin + top -= 1 + cs = stack[top] + _goto_level = _again + next + end + + end + end + when 29 then +# line 137 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + + text = ts ? copy(data, ts-1..-1) : data.pack('c*') + validation_error(:sequence, 'sequence', text) + end +# line 328 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + begin + top -= 1 + cs = stack[top] + _goto_level = _again + next + end + + end + end + when 7 then +# line 144 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + self.group_depth = group_depth - 1 end +# line 447 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + emit(:group, :comment, *text(data, ts, te)) + end + end + when 37 then +# line 144 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + self.group_depth = group_depth - 1 end +# line 516 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + if conditional_stack.last == group_depth + 1 + conditional_stack.pop + emit(:conditional, :close, *text(data, ts, te)) + else + if spacing_stack.length > 1 && + spacing_stack.last[:depth] == group_depth + 1 + spacing_stack.pop + self.free_spacing = spacing_stack.last[:free_spacing] + end + + emit(:group, :close, *text(data, ts, te)) + end + end + end + when 39 then +# line 145 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + self.set_depth = set_depth + 1 end +# line 422 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + emit(:set, :open, *text(data, ts, te)) + begin + stack[top] = cs + top+= 1 + cs = 129 + _goto_level = _again + next + end + + end + end + when 65 then +# line 146 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + self.set_depth = set_depth - 1 end +# line 152 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + emit(:set, :close, *text(data, ts, te)) + if in_set? + begin + top -= 1 + cs = stack[top] + _goto_level = _again + next + end + + else + begin + cs = 110 + _goto_level = _again + next + end + + end + end + end + when 70 then +# line 146 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + self.set_depth = set_depth - 1 end +# line 161 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin # special case, emits two tokens + emit(:literal, :literal, copy(data, ts..te-2), ts, te - 1) + emit(:set, :close, copy(data, ts+1..te-1), ts + 1, te) + if in_set? + begin + top -= 1 + cs = stack[top] + _goto_level = _again + next + end + + else + begin + cs = 110 + _goto_level = _again + next + end + + end + end + end + when 22 then +# line 146 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + self.set_depth = set_depth - 1 end +# line 210 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + text = text(data, ts, te).first + + type = :posixclass + class_name = text[2..-3] + if class_name[0].chr == '^' + class_name = class_name[1..-1] + type = :nonposixclass + end + + emit(type, class_name.to_sym, text, ts, te) + end + end + when 21 then +# line 146 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + self.set_depth = set_depth - 1 end +# line 223 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + emit(:set, :collation, *text(data, ts, te)) + end + end + when 23 then +# line 146 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + self.set_depth = set_depth - 1 end +# line 227 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +te = p+1 + begin + emit(:set, :equivalent, *text(data, ts, te)) + end + end + when 63 then +# line 1 "NONE" + begin +te = p+1 + end +# line 145 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + self.set_depth = set_depth + 1 end + when 78 then +# line 1 "NONE" + begin +te = p+1 + end +# line 265 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +act = 18; end + when 77 then +# line 1 "NONE" + begin +te = p+1 + end +# line 271 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +act = 19; end + when 2 then +# line 1 "NONE" + begin +te = p+1 + end +# line 656 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin +act = 54; end + when 45 then +# line 1 "NONE" + begin +te = p+1 + end +# line 144 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + self.group_depth = group_depth - 1 end +# line 143 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + self.group_depth = group_depth + 1 end +# line 2563 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner.rb" + end + end + end + if _goto_level <= _again + case _re_scanner_to_state_actions[cs] + when 60 then +# line 1 "NONE" + begin +ts = nil; end + when 32 then +# line 1 "NONE" + begin +ts = nil; end +# line 1 "NONE" + begin +act = 0 + end +# line 2581 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner.rb" + end + + if cs == 0 + _goto_level = _out + next + end + p += 1 + if p != pe + _goto_level = _resume + next + end + end + if _goto_level <= _test_eof + if p == eof + if _re_scanner_eof_trans[cs] > 0 + _trans = _re_scanner_eof_trans[cs] - 1; + _goto_level = _eof_trans + next; + end + case _re_scanner_eof_actions[cs] + when 13 then +# line 8 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/property.rl" + begin + + raise PrematureEndError.new('unicode property') + end + when 12 then +# line 131 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + begin + + text = ts ? copy(data, ts-1..-1) : data.pack('c*') + raise PrematureEndError.new( text ) + end +# line 2615 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner.rb" + end + end + + end + if _goto_level <= _out + break + end +end + end + +# line 755 "/Users/jannoschmuller/code/regexp_parser/lib/regexp_parser/scanner/scanner.rl" + + # to avoid "warning: assigned but unused variable - testEof" + testEof = testEof + + if cs == re_scanner_error + text = ts ? copy(data, ts-1..-1) : data.pack('c*') + raise ScannerError.new("Scan error at '#{text}'") + end + + raise PrematureEndError.new("(missing group closing paranthesis) "+ + "[#{group_depth}]") if in_group? + raise PrematureEndError.new("(missing set closing bracket) "+ + "[#{set_depth}]") if in_set? + + # when the entire expression is a literal run + emit_literal if literal + + tokens + end + + # lazy-load property maps when first needed + require 'yaml' + PROP_MAPS_DIR = File.expand_path('../scanner/properties', __FILE__) + + def self.short_prop_map + @short_prop_map ||= YAML.load_file("#{PROP_MAPS_DIR}/short.yml") + end + + def self.long_prop_map + @long_prop_map ||= YAML.load_file("#{PROP_MAPS_DIR}/long.yml") + end + + # Emits an array with the details of the scanned pattern + def emit(type, token, text, ts, te) + #puts "EMIT: type: #{type}, token: #{token}, text: #{text}, ts: #{ts}, te: #{te}" + + emit_literal if literal + + if block + block.call type, token, text, ts, te + end + + tokens << [type, token, text, ts, te] + end + + private + + attr_accessor :tokens, :literal, :block, :free_spacing, :spacing_stack, + :group_depth, :set_depth, :conditional_stack + + def in_group? + group_depth > 0 + end + + def in_set? + set_depth > 0 + end + + # Copy from ts to te from data as text + def copy(data, range) + data[range].pack('c*') + end + + # Copy from ts to te from data as text, returning an array with the text + # and the offsets used to copy it. + def text(data, ts, te, soff = 0) + [copy(data, ts-soff..te-1), ts-soff, te] + end + + # Appends one or more characters to the literal buffer, to be emitted later + # by a call to emit_literal. Contents can be a mix of ASCII and UTF-8. + def append_literal(data, ts, te) + self.literal = literal || [] + literal << text(data, ts, te) + end + + # Emits the literal run collected by calls to the append_literal method, + # using the total start (ts) and end (te) offsets of the run. + def emit_literal + ts, te = literal.first[1], literal.last[2] + text = literal.map {|t| t[0]}.join + + text.force_encoding('utf-8') if text.respond_to?(:force_encoding) + + self.literal = nil + emit(:literal, :literal, text, ts, te) + end + + def emit_options(text, ts, te) + token = nil + + # Ruby allows things like '(?-xxxx)' or '(?xx-xx--xx-:abc)'. + text =~ /\(\?([mixdau]*)(-(?:[mix]*))*(:)?/ + positive, negative, group_local = $1, $2, $3 + + if positive.include?('x') + self.free_spacing = true + end + + # If the x appears in both, treat it like ruby does, the second cancels + # the first. + if negative && negative.include?('x') + self.free_spacing = false + end + + if group_local + spacing_stack << {:free_spacing => free_spacing, :depth => group_depth} + token = :options + else + # switch for parent group level + spacing_stack.last[:free_spacing] = free_spacing + token = :options_switch + end + + emit(:group, token, text, ts, te) + end + + def emit_meta_control_sequence(data, ts, te, token) + if data.last < 0x00 || data.last > 0x7F + validation_error(:sequence, 'escape', token.to_s) + end + emit(:escape, token, *text(data, ts, te, 1)) + end + + # Centralizes and unifies the handling of validation related + # errors. + def validation_error(type, what, reason) + case type + when :group + error = InvalidGroupError.new(what, reason) + when :backref + error = InvalidBackrefError.new(what, reason) + when :sequence + error = InvalidSequenceError.new(what, reason) + end + + raise error # unless @@config.validation_ignore + end +end # module Regexp::Scanner diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/scanner/char_type.rl b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/scanner/char_type.rl new file mode 100644 index 00000000..071f86f4 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/scanner/char_type.rl @@ -0,0 +1,28 @@ +%%{ + machine re_char_type; + + single_codepoint_char_type = [dDhHsSwW]; + multi_codepoint_char_type = [RX]; + + char_type_char = single_codepoint_char_type | multi_codepoint_char_type; + + # Char types scanner + # -------------------------------------------------------------------------- + char_type := |* + char_type_char { + case text = text(data, ts, te, 1).first + when '\d'; emit(:type, :digit, text, ts - 1, te) + when '\D'; emit(:type, :nondigit, text, ts - 1, te) + when '\h'; emit(:type, :hex, text, ts - 1, te) + when '\H'; emit(:type, :nonhex, text, ts - 1, te) + when '\s'; emit(:type, :space, text, ts - 1, te) + when '\S'; emit(:type, :nonspace, text, ts - 1, te) + when '\w'; emit(:type, :word, text, ts - 1, te) + when '\W'; emit(:type, :nonword, text, ts - 1, te) + when '\R'; emit(:type, :linebreak, text, ts - 1, te) + when '\X'; emit(:type, :xgrapheme, text, ts - 1, te) + end + fret; + }; + *|; +}%% diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/scanner/properties/long.yml b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/scanner/properties/long.yml new file mode 100644 index 00000000..b3698933 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/scanner/properties/long.yml @@ -0,0 +1,594 @@ +# +# THIS FILE IS AUTO-GENERATED BY `rake props:update`, DO NOT EDIT +# +--- +adlam: adlam +age=1.1: age=1.1 +age=10.0: age=10.0 +age=11.0: age=11.0 +age=12.0: age=12.0 +age=12.1: age=12.1 +age=2.0: age=2.0 +age=2.1: age=2.1 +age=3.0: age=3.0 +age=3.1: age=3.1 +age=3.2: age=3.2 +age=4.0: age=4.0 +age=4.1: age=4.1 +age=5.0: age=5.0 +age=5.1: age=5.1 +age=5.2: age=5.2 +age=6.0: age=6.0 +age=6.1: age=6.1 +age=6.2: age=6.2 +age=6.3: age=6.3 +age=7.0: age=7.0 +age=8.0: age=8.0 +age=9.0: age=9.0 +ahom: ahom +alnum: alnum +alpha: alpha +alphabetic: alphabetic +anatolianhieroglyphs: anatolian_hieroglyphs +any: any +arabic: arabic +armenian: armenian +ascii: ascii +asciihexdigit: ascii_hex_digit +assigned: assigned +avestan: avestan +balinese: balinese +bamum: bamum +bassavah: bassa_vah +batak: batak +bengali: bengali +bhaiksuki: bhaiksuki +bidicontrol: bidi_control +blank: blank +bopomofo: bopomofo +brahmi: brahmi +braille: braille +buginese: buginese +buhid: buhid +canadianaboriginal: canadian_aboriginal +carian: carian +cased: cased +casedletter: cased_letter +caseignorable: case_ignorable +caucasianalbanian: caucasian_albanian +chakma: chakma +cham: cham +changeswhencasefolded: changes_when_casefolded +changeswhencasemapped: changes_when_casemapped +changeswhenlowercased: changes_when_lowercased +changeswhentitlecased: changes_when_titlecased +changeswhenuppercased: changes_when_uppercased +cherokee: cherokee +closepunctuation: close_punctuation +cntrl: cntrl +common: common +connectorpunctuation: connector_punctuation +control: control +coptic: coptic +cuneiform: cuneiform +currencysymbol: currency_symbol +cypriot: cypriot +cyrillic: cyrillic +dash: dash +dashpunctuation: dash_punctuation +decimalnumber: decimal_number +defaultignorablecodepoint: default_ignorable_code_point +deprecated: deprecated +deseret: deseret +devanagari: devanagari +diacritic: diacritic +digit: digit +dogra: dogra +duployan: duployan +egyptianhieroglyphs: egyptian_hieroglyphs +elbasan: elbasan +elymaic: elymaic +emoji: emoji +emojicomponent: emoji_component +emojimodifier: emoji_modifier +emojimodifierbase: emoji_modifier_base +emojipresentation: emoji_presentation +enclosingmark: enclosing_mark +ethiopic: ethiopic +extender: extender +finalpunctuation: final_punctuation +format: format +georgian: georgian +glagolitic: glagolitic +gothic: gothic +grantha: grantha +graph: graph +graphemebase: grapheme_base +graphemeextend: grapheme_extend +graphemelink: grapheme_link +greek: greek +gujarati: gujarati +gunjalagondi: gunjala_gondi +gurmukhi: gurmukhi +han: han +hangul: hangul +hanifirohingya: hanifi_rohingya +hanunoo: hanunoo +hatran: hatran +hebrew: hebrew +hexdigit: hex_digit +hiragana: hiragana +hyphen: hyphen +idcontinue: id_continue +ideographic: ideographic +idsbinaryoperator: ids_binary_operator +idstart: id_start +idstrinaryoperator: ids_trinary_operator +imperialaramaic: imperial_aramaic +inadlam: in_adlam +inaegeannumbers: in_aegean_numbers +inahom: in_ahom +inalchemicalsymbols: in_alchemical_symbols +inalphabeticpresentationforms: in_alphabetic_presentation_forms +inanatolianhieroglyphs: in_anatolian_hieroglyphs +inancientgreekmusicalnotation: in_ancient_greek_musical_notation +inancientgreeknumbers: in_ancient_greek_numbers +inancientsymbols: in_ancient_symbols +inarabic: in_arabic +inarabicextendeda: in_arabic_extended_a +inarabicmathematicalalphabeticsymbols: in_arabic_mathematical_alphabetic_symbols +inarabicpresentationformsa: in_arabic_presentation_forms_a +inarabicpresentationformsb: in_arabic_presentation_forms_b +inarabicsupplement: in_arabic_supplement +inarmenian: in_armenian +inarrows: in_arrows +inavestan: in_avestan +inbalinese: in_balinese +inbamum: in_bamum +inbamumsupplement: in_bamum_supplement +inbasiclatin: in_basic_latin +inbassavah: in_bassa_vah +inbatak: in_batak +inbengali: in_bengali +inbhaiksuki: in_bhaiksuki +inblockelements: in_block_elements +inbopomofo: in_bopomofo +inbopomofoextended: in_bopomofo_extended +inboxdrawing: in_box_drawing +inbrahmi: in_brahmi +inbraillepatterns: in_braille_patterns +inbuginese: in_buginese +inbuhid: in_buhid +inbyzantinemusicalsymbols: in_byzantine_musical_symbols +incarian: in_carian +incaucasianalbanian: in_caucasian_albanian +inchakma: in_chakma +incham: in_cham +incherokee: in_cherokee +incherokeesupplement: in_cherokee_supplement +inchesssymbols: in_chess_symbols +incjkcompatibility: in_cjk_compatibility +incjkcompatibilityforms: in_cjk_compatibility_forms +incjkcompatibilityideographs: in_cjk_compatibility_ideographs +incjkcompatibilityideographssupplement: in_cjk_compatibility_ideographs_supplement +incjkradicalssupplement: in_cjk_radicals_supplement +incjkstrokes: in_cjk_strokes +incjksymbolsandpunctuation: in_cjk_symbols_and_punctuation +incjkunifiedideographs: in_cjk_unified_ideographs +incjkunifiedideographsextensiona: in_cjk_unified_ideographs_extension_a +incjkunifiedideographsextensionb: in_cjk_unified_ideographs_extension_b +incjkunifiedideographsextensionc: in_cjk_unified_ideographs_extension_c +incjkunifiedideographsextensiond: in_cjk_unified_ideographs_extension_d +incjkunifiedideographsextensione: in_cjk_unified_ideographs_extension_e +incjkunifiedideographsextensionf: in_cjk_unified_ideographs_extension_f +incombiningdiacriticalmarks: in_combining_diacritical_marks +incombiningdiacriticalmarksextended: in_combining_diacritical_marks_extended +incombiningdiacriticalmarksforsymbols: in_combining_diacritical_marks_for_symbols +incombiningdiacriticalmarkssupplement: in_combining_diacritical_marks_supplement +incombininghalfmarks: in_combining_half_marks +incommonindicnumberforms: in_common_indic_number_forms +incontrolpictures: in_control_pictures +incoptic: in_coptic +incopticepactnumbers: in_coptic_epact_numbers +incountingrodnumerals: in_counting_rod_numerals +incuneiform: in_cuneiform +incuneiformnumbersandpunctuation: in_cuneiform_numbers_and_punctuation +incurrencysymbols: in_currency_symbols +incypriotsyllabary: in_cypriot_syllabary +incyrillic: in_cyrillic +incyrillicextendeda: in_cyrillic_extended_a +incyrillicextendedb: in_cyrillic_extended_b +incyrillicextendedc: in_cyrillic_extended_c +incyrillicsupplement: in_cyrillic_supplement +indeseret: in_deseret +indevanagari: in_devanagari +indevanagariextended: in_devanagari_extended +indingbats: in_dingbats +indogra: in_dogra +indominotiles: in_domino_tiles +induployan: in_duployan +inearlydynasticcuneiform: in_early_dynastic_cuneiform +inegyptianhieroglyphformatcontrols: in_egyptian_hieroglyph_format_controls +inegyptianhieroglyphs: in_egyptian_hieroglyphs +inelbasan: in_elbasan +inelymaic: in_elymaic +inemoticons: in_emoticons +inenclosedalphanumerics: in_enclosed_alphanumerics +inenclosedalphanumericsupplement: in_enclosed_alphanumeric_supplement +inenclosedcjklettersandmonths: in_enclosed_cjk_letters_and_months +inenclosedideographicsupplement: in_enclosed_ideographic_supplement +inethiopic: in_ethiopic +inethiopicextended: in_ethiopic_extended +inethiopicextendeda: in_ethiopic_extended_a +inethiopicsupplement: in_ethiopic_supplement +ingeneralpunctuation: in_general_punctuation +ingeometricshapes: in_geometric_shapes +ingeometricshapesextended: in_geometric_shapes_extended +ingeorgian: in_georgian +ingeorgianextended: in_georgian_extended +ingeorgiansupplement: in_georgian_supplement +inglagolitic: in_glagolitic +inglagoliticsupplement: in_glagolitic_supplement +ingothic: in_gothic +ingrantha: in_grantha +ingreekandcoptic: in_greek_and_coptic +ingreekextended: in_greek_extended +ingujarati: in_gujarati +ingunjalagondi: in_gunjala_gondi +ingurmukhi: in_gurmukhi +inhalfwidthandfullwidthforms: in_halfwidth_and_fullwidth_forms +inhangulcompatibilityjamo: in_hangul_compatibility_jamo +inhanguljamo: in_hangul_jamo +inhanguljamoextendeda: in_hangul_jamo_extended_a +inhanguljamoextendedb: in_hangul_jamo_extended_b +inhangulsyllables: in_hangul_syllables +inhanifirohingya: in_hanifi_rohingya +inhanunoo: in_hanunoo +inhatran: in_hatran +inhebrew: in_hebrew +inherited: inherited +inhighprivateusesurrogates: in_high_private_use_surrogates +inhighsurrogates: in_high_surrogates +inhiragana: in_hiragana +inideographicdescriptioncharacters: in_ideographic_description_characters +inideographicsymbolsandpunctuation: in_ideographic_symbols_and_punctuation +inimperialaramaic: in_imperial_aramaic +inindicsiyaqnumbers: in_indic_siyaq_numbers +ininscriptionalpahlavi: in_inscriptional_pahlavi +ininscriptionalparthian: in_inscriptional_parthian +inipaextensions: in_ipa_extensions +initialpunctuation: initial_punctuation +injavanese: in_javanese +inkaithi: in_kaithi +inkanaextendeda: in_kana_extended_a +inkanasupplement: in_kana_supplement +inkanbun: in_kanbun +inkangxiradicals: in_kangxi_radicals +inkannada: in_kannada +inkatakana: in_katakana +inkatakanaphoneticextensions: in_katakana_phonetic_extensions +inkayahli: in_kayah_li +inkharoshthi: in_kharoshthi +inkhmer: in_khmer +inkhmersymbols: in_khmer_symbols +inkhojki: in_khojki +inkhudawadi: in_khudawadi +inlao: in_lao +inlatin1supplement: in_latin_1_supplement +inlatinextendeda: in_latin_extended_a +inlatinextendedadditional: in_latin_extended_additional +inlatinextendedb: in_latin_extended_b +inlatinextendedc: in_latin_extended_c +inlatinextendedd: in_latin_extended_d +inlatinextendede: in_latin_extended_e +inlepcha: in_lepcha +inletterlikesymbols: in_letterlike_symbols +inlimbu: in_limbu +inlineara: in_linear_a +inlinearbideograms: in_linear_b_ideograms +inlinearbsyllabary: in_linear_b_syllabary +inlisu: in_lisu +inlowsurrogates: in_low_surrogates +inlycian: in_lycian +inlydian: in_lydian +inmahajani: in_mahajani +inmahjongtiles: in_mahjong_tiles +inmakasar: in_makasar +inmalayalam: in_malayalam +inmandaic: in_mandaic +inmanichaean: in_manichaean +inmarchen: in_marchen +inmasaramgondi: in_masaram_gondi +inmathematicalalphanumericsymbols: in_mathematical_alphanumeric_symbols +inmathematicaloperators: in_mathematical_operators +inmayannumerals: in_mayan_numerals +inmedefaidrin: in_medefaidrin +inmeeteimayek: in_meetei_mayek +inmeeteimayekextensions: in_meetei_mayek_extensions +inmendekikakui: in_mende_kikakui +inmeroiticcursive: in_meroitic_cursive +inmeroitichieroglyphs: in_meroitic_hieroglyphs +inmiao: in_miao +inmiscellaneousmathematicalsymbolsa: in_miscellaneous_mathematical_symbols_a +inmiscellaneousmathematicalsymbolsb: in_miscellaneous_mathematical_symbols_b +inmiscellaneoussymbols: in_miscellaneous_symbols +inmiscellaneoussymbolsandarrows: in_miscellaneous_symbols_and_arrows +inmiscellaneoussymbolsandpictographs: in_miscellaneous_symbols_and_pictographs +inmiscellaneoustechnical: in_miscellaneous_technical +inmodi: in_modi +inmodifiertoneletters: in_modifier_tone_letters +inmongolian: in_mongolian +inmongoliansupplement: in_mongolian_supplement +inmro: in_mro +inmultani: in_multani +inmusicalsymbols: in_musical_symbols +inmyanmar: in_myanmar +inmyanmarextendeda: in_myanmar_extended_a +inmyanmarextendedb: in_myanmar_extended_b +innabataean: in_nabataean +innandinagari: in_nandinagari +innewa: in_newa +innewtailue: in_new_tai_lue +innko: in_nko +innoblock: in_no_block +innumberforms: in_number_forms +innushu: in_nushu +innyiakengpuachuehmong: in_nyiakeng_puachue_hmong +inogham: in_ogham +inolchiki: in_ol_chiki +inoldhungarian: in_old_hungarian +inolditalic: in_old_italic +inoldnortharabian: in_old_north_arabian +inoldpermic: in_old_permic +inoldpersian: in_old_persian +inoldsogdian: in_old_sogdian +inoldsoutharabian: in_old_south_arabian +inoldturkic: in_old_turkic +inopticalcharacterrecognition: in_optical_character_recognition +inoriya: in_oriya +inornamentaldingbats: in_ornamental_dingbats +inosage: in_osage +inosmanya: in_osmanya +inottomansiyaqnumbers: in_ottoman_siyaq_numbers +inpahawhhmong: in_pahawh_hmong +inpalmyrene: in_palmyrene +inpaucinhau: in_pau_cin_hau +inphagspa: in_phags_pa +inphaistosdisc: in_phaistos_disc +inphoenician: in_phoenician +inphoneticextensions: in_phonetic_extensions +inphoneticextensionssupplement: in_phonetic_extensions_supplement +inplayingcards: in_playing_cards +inprivateusearea: in_private_use_area +inpsalterpahlavi: in_psalter_pahlavi +inrejang: in_rejang +inruminumeralsymbols: in_rumi_numeral_symbols +inrunic: in_runic +insamaritan: in_samaritan +insaurashtra: in_saurashtra +inscriptionalpahlavi: inscriptional_pahlavi +inscriptionalparthian: inscriptional_parthian +insharada: in_sharada +inshavian: in_shavian +inshorthandformatcontrols: in_shorthand_format_controls +insiddham: in_siddham +insinhala: in_sinhala +insinhalaarchaicnumbers: in_sinhala_archaic_numbers +insmallformvariants: in_small_form_variants +insmallkanaextension: in_small_kana_extension +insogdian: in_sogdian +insorasompeng: in_sora_sompeng +insoyombo: in_soyombo +inspacingmodifierletters: in_spacing_modifier_letters +inspecials: in_specials +insundanese: in_sundanese +insundanesesupplement: in_sundanese_supplement +insuperscriptsandsubscripts: in_superscripts_and_subscripts +insupplementalarrowsa: in_supplemental_arrows_a +insupplementalarrowsb: in_supplemental_arrows_b +insupplementalarrowsc: in_supplemental_arrows_c +insupplementalmathematicaloperators: in_supplemental_mathematical_operators +insupplementalpunctuation: in_supplemental_punctuation +insupplementalsymbolsandpictographs: in_supplemental_symbols_and_pictographs +insupplementaryprivateuseareaa: in_supplementary_private_use_area_a +insupplementaryprivateuseareab: in_supplementary_private_use_area_b +insuttonsignwriting: in_sutton_signwriting +insylotinagri: in_syloti_nagri +insymbolsandpictographsextendeda: in_symbols_and_pictographs_extended_a +insyriac: in_syriac +insyriacsupplement: in_syriac_supplement +intagalog: in_tagalog +intagbanwa: in_tagbanwa +intags: in_tags +intaile: in_tai_le +intaitham: in_tai_tham +intaiviet: in_tai_viet +intaixuanjingsymbols: in_tai_xuan_jing_symbols +intakri: in_takri +intamil: in_tamil +intamilsupplement: in_tamil_supplement +intangut: in_tangut +intangutcomponents: in_tangut_components +intelugu: in_telugu +inthaana: in_thaana +inthai: in_thai +intibetan: in_tibetan +intifinagh: in_tifinagh +intirhuta: in_tirhuta +intransportandmapsymbols: in_transport_and_map_symbols +inugaritic: in_ugaritic +inunifiedcanadianaboriginalsyllabics: in_unified_canadian_aboriginal_syllabics +inunifiedcanadianaboriginalsyllabicsextended: in_unified_canadian_aboriginal_syllabics_extended +invai: in_vai +invariationselectors: in_variation_selectors +invariationselectorssupplement: in_variation_selectors_supplement +invedicextensions: in_vedic_extensions +inverticalforms: in_vertical_forms +inwancho: in_wancho +inwarangciti: in_warang_citi +inyijinghexagramsymbols: in_yijing_hexagram_symbols +inyiradicals: in_yi_radicals +inyisyllables: in_yi_syllables +inzanabazarsquare: in_zanabazar_square +javanese: javanese +joincontrol: join_control +kaithi: kaithi +kannada: kannada +katakana: katakana +kayahli: kayah_li +kharoshthi: kharoshthi +khmer: khmer +khojki: khojki +khudawadi: khudawadi +lao: lao +latin: latin +lepcha: lepcha +letter: letter +letternumber: letter_number +limbu: limbu +lineara: linear_a +linearb: linear_b +lineseparator: line_separator +lisu: lisu +logicalorderexception: logical_order_exception +lower: lower +lowercase: lowercase +lowercaseletter: lowercase_letter +lycian: lycian +lydian: lydian +mahajani: mahajani +makasar: makasar +malayalam: malayalam +mandaic: mandaic +manichaean: manichaean +marchen: marchen +mark: mark +masaramgondi: masaram_gondi +math: math +mathsymbol: math_symbol +medefaidrin: medefaidrin +meeteimayek: meetei_mayek +mendekikakui: mende_kikakui +meroiticcursive: meroitic_cursive +meroitichieroglyphs: meroitic_hieroglyphs +miao: miao +modi: modi +modifierletter: modifier_letter +modifiersymbol: modifier_symbol +mongolian: mongolian +mro: mro +multani: multani +myanmar: myanmar +nabataean: nabataean +nandinagari: nandinagari +newa: newa +newline: newline +newtailue: new_tai_lue +nko: nko +noncharactercodepoint: noncharacter_code_point +nonspacingmark: nonspacing_mark +number: number +nushu: nushu +nyiakengpuachuehmong: nyiakeng_puachue_hmong +ogham: ogham +olchiki: ol_chiki +oldhungarian: old_hungarian +olditalic: old_italic +oldnortharabian: old_north_arabian +oldpermic: old_permic +oldpersian: old_persian +oldsogdian: old_sogdian +oldsoutharabian: old_south_arabian +oldturkic: old_turkic +openpunctuation: open_punctuation +oriya: oriya +osage: osage +osmanya: osmanya +other: other +otheralphabetic: other_alphabetic +otherdefaultignorablecodepoint: other_default_ignorable_code_point +othergraphemeextend: other_grapheme_extend +otheridcontinue: other_id_continue +otheridstart: other_id_start +otherletter: other_letter +otherlowercase: other_lowercase +othermath: other_math +othernumber: other_number +otherpunctuation: other_punctuation +othersymbol: other_symbol +otheruppercase: other_uppercase +pahawhhmong: pahawh_hmong +palmyrene: palmyrene +paragraphseparator: paragraph_separator +patternsyntax: pattern_syntax +patternwhitespace: pattern_white_space +paucinhau: pau_cin_hau +phagspa: phags_pa +phoenician: phoenician +prependedconcatenationmark: prepended_concatenation_mark +print: print +privateuse: private_use +psalterpahlavi: psalter_pahlavi +punct: punct +punctuation: punctuation +quotationmark: quotation_mark +radical: radical +regionalindicator: regional_indicator +rejang: rejang +runic: runic +samaritan: samaritan +saurashtra: saurashtra +sentenceterminal: sentence_terminal +separator: separator +sharada: sharada +shavian: shavian +siddham: siddham +signwriting: signwriting +sinhala: sinhala +softdotted: soft_dotted +sogdian: sogdian +sorasompeng: sora_sompeng +soyombo: soyombo +space: space +spaceseparator: space_separator +spacingmark: spacing_mark +sundanese: sundanese +surrogate: surrogate +sylotinagri: syloti_nagri +symbol: symbol +syriac: syriac +tagalog: tagalog +tagbanwa: tagbanwa +taile: tai_le +taitham: tai_tham +taiviet: tai_viet +takri: takri +tamil: tamil +tangut: tangut +telugu: telugu +terminalpunctuation: terminal_punctuation +thaana: thaana +thai: thai +tibetan: tibetan +tifinagh: tifinagh +tirhuta: tirhuta +titlecaseletter: titlecase_letter +ugaritic: ugaritic +unassigned: unassigned +unifiedideograph: unified_ideograph +unknown: unknown +upper: upper +uppercase: uppercase +uppercaseletter: uppercase_letter +vai: vai +variationselector: variation_selector +wancho: wancho +warangciti: warang_citi +whitespace: white_space +word: word +xdigit: xdigit +xidcontinue: xid_continue +xidstart: xid_start +xposixpunct: xposixpunct +yi: yi +zanabazarsquare: zanabazar_square diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/scanner/properties/short.yml b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/scanner/properties/short.yml new file mode 100644 index 00000000..7c85c352 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/scanner/properties/short.yml @@ -0,0 +1,237 @@ +# +# THIS FILE IS AUTO-GENERATED BY `rake props:update`, DO NOT EDIT +# +--- +adlm: adlam +aghb: caucasian_albanian +ahex: ascii_hex_digit +arab: arabic +armi: imperial_aramaic +armn: armenian +avst: avestan +bali: balinese +bamu: bamum +bass: bassa_vah +batk: batak +beng: bengali +bhks: bhaiksuki +bidic: bidi_control +bopo: bopomofo +brah: brahmi +brai: braille +bugi: buginese +buhd: buhid +c: other +cakm: chakma +cans: canadian_aboriginal +cari: carian +cc: control +cf: format +cher: cherokee +ci: case_ignorable +cn: unassigned +co: private_use +combiningmark: mark +copt: coptic +cprt: cypriot +cs: surrogate +cwcf: changes_when_casefolded +cwcm: changes_when_casemapped +cwl: changes_when_lowercased +cwt: changes_when_titlecased +cwu: changes_when_uppercased +cyrl: cyrillic +dep: deprecated +deva: devanagari +di: default_ignorable_code_point +dia: diacritic +dogr: dogra +dsrt: deseret +dupl: duployan +egyp: egyptian_hieroglyphs +elba: elbasan +elym: elymaic +ethi: ethiopic +ext: extender +geor: georgian +glag: glagolitic +gong: gunjala_gondi +gonm: masaram_gondi +goth: gothic +gran: grantha +grbase: grapheme_base +grek: greek +grext: grapheme_extend +grlink: grapheme_link +gujr: gujarati +guru: gurmukhi +hang: hangul +hani: han +hano: hanunoo +hatr: hatran +hebr: hebrew +hex: hex_digit +hira: hiragana +hluw: anatolian_hieroglyphs +hmng: pahawh_hmong +hmnp: nyiakeng_puachue_hmong +hung: old_hungarian +idc: id_continue +ideo: ideographic +ids: id_start +idsb: ids_binary_operator +idst: ids_trinary_operator +ital: old_italic +java: javanese +joinc: join_control +kali: kayah_li +kana: katakana +khar: kharoshthi +khmr: khmer +khoj: khojki +knda: kannada +kthi: kaithi +l: letter +lana: tai_tham +laoo: lao +latn: latin +lc: cased_letter +lepc: lepcha +limb: limbu +lina: linear_a +linb: linear_b +ll: lowercase_letter +lm: modifier_letter +lo: other_letter +loe: logical_order_exception +lt: titlecase_letter +lu: uppercase_letter +lyci: lycian +lydi: lydian +m: mark +mahj: mahajani +maka: makasar +mand: mandaic +mani: manichaean +marc: marchen +mc: spacing_mark +me: enclosing_mark +medf: medefaidrin +mend: mende_kikakui +merc: meroitic_cursive +mero: meroitic_hieroglyphs +mlym: malayalam +mn: nonspacing_mark +mong: mongolian +mroo: mro +mtei: meetei_mayek +mult: multani +mymr: myanmar +n: number +nand: nandinagari +narb: old_north_arabian +nbat: nabataean +nchar: noncharacter_code_point +nd: decimal_number +nkoo: nko +nl: letter_number +'no': other_number +nshu: nushu +oalpha: other_alphabetic +odi: other_default_ignorable_code_point +ogam: ogham +ogrext: other_grapheme_extend +oidc: other_id_continue +oids: other_id_start +olck: ol_chiki +olower: other_lowercase +omath: other_math +orkh: old_turkic +orya: oriya +osge: osage +osma: osmanya +oupper: other_uppercase +p: punctuation +palm: palmyrene +patsyn: pattern_syntax +patws: pattern_white_space +pauc: pau_cin_hau +pc: connector_punctuation +pcm: prepended_concatenation_mark +pd: dash_punctuation +pe: close_punctuation +perm: old_permic +pf: final_punctuation +phag: phags_pa +phli: inscriptional_pahlavi +phlp: psalter_pahlavi +phnx: phoenician +pi: initial_punctuation +plrd: miao +po: other_punctuation +prti: inscriptional_parthian +ps: open_punctuation +qaac: coptic +qaai: inherited +qmark: quotation_mark +ri: regional_indicator +rjng: rejang +rohg: hanifi_rohingya +runr: runic +s: symbol +samr: samaritan +sarb: old_south_arabian +saur: saurashtra +sc: currency_symbol +sd: soft_dotted +sgnw: signwriting +shaw: shavian +shrd: sharada +sidd: siddham +sind: khudawadi +sinh: sinhala +sk: modifier_symbol +sm: math_symbol +so: other_symbol +sogd: sogdian +sogo: old_sogdian +sora: sora_sompeng +soyo: soyombo +sterm: sentence_terminal +sund: sundanese +sylo: syloti_nagri +syrc: syriac +tagb: tagbanwa +takr: takri +tale: tai_le +talu: new_tai_lue +taml: tamil +tang: tangut +tavt: tai_viet +telu: telugu +term: terminal_punctuation +tfng: tifinagh +tglg: tagalog +thaa: thaana +tibt: tibetan +tirh: tirhuta +ugar: ugaritic +uideo: unified_ideograph +vaii: vai +vs: variation_selector +wara: warang_citi +wcho: wancho +wspace: white_space +xidc: xid_continue +xids: xid_start +xpeo: old_persian +xsux: cuneiform +yiii: yi +z: separator +zanb: zanabazar_square +zinh: inherited +zl: line_separator +zp: paragraph_separator +zs: space_separator +zyyy: common +zzzz: unknown diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/scanner/property.rl b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/scanner/property.rl new file mode 100644 index 00000000..fab82995 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/scanner/property.rl @@ -0,0 +1,30 @@ +%%{ + machine re_property; + + property_char = [pP]; + + property_sequence = property_char . '{' . '^'? (alnum|space|[_\-\.=])+ '}'; + + action premature_property_end { + raise PrematureEndError.new('unicode property') + } + + # Unicode properties scanner + # -------------------------------------------------------------------------- + unicode_property := |* + + property_sequence < eof(premature_property_end) { + text = text(data, ts, te, 1).first + type = (text[1] == 'P') ^ (text[3] == '^') ? :nonproperty : :property + + name = data[ts+2..te-2].pack('c*').gsub(/[\^\s_\-]/, '').downcase + + token = self.class.short_prop_map[name] || self.class.long_prop_map[name] + raise UnknownUnicodePropertyError.new(name) unless token + + self.emit(type, token.to_sym, text, ts-1, te) + + fret; + }; + *|; +}%% diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/scanner/scanner.rl b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/scanner/scanner.rl new file mode 100644 index 00000000..39f6995f --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/scanner/scanner.rl @@ -0,0 +1,893 @@ +%%{ + machine re_scanner; + include re_char_type "char_type.rl"; + include re_property "property.rl"; + + dot = '.'; + backslash = '\\'; + alternation = '|'; + beginning_of_line = '^'; + end_of_line = '$'; + + range_open = '{'; + range_close = '}'; + curlies = range_open | range_close; + + group_open = '('; + group_close = ')'; + parantheses = group_open | group_close; + + set_open = '['; + set_close = ']'; + brackets = set_open | set_close; + + comment = ('#' . [^\n]* . '\n'); + + class_name_posix = 'alnum' | 'alpha' | 'blank' | + 'cntrl' | 'digit' | 'graph' | + 'lower' | 'print' | 'punct' | + 'space' | 'upper' | 'xdigit' | + 'word' | 'ascii'; + + class_posix = ('[:' . '^'? . class_name_posix . ':]'); + + + # these are not supported in ruby, and need verification + collating_sequence = '[.' . (alpha | [\-])+ . '.]'; + character_equivalent = '[=' . alpha . '=]'; + + line_anchor = beginning_of_line | end_of_line; + anchor_char = [AbBzZG]; + + escaped_ascii = [abefnrtv]; + octal_sequence = [0-7]{1,3}; + + hex_sequence = 'x' . xdigit{1,2}; + hex_sequence_err = 'x' . [^0-9a-fA-F{]; + + codepoint_single = 'u' . xdigit{4}; + codepoint_list = 'u{' . xdigit{1,6} . (space . xdigit{1,6})* . '}'; + codepoint_sequence = codepoint_single | codepoint_list; + + control_sequence = ('c' | 'C-') . (backslash . 'M-')? . backslash? . any; + + meta_sequence = 'M-' . (backslash . ('c' | 'C-'))? . backslash? . any; + + zero_or_one = '?' | '??' | '?+'; + zero_or_more = '*' | '*?' | '*+'; + one_or_more = '+' | '+?' | '++'; + + quantifier_greedy = '?' | '*' | '+'; + quantifier_reluctant = '??' | '*?' | '+?'; + quantifier_possessive = '?+' | '*+' | '++'; + quantifier_mode = '?' | '+'; + + quantifier_interval = range_open . (digit+)? . ','? . (digit+)? . + range_close . quantifier_mode?; + + quantifiers = quantifier_greedy | quantifier_reluctant | + quantifier_possessive | quantifier_interval; + + + conditional = '(?('; + + group_comment = '?#' . [^)]* . group_close; + + group_atomic = '?>'; + group_passive = '?:'; + group_absence = '?~'; + + assertion_lookahead = '?='; + assertion_nlookahead = '?!'; + assertion_lookbehind = '?<='; + assertion_nlookbehind = '?~]+ . ':'? ) ?; + + group_ref = [gk]; + group_name_char = (alnum | '_'); + group_name_id = (group_name_char . (group_name_char+)?)?; + group_number = '-'? . [1-9] . ([0-9]+)?; + group_level = [+\-] . [0-9]+; + + group_name = ('<' . group_name_id . '>') | ("'" . group_name_id . "'"); + group_lookup = group_name | group_number; + + group_named = ('?' . group_name ); + + group_name_ref = group_ref . (('<' . group_name_id . group_level? '>') | + ("'" . group_name_id . group_level? "'")); + + group_number_ref = group_ref . (('<' . group_number . group_level? '>') | + ("'" . group_number . group_level? "'")); + + group_type = group_atomic | group_passive | group_absence | group_named; + + keep_mark = 'K'; + + assertion_type = assertion_lookahead | assertion_nlookahead | + assertion_lookbehind | assertion_nlookbehind; + + # characters that 'break' a literal + meta_char = dot | backslash | alternation | + curlies | parantheses | brackets | + line_anchor | quantifier_greedy; + + ascii_print = ((0x20..0x7e) - meta_char); + ascii_nonprint = (0x01..0x1f | 0x7f); + + utf8_2_byte = (0xc2..0xdf 0x80..0xbf); + utf8_3_byte = (0xe0..0xef 0x80..0xbf 0x80..0xbf); + utf8_4_byte = (0xf0..0xf4 0x80..0xbf 0x80..0xbf 0x80..0xbf); + + non_literal_escape = char_type_char | anchor_char | escaped_ascii | + group_ref | keep_mark | [xucCM]; + + non_set_escape = (anchor_char - 'b') | group_ref | keep_mark | + multi_codepoint_char_type | [0-9cCM]; + + # EOF error, used where it can be detected + action premature_end_error { + text = ts ? copy(data, ts-1..-1) : data.pack('c*') + raise PrematureEndError.new( text ) + } + + # Invalid sequence error, used from sequences, like escapes and sets + action invalid_sequence_error { + text = ts ? copy(data, ts-1..-1) : data.pack('c*') + validation_error(:sequence, 'sequence', text) + } + + # group (nesting) and set open/close actions + action group_opened { self.group_depth = group_depth + 1 } + action group_closed { self.group_depth = group_depth - 1 } + action set_opened { self.set_depth = set_depth + 1 } + action set_closed { self.set_depth = set_depth - 1 } + + # Character set scanner, continues consuming characters until it meets the + # closing bracket of the set. + # -------------------------------------------------------------------------- + character_set := |* + set_close > (set_meta, 2) @set_closed { + emit(:set, :close, *text(data, ts, te)) + if in_set? + fret; + else + fgoto main; + end + }; + + '-]' @set_closed { # special case, emits two tokens + emit(:literal, :literal, copy(data, ts..te-2), ts, te - 1) + emit(:set, :close, copy(data, ts+1..te-1), ts + 1, te) + if in_set? + fret; + else + fgoto main; + end + }; + + '-&&' { # special case, emits two tokens + emit(:literal, :literal, '-', ts, te) + emit(:set, :intersection, '&&', ts, te) + }; + + '^' { + text = text(data, ts, te).first + if tokens.last[1] == :open + emit(:set, :negate, text, ts, te) + else + emit(:literal, :literal, text, ts, te) + end + }; + + '-' { + text = text(data, ts, te).first + # ranges cant start with a subset or intersection/negation/range operator + if tokens.last[0] == :set + emit(:literal, :literal, text, ts, te) + else + emit(:set, :range, text, ts, te) + end + }; + + # Unlike ranges, intersections can start or end at set boundaries, whereupon + # they match nothing: r = /[a&&]/; [r =~ ?a, r =~ ?&] # => [nil, nil] + '&&' { + emit(:set, :intersection, *text(data, ts, te)) + }; + + backslash { + fcall set_escape_sequence; + }; + + set_open >(open_bracket, 1) >set_opened { + emit(:set, :open, *text(data, ts, te)) + fcall character_set; + }; + + class_posix >(open_bracket, 1) @set_closed @eof(premature_end_error) { + text = text(data, ts, te).first + + type = :posixclass + class_name = text[2..-3] + if class_name[0].chr == '^' + class_name = class_name[1..-1] + type = :nonposixclass + end + + emit(type, class_name.to_sym, text, ts, te) + }; + + collating_sequence >(open_bracket, 1) @set_closed @eof(premature_end_error) { + emit(:set, :collation, *text(data, ts, te)) + }; + + character_equivalent >(open_bracket, 1) @set_closed @eof(premature_end_error) { + emit(:set, :equivalent, *text(data, ts, te)) + }; + + meta_char > (set_meta, 1) { + emit(:literal, :literal, *text(data, ts, te)) + }; + + any | + ascii_nonprint | + utf8_2_byte | + utf8_3_byte | + utf8_4_byte { + char, *rest = *text(data, ts, te) + char.force_encoding('utf-8') if char.respond_to?(:force_encoding) + emit(:literal, :literal, char, *rest) + }; + *|; + + # set escapes scanner + # -------------------------------------------------------------------------- + set_escape_sequence := |* + non_set_escape > (escaped_set_alpha, 2) { + emit(:escape, :literal, *text(data, ts, te, 1)) + fret; + }; + + any > (escaped_set_alpha, 1) { + fhold; + fnext character_set; + fcall escape_sequence; + }; + *|; + + + # escape sequence scanner + # -------------------------------------------------------------------------- + escape_sequence := |* + [1-9] { + text = text(data, ts, te, 1).first + emit(:backref, :number, text, ts-1, te) + fret; + }; + + octal_sequence { + emit(:escape, :octal, *text(data, ts, te, 1)) + fret; + }; + + meta_char { + case text = text(data, ts, te, 1).first + when '\.'; emit(:escape, :dot, text, ts-1, te) + when '\|'; emit(:escape, :alternation, text, ts-1, te) + when '\^'; emit(:escape, :bol, text, ts-1, te) + when '\$'; emit(:escape, :eol, text, ts-1, te) + when '\?'; emit(:escape, :zero_or_one, text, ts-1, te) + when '\*'; emit(:escape, :zero_or_more, text, ts-1, te) + when '\+'; emit(:escape, :one_or_more, text, ts-1, te) + when '\('; emit(:escape, :group_open, text, ts-1, te) + when '\)'; emit(:escape, :group_close, text, ts-1, te) + when '\{'; emit(:escape, :interval_open, text, ts-1, te) + when '\}'; emit(:escape, :interval_close, text, ts-1, te) + when '\['; emit(:escape, :set_open, text, ts-1, te) + when '\]'; emit(:escape, :set_close, text, ts-1, te) + when "\\\\"; + emit(:escape, :backslash, text, ts-1, te) + end + fret; + }; + + escaped_ascii > (escaped_alpha, 7) { + # \b is emitted as backspace only when inside a character set, otherwise + # it is a word boundary anchor. A syntax might "normalize" it if needed. + case text = text(data, ts, te, 1).first + when '\a'; emit(:escape, :bell, text, ts-1, te) + when '\b'; emit(:escape, :backspace, text, ts-1, te) + when '\e'; emit(:escape, :escape, text, ts-1, te) + when '\f'; emit(:escape, :form_feed, text, ts-1, te) + when '\n'; emit(:escape, :newline, text, ts-1, te) + when '\r'; emit(:escape, :carriage, text, ts-1, te) + when '\t'; emit(:escape, :tab, text, ts-1, te) + when '\v'; emit(:escape, :vertical_tab, text, ts-1, te) + end + fret; + }; + + codepoint_sequence > (escaped_alpha, 6) $eof(premature_end_error) { + text = text(data, ts, te, 1).first + if text[2].chr == '{' + emit(:escape, :codepoint_list, text, ts-1, te) + else + emit(:escape, :codepoint, text, ts-1, te) + end + fret; + }; + + hex_sequence > (escaped_alpha, 5) $eof(premature_end_error) { + emit(:escape, :hex, *text(data, ts, te, 1)) + fret; + }; + + hex_sequence_err @invalid_sequence_error { + fret; + }; + + control_sequence >(escaped_alpha, 4) $eof(premature_end_error) { + emit_meta_control_sequence(data, ts, te, :control) + fret; + }; + + meta_sequence >(backslashed, 3) $eof(premature_end_error) { + emit_meta_control_sequence(data, ts, te, :meta_sequence) + fret; + }; + + char_type_char > (escaped_alpha, 2) { + fhold; + fnext *(in_set? ? fentry(character_set) : fentry(main)); + fcall char_type; + }; + + property_char > (escaped_alpha, 2) { + fhold; + fnext *(in_set? ? fentry(character_set) : fentry(main)); + fcall unicode_property; + }; + + (any -- non_literal_escape) > (escaped_alpha, 1) { + emit(:escape, :literal, *text(data, ts, te, 1)) + fret; + }; + *|; + + + # conditional expressions scanner + # -------------------------------------------------------------------------- + conditional_expression := |* + group_lookup . ')' { + text = text(data, ts, te-1).first + emit(:conditional, :condition, text, ts, te-1) + emit(:conditional, :condition_close, ')', te-1, te) + }; + + any { + fhold; + fcall main; + }; + *|; + + + # Main scanner + # -------------------------------------------------------------------------- + main := |* + + # Meta characters + # ------------------------------------------------------------------------ + dot { + emit(:meta, :dot, *text(data, ts, te)) + }; + + alternation { + if conditional_stack.last == group_depth + emit(:conditional, :separator, *text(data, ts, te)) + else + emit(:meta, :alternation, *text(data, ts, te)) + end + }; + + # Anchors + # ------------------------------------------------------------------------ + beginning_of_line { + emit(:anchor, :bol, *text(data, ts, te)) + }; + + end_of_line { + emit(:anchor, :eol, *text(data, ts, te)) + }; + + backslash . keep_mark > (backslashed, 4) { + emit(:keep, :mark, *text(data, ts, te)) + }; + + backslash . anchor_char > (backslashed, 3) { + case text = text(data, ts, te).first + when '\\A'; emit(:anchor, :bos, text, ts, te) + when '\\z'; emit(:anchor, :eos, text, ts, te) + when '\\Z'; emit(:anchor, :eos_ob_eol, text, ts, te) + when '\\b'; emit(:anchor, :word_boundary, text, ts, te) + when '\\B'; emit(:anchor, :nonword_boundary, text, ts, te) + when '\\G'; emit(:anchor, :match_start, text, ts, te) + end + }; + + # Character sets + # ------------------------------------------------------------------------ + set_open >set_opened { + emit(:set, :open, *text(data, ts, te)) + fcall character_set; + }; + + + # Conditional expression + # (?(condition)Y|N) conditional expression + # ------------------------------------------------------------------------ + conditional { + text = text(data, ts, te).first + + conditional_stack << group_depth + + emit(:conditional, :open, text[0..-2], ts, te-1) + emit(:conditional, :condition_open, '(', te-1, te) + fcall conditional_expression; + }; + + + # (?#...) comments: parsed as a single expression, without introducing a + # new nesting level. Comments may not include parentheses, escaped or not. + # special case for close, action performed on all transitions to get the + # correct closing count. + # ------------------------------------------------------------------------ + group_open . group_comment $group_closed { + emit(:group, :comment, *text(data, ts, te)) + }; + + # Expression options: + # (?imxdau-imx) option on/off + # i: ignore case + # m: multi-line (dot(.) match newline) + # x: extended form + # d: default class rules (1.9 compatible) + # a: ASCII class rules (\s, \w, etc.) + # u: Unicode class rules (\s, \w, etc.) + # + # (?imxdau-imx:subexp) option on/off for subexp + # ------------------------------------------------------------------------ + group_open . group_options >group_opened { + text = text(data, ts, te).first + if text[2..-1] =~ /([^\-mixdau:]|^$)|-.*([dau])/ + raise InvalidGroupOption.new($1 || "-#{$2}", text) + end + emit_options(text, ts, te) + }; + + # Assertions + # (?=subexp) look-ahead + # (?!subexp) negative look-ahead + # (?<=subexp) look-behind + # (?group_opened { + case text = text(data, ts, te).first + when '(?='; emit(:assertion, :lookahead, text, ts, te) + when '(?!'; emit(:assertion, :nlookahead, text, ts, te) + when '(?<='; emit(:assertion, :lookbehind, text, ts, te) + when '(?subexp) atomic group, don't backtrack in subexp. + # (?~subexp) absence group, matches anything that is not subexp + # (?subexp) named group + # (?'name'subexp) named group (single quoted version) + # (subexp) captured group + # ------------------------------------------------------------------------ + group_open . group_type >group_opened { + case text = text(data, ts, te).first + when '(?:'; emit(:group, :passive, text, ts, te) + when '(?>'; emit(:group, :atomic, text, ts, te) + when '(?~'; emit(:group, :absence, text, ts, te) + + when /^\(\?(?:<>|'')/ + validation_error(:group, 'named group', 'name is empty') + + when /^\(\?<\w*>/ + emit(:group, :named_ab, text, ts, te) + + when /^\(\?'\w*'/ + emit(:group, :named_sq, text, ts, te) + + end + }; + + group_open @group_opened { + text = text(data, ts, te).first + emit(:group, :capture, text, ts, te) + }; + + group_close @group_closed { + if conditional_stack.last == group_depth + 1 + conditional_stack.pop + emit(:conditional, :close, *text(data, ts, te)) + else + if spacing_stack.length > 1 && + spacing_stack.last[:depth] == group_depth + 1 + spacing_stack.pop + self.free_spacing = spacing_stack.last[:free_spacing] + end + + emit(:group, :close, *text(data, ts, te)) + end + }; + + + # Group backreference, named and numbered + # ------------------------------------------------------------------------ + backslash . (group_name_ref | group_number_ref) > (backslashed, 4) { + case text = text(data, ts, te).first + when /^\\([gk])(<>|'')/ # angle brackets + validation_error(:backref, 'ref/call', 'ref ID is empty') + + when /^\\([gk])<[^\d+-]\w*>/ # angle-brackets + if $1 == 'k' + emit(:backref, :name_ref_ab, text, ts, te) + else + emit(:backref, :name_call_ab, text, ts, te) + end + + when /^\\([gk])'[^\d+-]\w*'/ #single quotes + if $1 == 'k' + emit(:backref, :name_ref_sq, text, ts, te) + else + emit(:backref, :name_call_sq, text, ts, te) + end + + when /^\\([gk])<\d+>/ # angle-brackets + if $1 == 'k' + emit(:backref, :number_ref_ab, text, ts, te) + else + emit(:backref, :number_call_ab, text, ts, te) + end + + when /^\\([gk])'\d+'/ # single quotes + if $1 == 'k' + emit(:backref, :number_ref_sq, text, ts, te) + else + emit(:backref, :number_call_sq, text, ts, te) + end + + when /^\\(?:g<\+|g<-|(k)<-)\d+>/ # angle-brackets + if $1 == 'k' + emit(:backref, :number_rel_ref_ab, text, ts, te) + else + emit(:backref, :number_rel_call_ab, text, ts, te) + end + + when /^\\(?:g'\+|g'-|(k)'-)\d+'/ # single quotes + if $1 == 'k' + emit(:backref, :number_rel_ref_sq, text, ts, te) + else + emit(:backref, :number_rel_call_sq, text, ts, te) + end + + when /^\\k<[^\d+\-]\w*[+\-]\d+>/ # angle-brackets + emit(:backref, :name_recursion_ref_ab, text, ts, te) + + when /^\\k'[^\d+\-]\w*[+\-]\d+'/ # single-quotes + emit(:backref, :name_recursion_ref_sq, text, ts, te) + + when /^\\([gk])<[+\-]?\d+[+\-]\d+>/ # angle-brackets + emit(:backref, :number_recursion_ref_ab, text, ts, te) + + when /^\\([gk])'[+\-]?\d+[+\-]\d+'/ # single-quotes + emit(:backref, :number_recursion_ref_sq, text, ts, te) + + end + }; + + + # Quantifiers + # ------------------------------------------------------------------------ + zero_or_one { + case text = text(data, ts, te).first + when '?' ; emit(:quantifier, :zero_or_one, text, ts, te) + when '??'; emit(:quantifier, :zero_or_one_reluctant, text, ts, te) + when '?+'; emit(:quantifier, :zero_or_one_possessive, text, ts, te) + end + }; + + zero_or_more { + case text = text(data, ts, te).first + when '*' ; emit(:quantifier, :zero_or_more, text, ts, te) + when '*?'; emit(:quantifier, :zero_or_more_reluctant, text, ts, te) + when '*+'; emit(:quantifier, :zero_or_more_possessive, text, ts, te) + end + }; + + one_or_more { + case text = text(data, ts, te).first + when '+' ; emit(:quantifier, :one_or_more, text, ts, te) + when '+?'; emit(:quantifier, :one_or_more_reluctant, text, ts, te) + when '++'; emit(:quantifier, :one_or_more_possessive, text, ts, te) + end + }; + + quantifier_interval @err(premature_end_error) { + emit(:quantifier, :interval, *text(data, ts, te)) + }; + + # Escaped sequences + # ------------------------------------------------------------------------ + backslash > (backslashed, 1) { + fcall escape_sequence; + }; + + comment { + if free_spacing + emit(:free_space, :comment, *text(data, ts, te)) + else + append_literal(data, ts, te) + end + }; + + space+ { + if free_spacing + emit(:free_space, :whitespace, *text(data, ts, te)) + else + append_literal(data, ts, te) + end + }; + + # Literal: any run of ASCII (pritable or non-printable), and/or UTF-8, + # except meta characters. + # ------------------------------------------------------------------------ + (ascii_print -- space)+ | + ascii_nonprint+ | + utf8_2_byte+ | + utf8_3_byte+ | + utf8_4_byte+ { + append_literal(data, ts, te) + }; + + *|; +}%% + +# THIS IS A GENERATED FILE, DO NOT EDIT DIRECTLY +# This file was generated from lib/regexp_parser/scanner/scanner.rl + +class Regexp::Scanner + # General scanner error (catch all) + class ScannerError < StandardError; end + + # Base for all scanner validation errors + class ValidationError < StandardError + def initialize(reason) + super reason + end + end + + # Unexpected end of pattern + class PrematureEndError < ScannerError + def initialize(where = '') + super "Premature end of pattern at #{where}" + end + end + + # Invalid sequence format. Used for escape sequences, mainly. + class InvalidSequenceError < ValidationError + def initialize(what = 'sequence', where = '') + super "Invalid #{what} at #{where}" + end + end + + # Invalid group. Used for named groups. + class InvalidGroupError < ValidationError + def initialize(what, reason) + super "Invalid #{what}, #{reason}." + end + end + + # Invalid groupOption. Used for inline options. + class InvalidGroupOption < ValidationError + def initialize(option, text) + super "Invalid group option #{option} in #{text}" + end + end + + # Invalid back reference. Used for name a number refs/calls. + class InvalidBackrefError < ValidationError + def initialize(what, reason) + super "Invalid back reference #{what}, #{reason}" + end + end + + # The property name was not recognized by the scanner. + class UnknownUnicodePropertyError < ValidationError + def initialize(name) + super "Unknown unicode character property name #{name}" + end + end + + # Scans the given regular expression text, or Regexp object and collects the + # emitted token into an array that gets returned at the end. If a block is + # given, it gets called for each emitted token. + # + # This method may raise errors if a syntax error is encountered. + # -------------------------------------------------------------------------- + def self.scan(input_object, &block) + new.scan(input_object, &block) + end + + def scan(input_object, &block) + self.literal = nil + stack = [] + + if input_object.is_a?(Regexp) + input = input_object.source + self.free_spacing = (input_object.options & Regexp::EXTENDED != 0) + else + input = input_object + self.free_spacing = false + end + self.spacing_stack = [{:free_spacing => free_spacing, :depth => 0}] + + data = input.unpack("c*") if input.is_a?(String) + eof = data.length + + self.tokens = [] + self.block = block_given? ? block : nil + + self.set_depth = 0 + self.group_depth = 0 + self.conditional_stack = [] + + %% write data; + %% write init; + %% write exec; + + # to avoid "warning: assigned but unused variable - testEof" + testEof = testEof + + if cs == re_scanner_error + text = ts ? copy(data, ts-1..-1) : data.pack('c*') + raise ScannerError.new("Scan error at '#{text}'") + end + + raise PrematureEndError.new("(missing group closing paranthesis) "+ + "[#{group_depth}]") if in_group? + raise PrematureEndError.new("(missing set closing bracket) "+ + "[#{set_depth}]") if in_set? + + # when the entire expression is a literal run + emit_literal if literal + + tokens + end + + # lazy-load property maps when first needed + require 'yaml' + PROP_MAPS_DIR = File.expand_path('../scanner/properties', __FILE__) + + def self.short_prop_map + @short_prop_map ||= YAML.load_file("#{PROP_MAPS_DIR}/short.yml") + end + + def self.long_prop_map + @long_prop_map ||= YAML.load_file("#{PROP_MAPS_DIR}/long.yml") + end + + # Emits an array with the details of the scanned pattern + def emit(type, token, text, ts, te) + #puts "EMIT: type: #{type}, token: #{token}, text: #{text}, ts: #{ts}, te: #{te}" + + emit_literal if literal + + if block + block.call type, token, text, ts, te + end + + tokens << [type, token, text, ts, te] + end + + private + + attr_accessor :tokens, :literal, :block, :free_spacing, :spacing_stack, + :group_depth, :set_depth, :conditional_stack + + def in_group? + group_depth > 0 + end + + def in_set? + set_depth > 0 + end + + # Copy from ts to te from data as text + def copy(data, range) + data[range].pack('c*') + end + + # Copy from ts to te from data as text, returning an array with the text + # and the offsets used to copy it. + def text(data, ts, te, soff = 0) + [copy(data, ts-soff..te-1), ts-soff, te] + end + + # Appends one or more characters to the literal buffer, to be emitted later + # by a call to emit_literal. Contents can be a mix of ASCII and UTF-8. + def append_literal(data, ts, te) + self.literal = literal || [] + literal << text(data, ts, te) + end + + # Emits the literal run collected by calls to the append_literal method, + # using the total start (ts) and end (te) offsets of the run. + def emit_literal + ts, te = literal.first[1], literal.last[2] + text = literal.map {|t| t[0]}.join + + text.force_encoding('utf-8') if text.respond_to?(:force_encoding) + + self.literal = nil + emit(:literal, :literal, text, ts, te) + end + + def emit_options(text, ts, te) + token = nil + + # Ruby allows things like '(?-xxxx)' or '(?xx-xx--xx-:abc)'. + text =~ /\(\?([mixdau]*)(-(?:[mix]*))*(:)?/ + positive, negative, group_local = $1, $2, $3 + + if positive.include?('x') + self.free_spacing = true + end + + # If the x appears in both, treat it like ruby does, the second cancels + # the first. + if negative && negative.include?('x') + self.free_spacing = false + end + + if group_local + spacing_stack << {:free_spacing => free_spacing, :depth => group_depth} + token = :options + else + # switch for parent group level + spacing_stack.last[:free_spacing] = free_spacing + token = :options_switch + end + + emit(:group, token, text, ts, te) + end + + def emit_meta_control_sequence(data, ts, te, token) + if data.last < 0x00 || data.last > 0x7F + validation_error(:sequence, 'escape', token.to_s) + end + emit(:escape, token, *text(data, ts, te, 1)) + end + + # Centralizes and unifies the handling of validation related + # errors. + def validation_error(type, what, reason) + case type + when :group + error = InvalidGroupError.new(what, reason) + when :backref + error = InvalidBackrefError.new(what, reason) + when :sequence + error = InvalidSequenceError.new(what, reason) + end + + raise error # unless @@config.validation_ignore + end +end # module Regexp::Scanner diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax.rb new file mode 100644 index 00000000..3d1c3f08 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax.rb @@ -0,0 +1,9 @@ +require File.expand_path('../syntax/tokens', __FILE__) +require File.expand_path('../syntax/base', __FILE__) +require File.expand_path('../syntax/any', __FILE__) +require File.expand_path('../syntax/version_lookup', __FILE__) +require File.expand_path('../syntax/versions', __FILE__) + +module Regexp::Syntax + class SyntaxError < StandardError; end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/any.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/any.rb new file mode 100644 index 00000000..61c4cedf --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/any.rb @@ -0,0 +1,15 @@ +module Regexp::Syntax + + # A syntax that always returns true, passing all tokens as implemented. This + # is useful during development, testing, and should be useful for some types + # of transformations as well. + class Any < Base + def initialize + @implements = { :* => [:*] } + end + + def implements?(type, token) true end + def implements!(type, token) true end + end + +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/base.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/base.rb new file mode 100644 index 00000000..b35dffcf --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/base.rb @@ -0,0 +1,95 @@ +require 'set' + +module Regexp::Syntax + class NotImplementedError < SyntaxError + def initialize(syntax, type, token) + super "#{syntax.class.name} does not implement: [#{type}:#{token}]" + end + end + + # A lookup map of supported types and tokens in a given syntax + class Base + include Regexp::Syntax::Token + + def initialize + @implements = {} + + implements Token::Literal::Type, Token::Literal::All + implements Token::FreeSpace::Type, Token::FreeSpace::All + end + + def features + @implements + end + + def implementations(type) + @implements[type] ||= Set.new + end + + def implements(type, tokens) + implementations(type).merge(Array(tokens)) + end + + def excludes(type, tokens) + implementations(type).subtract(Array(tokens)) + end + + def implements?(type, token) + implementations(type).include?(token) + end + alias :check? :implements? + + def implements!(type, token) + raise NotImplementedError.new(self, type, token) unless + implements?(type, token) + end + alias :check! :implements! + + def normalize(type, token) + case type + when :group + normalize_group(type, token) + when :backref + normalize_backref(type, token) + else + [type, token] + end + end + + def normalize_group(type, token) + case token + when :named_ab, :named_sq + [:group, :named] + else + [type, token] + end + end + + def normalize_backref(type, token) + case token + when :name_ref_ab, :name_ref_sq + [:backref, :name_ref] + when :name_call_ab, :name_call_sq + [:backref, :name_call] + when :name_recursion_ref_ab, :name_recursion_ref_sq + [:backref, :name_recursion_ref] + when :number_ref_ab, :number_ref_sq + [:backref, :number_ref] + when :number_call_ab, :number_call_sq + [:backref, :number_call] + when :number_rel_ref_ab, :number_rel_ref_sq + [:backref, :number_rel_ref] + when :number_rel_call_ab, :number_rel_call_sq + [:backref, :number_rel_call] + when :number_recursion_ref_ab, :number_recursion_ref_sq + [:backref, :number_recursion_ref] + else + [type, token] + end + end + + def self.inspect + "#{super} (feature set of #{ancestors[1].to_s.split('::').last})" + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens.rb new file mode 100644 index 00000000..a9d852df --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens.rb @@ -0,0 +1,45 @@ +# Define the base module and the simplest of tokens. +module Regexp::Syntax + module Token + Map = {} + + module Literal + All = [:literal] + Type = :literal + end + + module FreeSpace + All = [:comment, :whitespace] + Type = :free_space + end + + Map[FreeSpace::Type] = FreeSpace::All + Map[Literal::Type] = Literal::All + end +end + + +# Load all the token files, they will populate the Map constant. +require 'regexp_parser/syntax/tokens/anchor' +require 'regexp_parser/syntax/tokens/assertion' +require 'regexp_parser/syntax/tokens/backref' +require 'regexp_parser/syntax/tokens/posix_class' +require 'regexp_parser/syntax/tokens/character_set' +require 'regexp_parser/syntax/tokens/character_type' +require 'regexp_parser/syntax/tokens/conditional' +require 'regexp_parser/syntax/tokens/escape' +require 'regexp_parser/syntax/tokens/group' +require 'regexp_parser/syntax/tokens/keep' +require 'regexp_parser/syntax/tokens/meta' +require 'regexp_parser/syntax/tokens/quantifier' +require 'regexp_parser/syntax/tokens/unicode_property' + + +# After loading all the tokens the map is full. Extract all tokens and types +# into the All and Types constants. +module Regexp::Syntax + module Token + All = Map.values.flatten.uniq.sort.freeze + Types = Map.keys.freeze + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/anchor.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/anchor.rb new file mode 100644 index 00000000..272bae88 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/anchor.rb @@ -0,0 +1,15 @@ +module Regexp::Syntax + module Token + module Anchor + Basic = [:bol, :eol] + Extended = Basic + [:word_boundary, :nonword_boundary] + String = [:bos, :eos, :eos_ob_eol] + MatchStart = [:match_start] + + All = Extended + String + MatchStart + Type = :anchor + end + + Map[Anchor::Type] = Anchor::All + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/assertion.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/assertion.rb new file mode 100644 index 00000000..cb30216e --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/assertion.rb @@ -0,0 +1,13 @@ +module Regexp::Syntax + module Token + module Assertion + Lookahead = [:lookahead, :nlookahead] + Lookbehind = [:lookbehind, :nlookbehind] + + All = Lookahead + Lookbehind + Type = :assertion + end + + Map[Assertion::Type] = Assertion::All + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/backref.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/backref.rb new file mode 100644 index 00000000..f548b4c1 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/backref.rb @@ -0,0 +1,24 @@ +module Regexp::Syntax + module Token + module Backreference + Name = [:name_ref] + Number = [:number, :number_ref, :number_rel_ref] + + RecursionLevel = [:name_recursion_ref, :number_recursion_ref] + + All = Name + Number + RecursionLevel + Type = :backref + end + + # Type is the same as Backreference so keeping it here, for now. + module SubexpressionCall + Name = [:name_call] + Number = [:number_call, :number_rel_call] + + All = Name + Number + end + + Map[Backreference::Type] = Backreference::All + + SubexpressionCall::All + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/character_set.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/character_set.rb new file mode 100644 index 00000000..cb106e74 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/character_set.rb @@ -0,0 +1,13 @@ +module Regexp::Syntax + module Token + module CharacterSet + Basic = [:open, :close, :negate, :range] + Extended = Basic + [:intersection] + + All = Extended + Type = :set + end + + Map[CharacterSet::Type] = CharacterSet::All + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/character_type.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/character_type.rb new file mode 100644 index 00000000..2cf74830 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/character_type.rb @@ -0,0 +1,16 @@ +module Regexp::Syntax + module Token + module CharacterType + Basic = [] + Extended = [:digit, :nondigit, :space, :nonspace, :word, :nonword] + Hex = [:hex, :nonhex] + + Clustered = [:linebreak, :xgrapheme] + + All = Basic + Extended + Hex + Clustered + Type = :type + end + + Map[CharacterType::Type] = CharacterType::All + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/conditional.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/conditional.rb new file mode 100644 index 00000000..bb0b2bdf --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/conditional.rb @@ -0,0 +1,16 @@ +module Regexp::Syntax + module Token + module Conditional + Delimiters = [:open, :close] + + Condition = [:condition_open, :condition, :condition_close] + Separator = [:separator] + + All = Conditional::Delimiters + Conditional::Condition + Conditional::Separator + + Type = :conditional + end + + Map[Conditional::Type] = Conditional::All + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/escape.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/escape.rb new file mode 100644 index 00000000..dae3d2e6 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/escape.rb @@ -0,0 +1,30 @@ +module Regexp::Syntax + module Token + module Escape + Basic = [:backslash, :literal] + + Control = [:control, :meta_sequence] + + ASCII = [:bell, :backspace, :escape, :form_feed, :newline, :carriage, + :tab, :vertical_tab] + + Unicode = [:codepoint, :codepoint_list] + + Meta = [:dot, :alternation, + :zero_or_one, :zero_or_more, :one_or_more, + :bol, :eol, + :group_open, :group_close, + :interval_open, :interval_close, + :set_open, :set_close] + + Hex = [:hex] + + Octal = [:octal] + + All = Basic + Control + ASCII + Unicode + Meta + Hex + Octal + Type = :escape + end + + Map[Escape::Type] = Escape::All + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/group.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/group.rb new file mode 100644 index 00000000..c1ec7115 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/group.rb @@ -0,0 +1,23 @@ +module Regexp::Syntax + module Token + module Group + Basic = [:capture, :close] + Extended = Basic + [:options, :options_switch] + + Named = [:named] + Atomic = [:atomic] + Passive = [:passive] + Comment = [:comment] + + V1_8_6 = Group::Extended + Group::Named + Group::Atomic + + Group::Passive + Group::Comment + + V2_4_1 = [:absence] + + All = V1_8_6 + V2_4_1 + Type = :group + end + + Map[Group::Type] = Group::All + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/keep.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/keep.rb new file mode 100644 index 00000000..e4ce1c6c --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/keep.rb @@ -0,0 +1,12 @@ +module Regexp::Syntax + module Token + module Keep + Mark = [:mark] + + All = Mark + Type = :keep + end + + Map[Keep::Type] = Keep::All + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/meta.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/meta.rb new file mode 100644 index 00000000..f25690cd --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/meta.rb @@ -0,0 +1,13 @@ +module Regexp::Syntax + module Token + module Meta + Basic = [:dot] + Extended = Basic + [:alternation] + + All = Extended + Type = :meta + end + + Map[Meta::Type] = Meta::All + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/posix_class.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/posix_class.rb new file mode 100644 index 00000000..524d5b3d --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/posix_class.rb @@ -0,0 +1,17 @@ +module Regexp::Syntax + module Token + module PosixClass + Standard = [:alnum, :alpha, :blank, :cntrl, :digit, :graph, + :lower, :print, :punct, :space, :upper, :xdigit] + + Extensions = [:ascii, :word] + + All = Standard + Extensions + Type = :posixclass + NonType = :nonposixclass + end + + Map[PosixClass::Type] = PosixClass::All + Map[PosixClass::NonType] = PosixClass::All + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/quantifier.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/quantifier.rb new file mode 100644 index 00000000..c9a6ffe6 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/quantifier.rb @@ -0,0 +1,35 @@ +module Regexp::Syntax + module Token + module Quantifier + Greedy = [ + :zero_or_one, + :zero_or_more, + :one_or_more + ] + + Reluctant = [ + :zero_or_one_reluctant, + :zero_or_more_reluctant, + :one_or_more_reluctant + ] + + Possessive = [ + :zero_or_one_possessive, + :zero_or_more_possessive, + :one_or_more_possessive + ] + + Interval = [:interval] + IntervalReluctant = [:interval_reluctant] + IntervalPossessive = [:interval_possessive] + + IntervalAll = Interval + IntervalReluctant + + IntervalPossessive + + All = Greedy + Reluctant + Possessive + IntervalAll + Type = :quantifier + end + + Map[Quantifier::Type] = Quantifier::All + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/unicode_property.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/unicode_property.rb new file mode 100644 index 00000000..7a8453c9 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/tokens/unicode_property.rb @@ -0,0 +1,675 @@ +module Regexp::Syntax + module Token + module UnicodeProperty + all = proc { |name| constants.grep(/#{name}/).flat_map(&method(:const_get)) } + + CharType_V1_9_0 = [:alnum, :alpha, :ascii, :blank, :cntrl, :digit, :graph, + :lower, :print, :punct, :space, :upper, :word, :xdigit] + + CharType_V2_5_0 = [:xposixpunct] + + POSIX = [:any, :assigned, :newline] + + module Category + Letter = [:letter, :uppercase_letter, :lowercase_letter, + :titlecase_letter, :modifier_letter, :other_letter] + + Mark = [:mark, :nonspacing_mark, :spacing_mark, + :enclosing_mark] + + Number = [:number, :decimal_number, :letter_number, + :other_number] + + Punctuation = [:punctuation, :connector_punctuation, :dash_punctuation, + :open_punctuation, :close_punctuation, :initial_punctuation, + :final_punctuation, :other_punctuation] + + Symbol = [:symbol, :math_symbol, :currency_symbol, + :modifier_symbol, :other_symbol] + + Separator = [:separator, :space_separator, :line_separator, + :paragraph_separator] + + Codepoint = [:other, :control, :format, + :surrogate, :private_use, :unassigned] + + All = Letter + Mark + Number + Punctuation + + Symbol + Separator + Codepoint + end + + Age_V1_9_3 = [:'age=1.1', :'age=2.0', :'age=2.1', :'age=3.0', :'age=3.1', + :'age=3.2', :'age=4.0', :'age=4.1', :'age=5.0', :'age=5.1', + :'age=5.2', :'age=6.0'] + + Age_V2_0_0 = [:'age=6.1'] + + Age_V2_2_0 = [:'age=6.2', :'age=6.3', :'age=7.0'] + + Age_V2_3_0 = [:'age=8.0'] + + Age_V2_4_0 = [:'age=9.0'] + + Age_V2_5_0 = [:'age=10.0'] + + Age_V2_6_0 = [:'age=11.0'] + + Age_V2_6_2 = [:'age=12.0'] + + Age_V2_6_3 = [:'age=12.1'] + + Age = all[:Age_V] + + Derived_V1_9_0 = [ + :ascii_hex_digit, + :alphabetic, + :cased, + :changes_when_casefolded, + :changes_when_casemapped, + :changes_when_lowercased, + :changes_when_titlecased, + :changes_when_uppercased, + :case_ignorable, + :bidi_control, + :dash, + :deprecated, + :default_ignorable_code_point, + :diacritic, + :extender, + :grapheme_base, + :grapheme_extend, + :grapheme_link, + :hex_digit, + :hyphen, + :id_continue, + :ideographic, + :id_start, + :ids_binary_operator, + :ids_trinary_operator, + :join_control, + :logical_order_exception, + :lowercase, + :math, + :noncharacter_code_point, + :other_alphabetic, + :other_default_ignorable_code_point, + :other_grapheme_extend, + :other_id_continue, + :other_id_start, + :other_lowercase, + :other_math, + :other_uppercase, + :pattern_syntax, + :pattern_white_space, + :quotation_mark, + :radical, + :sentence_terminal, + :soft_dotted, + :terminal_punctuation, + :unified_ideograph, + :uppercase, + :variation_selector, + :white_space, + :xid_start, + :xid_continue, + ] + + Derived_V2_0_0 = [ + :cased_letter, + :combining_mark, + ] + + Derived_V2_4_0 = [ + :prepended_concatenation_mark, + ] + + Derived_V2_5_0 = [ + :regional_indicator + ] + + Derived = all[:Derived_V] + + Script_V1_9_0 = [ + :arabic, + :imperial_aramaic, + :armenian, + :avestan, + :balinese, + :bamum, + :bengali, + :bopomofo, + :braille, + :buginese, + :buhid, + :canadian_aboriginal, + :carian, + :cham, + :cherokee, + :coptic, + :cypriot, + :cyrillic, + :devanagari, + :deseret, + :egyptian_hieroglyphs, + :ethiopic, + :georgian, + :glagolitic, + :gothic, + :greek, + :gujarati, + :gurmukhi, + :hangul, + :han, + :hanunoo, + :hebrew, + :hiragana, + :old_italic, + :javanese, + :kayah_li, + :katakana, + :kharoshthi, + :khmer, + :kannada, + :kaithi, + :tai_tham, + :lao, + :latin, + :lepcha, + :limbu, + :linear_b, + :lisu, + :lycian, + :lydian, + :malayalam, + :mongolian, + :meetei_mayek, + :myanmar, + :nko, + :ogham, + :ol_chiki, + :old_turkic, + :oriya, + :osmanya, + :phags_pa, + :inscriptional_pahlavi, + :phoenician, + :inscriptional_parthian, + :rejang, + :runic, + :samaritan, + :old_south_arabian, + :saurashtra, + :shavian, + :sinhala, + :sundanese, + :syloti_nagri, + :syriac, + :tagbanwa, + :tai_le, + :new_tai_lue, + :tamil, + :tai_viet, + :telugu, + :tifinagh, + :tagalog, + :thaana, + :thai, + :tibetan, + :ugaritic, + :vai, + :old_persian, + :cuneiform, + :yi, + :inherited, + :common, + :unknown + ] + + Script_V1_9_3 = [ + :brahmi, + :batak, + :mandaic + ] + + Script_V2_0_0 = [ + :chakma, + :meroitic_cursive, + :meroitic_hieroglyphs, + :miao, + :sharada, + :sora_sompeng, + :takri, + ] + + Script_V2_2_0 = [ + :caucasian_albanian, + :bassa_vah, + :duployan, + :elbasan, + :grantha, + :pahawh_hmong, + :khojki, + :linear_a, + :mahajani, + :manichaean, + :mende_kikakui, + :modi, + :mro, + :old_north_arabian, + :nabataean, + :palmyrene, + :pau_cin_hau, + :old_permic, + :psalter_pahlavi, + :siddham, + :khudawadi, + :tirhuta, + :warang_citi + ] + + Script_V2_3_0 = [ + :ahom, + :anatolian_hieroglyphs, + :hatran, + :multani, + :old_hungarian, + :signwriting, + ] + + Script_V2_4_0 = [ + :adlam, + :bhaiksuki, + :marchen, + :newa, + :osage, + :tangut, + ] + + Script_V2_5_0 = [ + :masaram_gondi, + :nushu, + :soyombo, + :zanabazar_square, + ] + + Script_V2_6_0 = [ + :dogra, + :gunjala_gondi, + :hanifi_rohingya, + :makasar, + :medefaidrin, + :old_sogdian, + :sogdian, + ] + + Script_V2_6_2 = [ + :egyptian_hieroglyph_format_controls, + :elymaic, + :nandinagari, + :nyiakeng_puachue_hmong, + :ottoman_siyaq_numbers, + :small_kana_extension, + :symbols_and_pictographs_extended_a, + :tamil_supplement, + :wancho, + ] + + Script = all[:Script_V] + + UnicodeBlock_V1_9_0 = [ + :in_alphabetic_presentation_forms, + :in_arabic, + :in_armenian, + :in_arrows, + :in_basic_latin, + :in_bengali, + :in_block_elements, + :in_bopomofo_extended, + :in_bopomofo, + :in_box_drawing, + :in_braille_patterns, + :in_buhid, + :in_cjk_compatibility_forms, + :in_cjk_compatibility_ideographs, + :in_cjk_compatibility, + :in_cjk_radicals_supplement, + :in_cjk_symbols_and_punctuation, + :in_cjk_unified_ideographs_extension_a, + :in_cjk_unified_ideographs, + :in_cherokee, + :in_combining_diacritical_marks_for_symbols, + :in_combining_diacritical_marks, + :in_combining_half_marks, + :in_control_pictures, + :in_currency_symbols, + :in_cyrillic_supplement, + :in_cyrillic, + :in_devanagari, + :in_dingbats, + :in_enclosed_alphanumerics, + :in_enclosed_cjk_letters_and_months, + :in_ethiopic, + :in_general_punctuation, + :in_geometric_shapes, + :in_georgian, + :in_greek_extended, + :in_greek_and_coptic, + :in_gujarati, + :in_gurmukhi, + :in_halfwidth_and_fullwidth_forms, + :in_hangul_compatibility_jamo, + :in_hangul_jamo, + :in_hangul_syllables, + :in_hanunoo, + :in_hebrew, + :in_high_private_use_surrogates, + :in_high_surrogates, + :in_hiragana, + :in_ipa_extensions, + :in_ideographic_description_characters, + :in_kanbun, + :in_kangxi_radicals, + :in_kannada, + :in_katakana_phonetic_extensions, + :in_katakana, + :in_khmer_symbols, + :in_khmer, + :in_lao, + :in_latin_extended_additional, + :in_letterlike_symbols, + :in_limbu, + :in_low_surrogates, + :in_malayalam, + :in_mathematical_operators, + :in_miscellaneous_symbols_and_arrows, + :in_miscellaneous_symbols, + :in_miscellaneous_technical, + :in_mongolian, + :in_myanmar, + :in_number_forms, + :in_ogham, + :in_optical_character_recognition, + :in_oriya, + :in_phonetic_extensions, + :in_private_use_area, + :in_runic, + :in_sinhala, + :in_small_form_variants, + :in_spacing_modifier_letters, + :in_specials, + :in_superscripts_and_subscripts, + :in_supplemental_mathematical_operators, + :in_syriac, + :in_tagalog, + :in_tagbanwa, + :in_tai_le, + :in_tamil, + :in_telugu, + :in_thaana, + :in_thai, + :in_tibetan, + :in_unified_canadian_aboriginal_syllabics, + :in_variation_selectors, + :in_yi_radicals, + :in_yi_syllables, + :in_yijing_hexagram_symbols, + ] + + UnicodeBlock_V2_0_0 = [ + :in_aegean_numbers, + :in_alchemical_symbols, + :in_ancient_greek_musical_notation, + :in_ancient_greek_numbers, + :in_ancient_symbols, + :in_arabic_extended_a, + :in_arabic_mathematical_alphabetic_symbols, + :in_arabic_presentation_forms_a, + :in_arabic_presentation_forms_b, + :in_arabic_supplement, + :in_avestan, + :in_balinese, + :in_bamum, + :in_bamum_supplement, + :in_batak, + :in_brahmi, + :in_buginese, + :in_byzantine_musical_symbols, + :in_cjk_compatibility_ideographs_supplement, + :in_cjk_strokes, + :in_cjk_unified_ideographs_extension_b, + :in_cjk_unified_ideographs_extension_c, + :in_cjk_unified_ideographs_extension_d, + :in_carian, + :in_chakma, + :in_cham, + :in_combining_diacritical_marks_supplement, + :in_common_indic_number_forms, + :in_coptic, + :in_counting_rod_numerals, + :in_cuneiform, + :in_cuneiform_numbers_and_punctuation, + :in_cypriot_syllabary, + :in_cyrillic_extended_a, + :in_cyrillic_extended_b, + :in_deseret, + :in_devanagari_extended, + :in_domino_tiles, + :in_egyptian_hieroglyphs, + :in_emoticons, + :in_enclosed_alphanumeric_supplement, + :in_enclosed_ideographic_supplement, + :in_ethiopic_extended, + :in_ethiopic_extended_a, + :in_ethiopic_supplement, + :in_georgian_supplement, + :in_glagolitic, + :in_gothic, + :in_hangul_jamo_extended_a, + :in_hangul_jamo_extended_b, + :in_imperial_aramaic, + :in_inscriptional_pahlavi, + :in_inscriptional_parthian, + :in_javanese, + :in_kaithi, + :in_kana_supplement, + :in_kayah_li, + :in_kharoshthi, + :in_latin_1_supplement, + :in_latin_extended_a, + :in_latin_extended_b, + :in_latin_extended_c, + :in_latin_extended_d, + :in_lepcha, + :in_linear_b_ideograms, + :in_linear_b_syllabary, + :in_lisu, + :in_lycian, + :in_lydian, + :in_mahjong_tiles, + :in_mandaic, + :in_mathematical_alphanumeric_symbols, + :in_meetei_mayek, + :in_meetei_mayek_extensions, + :in_meroitic_cursive, + :in_meroitic_hieroglyphs, + :in_miao, + :in_miscellaneous_mathematical_symbols_a, + :in_miscellaneous_mathematical_symbols_b, + :in_miscellaneous_symbols_and_pictographs, + :in_modifier_tone_letters, + :in_musical_symbols, + :in_myanmar_extended_a, + :in_nko, + :in_new_tai_lue, + :in_no_block, + :in_ol_chiki, + :in_old_italic, + :in_old_persian, + :in_old_south_arabian, + :in_old_turkic, + :in_osmanya, + :in_phags_pa, + :in_phaistos_disc, + :in_phoenician, + :in_phonetic_extensions_supplement, + :in_playing_cards, + :in_rejang, + :in_rumi_numeral_symbols, + :in_samaritan, + :in_saurashtra, + :in_sharada, + :in_shavian, + :in_sora_sompeng, + :in_sundanese, + :in_sundanese_supplement, + :in_supplemental_arrows_a, + :in_supplemental_arrows_b, + :in_supplemental_punctuation, + :in_supplementary_private_use_area_a, + :in_supplementary_private_use_area_b, + :in_syloti_nagri, + :in_tags, + :in_tai_tham, + :in_tai_viet, + :in_tai_xuan_jing_symbols, + :in_takri, + :in_tifinagh, + :in_transport_and_map_symbols, + :in_ugaritic, + :in_unified_canadian_aboriginal_syllabics_extended, + :in_vai, + :in_variation_selectors_supplement, + :in_vedic_extensions, + :in_vertical_forms, + ] + + UnicodeBlock_V2_2_0 = [ + :in_bassa_vah, + :in_caucasian_albanian, + :in_combining_diacritical_marks_extended, + :in_coptic_epact_numbers, + :in_duployan, + :in_elbasan, + :in_geometric_shapes_extended, + :in_grantha, + :in_khojki, + :in_khudawadi, + :in_latin_extended_e, + :in_linear_a, + :in_mahajani, + :in_manichaean, + :in_mende_kikakui, + :in_modi, + :in_mro, + :in_myanmar_extended_b, + :in_nabataean, + :in_old_north_arabian, + :in_old_permic, + :in_ornamental_dingbats, + :in_pahawh_hmong, + :in_palmyrene, + :in_pau_cin_hau, + :in_psalter_pahlavi, + :in_shorthand_format_controls, + :in_siddham, + :in_sinhala_archaic_numbers, + :in_supplemental_arrows_c, + :in_tirhuta, + :in_warang_citi, + ] + + UnicodeBlock_V2_3_0 = [ + :in_ahom, + :in_anatolian_hieroglyphs, + :in_cjk_unified_ideographs_extension_e, + :in_cherokee_supplement, + :in_early_dynastic_cuneiform, + :in_hatran, + :in_multani, + :in_old_hungarian, + :in_supplemental_symbols_and_pictographs, + :in_sutton_signwriting, + ] + + UnicodeBlock_V2_4_0 = [ + :in_adlam, + :in_bhaiksuki, + :in_cyrillic_extended_c, + :in_glagolitic_supplement, + :in_ideographic_symbols_and_punctuation, + :in_marchen, + :in_mongolian_supplement, + :in_newa, + :in_osage, + :in_tangut, + :in_tangut_components, + ] + + UnicodeBlock_V2_5_0 = [ + :in_cjk_unified_ideographs_extension_f, + :in_kana_extended_a, + :in_masaram_gondi, + :in_nushu, + :in_soyombo, + :in_syriac_supplement, + :in_zanabazar_square, + ] + + UnicodeBlock_V2_6_0 = [ + :in_chess_symbols, + :in_dogra, + :in_georgian_extended, + :in_gunjala_gondi, + :in_hanifi_rohingya, + :in_indic_siyaq_numbers, + :in_makasar, + :in_mayan_numerals, + :in_medefaidrin, + :in_old_sogdian, + :in_sogdian, + ] + + UnicodeBlock_V2_6_2 = [ + :in_egyptian_hieroglyph_format_controls, + :in_elymaic, + :in_nandinagari, + :in_nyiakeng_puachue_hmong, + :in_ottoman_siyaq_numbers, + :in_small_kana_extension, + :in_symbols_and_pictographs_extended_a, + :in_tamil_supplement, + :in_wancho, + ] + + UnicodeBlock = all[:UnicodeBlock_V] + + Emoji_V2_5_0 = [ + :emoji, + :emoji_component, + :emoji_modifier, + :emoji_modifier_base, + :emoji_presentation, + ] + + Emoji = all[:Emoji_V] + + V1_9_0 = Category::All + POSIX + all[:V1_9_0] + V1_9_3 = all[:V1_9_3] + V2_0_0 = all[:V2_0_0] + V2_2_0 = all[:V2_2_0] + V2_3_0 = all[:V2_3_0] + V2_4_0 = all[:V2_4_0] + V2_5_0 = all[:V2_5_0] + V2_6_0 = all[:V2_6_0] + V2_6_2 = all[:V2_6_2] + V2_6_3 = all[:V2_6_3] + + All = all[/^V\d+_\d+_\d+$/] + + Type = :property + NonType = :nonproperty + end + + Map[UnicodeProperty::Type] = UnicodeProperty::All + Map[UnicodeProperty::NonType] = UnicodeProperty::All + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/version_lookup.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/version_lookup.rb new file mode 100644 index 00000000..12a25ce6 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/version_lookup.rb @@ -0,0 +1,82 @@ +module Regexp::Syntax + VERSION_FORMAT = '\Aruby/\d+\.\d+(\.\d+)?\z' + VERSION_REGEXP = /#{VERSION_FORMAT}/ + VERSION_CONST_REGEXP = /\AV\d+_\d+(?:_\d+)?\z/ + + class InvalidVersionNameError < SyntaxError + def initialize(name) + super "Invalid version name '#{name}'. Expected format is '#{VERSION_FORMAT}'" + end + end + + class UnknownSyntaxNameError < SyntaxError + def initialize(name) + super "Unknown syntax name '#{name}'." + end + end + + module_function + + # Loads and instantiates an instance of the syntax specification class for + # the given syntax version name. The special names 'any' and '*' return an + # instance of Syntax::Any. + def new(name) + return Regexp::Syntax::Any.new if ['*', 'any'].include?(name.to_s) + version_class(name).new + end + + def supported?(name) + name =~ VERSION_REGEXP && + comparable_version(name) >= comparable_version('1.8.6') + end + + def version_class(version) + version =~ VERSION_REGEXP || raise(InvalidVersionNameError, version) + version_const_name = version_const_name(version) + const_get(version_const_name) || raise(UnknownSyntaxNameError, version) + end + + def version_const_name(version_string) + "V#{version_string.to_s.scan(/\d+/).join('_')}" + end + + def const_missing(const_name) + if const_name =~ VERSION_CONST_REGEXP + return fallback_version_class(const_name) + end + super + end + + def fallback_version_class(version) + sorted_versions = (specified_versions + [version]) + .sort_by { |name| comparable_version(name) } + return if (version_index = sorted_versions.index(version)) < 1 + + next_lower_version = sorted_versions[version_index - 1] + inherit_from_version(next_lower_version, version) + end + + def inherit_from_version(parent_version, new_version) + new_const = version_const_name(new_version) + parent = const_get(version_const_name(parent_version)) + const_defined?(new_const) || const_set(new_const, Class.new(parent)) + warn_if_future_version(new_const) + const_get(new_const) + end + + def specified_versions + constants.select { |const_name| const_name =~ VERSION_CONST_REGEXP } + end + + def comparable_version(name) + # add .99 to treat versions without a patch value as latest patch version + Gem::Version.new((name.to_s.scan(/\d+/) << 99).join('.')) + end + + def warn_if_future_version(const_name) + return if comparable_version(const_name) < comparable_version('3.0.0') + + warn('This library has only been tested up to Ruby 2.x, '\ + "but you are running with #{const_get(const_name).inspect}") + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions.rb new file mode 100644 index 00000000..7cbdd0e7 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions.rb @@ -0,0 +1,6 @@ +# Ruby 1.8.x is no longer a supported runtime, +# but its regex features are still recognized. +# +# Aliases for the latest patch version are provided as 'ruby/n.n', +# e.g. 'ruby/1.9' refers to Ruby v1.9.3. +Dir[File.expand_path('../versions/*.rb', __FILE__)].sort.each { |f| require f } diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/1.8.6.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/1.8.6.rb new file mode 100644 index 00000000..a2a04eea --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/1.8.6.rb @@ -0,0 +1,21 @@ +module Regexp::Syntax + class V1_8_6 < Regexp::Syntax::Base + def initialize + super + + implements :anchor, Anchor::All + implements :assertion, Assertion::Lookahead + implements :backref, [:number] + implements :posixclass, PosixClass::Standard + implements :group, Group::All + implements :meta, Meta::Extended + implements :set, CharacterSet::All + implements :type, CharacterType::Extended + implements :escape, + Escape::Basic + Escape::ASCII + Escape::Meta + Escape::Control + implements :quantifier, + Quantifier::Greedy + Quantifier::Reluctant + + Quantifier::Interval + Quantifier::IntervalReluctant + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/1.9.1.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/1.9.1.rb new file mode 100644 index 00000000..f9de64ef --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/1.9.1.rb @@ -0,0 +1,18 @@ +module Regexp::Syntax + class V1_9_1 < Regexp::Syntax::V1_8_6 + def initialize + super + + implements :assertion, Assertion::Lookbehind + implements :backref, Backreference::All + SubexpressionCall::All + implements :posixclass, PosixClass::Extensions + implements :nonposixclass, PosixClass::All + implements :escape, Escape::Unicode + Escape::Hex + Escape::Octal + implements :type, CharacterType::Hex + implements :property, UnicodeProperty::V1_9_0 + implements :nonproperty, UnicodeProperty::V1_9_0 + implements :quantifier, + Quantifier::Possessive + Quantifier::IntervalPossessive + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/1.9.3.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/1.9.3.rb new file mode 100644 index 00000000..699943d1 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/1.9.3.rb @@ -0,0 +1,11 @@ +module Regexp::Syntax + class V1_9_3 < Regexp::Syntax::V1_9_1 + def initialize + super + + # these were added with update of Oniguruma to Unicode 6.0 + implements :property, UnicodeProperty::V1_9_3 + implements :nonproperty, UnicodeProperty::V1_9_3 + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/2.0.0.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/2.0.0.rb new file mode 100644 index 00000000..195d3fed --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/2.0.0.rb @@ -0,0 +1,17 @@ +module Regexp::Syntax + # use the last 1.9 release as the base + class V2_0_0 < Regexp::Syntax::V1_9 + def initialize + super + + implements :keep, Keep::All + implements :conditional, Conditional::All + implements :property, UnicodeProperty::V2_0_0 + implements :nonproperty, UnicodeProperty::V2_0_0 + implements :type, CharacterType::Clustered + + excludes :property, :newline + excludes :nonproperty, :newline + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/2.2.0.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/2.2.0.rb new file mode 100644 index 00000000..b984f6bf --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/2.2.0.rb @@ -0,0 +1,10 @@ +module Regexp::Syntax + class V2_2_0 < Regexp::Syntax::V2_1 + def initialize + super + + implements :property, UnicodeProperty::V2_2_0 + implements :nonproperty, UnicodeProperty::V2_2_0 + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/2.3.0.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/2.3.0.rb new file mode 100644 index 00000000..81df90ad --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/2.3.0.rb @@ -0,0 +1,10 @@ +module Regexp::Syntax + class V2_3_0 < Regexp::Syntax::V2_2 + def initialize + super + + implements :property, UnicodeProperty::V2_3_0 + implements :nonproperty, UnicodeProperty::V2_3_0 + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/2.4.0.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/2.4.0.rb new file mode 100644 index 00000000..5c7d960a --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/2.4.0.rb @@ -0,0 +1,10 @@ +module Regexp::Syntax + class V2_4_0 < Regexp::Syntax::V2_3 + def initialize + super + + implements :property, UnicodeProperty::V2_4_0 + implements :nonproperty, UnicodeProperty::V2_4_0 + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/2.4.1.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/2.4.1.rb new file mode 100644 index 00000000..7e11afae --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/2.4.1.rb @@ -0,0 +1,9 @@ +module Regexp::Syntax + class V2_4_1 < Regexp::Syntax::V2_4_0 + def initialize + super + + implements :group, Group::V2_4_1 + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/2.5.0.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/2.5.0.rb new file mode 100644 index 00000000..eb7de72a --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/2.5.0.rb @@ -0,0 +1,10 @@ +module Regexp::Syntax + class V2_5_0 < Regexp::Syntax::V2_4 + def initialize + super + + implements :property, UnicodeProperty::V2_5_0 + implements :nonproperty, UnicodeProperty::V2_5_0 + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/2.6.0.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/2.6.0.rb new file mode 100644 index 00000000..f2030200 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/2.6.0.rb @@ -0,0 +1,10 @@ +module Regexp::Syntax + class V2_6_0 < Regexp::Syntax::V2_5 + def initialize + super + + implements :property, UnicodeProperty::V2_6_0 + implements :nonproperty, UnicodeProperty::V2_6_0 + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/2.6.2.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/2.6.2.rb new file mode 100644 index 00000000..408c88b4 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/2.6.2.rb @@ -0,0 +1,10 @@ +module Regexp::Syntax + class V2_6_2 < Regexp::Syntax::V2_6_0 + def initialize + super + + implements :property, UnicodeProperty::V2_6_2 + implements :nonproperty, UnicodeProperty::V2_6_2 + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/2.6.3.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/2.6.3.rb new file mode 100644 index 00000000..243bb18c --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/syntax/versions/2.6.3.rb @@ -0,0 +1,10 @@ +module Regexp::Syntax + class V2_6_3 < Regexp::Syntax::V2_6_2 + def initialize + super + + implements :property, UnicodeProperty::V2_6_3 + implements :nonproperty, UnicodeProperty::V2_6_3 + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/token.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/token.rb new file mode 100644 index 00000000..515d754b --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/token.rb @@ -0,0 +1,35 @@ +class Regexp + + TOKEN_KEYS = [ + :type, + :token, + :text, + :ts, + :te, + :level, + :set_level, + :conditional_level + ].freeze + + Token = Struct.new(*TOKEN_KEYS) do + attr_accessor :previous, :next + + def offset + [ts, te] + end + + def length + te - ts + end + + if RUBY_VERSION < '2.0.0' + def to_h + members.inject({}) do |hash, member| + hash[member.to_sym] = self[member] + hash + end + end + end + end + +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/version.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/version.rb new file mode 100644 index 00000000..d3c0bf84 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/lib/regexp_parser/version.rb @@ -0,0 +1,5 @@ +class Regexp + class Parser + VERSION = '1.6.0' + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/regexp_parser.gemspec b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/regexp_parser.gemspec new file mode 100644 index 00000000..503ccc8c --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/regexp_parser.gemspec @@ -0,0 +1,36 @@ +$:.unshift File.join(File.dirname(__FILE__), 'lib') + +require 'regexp_parser/version' + +Gem::Specification.new do |gem| + gem.name = 'regexp_parser' + gem.version = ::Regexp::Parser::VERSION + + gem.summary = "Scanner, lexer, parser for ruby's regular expressions" + gem.description = 'A library for tokenizing, lexing, and parsing Ruby regular expressions.' + gem.homepage = 'https://github.com/ammar/regexp_parser' + + if gem.respond_to?(:metadata) + gem.metadata = { 'issue_tracker' => 'https://github.com/ammar/regexp_parser/issues' } + end + + gem.authors = ['Ammar Ali'] + gem.email = ['ammarabuali@gmail.com'] + + gem.license = 'MIT' + + gem.require_paths = ['lib'] + + gem.files = Dir.glob('{lib,spec}/**/*.rb') + + Dir.glob('lib/**/*.rl') + + Dir.glob('lib/**/*.yml') + + %w(Gemfile Rakefile LICENSE README.md CHANGELOG.md regexp_parser.gemspec) + + gem.test_files = Dir.glob('spec/**/*.rb') + + gem.rdoc_options = ["--inline-source", "--charset=UTF-8"] + + gem.platform = Gem::Platform::RUBY + + gem.required_ruby_version = '>= 1.9.1' +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/base_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/base_spec.rb new file mode 100644 index 00000000..c64890da --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/base_spec.rb @@ -0,0 +1,94 @@ +require 'spec_helper' + +RSpec.describe(Regexp::Expression::Base) do + specify('#to_re') do + re_text = '^a*(b([cde]+))+f?$' + + re = RP.parse(re_text).to_re + + expect(re).to be_a(::Regexp) + expect(re_text).to eq re.source + end + + specify('#level') do + regexp = /^a(b(c(d)))e$/ + root = RP.parse(regexp) + + ['^', 'a', '(b(c(d)))', 'e', '$'].each_with_index do |t, i| + expect(root[i].to_s).to eq t + expect(root[i].level).to eq 0 + end + + expect(root[2][0].to_s).to eq 'b' + expect(root[2][0].level).to eq 1 + + expect(root[2][1][0].to_s).to eq 'c' + expect(root[2][1][0].level).to eq 2 + + expect(root[2][1][1][0].to_s).to eq 'd' + expect(root[2][1][1][0].level).to eq 3 + end + + specify('#terminal?') do + root = RP.parse('^a([b]+)c$') + + expect(root).not_to be_terminal + + expect(root[0]).to be_terminal + expect(root[1]).to be_terminal + expect(root[2]).not_to be_terminal + expect(root[2][0]).not_to be_terminal + expect(root[2][0][0]).to be_terminal + expect(root[3]).to be_terminal + expect(root[4]).to be_terminal + end + + specify('alt #terminal?') do + root = RP.parse('^(ab|cd)$') + + expect(root).not_to be_terminal + + expect(root[0]).to be_terminal + expect(root[1]).not_to be_terminal + expect(root[1][0]).not_to be_terminal + expect(root[1][0][0]).not_to be_terminal + expect(root[1][0][0][0]).to be_terminal + expect(root[1][0][1]).not_to be_terminal + expect(root[1][0][1][0]).to be_terminal + end + + specify('#coded_offset') do + root = RP.parse('^a*(b+(c?))$') + + expect(root.coded_offset).to eq '@0+12' + + [ + ['@0+1', '^'], + ['@1+2', 'a*'], + ['@3+8', '(b+(c?))'], + ['@11+1', '$'], + ].each_with_index do |check, i| + against = [root[i].coded_offset, root[i].to_s] + + expect(against).to eq check + end + + expect([root[2][0].coded_offset, root[2][0].to_s]).to eq ['@4+2', 'b+'] + expect([root[2][1].coded_offset, root[2][1].to_s]).to eq ['@6+4', '(c?)'] + expect([root[2][1][0].coded_offset, root[2][1][0].to_s]).to eq ['@7+2', 'c?'] + end + + specify('#quantity') do + expect(RP.parse(/aa/)[0].quantity).to eq [nil, nil] + expect(RP.parse(/a?/)[0].quantity).to eq [0, 1] + expect(RP.parse(/a*/)[0].quantity).to eq [0, -1] + expect(RP.parse(/a+/)[0].quantity).to eq [1, -1] + end + + specify('#repetitions') do + expect(RP.parse(/aa/)[0].repetitions).to eq 1..1 + expect(RP.parse(/a?/)[0].repetitions).to eq 0..1 + expect(RP.parse(/a*/)[0].repetitions).to eq 0..(Float::INFINITY) + expect(RP.parse(/a+/)[0].repetitions).to eq 1..(Float::INFINITY) + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/clone_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/clone_spec.rb new file mode 100644 index 00000000..6b8cbcba --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/clone_spec.rb @@ -0,0 +1,120 @@ +require 'spec_helper' + +RSpec.describe('Expression#clone') do + specify('Base#clone') do + root = RP.parse(/^(?i:a)b+$/i) + copy = root.clone + + expect(copy.to_s).to eq root.to_s + + expect(root.object_id).not_to eq copy.object_id + expect(root.text).to eq copy.text + expect(root.text.object_id).not_to eq copy.text.object_id + + root_1 = root[1] + copy_1 = copy[1] + + expect(root_1.options).to eq copy_1.options + expect(root_1.options.object_id).not_to eq copy_1.options.object_id + + root_2 = root[2] + copy_2 = copy[2] + + expect(root_2).to be_quantified + expect(copy_2).to be_quantified + expect(root_2.quantifier.text).to eq copy_2.quantifier.text + expect(root_2.quantifier.text.object_id).not_to eq copy_2.quantifier.text.object_id + expect(root_2.quantifier.object_id).not_to eq copy_2.quantifier.object_id + + # regression test + expect { root_2.clone }.not_to change { root_2.quantifier.object_id } + expect { root_2.clone }.not_to change { root_2.quantifier.text.object_id } + end + + specify('Subexpression#clone') do + root = RP.parse(/^a(b([cde])f)g$/) + copy = root.clone + + expect(copy.to_s).to eq root.to_s + + expect(root).to respond_to(:expressions) + expect(copy).to respond_to(:expressions) + expect(root.expressions.object_id).not_to eq copy.expressions.object_id + copy.expressions.each_with_index do |exp, index| + expect(root[index].object_id).not_to eq exp.object_id + end + copy[2].each_with_index do |exp, index| + expect(root[2][index].object_id).not_to eq exp.object_id + end + + # regression test + expect { root.clone }.not_to change { root.expressions.object_id } + end + + specify('Group::Named#clone') do + root = RP.parse('^(?a)+bc$') + copy = root.clone + + expect(copy.to_s).to eq root.to_s + + root_1 = root[1] + copy_1 = copy[1] + + expect(root_1.name).to eq copy_1.name + expect(root_1.name.object_id).not_to eq copy_1.name.object_id + expect(root_1.text).to eq copy_1.text + expect(root_1.expressions.object_id).not_to eq copy_1.expressions.object_id + copy_1.expressions.each_with_index do |exp, index| + expect(root_1[index].object_id).not_to eq exp.object_id + end + + # regression test + expect { root_1.clone }.not_to change { root_1.name.object_id } + end + + specify('Sequence#clone') do + root = RP.parse(/(a|b)/) + copy = root.clone + + # regression test + expect(copy.to_s).to eq root.to_s + + root_seq_op = root[0][0] + copy_seq_op = copy[0][0] + root_seq_1 = root[0][0][0] + copy_seq_1 = copy[0][0][0] + + expect(root_seq_op.object_id).not_to eq copy_seq_op.object_id + expect(root_seq_1.object_id).not_to eq copy_seq_1.object_id + copy_seq_1.expressions.each_with_index do |exp, index| + expect(root_seq_1[index].object_id).not_to eq exp.object_id + end + end + + describe('Base#unquantified_clone') do + it 'produces a clone' do + root = RP.parse(/^a(b([cde])f)g$/) + copy = root.unquantified_clone + + expect(copy.to_s).to eq root.to_s + + expect(copy.object_id).not_to eq root.object_id + end + + it 'does not carry over the callee quantifier' do + expect(RP.parse(/a{3}/)[0]).to be_quantified + expect(RP.parse(/a{3}/)[0].unquantified_clone).not_to be_quantified + + expect(RP.parse(/[a]{3}/)[0]).to be_quantified + expect(RP.parse(/[a]{3}/)[0].unquantified_clone).not_to be_quantified + + expect(RP.parse(/(a|b){3}/)[0]).to be_quantified + expect(RP.parse(/(a|b){3}/)[0].unquantified_clone).not_to be_quantified + end + + it 'keeps quantifiers of callee children' do + expect(RP.parse(/(a{3}){3}/)[0][0]).to be_quantified + expect(RP.parse(/(a{3}){3}/)[0].unquantified_clone[0]).to be_quantified + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/conditional_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/conditional_spec.rb new file mode 100644 index 00000000..821ef6a1 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/conditional_spec.rb @@ -0,0 +1,89 @@ +require 'spec_helper' + +RSpec.describe(Regexp::Expression::Conditional) do + let(:root) { RP.parse('^(a(b))(b(?(1)c|(?(2)d|(?(3)e|f)))g)$') } + let(:cond_1) { root[2][1] } + let(:cond_2) { root[2][1][2][0] } + let(:cond_3) { root[2][1][2][0][2][0] } + + specify('root level') do + [ + '^', + '(a(b))', + '(b(?(1)c|(?(2)d|(?(3)e|f)))g)', + '$' + ].each_with_index do |t, i| + expect(root[i].conditional_level).to eq 0 + expect(root[i].to_s).to eq t + end + + expect(root[2][0].to_s).to eq 'b' + expect(root[2][0].conditional_level).to eq 0 + end + + specify('level one') do + condition = cond_1.condition + branch_1 = cond_1.branches.first + + expect(condition).to be_a Conditional::Condition + expect(condition.to_s).to eq '(1)' + expect(condition.conditional_level).to eq 1 + + expect(branch_1).to be_a Conditional::Branch + expect(branch_1.to_s).to eq 'c' + expect(branch_1.conditional_level).to eq 1 + + expect(branch_1.first.to_s).to eq 'c' + expect(branch_1.first.conditional_level).to eq 1 + end + + specify('level two') do + condition = cond_2.condition + branch_1 = cond_2.branches.first + branch_2 = cond_2.branches.last + + expect(cond_2.to_s).to start_with '(?' + expect(cond_2.conditional_level).to eq 1 + + expect(condition).to be_a Conditional::Condition + expect(condition.to_s).to eq '(2)' + expect(condition.conditional_level).to eq 2 + + expect(branch_1).to be_a Conditional::Branch + expect(branch_1.to_s).to eq 'd' + expect(branch_1.conditional_level).to eq 2 + + expect(branch_1.first.to_s).to eq 'd' + expect(branch_1.first.conditional_level).to eq 2 + + expect(branch_2.first.to_s).to start_with '(?' + expect(branch_2.first.conditional_level).to eq 2 + end + + specify('level three') do + condition = cond_3.condition + branch_1 = cond_3.branches.first + branch_2 = cond_3.branches.last + + expect(condition).to be_a Conditional::Condition + expect(condition.to_s).to eq '(3)' + expect(condition.conditional_level).to eq 3 + + expect(cond_3.to_s).to eq '(?(3)e|f)' + expect(cond_3.conditional_level).to eq 2 + + expect(branch_1).to be_a Conditional::Branch + expect(branch_1.to_s).to eq 'e' + expect(branch_1.conditional_level).to eq 3 + + expect(branch_1.first.to_s).to eq 'e' + expect(branch_1.first.conditional_level).to eq 3 + + expect(branch_2).to be_a Conditional::Branch + expect(branch_2.to_s).to eq 'f' + expect(branch_2.conditional_level).to eq 3 + + expect(branch_2.first.to_s).to eq 'f' + expect(branch_2.first.conditional_level).to eq 3 + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/free_space_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/free_space_spec.rb new file mode 100644 index 00000000..c506fff6 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/free_space_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +RSpec.describe(Regexp::Expression::FreeSpace) do + specify('white space quantify raises error') do + regexp = / + a # Comment + /x + + root = RP.parse(regexp) + space = root[0] + + expect(space).to be_instance_of(FreeSpace::WhiteSpace) + expect { space.quantify(:dummy, '#') }.to raise_error(RuntimeError) + end + + specify('comment quantify raises error') do + regexp = / + a # Comment + /x + + root = RP.parse(regexp) + comment = root[3] + + expect(comment).to be_instance_of(FreeSpace::Comment) + expect { comment.quantify(:dummy, '#') }.to raise_error(RuntimeError) + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/methods/match_length_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/methods/match_length_spec.rb new file mode 100644 index 00000000..a855a067 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/methods/match_length_spec.rb @@ -0,0 +1,154 @@ +require 'spec_helper' + +RSpec.describe(Regexp::MatchLength) do + ML = described_class + + specify('literal') { expect(ML.of(/a/).minmax).to eq [1, 1] } + specify('literal sequence') { expect(ML.of(/abc/).minmax).to eq [3, 3] } + specify('dot') { expect(ML.of(/./).minmax).to eq [1, 1] } + specify('set') { expect(ML.of(/[abc]/).minmax).to eq [1, 1] } + specify('type') { expect(ML.of(/\d/).minmax).to eq [1, 1] } + specify('escape') { expect(ML.of(/\n/).minmax).to eq [1, 1] } + specify('property') { expect(ML.of(/\p{ascii}/).minmax).to eq [1, 1] } + specify('codepoint list') { expect(ML.of(/\u{61 62 63}/).minmax).to eq [3, 3] } + specify('multi-char literal') { expect(ML.of(/abc/).minmax).to eq [3, 3] } + specify('fixed quantified') { expect(ML.of(/a{5}/).minmax).to eq [5, 5] } + specify('range quantified') { expect(ML.of(/a{5,9}/).minmax).to eq [5, 9] } + specify('nested quantified') { expect(ML.of(/(a{2}){3,4}/).minmax).to eq [6, 8] } + specify('open-end quantified') { expect(ML.of(/a*/).minmax).to eq [0, Float::INFINITY] } + specify('empty subexpression') { expect(ML.of(//).minmax).to eq [0, 0] } + specify('anchor') { expect(ML.of(/^$/).minmax).to eq [0, 0] } + specify('lookaround') { expect(ML.of(/(?=abc)/).minmax).to eq [0, 0] } + specify('free space') { expect(ML.of(/ /x).minmax).to eq [0, 0] } + specify('comment') { expect(ML.of(/(?#comment)/x).minmax).to eq [0, 0] } + specify('backreference') { expect(ML.of(/(abc){2}\1/).minmax).to eq [9, 9] } + specify('subexp call') { expect(ML.of(/(abc){2}\g<-1>/).minmax).to eq [9, 9] } + specify('alternation') { expect(ML.of(/a|bcde/).minmax).to eq [1, 4] } + specify('nested alternation') { expect(ML.of(/a|bc(d|efg)/).minmax).to eq [1, 5] } + specify('quantified alternation') { expect(ML.of(/a|bcde?/).minmax).to eq [1, 4] } + if ruby_version_at_least('2.4.1') + specify('absence group') { expect(ML.of('(?~abc)').minmax).to eq [0, Float::INFINITY] } + end + + specify('raises for missing references') do + exp = RP.parse(/(a)\1/).last + exp.referenced_expression = nil + expect { exp.match_length }.to raise_error(ArgumentError) + end + + describe('::of') do + it('works with Regexps') { expect(ML.of(/foo/).minmax).to eq [3, 3] } + it('works with Strings') { expect(ML.of('foo').minmax).to eq [3, 3] } + it('works with Expressions') { expect(ML.of(RP.parse(/foo/)).minmax).to eq [3, 3] } + end + + describe('Expression#match_length') do + it('returns the MatchLength') { expect(RP.parse(/abc/).match_length.minmax).to eq [3, 3] } + end + + describe('Expression#inner_match_length') do + it 'returns the MatchLength of an expression that does not count towards parent match_length' do + exp = RP.parse(/(?=ab|cdef)/)[0] + expect(exp).to be_a Regexp::Expression::Assertion::Base + expect(exp.match_length.minmax).to eq [0, 0] + expect(exp.inner_match_length.minmax).to eq [2, 4] + end + end + + describe('#include?') do + specify('unquantified') do + expect(ML.of(/a/)).to include 1 + expect(ML.of(/a/)).not_to include 0 + expect(ML.of(/a/)).not_to include 2 + end + + specify('fixed quantified') do + expect(ML.of(/a{5}/)).to include 5 + expect(ML.of(/a{5}/)).not_to include 0 + expect(ML.of(/a{5}/)).not_to include 4 + expect(ML.of(/a{5}/)).not_to include 6 + end + + specify('variably quantified') do + expect(ML.of(/a?/)).to include 0 + expect(ML.of(/a?/)).to include 1 + expect(ML.of(/a?/)).not_to include 2 + end + + specify('nested quantified') do + expect(ML.of(/(a{2}){3,4}/)).to include 6 + expect(ML.of(/(a{2}){3,4}/)).to include 8 + expect(ML.of(/(a{2}){3,4}/)).not_to include 0 + expect(ML.of(/(a{2}){3,4}/)).not_to include 5 + expect(ML.of(/(a{2}){3,4}/)).not_to include 7 + expect(ML.of(/(a{2}){3,4}/)).not_to include 9 + end + + specify('branches') do + expect(ML.of(/ab|cdef/)).to include 2 + expect(ML.of(/ab|cdef/)).to include 4 + expect(ML.of(/ab|cdef/)).not_to include 0 + expect(ML.of(/ab|cdef/)).not_to include 3 + expect(ML.of(/ab|cdef/)).not_to include 5 + end + + specify('called on leaf node') do + expect(ML.of(RP.parse(/a{2}/)[0])).to include 2 + expect(ML.of(RP.parse(/a{2}/)[0])).not_to include 0 + expect(ML.of(RP.parse(/a{2}/)[0])).not_to include 1 + expect(ML.of(RP.parse(/a{2}/)[0])).not_to include 3 + end + end + + describe('#fixed?') do + specify('unquantified') { expect(ML.of(/a/)).to be_fixed } + specify('fixed quantified') { expect(ML.of(/a{5}/)).to be_fixed } + specify('variably quantified') { expect(ML.of(/a?/)).not_to be_fixed } + specify('equal branches') { expect(ML.of(/ab|cd/)).to be_fixed } + specify('unequal branches') { expect(ML.of(/ab|cdef/)).not_to be_fixed } + specify('equal quantified branches') { expect(ML.of(/a{2}|cd/)).to be_fixed } + specify('unequal quantified branches') { expect(ML.of(/a{3}|cd/)).not_to be_fixed } + specify('empty') { expect(ML.of(//)).to be_fixed } + end + + describe('#each') do + it 'returns an Enumerator if called without a block' do + result = ML.of(/a?/).each + expect(result).to be_a(Enumerator) + expect(result.next).to eq 0 + expect(result.next).to eq 1 + expect { result.next }.to raise_error(StopIteration) + end + + it 'is limited to 1000 iterations in case there are infinite match lengths' do + expect(ML.of(/a*/).first(3000).size).to eq 1000 + end + + it 'scaffolds the Enumerable interface' do + expect(ML.of(/abc|defg/).count).to eq 2 + expect(ML.of(/(ab)*/).first(5)).to eq [0, 2, 4, 6, 8] + expect(ML.of(/a{,10}/).any? { |len| len > 20 }).to be false + end + end + + describe('#endless_each') do + it 'returns an Enumerator if called without a block' do + result = ML.of(/a?/).endless_each + expect(result).to be_a(Enumerator) + expect(result.next).to eq 0 + expect(result.next).to eq 1 + expect { result.next }.to raise_error(StopIteration) + end + + it 'never stops iterating for infinite match lengths' do + expect(ML.of(/a*/).endless_each.first(3000).size).to eq 3000 + end + end + + describe('#inspect') do + it 'is nice' do + result = RP.parse(/a{2,4}/)[0].match_length + expect(result.inspect).to eq '# min=2 max=4>' + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/methods/match_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/methods/match_spec.rb new file mode 100644 index 00000000..46fe6464 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/methods/match_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +RSpec.describe('Expression#match') do + it 'returns the #match result of the respective Regexp' do + expect(RP.parse(/a/).match('a')[0]).to eq 'a' + end + + it 'can be given an offset, just like Regexp#match' do + expect(RP.parse(/./).match('ab', 1)[0]).to eq 'b' + end + + it 'works with the #=~ alias' do + expect(RP.parse(/a/) =~ 'a').to be_a MatchData + end +end + +RSpec.describe('Expression#match?') do + it 'returns true if the Respective Regexp matches' do + expect(RP.parse(/a/).match?('a')).to be true + end + + it 'returns false if the Respective Regexp does not match' do + expect(RP.parse(/a/).match?('b')).to be false + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/methods/strfregexp_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/methods/strfregexp_spec.rb new file mode 100644 index 00000000..a97eea2a --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/methods/strfregexp_spec.rb @@ -0,0 +1,224 @@ +require 'spec_helper' + +RSpec.describe('Expression#strfregexp') do + specify('#strfre alias') do + expect(RP.parse(/a/)).to respond_to(:strfre) + end + + specify('#strfregexp level') do + root = RP.parse(/a(b(c))/) + + expect(root.strfregexp('%l')).to eq 'root' + + a = root.first + expect(a.strfregexp('%%l')).to eq '%0' + + b = root[1].first + expect(b.strfregexp('<%l>')).to eq '<1>' + + c = root[1][1].first + expect(c.strfregexp('[at: %l]')).to eq '[at: 2]' + end + + specify('#strfregexp start end') do + root = RP.parse(/a(b(c))/) + + expect(root.strfregexp('%s')).to eq '0' + expect(root.strfregexp('%e')).to eq '7' + + a = root.first + expect(a.strfregexp('%%s')).to eq '%0' + expect(a.strfregexp('%e')).to eq '1' + + group_1 = root[1] + expect(group_1.strfregexp('GRP:%s')).to eq 'GRP:1' + expect(group_1.strfregexp('%e')).to eq '7' + + b = group_1.first + expect(b.strfregexp('<@%s>')).to eq '<@2>' + expect(b.strfregexp('%e')).to eq '3' + + c = group_1.last.first + expect(c.strfregexp('[at: %s]')).to eq '[at: 4]' + expect(c.strfregexp('%e')).to eq '5' + end + + specify('#strfregexp length') do + root = RP.parse(/a[b]c/) + + expect(root.strfregexp('%S')).to eq '5' + + a = root.first + expect(a.strfregexp('%S')).to eq '1' + + set = root[1] + expect(set.strfregexp('%S')).to eq '3' + end + + specify('#strfregexp coded offset') do + root = RP.parse(/a[b]c/) + + expect(root.strfregexp('%o')).to eq '@0+5' + + a = root.first + expect(a.strfregexp('%o')).to eq '@0+1' + + set = root[1] + expect(set.strfregexp('%o')).to eq '@1+3' + end + + specify('#strfregexp type token') do + root = RP.parse(/a[b](c)/) + + expect(root.strfregexp('%y')).to eq 'expression' + expect(root.strfregexp('%k')).to eq 'root' + expect(root.strfregexp('%i')).to eq 'expression:root' + expect(root.strfregexp('%c')).to eq 'Regexp::Expression::Root' + + a = root.first + expect(a.strfregexp('%y')).to eq 'literal' + expect(a.strfregexp('%k')).to eq 'literal' + expect(a.strfregexp('%i')).to eq 'literal:literal' + expect(a.strfregexp('%c')).to eq 'Regexp::Expression::Literal' + + set = root[1] + expect(set.strfregexp('%y')).to eq 'set' + expect(set.strfregexp('%k')).to eq 'character' + expect(set.strfregexp('%i')).to eq 'set:character' + expect(set.strfregexp('%c')).to eq 'Regexp::Expression::CharacterSet' + + group = root.last + expect(group.strfregexp('%y')).to eq 'group' + expect(group.strfregexp('%k')).to eq 'capture' + expect(group.strfregexp('%i')).to eq 'group:capture' + expect(group.strfregexp('%c')).to eq 'Regexp::Expression::Group::Capture' + end + + specify('#strfregexp quantifier') do + root = RP.parse(/a+[b](c)?d{3,4}/) + + expect(root.strfregexp('%q')).to eq '{1}' + expect(root.strfregexp('%Q')).to eq '' + expect(root.strfregexp('%z, %Z')).to eq '1, 1' + + a = root.first + expect(a.strfregexp('%q')).to eq '{1, or-more}' + expect(a.strfregexp('%Q')).to eq '+' + expect(a.strfregexp('%z, %Z')).to eq '1, -1' + + set = root[1] + expect(set.strfregexp('%q')).to eq '{1}' + expect(set.strfregexp('%Q')).to eq '' + expect(set.strfregexp('%z, %Z')).to eq '1, 1' + + group = root[2] + expect(group.strfregexp('%q')).to eq '{0, 1}' + expect(group.strfregexp('%Q')).to eq '?' + expect(group.strfregexp('%z, %Z')).to eq '0, 1' + + d = root.last + expect(d.strfregexp('%q')).to eq '{3, 4}' + expect(d.strfregexp('%Q')).to eq '{3,4}' + expect(d.strfregexp('%z, %Z')).to eq '3, 4' + end + + specify('#strfregexp text') do + root = RP.parse(/a(b(c))|[d-gk-p]+/) + + expect(root.strfregexp('%t')).to eq 'a(b(c))|[d-gk-p]+' + expect(root.strfregexp('%~t')).to eq 'expression:root' + + alt = root.first + expect(alt.strfregexp('%t')).to eq 'a(b(c))|[d-gk-p]+' + expect(alt.strfregexp('%T')).to eq 'a(b(c))|[d-gk-p]+' + expect(alt.strfregexp('%~t')).to eq 'meta:alternation' + + seq_1 = alt.first + expect(seq_1.strfregexp('%t')).to eq 'a(b(c))' + expect(seq_1.strfregexp('%T')).to eq 'a(b(c))' + expect(seq_1.strfregexp('%~t')).to eq 'expression:sequence' + + group = seq_1[1] + expect(group.strfregexp('%t')).to eq '(b(c))' + expect(group.strfregexp('%T')).to eq '(b(c))' + expect(group.strfregexp('%~t')).to eq 'group:capture' + + seq_2 = alt.last + expect(seq_2.strfregexp('%t')).to eq '[d-gk-p]+' + expect(seq_2.strfregexp('%T')).to eq '[d-gk-p]+' + + set = seq_2.first + expect(set.strfregexp('%t')).to eq '[d-gk-p]' + expect(set.strfregexp('%T')).to eq '[d-gk-p]+' + expect(set.strfregexp('%~t')).to eq 'set:character' + end + + specify('#strfregexp combined') do + root = RP.parse(/a{5}|[b-d]+/) + + expect(root.strfregexp('%b')).to eq '@0+11 expression:root' + expect(root.strfregexp('%b')).to eq root.strfregexp('%o %i') + + expect(root.strfregexp('%m')).to eq '@0+11 expression:root {1}' + expect(root.strfregexp('%m')).to eq root.strfregexp('%b %q') + + expect(root.strfregexp('%a')).to eq '@0+11 expression:root {1} a{5}|[b-d]+' + expect(root.strfregexp('%a')).to eq root.strfregexp('%m %t') + end + + specify('#strfregexp conditional') do + root = RP.parse('(?a)(?()b|c)', 'ruby/2.0') + + expect { root.strfregexp }.not_to(raise_error) + end + + specify('#strfregexp_tree') do + root = RP.parse(/a[b-d]*(e(f+))?/) + + expect(root.strfregexp_tree('%>%o %~t')).to eq( + "@0+15 expression:root\n" + + " @0+1 a\n" + + " @1+6 set:character\n" + + " @2+3 set:range\n" + + " @2+1 b\n" + + " @4+1 d\n" + + " @7+8 group:capture\n" + + " @8+1 e\n" + + " @9+4 group:capture\n" + + " @10+2 f+" + ) + end + + specify('#strfregexp_tree separator') do + root = RP.parse(/a[b-d]*(e(f+))?/) + + expect(root.strfregexp_tree('%>%o %~t', true, '-SEP-')).to eq( + "@0+15 expression:root-SEP-" + + " @0+1 a-SEP-" + + " @1+6 set:character-SEP-" + + " @2+3 set:range-SEP-" + + " @2+1 b-SEP-" + + " @4+1 d-SEP-" + + " @7+8 group:capture-SEP-" + + " @8+1 e-SEP-" + + " @9+4 group:capture-SEP-" + + " @10+2 f+" + ) + end + + specify('#strfregexp_tree excluding self') do + root = RP.parse(/a[b-d]*(e(f+))?/) + + expect(root.strfregexp_tree('%>%o %~t', false)).to eq( + "@0+1 a\n" + + "@1+6 set:character\n" + + " @2+3 set:range\n" + + " @2+1 b\n" + + " @4+1 d\n" + + "@7+8 group:capture\n" + + " @8+1 e\n" + + " @9+4 group:capture\n" + + " @10+2 f+" + ) + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/methods/tests_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/methods/tests_spec.rb new file mode 100644 index 00000000..ed847de9 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/methods/tests_spec.rb @@ -0,0 +1,99 @@ +require 'spec_helper' + +RSpec.describe('ExpressionTests') do + specify('#type?') do + root = RP.parse(/abcd|(ghij)|[klmn]/) + + alt = root.first + + expect(alt.type?(:meta)).to be true + expect(alt.type?(:escape)).to be false + expect(alt.type?(%i[meta escape])).to be true + expect(alt.type?(%i[literal escape])).to be false + expect(alt.type?(:*)).to be true + expect(alt.type?([:*])).to be true + expect(alt.type?(%i[literal escape *])).to be true + + seq_1 = alt[0] + expect(seq_1.type?(:expression)).to be true + expect(seq_1.first.type?(:literal)).to be true + + seq_2 = alt[1] + expect(seq_2.type?(:*)).to be true + expect(seq_2.first.type?(:group)).to be true + + seq_3 = alt[2] + expect(seq_3.first.type?(:set)).to be true + end + + specify('#is?') do + root = RP.parse(/.+|\.?/) + + expect(root.is?(:*)).to be true + + alt = root.first + expect(alt.is?(:*)).to be true + expect(alt.is?(:alternation)).to be true + expect(alt.is?(:alternation, :meta)).to be true + + seq_1 = alt[0] + expect(seq_1.is?(:sequence)).to be true + expect(seq_1.is?(:sequence, :expression)).to be true + + expect(seq_1.first.is?(:dot)).to be true + expect(seq_1.first.is?(:dot, :escape)).to be false + expect(seq_1.first.is?(:dot, :meta)).to be true + expect(seq_1.first.is?(:dot, %i[escape meta])).to be true + + seq_2 = alt[1] + expect(seq_2.first.is?(:dot)).to be true + expect(seq_2.first.is?(:dot, :escape)).to be true + expect(seq_2.first.is?(:dot, :meta)).to be false + expect(seq_2.first.is?(:dot, %i[meta escape])).to be true + end + + specify('#one_of?') do + root = RP.parse(/\Aab(c[\w])d|e.\z/) + + expect(root.one_of?(:*)).to be true + expect(root.one_of?(:* => :*)).to be true + expect(root.one_of?(:* => [:*])).to be true + + alt = root.first + expect(alt.one_of?(:*)).to be true + expect(alt.one_of?(:meta)).to be true + expect(alt.one_of?(:meta, :alternation)).to be true + expect(alt.one_of?(meta: %i[dot bogus])).to be false + expect(alt.one_of?(meta: %i[dot alternation])).to be true + + seq_1 = alt[0] + expect(seq_1.one_of?(:expression)).to be true + expect(seq_1.one_of?(expression: :sequence)).to be true + + expect(seq_1.first.one_of?(:anchor)).to be true + expect(seq_1.first.one_of?(anchor: :bos)).to be true + expect(seq_1.first.one_of?(anchor: :eos)).to be false + expect(seq_1.first.one_of?(anchor: %i[escape meta bos])).to be true + expect(seq_1.first.one_of?(anchor: %i[escape meta eos])).to be false + + seq_2 = alt[1] + expect(seq_2.first.one_of?(:literal)).to be true + + expect(seq_2[1].one_of?(:meta)).to be true + expect(seq_2[1].one_of?(meta: :dot)).to be true + expect(seq_2[1].one_of?(meta: :alternation)).to be false + expect(seq_2[1].one_of?(meta: [:dot])).to be true + + expect(seq_2.last.one_of?(:group)).to be false + expect(seq_2.last.one_of?(group: [:*])).to be false + expect(seq_2.last.one_of?(group: [:*], meta: :*)).to be false + + expect(seq_2.last.one_of?(:meta => [:*], :* => :*)).to be true + expect(seq_2.last.one_of?(meta: [:*], anchor: :*)).to be true + expect(seq_2.last.one_of?(meta: [:*], anchor: :eos)).to be true + expect(seq_2.last.one_of?(meta: [:*], anchor: [:bos])).to be false + expect(seq_2.last.one_of?(meta: [:*], anchor: %i[bos eos])).to be true + + expect { root.one_of?(Object.new) }.to raise_error(ArgumentError) + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/methods/traverse_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/methods/traverse_spec.rb new file mode 100644 index 00000000..296d3fb5 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/methods/traverse_spec.rb @@ -0,0 +1,140 @@ +require 'spec_helper' + +RSpec.describe('Subexpression#traverse') do + specify('Subexpression#traverse') do + root = RP.parse(/a(b(c(d)))|g[h-i]j|klmn/) + + enters = 0 + visits = 0 + exits = 0 + + root.traverse do |event, _exp, _index| + enters = (enters + 1) if event == :enter + visits = (visits + 1) if event == :visit + exits = (exits + 1) if event == :exit + end + + expect(enters).to eq 9 + expect(enters).to eq exits + + expect(visits).to eq 9 + end + + specify('Subexpression#traverse including self') do + root = RP.parse(/a(b(c(d)))|g[h-i]j|klmn/) + + enters = 0 + visits = 0 + exits = 0 + + root.traverse(true) do |event, _exp, _index| + enters = (enters + 1) if event == :enter + visits = (visits + 1) if event == :visit + exits = (exits + 1) if event == :exit + end + + expect(enters).to eq 10 + expect(enters).to eq exits + + expect(visits).to eq 9 + end + + specify('Subexpression#walk alias') do + root = RP.parse(/abc/) + + expect(root).to respond_to(:walk) + end + + specify('Subexpression#each_expression') do + root = RP.parse(/a(?x:b(c))|g[h-k]/) + + count = 0 + root.each_expression { count += 1 } + + expect(count).to eq 13 + end + + specify('Subexpression#each_expression including self') do + root = RP.parse(/a(?x:b(c))|g[h-k]/) + + count = 0 + root.each_expression(true) { count += 1 } + + expect(count).to eq 14 + end + + specify('Subexpression#each_expression indices') do + root = RP.parse(/a(b)c/) + + indices = [] + root.each_expression { |_exp, index| (indices << index) } + + expect(indices).to eq [0, 1, 0, 2] + end + + specify('Subexpression#each_expression indices including self') do + root = RP.parse(/a(b)c/) + + indices = [] + root.each_expression(true) { |_exp, index| (indices << index) } + + expect(indices).to eq [0, 0, 1, 0, 2] + end + + specify('Subexpression#flat_map without block') do + root = RP.parse(/a(b([c-e]+))?/) + + array = root.flat_map + + expect(array).to be_instance_of(Array) + expect(array.length).to eq 8 + + array.each do |item| + expect(item).to be_instance_of(Array) + expect(item.length).to eq 2 + expect(item.first).to be_a(Regexp::Expression::Base) + expect(item.last).to be_a(Integer) + end + end + + specify('Subexpression#flat_map without block including self') do + root = RP.parse(/a(b([c-e]+))?/) + + array = root.flat_map(true) + + expect(array).to be_instance_of(Array) + expect(array.length).to eq 9 + end + + specify('Subexpression#flat_map indices') do + root = RP.parse(/a(b([c-e]+))?f*g/) + + indices = root.flat_map { |_exp, index| index } + + expect(indices).to eq [0, 1, 0, 1, 0, 0, 0, 1, 2, 3] + end + + specify('Subexpression#flat_map indices including self') do + root = RP.parse(/a(b([c-e]+))?f*g/) + + indices = root.flat_map(true) { |_exp, index| index } + + expect(indices).to eq [0, 0, 1, 0, 1, 0, 0, 0, 1, 2, 3] + end + + specify('Subexpression#flat_map expressions') do + root = RP.parse(/a(b(c(d)))/) + + levels = root.flat_map { |exp, _index| [exp.level, exp.text] if exp.terminal? }.compact + + expect(levels).to eq [[0, 'a'], [1, 'b'], [2, 'c'], [3, 'd']] + end + + specify('Subexpression#flat_map expressions including self') do + root = RP.parse(/a(b(c(d)))/) + + levels = root.flat_map(true) { |exp, _index| [exp.level, exp.to_s] }.compact + + expect(levels).to eq [[nil, 'a(b(c(d)))'], [0, 'a'], [0, '(b(c(d)))'], [1, 'b'], [1, '(c(d))'], [2, 'c'], [2, '(d)'], [3, 'd']] + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/options_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/options_spec.rb new file mode 100644 index 00000000..7c12bc33 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/options_spec.rb @@ -0,0 +1,128 @@ +require 'spec_helper' + +RSpec.describe('Expression#options') do + it 'returns a hash of options/flags that affect the expression' do + exp = RP.parse(/a/ix)[0] + expect(exp).to be_a Literal + expect(exp.options).to eq(i: true, x: true) + end + + it 'includes options that are locally enabled via special groups' do + exp = RP.parse(/(?x)(?m:a)/i)[1][0] + expect(exp).to be_a Literal + expect(exp.options).to eq(i: true, m: true, x: true) + end + + it 'excludes locally disabled options' do + exp = RP.parse(/(?x)(?-im:a)/i)[1][0] + expect(exp).to be_a Literal + expect(exp.options).to eq(x: true) + end + + it 'gives correct precedence to negative options' do + # Negative options have precedence. E.g. /(?i-i)a/ is case-sensitive. + regexp = /(?i-i:a)/ + expect(regexp).to match 'a' + expect(regexp).not_to match 'A' + + exp = RP.parse(regexp)[0][0] + expect(exp).to be_a Literal + expect(exp.options).to eq({}) + end + + it 'correctly handles multiple negative option parts' do + regexp = /(?--m--mx--) . /mx + expect(regexp).to match ' . ' + expect(regexp).not_to match '.' + expect(regexp).not_to match "\n" + + exp = RP.parse(regexp)[2] + expect(exp.options).to eq({}) + end + + it 'gives correct precedence when encountering multiple encoding flags' do + # Any encoding flag overrides all previous encoding flags. If there are + # multiple encoding flags in an options string, the last one wins. + # E.g. /(?dau)\w/ matches UTF8 chars but /(?dua)\w/ only ASCII chars. + regexp1 = /(?dau)\w/ + regexp2 = /(?dua)\w/ + expect(regexp1).to match 'ü' + expect(regexp2).not_to match 'ü' + + exp1 = RP.parse(regexp1)[1] + exp2 = RP.parse(regexp2)[1] + expect(exp1.options).to eq(u: true) + expect(exp2.options).to eq(a: true) + end + + it 'is accessible via shortcuts' do + exp = Root.build + + expect { exp.options[:i] = true } + .to change { exp.i? }.from(false).to(true) + .and change { exp.ignore_case? }.from(false).to(true) + .and change { exp.case_insensitive? }.from(false).to(true) + + expect { exp.options[:m] = true } + .to change { exp.m? }.from(false).to(true) + .and change { exp.multiline? }.from(false).to(true) + + expect { exp.options[:x] = true } + .to change { exp.x? }.from(false).to(true) + .and change { exp.extended? }.from(false).to(true) + .and change { exp.free_spacing? }.from(false).to(true) + + expect { exp.options[:a] = true } + .to change { exp.a? }.from(false).to(true) + .and change { exp.ascii_classes? }.from(false).to(true) + + expect { exp.options[:d] = true } + .to change { exp.d? }.from(false).to(true) + .and change { exp.default_classes? }.from(false).to(true) + + expect { exp.options[:u] = true } + .to change { exp.u? }.from(false).to(true) + .and change { exp.unicode_classes? }.from(false).to(true) + end + + RSpec.shared_examples '#options' do |regexp, klass, at: []| + it "works for expression class #{klass}" do + exp = RP.parse(/#{regexp.source}/i).dig(*at) + expect(exp).to be_a(klass) + expect(exp).to be_i + expect(exp).not_to be_x + end + end + + include_examples '#options', //, Root + include_examples '#options', /a/, Literal, at: [0] + include_examples '#options', /\A/, Anchor::Base, at: [0] + include_examples '#options', /\d/, CharacterType::Base, at: [0] + include_examples '#options', /\n/, EscapeSequence::Base, at: [0] + include_examples '#options', /\K/, Keep::Mark, at: [0] + include_examples '#options', /./, CharacterType::Any, at: [0] + include_examples '#options', /(a)/, Group::Base, at: [0] + include_examples '#options', /(a)/, Literal, at: [0, 0] + include_examples '#options', /(?=a)/, Assertion::Base, at: [0] + include_examples '#options', /(?=a)/, Literal, at: [0, 0] + include_examples '#options', /(a|b)/, Group::Base, at: [0] + include_examples '#options', /(a|b)/, Alternation, at: [0, 0] + include_examples '#options', /(a|b)/, Alternative, at: [0, 0, 0] + include_examples '#options', /(a|b)/, Literal, at: [0, 0, 0, 0] + include_examples '#options', /(a)\1/, Backreference::Base, at: [1] + include_examples '#options', /(a)\k<1>/, Backreference::Number, at: [1] + include_examples '#options', /(a)\g<1>/, Backreference::NumberCall, at: [1] + include_examples '#options', /[a]/, CharacterSet, at: [0] + include_examples '#options', /[a]/, Literal, at: [0, 0] + include_examples '#options', /[a-z]/, CharacterSet::Range, at: [0, 0] + include_examples '#options', /[a-z]/, Literal, at: [0, 0, 0] + include_examples '#options', /[a&&z]/, CharacterSet::Intersection, at: [0, 0] + include_examples '#options', /[a&&z]/, CharacterSet::IntersectedSequence, at: [0, 0, 0] + include_examples '#options', /[a&&z]/, Literal, at: [0, 0, 0, 0] + include_examples '#options', /[[:ascii:]]/, PosixClass, at: [0, 0] + include_examples '#options', /\p{word}/, UnicodeProperty::Base, at: [0] + include_examples '#options', /(a)(?(1)b|c)/, Conditional::Expression, at: [1] + include_examples '#options', /(a)(?(1)b|c)/, Conditional::Condition, at: [1, 0] + include_examples '#options', /(a)(?(1)b|c)/, Conditional::Branch, at: [1, 1] + include_examples '#options', /(a)(?(1)b|c)/, Literal, at: [1, 1, 0] +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/root_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/root_spec.rb new file mode 100644 index 00000000..977ee072 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/root_spec.rb @@ -0,0 +1,9 @@ +require 'spec_helper' + +RSpec.describe(Regexp::Expression::Root) do + describe('#initialize') do + it 'supports the old, nonstandard arity for backwards compatibility' do + expect { Root.new }.to output.to_stderr + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/sequence_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/sequence_spec.rb new file mode 100644 index 00000000..0f2dfdf0 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/sequence_spec.rb @@ -0,0 +1,9 @@ +require 'spec_helper' + +RSpec.describe(Regexp::Expression::Sequence) do + describe('#initialize') do + it 'supports the old, nonstandard arity for backwards compatibility' do + expect { Sequence.new(0, 0, 0) }.to output.to_stderr + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/subexpression_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/subexpression_spec.rb new file mode 100644 index 00000000..c561c397 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/subexpression_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' + +RSpec.describe(Regexp::Expression::Subexpression) do + specify('#ts, #te') do + regx = /abcd|ghij|klmn|pqur/ + root = RP.parse(regx) + + alt = root.first + + { 0 => [0, 4], 1 => [5, 9], 2 => [10, 14], 3 => [15, 19] }.each do |index, span| + sequence = alt[index] + + expect(sequence.ts).to eq span[0] + expect(sequence.te).to eq span[1] + end + end + + specify('#nesting_level') do + root = RP.parse(/a(b(\d|[ef-g[h]]))/) + + tests = { + 'a' => 1, + 'b' => 2, + '\d|[ef-g[h]]' => 3, # alternation + '\d' => 4, # first alternative + '[ef-g[h]]' => 4, # second alternative + 'e' => 5, + 'f-g' => 5, + 'f' => 6, + 'g' => 6, + 'h' => 6, + } + + root.each_expression do |exp| + next unless expected_nesting_level = tests.delete(exp.to_s) + expect(expected_nesting_level).to eq exp.nesting_level + end + + expect(tests).to be_empty + end + + specify('#dig') do + root = RP.parse(/(((a)))/) + + expect(root.dig(0).to_s).to eq '(((a)))' + expect(root.dig(0, 0, 0, 0).to_s).to eq 'a' + expect(root.dig(0, 0, 0, 0, 0)).to be_nil + expect(root.dig(3, 7)).to be_nil + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/to_h_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/to_h_spec.rb new file mode 100644 index 00000000..69134aeb --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/to_h_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +RSpec.describe('Expression#to_h') do + specify('Root#to_h') do + root = RP.parse('abc') + + hash = root.to_h + + expect(token: :root, type: :expression, text: 'abc', starts_at: 0, length: 3, quantifier: nil, options: {}, level: nil, set_level: nil, conditional_level: nil, expressions: [{ token: :literal, type: :literal, text: 'abc', starts_at: 0, length: 3, quantifier: nil, options: {}, level: 0, set_level: 0, conditional_level: 0 }]).to eq hash + end + + specify('Quantifier#to_h') do + root = RP.parse('a{2,4}') + exp = root.expressions.at(0) + + hash = exp.quantifier.to_h + + expect(max: 4, min: 2, mode: :greedy, text: '{2,4}', token: :interval).to eq hash + end + + specify('Conditional#to_h') do + root = RP.parse('(?a)(?()b|c)', 'ruby/2.0') + + expect { root.to_h }.not_to(raise_error) + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/to_s_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/to_s_spec.rb new file mode 100644 index 00000000..99801d9a --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/expression/to_s_spec.rb @@ -0,0 +1,100 @@ +require 'spec_helper' + +RSpec.describe('Expression#to_s') do + specify('literal alternation') do + pattern = 'abcd|ghij|klmn|pqur' + + expect(RP.parse(pattern).to_s).to eq pattern + end + + specify('quantified alternations') do + pattern = '(?:a?[b]+(c){2}|d+[e]*(f)?)|(?:g+[h]?(i){2,3}|j*[k]{3,5}(l)?)' + + expect(RP.parse(pattern).to_s).to eq pattern + end + + specify('quantified sets') do + pattern = '[abc]+|[^def]{3,6}' + + expect(RP.parse(pattern).to_s).to eq pattern + end + + specify('property sets') do + pattern = '[\\a\\b\\p{Lu}\\P{Z}\\c\\d]+' + + expect(RP.parse(pattern, 'ruby/1.9').to_s).to eq pattern + end + + specify('groups') do + pattern = "(a(?>b(?:c(?d(?'N'e)??f)+g)*+h)*i)++" + + expect(RP.parse(pattern, 'ruby/1.9').to_s).to eq pattern + end + + specify('assertions') do + pattern = '(a+(?=b+(?!c+(?<=d+(?a)(?()b|c)/, + 3 => [:conditional, :open, '(?', 7, 9, 0, 0, 0], + 4 => [:conditional, :condition, '()', 9, 14, 0, 0, 1], + 6 => [:conditional, :separator, '|', 15, 16, 0, 0, 1], + 8 => [:conditional, :close, ')', 17, 18, 0, 0, 0] + + include_examples 'lex', /((?a)(?(?()b|((?()[e-g]|[h-j])))))/, + 0 => [:group, :capture, '(', 0, 1, 0, 0, 0], + 1 => [:group, :named, '(?', 1, 6, 1, 0, 0], + 5 => [:conditional, :open, '(?', 13, 15, 2, 0, 0], + 6 => [:conditional, :condition, '()', 15, 20, 2, 0, 1], + 8 => [:conditional, :separator, '|', 21, 22, 2, 0, 1], + 10 => [:conditional, :open, '(?', 23, 25, 3, 0, 1], + 11 => [:conditional, :condition, '()', 25, 30, 3, 0, 2], + 12 => [:set, :open, '[', 30, 31, 3, 0, 2], + 13 => [:literal, :literal, 'e', 31, 32, 3, 1, 2], + 14 => [:set, :range, '-', 32, 33, 3, 1, 2], + 15 => [:literal, :literal, 'g', 33, 34, 3, 1, 2], + 16 => [:set, :close, ']', 34, 35, 3, 0, 2], + 17 => [:conditional, :separator, '|', 35, 36, 3, 0, 2], + 23 => [:conditional, :close, ')', 41, 42, 3, 0, 1], + 25 => [:conditional, :close, ')', 43, 44, 2, 0, 0], + 26 => [:group, :close, ')', 44, 45, 1, 0, 0], + 27 => [:group, :close, ')', 45, 46, 0, 0, 0] + + include_examples 'lex', /(a(b(c)))(?(1)(?(2)(?(3)d|e))|(?(3)(?(2)f|g)|(?(1)f|g)))/, + 9 => [:conditional, :open, '(?', 9, 11, 0, 0, 0], + 10 => [:conditional, :condition, '(1)', 11, 14, 0, 0, 1], + 11 => [:conditional, :open, '(?', 14, 16, 0, 0, 1], + 12 => [:conditional, :condition, '(2)', 16, 19, 0, 0, 2], + 13 => [:conditional, :open, '(?', 19, 21, 0, 0, 2], + 14 => [:conditional, :condition, '(3)', 21, 24, 0, 0, 3], + 16 => [:conditional, :separator, '|', 25, 26, 0, 0, 3], + 18 => [:conditional, :close, ')', 27, 28, 0, 0, 2], + 19 => [:conditional, :close, ')', 28, 29, 0, 0, 1], + 20 => [:conditional, :separator, '|', 29, 30, 0, 0, 1], + 21 => [:conditional, :open, '(?', 30, 32, 0, 0, 1], + 22 => [:conditional, :condition, '(3)', 32, 35, 0, 0, 2], + 23 => [:conditional, :open, '(?', 35, 37, 0, 0, 2], + 24 => [:conditional, :condition, '(2)', 37, 40, 0, 0, 3], + 26 => [:conditional, :separator, '|', 41, 42, 0, 0, 3], + 28 => [:conditional, :close, ')', 43, 44, 0, 0, 2], + 29 => [:conditional, :separator, '|', 44, 45, 0, 0, 2], + 30 => [:conditional, :open, '(?', 45, 47, 0, 0, 2], + 31 => [:conditional, :condition, '(1)', 47, 50, 0, 0, 3], + 33 => [:conditional, :separator, '|', 51, 52, 0, 0, 3], + 35 => [:conditional, :close, ')', 53, 54, 0, 0, 2], + 36 => [:conditional, :close, ')', 54, 55, 0, 0, 1], + 37 => [:conditional, :close, ')', 55, 56, 0, 0, 0] +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/lexer/escapes_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/lexer/escapes_spec.rb new file mode 100644 index 00000000..3ea37983 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/lexer/escapes_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +RSpec.describe('Escape lexing') do + include_examples 'lex', '\u{62}', + 0 => [:escape, :codepoint_list, '\u{62}', 0, 6, 0, 0, 0] + + include_examples 'lex', '\u{62 63 64}', + 0 => [:escape, :codepoint_list, '\u{62 63 64}', 0, 12, 0, 0, 0] + + include_examples 'lex', '\u{62 63 64}+', + 0 => [:escape, :codepoint_list, '\u{62 63}', 0, 9, 0, 0, 0], + 1 => [:escape, :codepoint_list, '\u{64}', 9, 15, 0, 0, 0], + 2 => [:quantifier, :one_or_more, '+', 15, 16, 0, 0, 0] +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/lexer/keep_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/lexer/keep_spec.rb new file mode 100644 index 00000000..9748cb3c --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/lexer/keep_spec.rb @@ -0,0 +1,10 @@ +require 'spec_helper' + +RSpec.describe('Keep lexing') do + include_examples 'lex', /ab\Kcd/, + 1 => [:keep, :mark, '\K', 2, 4, 0, 0, 0] + + include_examples 'lex', /(a\Kb)|(c\\\Kd)ef/, + 2 => [:keep, :mark, '\K', 2, 4, 1, 0, 0], + 9 => [:keep, :mark, '\K', 11, 13, 1, 0, 0] +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/lexer/literals_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/lexer/literals_spec.rb new file mode 100644 index 00000000..a298b39c --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/lexer/literals_spec.rb @@ -0,0 +1,89 @@ +require 'spec_helper' + +RSpec.describe('Literal lexing') do + # ascii, single byte characters + include_examples 'lex', 'a', + 0 => [:literal, :literal, 'a', 0, 1, 0, 0, 0] + + include_examples 'lex', 'ab+', + 0 => [:literal, :literal, 'a', 0, 1, 0, 0, 0], + 1 => [:literal, :literal, 'b', 1, 2, 0, 0, 0], + 2 => [:quantifier, :one_or_more, '+', 2, 3, 0, 0, 0] + + # 2 byte wide characters, Arabic + include_examples 'lex', 'ا', + 0 => [:literal, :literal, 'ا', 0, 2, 0, 0, 0] + + include_examples 'lex', 'aاbبcت', + 0 => [:literal, :literal, 'aاbبcت', 0, 9, 0, 0, 0] + + include_examples 'lex', 'aاbبت?', + 0 => [:literal, :literal, 'aاbب', 0, 6, 0, 0, 0], + 1 => [:literal, :literal, 'ت', 6, 8, 0, 0, 0], + 2 => [:quantifier, :zero_or_one, '?', 8, 9, 0, 0, 0] + + include_examples 'lex', 'aا?bبcت+', + 0 => [:literal, :literal, 'a', 0, 1, 0, 0, 0], + 1 => [:literal, :literal, 'ا', 1, 3, 0, 0, 0], + 2 => [:quantifier, :zero_or_one, '?', 3, 4, 0, 0, 0], + 3 => [:literal, :literal, 'bبc', 4, 8, 0, 0, 0], + 4 => [:literal, :literal, 'ت', 8, 10, 0, 0, 0], + 5 => [:quantifier, :one_or_more, '+', 10, 11, 0, 0, 0] + + include_examples 'lex', 'a(اbب+)cت?', + 0 => [:literal, :literal, 'a', 0, 1, 0, 0, 0], + 1 => [:group, :capture, '(', 1, 2, 0, 0, 0], + 2 => [:literal, :literal, 'اb', 2, 5, 1, 0, 0], + 3 => [:literal, :literal, 'ب', 5, 7, 1, 0, 0], + 4 => [:quantifier, :one_or_more, '+', 7, 8, 1, 0, 0], + 5 => [:group, :close, ')', 8, 9, 0, 0, 0], + 6 => [:literal, :literal, 'c', 9, 10, 0, 0, 0], + 7 => [:literal, :literal, 'ت', 10, 12, 0, 0, 0], + 8 => [:quantifier, :zero_or_one, '?', 12, 13, 0, 0, 0] + + # 3 byte wide characters, Japanese + include_examples 'lex', 'ab?れます+cd', + 0 => [:literal, :literal, 'a', 0, 1, 0, 0, 0], + 1 => [:literal, :literal, 'b', 1, 2, 0, 0, 0], + 2 => [:quantifier, :zero_or_one, '?', 2, 3, 0, 0, 0], + 3 => [:literal, :literal, 'れま', 3, 9, 0, 0, 0], + 4 => [:literal, :literal, 'す', 9, 12, 0, 0, 0], + 5 => [:quantifier, :one_or_more, '+', 12, 13, 0, 0, 0], + 6 => [:literal, :literal, 'cd', 13, 15, 0, 0, 0] + + # 4 byte wide characters, Osmanya + include_examples 'lex', '𐒀𐒁?𐒂ab+𐒃', + 0 => [:literal, :literal, '𐒀', 0, 4, 0, 0, 0], + 1 => [:literal, :literal, '𐒁', 4, 8, 0, 0, 0], + 2 => [:quantifier, :zero_or_one, '?', 8, 9, 0, 0, 0], + 3 => [:literal, :literal, '𐒂a', 9, 14, 0, 0, 0], + 4 => [:literal, :literal, 'b', 14, 15, 0, 0, 0], + 5 => [:quantifier, :one_or_more, '+', 15, 16, 0, 0, 0], + 6 => [:literal, :literal, '𐒃', 16, 20, 0, 0, 0] + + include_examples 'lex', 'mu𝄞?si*𝄫c+', + 0 => [:literal, :literal, 'mu', 0, 2, 0, 0, 0], + 1 => [:literal, :literal, '𝄞', 2, 6, 0, 0, 0], + 2 => [:quantifier, :zero_or_one, '?', 6, 7, 0, 0, 0], + 3 => [:literal, :literal, 's', 7, 8, 0, 0, 0], + 4 => [:literal, :literal, 'i', 8, 9, 0, 0, 0], + 5 => [:quantifier, :zero_or_more, '*', 9, 10, 0, 0, 0], + 6 => [:literal, :literal, '𝄫', 10, 14, 0, 0, 0], + 7 => [:literal, :literal, 'c', 14, 15, 0, 0, 0], + 8 => [:quantifier, :one_or_more, '+', 15, 16, 0, 0, 0] + + specify('lex single 2 byte char') do + tokens = RL.lex("\u0627+") + expect(tokens.count).to eq 2 + end + + specify('lex single 3 byte char') do + tokens = RL.lex("\u308C+") + expect(tokens.count).to eq 2 + end + + specify('lex single 4 byte char') do + tokens = RL.lex("\u{1D11E}+") + expect(tokens.count).to eq 2 + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/lexer/nesting_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/lexer/nesting_spec.rb new file mode 100644 index 00000000..277817bd --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/lexer/nesting_spec.rb @@ -0,0 +1,99 @@ +require 'spec_helper' + +RSpec.describe('Nesting lexing') do + include_examples 'lex', /(((b)))/, + 0 => [:group, :capture, '(', 0, 1, 0, 0, 0], + 1 => [:group, :capture, '(', 1, 2, 1, 0, 0], + 2 => [:group, :capture, '(', 2, 3, 2, 0, 0], + 3 => [:literal, :literal, 'b', 3, 4, 3, 0, 0], + 4 => [:group, :close, ')', 4, 5, 2, 0, 0], + 5 => [:group, :close, ')', 5, 6, 1, 0, 0], + 6 => [:group, :close, ')', 6, 7, 0, 0, 0] + + include_examples 'lex', /(\((b)\))/, + 0 => [:group, :capture, '(', 0, 1, 0, 0, 0], + 1 => [:escape, :group_open, '\(', 1, 3, 1, 0, 0], + 2 => [:group, :capture, '(', 3, 4, 1, 0, 0], + 3 => [:literal, :literal, 'b', 4, 5, 2, 0, 0], + 4 => [:group, :close, ')', 5, 6, 1, 0, 0], + 5 => [:escape, :group_close, '\)', 6, 8, 1, 0, 0], + 6 => [:group, :close, ')', 8, 9, 0, 0, 0] + + include_examples 'lex', /(?>a(?>b(?>c)))/, + 0 => [:group, :atomic, '(?>', 0, 3, 0, 0, 0], + 2 => [:group, :atomic, '(?>', 4, 7, 1, 0, 0], + 4 => [:group, :atomic, '(?>', 8, 11, 2, 0, 0], + 6 => [:group, :close, ')', 12, 13, 2, 0, 0], + 7 => [:group, :close, ')', 13, 14, 1, 0, 0], + 8 => [:group, :close, ')', 14, 15, 0, 0, 0] + + include_examples 'lex', /(?:a(?:b(?:c)))/, + 0 => [:group, :passive, '(?:', 0, 3, 0, 0, 0], + 2 => [:group, :passive, '(?:', 4, 7, 1, 0, 0], + 4 => [:group, :passive, '(?:', 8, 11, 2, 0, 0], + 6 => [:group, :close, ')', 12, 13, 2, 0, 0], + 7 => [:group, :close, ')', 13, 14, 1, 0, 0], + 8 => [:group, :close, ')', 14, 15, 0, 0, 0] + + include_examples 'lex', /(?=a(?!b(?<=c(? [:assertion, :lookahead, '(?=', 0, 3, 0, 0, 0], + 2 => [:assertion, :nlookahead, '(?!', 4, 7, 1, 0, 0], + 4 => [:assertion, :lookbehind, '(?<=', 8, 12, 2, 0, 0], + 6 => [:assertion, :nlookbehind, '(? [:group, :close, ')', 18, 19, 3, 0, 0], + 9 => [:group, :close, ')', 19, 20, 2, 0, 0], + 10 => [:group, :close, ')', 20, 21, 1, 0, 0], + 11 => [:group, :close, ')', 21, 22, 0, 0, 0] + + include_examples 'lex', /((?#a)b(?#c)d(?#e))/, + 0 => [:group, :capture, '(', 0, 1, 0, 0, 0], + 1 => [:group, :comment, '(?#a)', 1, 6, 1, 0, 0], + 3 => [:group, :comment, '(?#c)', 7, 12, 1, 0, 0], + 5 => [:group, :comment, '(?#e)', 13, 18, 1, 0, 0], + 6 => [:group, :close, ')', 18, 19, 0, 0, 0] + + include_examples 'lex', /a[b-e]f/, + 1 => [:set, :open, '[', 1, 2, 0, 0, 0], + 2 => [:literal, :literal, 'b', 2, 3, 0, 1, 0], + 3 => [:set, :range, '-', 3, 4, 0, 1, 0], + 4 => [:literal, :literal, 'e', 4, 5, 0, 1, 0], + 5 => [:set, :close, ']', 5, 6, 0, 0, 0] + + include_examples 'lex', /[[:word:]&&[^c]z]/, + 0 => [:set, :open, '[', 0, 1, 0, 0, 0], + 1 => [:posixclass, :word, '[:word:]', 1, 9, 0, 1, 0], + 2 => [:set, :intersection, '&&', 9, 11, 0, 1, 0], + 3 => [:set, :open, '[', 11, 12, 0, 1, 0], + 4 => [:set, :negate, '^', 12, 13, 0, 2, 0], + 5 => [:literal, :literal, 'c', 13, 14, 0, 2, 0], + 6 => [:set, :close, ']', 14, 15, 0, 1, 0], + 7 => [:literal, :literal, 'z', 15, 16, 0, 1, 0], + 8 => [:set, :close, ']', 16, 17, 0, 0, 0] + + include_examples 'lex', /[\p{word}&&[^c]z]/, + 0 => [:set, :open, '[', 0, 1, 0, 0, 0], + 1 => [:property, :word, '\p{word}', 1, 9, 0, 1, 0], + 2 => [:set, :intersection, '&&', 9, 11, 0, 1, 0], + 3 => [:set, :open, '[', 11, 12, 0, 1, 0], + 4 => [:set, :negate, '^', 12, 13, 0, 2, 0], + 5 => [:literal, :literal, 'c', 13, 14, 0, 2, 0], + 6 => [:set, :close, ']', 14, 15, 0, 1, 0], + 7 => [:literal, :literal, 'z', 15, 16, 0, 1, 0], + 8 => [:set, :close, ']', 16, 17, 0, 0, 0] + + include_examples 'lex', /[a[b[c[d-g]]]]/, + 0 => [:set, :open, '[', 0, 1, 0, 0, 0], + 1 => [:literal, :literal, 'a', 1, 2, 0, 1, 0], + 2 => [:set, :open, '[', 2, 3, 0, 1, 0], + 3 => [:literal, :literal, 'b', 3, 4, 0, 2, 0], + 4 => [:set, :open, '[', 4, 5, 0, 2, 0], + 5 => [:literal, :literal, 'c', 5, 6, 0, 3, 0], + 6 => [:set, :open, '[', 6, 7, 0, 3, 0], + 7 => [:literal, :literal, 'd', 7, 8, 0, 4, 0], + 8 => [:set, :range, '-', 8, 9, 0, 4, 0], + 9 => [:literal, :literal, 'g', 9, 10, 0, 4, 0], + 10 => [:set, :close, ']', 10, 11, 0, 3, 0], + 11 => [:set, :close, ']', 11, 12, 0, 2, 0], + 12 => [:set, :close, ']', 12, 13, 0, 1, 0], + 13 => [:set, :close, ']', 13, 14, 0, 0, 0] +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/lexer/refcalls_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/lexer/refcalls_spec.rb new file mode 100644 index 00000000..a41868b6 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/lexer/refcalls_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +RSpec.describe('RefCall lexing') do + # Traditional numerical group back-reference + include_examples 'lex', '(abc)\1', + 3 => [:backref, :number, '\1', 5, 7, 0, 0, 0] + + # Group back-references, named, numbered, and relative + include_examples 'lex', '(?abc)\k', + 3 => [:backref, :name_ref, '\k', 9, 14, 0, 0, 0] + include_examples 'lex', "(?abc)\\k'X'", + 3 => [:backref, :name_ref, "\\k'X'", 9, 14, 0, 0, 0] + + include_examples 'lex', '(abc)\k<1>', + 3 => [:backref, :number_ref, '\k<1>', 5, 10, 0, 0, 0] + include_examples 'lex', "(abc)\\k'1'", + 3 => [:backref, :number_ref, "\\k'1'", 5, 10, 0, 0, 0] + + include_examples 'lex', '(abc)\k<-1>', + 3 => [:backref, :number_rel_ref, '\k<-1>', 5, 11, 0, 0, 0] + include_examples 'lex', "(abc)\\k'-1'", + 3 => [:backref, :number_rel_ref, "\\k'-1'", 5, 11, 0, 0, 0] + + # Sub-expression invocation, named, numbered, and relative + include_examples 'lex', '(?abc)\g', + 3 => [:backref, :name_call, '\g', 9, 14, 0, 0, 0] + include_examples 'lex', "(?abc)\\g'X'", + 3 => [:backref, :name_call, "\\g'X'", 9, 14, 0, 0, 0] + + include_examples 'lex', '(abc)\g<1>', + 3 => [:backref, :number_call, '\g<1>', 5, 10, 0, 0, 0] + include_examples 'lex', "(abc)\\g'1'", + 3 => [:backref, :number_call, "\\g'1'", 5, 10, 0, 0, 0] + + include_examples 'lex', '(abc)\g<-1>', + 3 => [:backref, :number_rel_call, '\g<-1>', 5, 11, 0, 0, 0] + include_examples 'lex', "(abc)\\g'-1'", + 3 => [:backref, :number_rel_call, "\\g'-1'", 5, 11, 0, 0, 0] + + include_examples 'lex', '(abc)\g<+1>', + 3 => [:backref, :number_rel_call, '\g<+1>', 5, 11, 0, 0, 0] + include_examples 'lex', "(abc)\\g'+1'", + 3 => [:backref, :number_rel_call, "\\g'+1'", 5, 11, 0, 0, 0] + + # Group back-references, with nesting level + include_examples 'lex', '(?abc)\k', + 3 => [:backref, :name_recursion_ref, '\k', 9, 16, 0, 0, 0] + include_examples 'lex', "(?abc)\\k'X-0'", + 3 => [:backref, :name_recursion_ref, "\\k'X-0'", 9, 16, 0, 0, 0] + + include_examples 'lex', '(abc)\k<1-0>', + 3 => [:backref, :number_recursion_ref, '\k<1-0>', 5, 12, 0, 0, 0] + include_examples 'lex', "(abc)\\k'1-0'", + 3 => [:backref, :number_recursion_ref, "\\k'1-0'", 5, 12, 0, 0, 0] +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/all_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/all_spec.rb new file mode 100644 index 00000000..8517c717 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/all_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' + +RSpec.describe(Regexp::Parser) do + specify('parse returns a root expression') do + expect(RP.parse('abc')).to be_instance_of(Root) + end + + specify('parse can be called with block') do + expect(RP.parse('abc') { |root| root.class }).to eq Root + end + + specify('parse root contains expressions') do + root = RP.parse(/^a.c+[^one]{2,3}\b\d\\\C-C$/) + expect(root.expressions).to all(be_a Regexp::Expression::Base) + end + + specify('parse root options mi') do + root = RP.parse(/[abc]/mi, 'ruby/1.8') + + expect(root.m?).to be true + expect(root.i?).to be true + expect(root.x?).to be false + end + + specify('parse node types') do + root = RP.parse('^(one){2,3}([^d\\]efm-qz\\,\\-]*)(ghi)+$') + + expect(root[1][0]).to be_a(Literal) + expect(root[1]).to be_quantified + expect(root[2][0]).to be_a(CharacterSet) + expect(root[2]).not_to be_quantified + expect(root[3]).to be_a(Group::Capture) + expect(root[3]).to be_quantified + end + + specify('parse no quantifier target raises error') do + expect { RP.parse('?abc') }.to raise_error(ArgumentError) + end + + specify('parse sequence no quantifier target raises error') do + expect { RP.parse('abc|?def') }.to raise_error(ArgumentError) + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/alternation_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/alternation_spec.rb new file mode 100644 index 00000000..5c45901b --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/alternation_spec.rb @@ -0,0 +1,88 @@ +require 'spec_helper' + +RSpec.describe('Alternation parsing') do + let(:root) { RP.parse('(ab??|cd*|ef+)*|(gh|ij|kl)?') } + + specify('parse alternation root') do + e = root[0] + expect(e).to be_a(Alternation) + end + + specify('parse alternation alts') do + alts = root[0].alternatives + + expect(alts[0]).to be_a(Alternative) + expect(alts[1]).to be_a(Alternative) + + expect(alts[0][0]).to be_a(Group::Capture) + expect(alts[1][0]).to be_a(Group::Capture) + + expect(alts.length).to eq 2 + end + + specify('parse alternation nested') do + e = root[0].alternatives[0][0][0] + + expect(e).to be_a(Alternation) + end + + specify('parse alternation nested sequence') do + alts = root[0][0] + nested = alts[0][0][0] + + expect(nested).to be_a(Alternative) + + expect(nested[0]).to be_a(Literal) + expect(nested[1]).to be_a(Literal) + expect(nested.expressions.length).to eq 2 + end + + specify('parse alternation nested groups') do + root = RP.parse('(i|ey|([ougfd]+)|(ney))') + + alts = root[0][0].alternatives + expect(alts.length).to eq 4 + end + + specify('parse alternation grouped alts') do + root = RP.parse('ca((n)|(t)|(ll)|(b))') + + alts = root[1][0].alternatives + + expect(alts.length).to eq 4 + + expect(alts[0]).to be_a(Alternative) + expect(alts[1]).to be_a(Alternative) + expect(alts[2]).to be_a(Alternative) + expect(alts[3]).to be_a(Alternative) + end + + specify('parse alternation nested grouped alts') do + root = RP.parse('ca((n|t)|(ll|b))') + + alts = root[1][0].alternatives + + expect(alts.length).to eq 2 + + expect(alts[0]).to be_a(Alternative) + expect(alts[1]).to be_a(Alternative) + + subalts = root[1][0][0][0][0].alternatives + + expect(alts.length).to eq 2 + + expect(subalts[0]).to be_a(Alternative) + expect(subalts[1]).to be_a(Alternative) + end + + specify('parse alternation continues after nesting') do + root = RP.parse(/a|(b)c/) + + seq = root[0][1].expressions + + expect(seq.length).to eq 2 + + expect(seq[0]).to be_a(Group::Capture) + expect(seq[1]).to be_a(Literal) + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/anchors_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/anchors_spec.rb new file mode 100644 index 00000000..f74bed46 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/anchors_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +RSpec.describe('Anchor parsing') do + include_examples 'parse', /^a/, 0 => [:anchor, :bol, Anchor::BOL] + include_examples 'parse', /a$/, 1 => [:anchor, :eol, Anchor::EOL] + + include_examples 'parse', /\Aa/, 0 => [:anchor, :bos, Anchor::BOS] + include_examples 'parse', /a\z/, 1 => [:anchor, :eos, Anchor::EOS] + include_examples 'parse', /a\Z/, 1 => [:anchor, :eos_ob_eol, Anchor::EOSobEOL] + + include_examples 'parse', /a\b/, 1 => [:anchor, :word_boundary, Anchor::WordBoundary] + include_examples 'parse', /a\B/, 1 => [:anchor, :nonword_boundary, Anchor::NonWordBoundary] + + include_examples 'parse', /a\G/, 1 => [:anchor, :match_start, Anchor::MatchStart] + + include_examples 'parse', /\\A/, 0 => [:escape, :backslash, EscapeSequence::Literal] +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/conditionals_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/conditionals_spec.rb new file mode 100644 index 00000000..55397d9f --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/conditionals_spec.rb @@ -0,0 +1,179 @@ +require 'spec_helper' + +RSpec.describe('Conditional parsing') do + specify('parse conditional') do + regexp = /(?a)(?()T|F)/ + + root = RP.parse(regexp, 'ruby/2.0') + exp = root[1] + + expect(exp).to be_a(Conditional::Expression) + + expect(exp.type).to eq :conditional + expect(exp.token).to eq :open + expect(exp.to_s).to eq '(?()T|F)' + expect(exp.reference).to eq 'A' + end + + specify('parse conditional condition') do + regexp = /(?a)(?()T|F)/ + + root = RP.parse(regexp, 'ruby/2.0') + exp = root[1].condition + + expect(exp).to be_a(Conditional::Condition) + + expect(exp.type).to eq :conditional + expect(exp.token).to eq :condition + expect(exp.to_s).to eq '()' + expect(exp.reference).to eq 'A' + expect(exp.referenced_expression.to_s).to eq '(?a)' + end + + specify('parse conditional condition with number ref') do + regexp = /(a)(?(1)T|F)/ + + root = RP.parse(regexp, 'ruby/2.0') + exp = root[1].condition + + expect(exp).to be_a(Conditional::Condition) + + expect(exp.type).to eq :conditional + expect(exp.token).to eq :condition + expect(exp.to_s).to eq '(1)' + expect(exp.reference).to eq 1 + expect(exp.referenced_expression.to_s).to eq '(a)' + end + + specify('parse conditional nested groups') do + regexp = /((a)|(b)|((?(2)(c(d|e)+)?|(?(3)f|(?(4)(g|(h)(i)))))))/ + + root = RP.parse(regexp, 'ruby/2.0') + + expect(root.to_s).to eq regexp.source + + group = root.first + expect(group).to be_instance_of(Group::Capture) + + alt = group.first + expect(alt).to be_instance_of(Alternation) + expect(alt.length).to eq 3 + + expect(alt.map(&:first)).to all(be_a Group::Capture) + + subgroup = alt[2].first + conditional = subgroup.first + + expect(conditional).to be_instance_of(Conditional::Expression) + expect(conditional.length).to eq 3 + + expect(conditional[0]).to be_instance_of(Conditional::Condition) + expect(conditional[0].to_s).to eq '(2)' + + condition = conditional.condition + expect(condition).to be_instance_of(Conditional::Condition) + expect(condition.to_s).to eq '(2)' + + branches = conditional.branches + expect(branches.length).to eq 2 + expect(branches).to be_instance_of(Array) + end + + specify('parse conditional nested') do + regexp = /(a(b(c(d)(e))))(?(1)(?(2)d|(?(3)e|f))|(?(4)(?(5)g|h)))/ + + root = RP.parse(regexp, 'ruby/2.0') + + expect(root.to_s).to eq regexp.source + + { + 1 => [2, root[1]], + 2 => [2, root[1][1][0]], + 3 => [2, root[1][1][0][2][0]], + 4 => [1, root[1][2][0]], + 5 => [2, root[1][2][0][1][0]] + }.each do |index, example| + branch_count, exp = example + + expect(exp).to be_instance_of(Conditional::Expression) + expect(exp.condition.to_s).to eq "(#{index})" + expect(exp.branches.length).to eq branch_count + end + end + + specify('parse conditional nested alternation') do + regexp = /(a)(?(1)(b|c|d)|(e|f|g))(h)(?(2)(i|j|k)|(l|m|n))|o|p/ + + root = RP.parse(regexp, 'ruby/2.0') + + expect(root.to_s).to eq regexp.source + + expect(root.first).to be_instance_of(Alternation) + + [ + [3, 'b|c|d', root[0][0][1][1][0][0]], + [3, 'e|f|g', root[0][0][1][2][0][0]], + [3, 'i|j|k', root[0][0][3][1][0][0]], + [3, 'l|m|n', root[0][0][3][2][0][0]] + ].each do |example| + alt_count, alt_text, exp = example + + expect(exp).to be_instance_of(Alternation) + expect(exp.to_s).to eq alt_text + expect(exp.alternatives.length).to eq alt_count + end + end + + specify('parse conditional extra separator') do + regexp = /(?a)(?()T|)/ + + root = RP.parse(regexp, 'ruby/2.0') + branches = root[1].branches + + expect(branches.length).to eq 2 + + seq_1, seq_2 = branches + + [seq_1, seq_2].each do |seq| + expect(seq).to be_a(Sequence) + + expect(seq.type).to eq :expression + expect(seq.token).to eq :sequence + end + + expect(seq_1.to_s).to eq 'T' + expect(seq_2.to_s).to eq '' + end + + specify('parse conditional quantified') do + regexp = /(foo)(?(1)\d|(\w)){42}/ + + root = RP.parse(regexp, 'ruby/2.0') + conditional = root[1] + + expect(conditional).to be_quantified + expect(conditional.quantifier.to_s).to eq '{42}' + expect(conditional.to_s).to eq '(?(1)\\d|(\\w)){42}' + expect(conditional.branches.any?(&:quantified?)).to be false + end + + specify('parse conditional branch content quantified') do + regexp = /(foo)(?(1)\d{23}|(\w){42})/ + + root = RP.parse(regexp, 'ruby/2.0') + conditional = root[1] + + expect(conditional).not_to be_quantified + expect(conditional.branches.any?(&:quantified?)).to be false + expect(conditional.branches[0][0]).to be_quantified + expect(conditional.branches[0][0].quantifier.to_s).to eq '{23}' + expect(conditional.branches[1][0]).to be_quantified + expect(conditional.branches[1][0].quantifier.to_s).to eq '{42}' + end + + specify('parse conditional excessive branches') do + regexp = '(?a)(?()T|F|X)' + + expect { RP.parse(regexp, 'ruby/2.0') }.to raise_error(Conditional::TooManyBranches) + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/errors_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/errors_spec.rb new file mode 100644 index 00000000..18658678 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/errors_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +RSpec.describe('Parsing errors') do + let(:parser) { Regexp::Parser.new } + before { parser.parse(/foo/) } # initializes ivars + + it('raises UnknownTokenTypeError for unknown token types') do + expect { parser.send(:parse_token, Regexp::Token.new(:foo, :bar)) } + .to raise_error(Regexp::Parser::UnknownTokenTypeError) + end + + RSpec.shared_examples 'UnknownTokenError' do |type, token| + it "raises for unkown tokens of type #{type}" do + expect { parser.send(:parse_token, Regexp::Token.new(type, :foo)) } + .to raise_error(Regexp::Parser::UnknownTokenError) + end + end + + include_examples 'UnknownTokenError', :anchor + include_examples 'UnknownTokenError', :backref + include_examples 'UnknownTokenError', :conditional + include_examples 'UnknownTokenError', :free_space + include_examples 'UnknownTokenError', :group + include_examples 'UnknownTokenError', :meta + include_examples 'UnknownTokenError', :nonproperty + include_examples 'UnknownTokenError', :property + include_examples 'UnknownTokenError', :quantifier + include_examples 'UnknownTokenError', :set + include_examples 'UnknownTokenError', :type +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/escapes_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/escapes_spec.rb new file mode 100644 index 00000000..7c7d822f --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/escapes_spec.rb @@ -0,0 +1,121 @@ +require 'spec_helper' + +RSpec.describe('EscapeSequence parsing') do + include_examples 'parse', /a\ac/, 1 => [:escape, :bell, EscapeSequence::Bell] + include_examples 'parse', /a\ec/, 1 => [:escape, :escape, EscapeSequence::AsciiEscape] + include_examples 'parse', /a\fc/, 1 => [:escape, :form_feed, EscapeSequence::FormFeed] + include_examples 'parse', /a\nc/, 1 => [:escape, :newline, EscapeSequence::Newline] + include_examples 'parse', /a\rc/, 1 => [:escape, :carriage, EscapeSequence::Return] + include_examples 'parse', /a\tc/, 1 => [:escape, :tab, EscapeSequence::Tab] + include_examples 'parse', /a\vc/, 1 => [:escape, :vertical_tab, EscapeSequence::VerticalTab] + + # meta character escapes + include_examples 'parse', /a\.c/, 1 => [:escape, :dot, EscapeSequence::Literal] + include_examples 'parse', /a\?c/, 1 => [:escape, :zero_or_one, EscapeSequence::Literal] + include_examples 'parse', /a\*c/, 1 => [:escape, :zero_or_more, EscapeSequence::Literal] + include_examples 'parse', /a\+c/, 1 => [:escape, :one_or_more, EscapeSequence::Literal] + include_examples 'parse', /a\|c/, 1 => [:escape, :alternation, EscapeSequence::Literal] + include_examples 'parse', /a\(c/, 1 => [:escape, :group_open, EscapeSequence::Literal] + include_examples 'parse', /a\)c/, 1 => [:escape, :group_close, EscapeSequence::Literal] + include_examples 'parse', /a\{c/, 1 => [:escape, :interval_open, EscapeSequence::Literal] + include_examples 'parse', /a\}c/, 1 => [:escape, :interval_close, EscapeSequence::Literal] + + # unicode escapes + include_examples 'parse', /a\u0640/, 1 => [:escape, :codepoint, EscapeSequence::Codepoint] + include_examples 'parse', /a\u{41 1F60D}/, 1 => [:escape, :codepoint_list, EscapeSequence::CodepointList] + include_examples 'parse', /a\u{10FFFF}/, 1 => [:escape, :codepoint_list, EscapeSequence::CodepointList] + + # hex escapes + include_examples 'parse', /a\xFF/n, 1 => [:escape, :hex, EscapeSequence::Hex] + + # octal escapes + include_examples 'parse', /a\177/n, 1 => [:escape, :octal, EscapeSequence::Octal] + + specify('parse chars and codepoints') do + root = RP.parse(/\n\?\101\x42\u0043\u{44 45}/) + + expect(root[0].char).to eq "\n" + expect(root[0].codepoint).to eq 10 + + expect(root[1].char).to eq '?' + expect(root[1].codepoint).to eq 63 + + expect(root[2].char).to eq 'A' + expect(root[2].codepoint).to eq 65 + + expect(root[3].char).to eq 'B' + expect(root[3].codepoint).to eq 66 + + expect(root[4].char).to eq 'C' + expect(root[4].codepoint).to eq 67 + + expect(root[5].chars).to eq %w[D E] + expect(root[5].codepoints).to eq [68, 69] + + expect { root[5].char }.to raise_error(/#chars/) + expect { root[5].codepoint }.to raise_error(/#codepoints/) + end + + specify('parse escape control sequence lower') do + root = RP.parse(/a\\\c2b/) + + expect(root[2]).to be_instance_of(EscapeSequence::Control) + expect(root[2].text).to eq '\\c2' + expect(root[2].char).to eq "\x12" + expect(root[2].codepoint).to eq 18 + end + + specify('parse escape control sequence upper') do + root = RP.parse(/\d\\\C-C\w/) + + expect(root[2]).to be_instance_of(EscapeSequence::Control) + expect(root[2].text).to eq '\\C-C' + expect(root[2].char).to eq "\x03" + expect(root[2].codepoint).to eq 3 + end + + specify('parse escape meta sequence') do + root = RP.parse(/\Z\\\M-Z/n) + + expect(root[2]).to be_instance_of(EscapeSequence::Meta) + expect(root[2].text).to eq '\\M-Z' + expect(root[2].char).to eq "\u00DA" + expect(root[2].codepoint).to eq 218 + end + + specify('parse escape meta control sequence') do + root = RP.parse(/\A\\\M-\C-X/n) + + expect(root[2]).to be_instance_of(EscapeSequence::MetaControl) + expect(root[2].text).to eq '\\M-\\C-X' + expect(root[2].char).to eq "\u0098" + expect(root[2].codepoint).to eq 152 + end + + specify('parse lower c meta control sequence') do + root = RP.parse(/\A\\\M-\cX/n) + + expect(root[2]).to be_instance_of(EscapeSequence::MetaControl) + expect(root[2].text).to eq '\\M-\\cX' + expect(root[2].char).to eq "\u0098" + expect(root[2].codepoint).to eq 152 + end + + specify('parse escape reverse meta control sequence') do + root = RP.parse(/\A\\\C-\M-X/n) + + expect(root[2]).to be_instance_of(EscapeSequence::MetaControl) + expect(root[2].text).to eq '\\C-\\M-X' + expect(root[2].char).to eq "\u0098" + expect(root[2].codepoint).to eq 152 + end + + specify('parse escape reverse lower c meta control sequence') do + root = RP.parse(/\A\\\c\M-X/n) + + expect(root[2]).to be_instance_of(EscapeSequence::MetaControl) + expect(root[2].text).to eq '\\c\\M-X' + expect(root[2].char).to eq "\u0098" + expect(root[2].codepoint).to eq 152 + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/free_space_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/free_space_spec.rb new file mode 100644 index 00000000..e6841565 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/free_space_spec.rb @@ -0,0 +1,130 @@ +require 'spec_helper' + +RSpec.describe('FreeSpace parsing') do + specify('parse free space spaces') do + regexp = /a ? b * c + d{2,4}/x + root = RP.parse(regexp) + + 0.upto(6) do |i| + if i.odd? + expect(root[i]).to be_instance_of(WhiteSpace) + expect(root[i].text).to eq ' ' + else + expect(root[i]).to be_instance_of(Literal) + expect(root[i]).to be_quantified + end + end + end + + specify('parse non free space literals') do + regexp = /a b c d/ + root = RP.parse(regexp) + + expect(root.first).to be_instance_of(Literal) + expect(root.first.text).to eq 'a b c d' + end + + specify('parse free space comments') do + regexp = / + a ? # One letter + b {2,5} # Another one + [c-g] + # A set + (h|i|j) | # A group + klm * + nop + + /x + + root = RP.parse(regexp) + + alt = root.first + expect(alt).to be_instance_of(Alternation) + + alt_1 = alt.alternatives.first + expect(alt_1).to be_instance_of(Alternative) + expect(alt_1.length).to eq 15 + + [0, 2, 4, 6, 8, 12, 14].each do |i| + expect(alt_1[i]).to be_instance_of(WhiteSpace) + end + + [3, 7, 11].each { |i| expect(alt_1[i].class).to eq Comment } + + alt_2 = alt.alternatives.last + expect(alt_2).to be_instance_of(Alternative) + expect(alt_2.length).to eq 7 + + [0, 2, 4, 6].each { |i| expect(alt_2[i].class).to eq WhiteSpace } + + expect(alt_2[1]).to be_instance_of(Comment) + end + + specify('parse free space nested comments') do + regexp = / + # Group one + ( + abc # Comment one + \d? # Optional \d + )+ + + # Group two + ( + def # Comment two + \s? # Optional \s + )? + /x + + root = RP.parse(regexp) + + top_comment_1 = root[1] + expect(top_comment_1).to be_instance_of(Comment) + expect(top_comment_1.text).to eq "# Group one\n" + expect(top_comment_1.starts_at).to eq 7 + + top_comment_2 = root[5] + expect(top_comment_2).to be_instance_of(Comment) + expect(top_comment_2.text).to eq "# Group two\n" + expect(top_comment_2.starts_at).to eq 95 + + [3, 7].each do |g,| + group = root[g] + + [3, 7].each do |c| + comment = group[c] + expect(comment).to be_instance_of(Comment) + expect(comment.text.length).to eq 14 + end + end + end + + specify('parse free space quantifiers') do + regexp = / + a + # comment 1 + ? + ( + b # comment 2 + # comment 3 + + + ) + # comment 4 + * + /x + + root = RP.parse(regexp) + + literal_1 = root[1] + expect(literal_1).to be_instance_of(Literal) + expect(literal_1).to be_quantified + expect(literal_1.quantifier.token).to eq :zero_or_one + + group = root[5] + expect(group).to be_instance_of(Group::Capture) + expect(group).to be_quantified + expect(group.quantifier.token).to eq :zero_or_more + + literal_2 = group[1] + expect(literal_2).to be_instance_of(Literal) + expect(literal_2).to be_quantified + expect(literal_2.quantifier.token).to eq :one_or_more + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/groups_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/groups_spec.rb new file mode 100644 index 00000000..e5970699 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/groups_spec.rb @@ -0,0 +1,108 @@ +require 'spec_helper' + +RSpec.describe('Group parsing') do + include_examples 'parse', /(?=abc)(?!def)/, + 0 => [:assertion, :lookahead, Assertion::Lookahead], + 1 => [:assertion, :nlookahead, Assertion::NegativeLookahead] + + include_examples 'parse', /(?<=abc)(? [:assertion, :lookbehind, Assertion::Lookbehind], + 1 => [:assertion, :nlookbehind, Assertion::NegativeLookbehind] + + include_examples 'parse', /a(?# is for apple)b(?# for boy)c(?# cat)/, + 1 => [:group, :comment, Group::Comment], + 3 => [:group, :comment, Group::Comment], + 5 => [:group, :comment, Group::Comment] + + if ruby_version_at_least('2.4.1') + include_examples 'parse', 'a(?~b)c(?~d)e', + 1 => [:group, :absence, Group::Absence], + 3 => [:group, :absence, Group::Absence] + end + + include_examples 'parse', /(?m:a)/, + 0 => [:group, :options, Group::Options, options: { m: true }, option_changes: { m: true }] + + # self-defeating group option + include_examples 'parse', /(?m-m:a)/, + 0 => [:group, :options, Group::Options, options: {}, option_changes: { m: false }] + + # activate one option in nested group + include_examples 'parse', /(?x-mi:a(?m:b))/, + 0 => [:group, :options, Group::Options, options: { x: true }, option_changes: { i: false, m: false, x: true }], + [0, 1] => [:group, :options, Group::Options, options: { m: true, x: true }, option_changes: { m: true }] + + # deactivate one option in nested group + include_examples 'parse', /(?ix-m:a(?-i:b))/, + 0 => [:group, :options, Group::Options, options: { i: true, x: true }, option_changes: { i: true, m: false, x: true }], + [0, 1] => [:group, :options, Group::Options, options: { x: true }, option_changes: { i: false }] + + # invert all options in nested group + include_examples 'parse', /(?xi-m:a(?m-ix:b))/, + 0 => [:group, :options, Group::Options, options: { i: true, x: true }, option_changes: { i: true, m: false, x: true }], + [0, 1] => [:group, :options, Group::Options, options: { m: true }, option_changes: { i: false, m: true, x: false }] + + # nested options affect literal subexpressions + include_examples 'parse', /(?x-mi:a(?m:b))/, + [0, 0] => [:literal, :literal, Literal, text: 'a', options: { x: true }], + [0, 1, 0] => [:literal, :literal, Literal, text: 'b', options: { m: true, x: true }] + + # option switching group + include_examples 'parse', /a(?i-m)b/m, + 0 => [:literal, :literal, Literal, text: 'a', options: { m: true }], + 1 => [:group, :options_switch, Group::Options, options: { i: true }, option_changes: { i: true, m: false }], + 2 => [:literal, :literal, Literal, text: 'b', options: { i: true }] + + # option switch in group + include_examples 'parse', /(a(?i-m)b)c/m, + 0 => [:group, :capture, Group::Capture, options: { m: true }], + [0, 0] => [:literal, :literal, Literal, text: 'a', options: { m: true }], + [0, 1] => [:group, :options_switch, Group::Options, options: { i: true }, option_changes: { i: true, m: false }], + [0, 2] => [:literal, :literal, Literal, text: 'b', options: { i: true }], + 1 => [:literal, :literal, Literal, text: 'c', options: { m: true }] + + # nested option switch in group + include_examples 'parse', /((?i-m)(a(?-i)b))/m, + [0, 1] => [:group, :capture, Group::Capture, options: { i: true }], + [0, 1, 0] => [:literal, :literal, Literal, text: 'a', options: { i: true }], + [0, 1, 1] => [:group, :options_switch, Group::Options, options: {}, option_changes: { i: false }], + [0, 1, 2] => [:literal, :literal, Literal, text: 'b', options: {}] + + # options dau + include_examples 'parse', /(?dua:abc)/, + 0 => [:group, :options, Group::Options, options: { a: true }, option_changes: { a: true }] + + # nested options dau + include_examples 'parse', /(?u:a(?d:b))/, + 0 => [:group, :options, Group::Options, options: { u: true }, option_changes: { u: true }], + [0, 1] => [:group, :options, Group::Options, options: { d: true }, option_changes: { d: true, u: false }], + [0, 1, 0] => [:literal, :literal, Literal, text: 'b', options: { d: true }] + + # nested options da + include_examples 'parse', /(?di-xm:a(?da-x:b))/, + 0 => [:group, :options, Group::Options, options: { d: true, i:true }], + [0, 1] => [:group, :options, Group::Options, options: { a: true, i: true }, option_changes: { a: true, d: false, x: false}], + [0, 1, 0] => [:literal, :literal, Literal, text: 'b', options: { a: true, i: true }] + + specify('parse group number') do + root = RP.parse(/(a)(?=b)((?:c)(d|(e)))/) + + expect(root[0].number).to eq 1 + expect(root[1]).not_to respond_to(:number) + expect(root[2].number).to eq 2 + expect(root[2][0]).not_to respond_to(:number) + expect(root[2][1].number).to eq 3 + expect(root[2][1][0][1][0].number).to eq 4 + end + + specify('parse group number at level') do + root = RP.parse(/(a)(?=b)((?:c)(d|(e)))/) + + expect(root[0].number_at_level).to eq 1 + expect(root[1]).not_to respond_to(:number_at_level) + expect(root[2].number_at_level).to eq 2 + expect(root[2][0]).not_to respond_to(:number_at_level) + expect(root[2][1].number_at_level).to eq 1 + expect(root[2][1][0][1][0].number_at_level).to eq 1 + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/keep_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/keep_spec.rb new file mode 100644 index 00000000..6546822a --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/keep_spec.rb @@ -0,0 +1,6 @@ +require 'spec_helper' + +RSpec.describe('Keep parsing') do + include_examples 'parse', /ab\Kcd/, 1 => [:keep, :mark, Keep::Mark, text: '\K'] + include_examples 'parse', /(a\K)/, [0, 1] => [:keep, :mark, Keep::Mark, text: '\K'] +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/posix_classes_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/posix_classes_spec.rb new file mode 100644 index 00000000..bc0172b6 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/posix_classes_spec.rb @@ -0,0 +1,8 @@ +require 'spec_helper' + +RSpec.describe('PosixClass parsing') do + include_examples 'parse', /[[:word:]]/, [0, 0] => [:posixclass, :word, PosixClass, + name: 'word', text: '[:word:]', negative?: false] + include_examples 'parse', /[[:^word:]]/, [0, 0] => [:nonposixclass, :word, PosixClass, + name: 'word', text: '[:^word:]', negative?: true] +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/properties_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/properties_spec.rb new file mode 100644 index 00000000..f09e3773 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/properties_spec.rb @@ -0,0 +1,115 @@ +require 'spec_helper' + +RSpec.describe('Property parsing') do + example_props = [ + 'Alnum', + 'Any', + 'Age=1.1', + 'Dash', + 'di', + 'Default_Ignorable_Code_Point', + 'Math', + 'Noncharacter-Code_Point', # test dash + 'sd', + 'Soft Dotted', # test whitespace + 'sterm', + 'xidc', + 'XID_Continue', + 'Emoji', + 'InChessSymbols' + ] + + example_props.each do |name| + it("parses property #{name}") do + exp = RP.parse("ab\\p{#{name}}", '*').last + + expect(exp).to be_a(UnicodeProperty::Base) + expect(exp.type).to eq :property + expect(exp.name).to eq name + end + + it("parses nonproperty #{name}") do + exp = RP.parse("ab\\P{#{name}}", '*').last + + expect(exp).to be_a(UnicodeProperty::Base) + expect(exp.type).to eq :nonproperty + expect(exp.name).to eq name + end + end + + specify('parse all properties of current ruby') do + unsupported = RegexpPropertyValues.all_for_current_ruby.reject do |prop| + RP.parse("\\p{#{prop}}") rescue false + end + expect(unsupported).to be_empty + end + + specify('parse property negative') do + root = RP.parse('ab\p{L}cd', 'ruby/1.9') + expect(root[1]).not_to be_negative + end + + specify('parse nonproperty negative') do + root = RP.parse('ab\P{L}cd', 'ruby/1.9') + expect(root[1]).to be_negative + end + + specify('parse caret nonproperty negative') do + root = RP.parse('ab\p{^L}cd', 'ruby/1.9') + expect(root[1]).to be_negative + end + + specify('parse double negated property negative') do + root = RP.parse('ab\P{^L}cd', 'ruby/1.9') + expect(root[1]).not_to be_negative + end + + specify('parse property shortcut') do + expect(RP.parse('\p{lowercase_letter}')[0].shortcut).to eq 'll' + expect(RP.parse('\p{sc}')[0].shortcut).to eq 'sc' + expect(RP.parse('\p{in_bengali}')[0].shortcut).to be_nil + end + + specify('parse property age') do + root = RP.parse('ab\p{age=5.2}cd', 'ruby/1.9') + expect(root[1]).to be_a(UnicodeProperty::Age) + end + + specify('parse property derived') do + root = RP.parse('ab\p{Math}cd', 'ruby/1.9') + expect(root[1]).to be_a(UnicodeProperty::Derived) + end + + specify('parse property script') do + root = RP.parse('ab\p{Hiragana}cd', 'ruby/1.9') + expect(root[1]).to be_a(UnicodeProperty::Script) + end + + specify('parse property script V1 9 3') do + root = RP.parse('ab\p{Brahmi}cd', 'ruby/1.9.3') + expect(root[1]).to be_a(UnicodeProperty::Script) + end + + specify('parse property script V2 2 0') do + root = RP.parse('ab\p{Caucasian_Albanian}cd', 'ruby/2.2') + expect(root[1]).to be_a(UnicodeProperty::Script) + end + + specify('parse property block') do + root = RP.parse('ab\p{InArmenian}cd', 'ruby/1.9') + expect(root[1]).to be_a(UnicodeProperty::Block) + end + + specify('parse property following literal') do + root = RP.parse('ab\p{Lu}cd', 'ruby/1.9') + expect(root[2]).to be_a(Literal) + end + + specify('parse abandoned newline property') do + root = RP.parse('\p{newline}', 'ruby/1.9') + expect(root.expressions.last).to be_a(UnicodeProperty::Base) + + expect { RP.parse('\p{newline}', 'ruby/2.0') } + .to raise_error(Regexp::Syntax::NotImplementedError) + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/quantifiers_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/quantifiers_spec.rb new file mode 100644 index 00000000..3d0cf2c7 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/quantifiers_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +RSpec.describe('Quantifier parsing') do + RSpec.shared_examples 'quantifier' do |pattern, text, mode, token, min, max| + it "parses the quantifier in #{pattern} as #{mode} #{token}" do + root = RP.parse(pattern, '*') + exp = root[0] + + expect(exp).to be_quantified + expect(exp.quantifier.token).to eq token + expect(exp.quantifier.min).to eq min + expect(exp.quantifier.max).to eq max + expect(exp.quantifier.mode).to eq mode + end + end + + include_examples 'quantifier', /a?b/, '?', :greedy, :zero_or_one, 0, 1 + include_examples 'quantifier', /a??b/, '??', :reluctant, :zero_or_one, 0, 1 + include_examples 'quantifier', /a?+b/, '?+', :possessive, :zero_or_one, 0, 1 + include_examples 'quantifier', /a*b/, '*', :greedy, :zero_or_more, 0, -1 + include_examples 'quantifier', /a*?b/, '*?', :reluctant, :zero_or_more, 0, -1 + include_examples 'quantifier', /a*+b/, '*+', :possessive, :zero_or_more, 0, -1 + include_examples 'quantifier', /a+b/, '+', :greedy, :one_or_more, 1, -1 + include_examples 'quantifier', /a+?b/, '+?', :reluctant, :one_or_more, 1, -1 + include_examples 'quantifier', /a++b/, '++', :possessive, :one_or_more, 1, -1 + include_examples 'quantifier', /a{2,4}b/, '{2,4}', :greedy, :interval, 2, 4 + include_examples 'quantifier', /a{2,4}?b/, '{2,4}?', :reluctant, :interval, 2, 4 + include_examples 'quantifier', /a{2,4}+b/, '{2,4}+', :possessive, :interval, 2, 4 + include_examples 'quantifier', /a{2,}b/, '{2,}', :greedy, :interval, 2, -1 + include_examples 'quantifier', /a{2,}?b/, '{2,}?', :reluctant, :interval, 2, -1 + include_examples 'quantifier', /a{2,}+b/, '{2,}+', :possessive, :interval, 2, -1 + include_examples 'quantifier', /a{,3}b/, '{,3}', :greedy, :interval, 0, 3 + include_examples 'quantifier', /a{,3}?b/, '{,3}?', :reluctant, :interval, 0, 3 + include_examples 'quantifier', /a{,3}+b/, '{,3}+', :possessive, :interval, 0, 3 + include_examples 'quantifier', /a{4}b/, '{4}', :greedy, :interval, 4, 4 + include_examples 'quantifier', /a{4}?b/, '{4}?', :reluctant, :interval, 4, 4 + include_examples 'quantifier', /a{4}+b/, '{4}+', :possessive, :interval, 4, 4 + + specify('mode-checking methods') do + exp = RP.parse(/a??/).first + + expect(exp).to be_reluctant + expect(exp).to be_lazy + expect(exp).not_to be_greedy + expect(exp).not_to be_possessive + expect(exp.quantifier).to be_reluctant + expect(exp.quantifier).to be_lazy + expect(exp.quantifier).not_to be_greedy + expect(exp.quantifier).not_to be_possessive + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/refcalls_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/refcalls_spec.rb new file mode 100644 index 00000000..7528c816 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/refcalls_spec.rb @@ -0,0 +1,112 @@ +require 'spec_helper' + +RSpec.describe('Refcall parsing') do + include_examples 'parse', /(abc)\1/, + 1 => [:backref, :number, Backreference::Number, number: 1] + + include_examples 'parse', /(?abc)\k/, + 1 => [:backref, :name_ref, Backreference::Name, name: 'X'] + include_examples 'parse', /(?abc)\k'X'/, + 1 => [:backref, :name_ref, Backreference::Name, name: 'X'] + + include_examples 'parse', /(abc)\k<1>/, + 1 => [:backref, :number_ref, Backreference::Number, number: 1] + include_examples 'parse', /(abc)\k'1'/, + 1 => [:backref, :number_ref, Backreference::Number, number: 1] + + include_examples 'parse', /(abc)\k<-1>/, + 1 => [:backref, :number_rel_ref, Backreference::NumberRelative, number: -1] + include_examples 'parse', /(abc)\k'-1'/, + 1 => [:backref, :number_rel_ref, Backreference::NumberRelative, number: -1] + + include_examples 'parse', /(?abc)\g/, + 1 => [:backref, :name_call, Backreference::NameCall, name: 'X'] + include_examples 'parse', /(?abc)\g'X'/, + 1 => [:backref, :name_call, Backreference::NameCall, name: 'X'] + + include_examples 'parse', /(abc)\g<1>/, + 1 => [:backref, :number_call, Backreference::NumberCall, number: 1] + include_examples 'parse', /(abc)\g'1'/, + 1 => [:backref, :number_call, Backreference::NumberCall, number: 1] + + include_examples 'parse', /(abc)\g<-1>/, + 1 => [:backref, :number_rel_call, Backreference::NumberCallRelative, number: -1] + include_examples 'parse', /(abc)\g'-1'/, + 1 => [:backref, :number_rel_call, Backreference::NumberCallRelative, number: -1] + + include_examples 'parse', /\g<+1>(abc)/, + 0 => [:backref, :number_rel_call, Backreference::NumberCallRelative, number: 1] + include_examples 'parse', /\g'+1'(abc)/, + 0 => [:backref, :number_rel_call, Backreference::NumberCallRelative, number: 1] + + include_examples 'parse', /(?abc)\k/, + 1 => [:backref, :name_recursion_ref, Backreference::NameRecursionLevel, + name: 'X', recursion_level: 0] + include_examples 'parse', /(?abc)\k'X-0'/, + 1 => [:backref, :name_recursion_ref, Backreference::NameRecursionLevel, + name: 'X', recursion_level: 0] + + include_examples 'parse', /(abc)\k<1-0>/, + 1 => [:backref, :number_recursion_ref, Backreference::NumberRecursionLevel, + number: 1, recursion_level: 0] + include_examples 'parse', /(abc)\k'1-0'/, + 1 => [:backref, :number_recursion_ref, Backreference::NumberRecursionLevel, + number: 1, recursion_level: 0] + include_examples 'parse', /(abc)\k'-1+0'/, + 1 => [:backref, :number_recursion_ref, Backreference::NumberRecursionLevel, + number: -1, recursion_level: 0] + include_examples 'parse', /(abc)\k'1+1'/, + 1 => [:backref, :number_recursion_ref, Backreference::NumberRecursionLevel, + number: 1, recursion_level: 1] + include_examples 'parse', /(abc)\k'1-1'/, + 1 => [:backref, :number_recursion_ref, Backreference::NumberRecursionLevel, + number: 1, recursion_level: -1] + + specify('parse backref effective_number') do + root = RP.parse('(abc)(def)\\k<-1>(ghi)\\k<-3>\\k<-1>', 'ruby/1.9') + exp1 = root[2] + exp2 = root[4] + exp3 = root[5] + + expect([exp1, exp2, exp3]).to all be_instance_of(Backreference::NumberRelative) + expect(exp1.effective_number).to eq 2 + expect(exp2.effective_number).to eq 1 + expect(exp3.effective_number).to eq 3 + end + + specify('parse backref referenced_expression') do + root = RP.parse('(abc)(def)\\k<-1>(ghi)\\k<-3>\\k<-1>', 'ruby/1.9') + exp1 = root[2] + exp2 = root[4] + exp3 = root[5] + + expect([exp1, exp2, exp3]).to all be_instance_of(Backreference::NumberRelative) + expect(exp1.referenced_expression.to_s).to eq '(def)' + expect(exp2.referenced_expression.to_s).to eq '(abc)' + expect(exp3.referenced_expression.to_s).to eq '(ghi)' + end + + specify('parse backref call effective_number') do + root = RP.parse('\\g<+1>(abc)\\g<+2>(def)(ghi)\\g<-2>', 'ruby/1.9') + exp1 = root[0] + exp2 = root[2] + exp3 = root[5] + + expect([exp1, exp2, exp3]).to all be_instance_of(Backreference::NumberCallRelative) + expect(exp1.effective_number).to eq 1 + expect(exp2.effective_number).to eq 3 + expect(exp3.effective_number).to eq 2 + end + + specify('parse backref call referenced_expression') do + root = RP.parse('\\g<+1>(abc)\\g<+2>(def)(ghi)\\g<-2>', 'ruby/1.9') + exp1 = root[0] + exp2 = root[2] + exp3 = root[5] + + expect([exp1, exp2, exp3]).to all be_instance_of(Backreference::NumberCallRelative) + expect(exp1.referenced_expression.to_s).to eq '(abc)' + expect(exp2.referenced_expression.to_s).to eq '(ghi)' + expect(exp3.referenced_expression.to_s).to eq '(def)' + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/set/intersections_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/set/intersections_spec.rb new file mode 100644 index 00000000..6ae94a2e --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/set/intersections_spec.rb @@ -0,0 +1,127 @@ +require 'spec_helper' + +# edge cases with `...-&&...` and `...&&-...` are checked in test_ranges.rb + +RSpec.describe('CharacterSet::Intersection parsing') do + specify('parse set intersection') do + root = RP.parse('[a&&z]') + set = root[0] + ints = set[0] + + expect(set.count).to eq 1 + expect(ints).to be_instance_of(CharacterSet::Intersection) + expect(ints.count).to eq 2 + + seq1, seq2 = ints.expressions + expect(seq1).to be_instance_of(CharacterSet::IntersectedSequence) + expect(seq1.count).to eq 1 + expect(seq1.first.to_s).to eq 'a' + expect(seq1.first).to be_instance_of(Literal) + expect(seq2).to be_instance_of(CharacterSet::IntersectedSequence) + expect(seq2.count).to eq 1 + expect(seq2.first.to_s).to eq 'z' + expect(seq2.first).to be_instance_of(Literal) + + expect(set).not_to match 'a' + expect(set).not_to match '&' + expect(set).not_to match 'z' + end + + specify('parse set intersection range and subset') do + root = RP.parse('[a-z&&[^a]]') + set = root[0] + ints = set[0] + + expect(set.count).to eq 1 + expect(ints).to be_instance_of(CharacterSet::Intersection) + expect(ints.count).to eq 2 + + seq1, seq2 = ints.expressions + expect(seq1).to be_instance_of(CharacterSet::IntersectedSequence) + expect(seq1.count).to eq 1 + expect(seq1.first.to_s).to eq 'a-z' + expect(seq1.first).to be_instance_of(CharacterSet::Range) + expect(seq2).to be_instance_of(CharacterSet::IntersectedSequence) + expect(seq2.count).to eq 1 + expect(seq2.first.to_s).to eq '[^a]' + expect(seq2.first).to be_instance_of(CharacterSet) + + expect(set).not_to match 'a' + expect(set).not_to match '&' + expect(set).to match 'b' + end + + specify('parse set intersection trailing range') do + root = RP.parse('[a&&a-z]') + set = root[0] + ints = set[0] + + expect(set.count).to eq 1 + expect(ints).to be_instance_of(CharacterSet::Intersection) + expect(ints.count).to eq 2 + + seq1, seq2 = ints.expressions + expect(seq1).to be_instance_of(CharacterSet::IntersectedSequence) + expect(seq1.count).to eq 1 + expect(seq1.first.to_s).to eq 'a' + expect(seq1.first).to be_instance_of(Literal) + expect(seq2).to be_instance_of(CharacterSet::IntersectedSequence) + expect(seq2.count).to eq 1 + expect(seq2.first.to_s).to eq 'a-z' + expect(seq2.first).to be_instance_of(CharacterSet::Range) + + expect(set).to match 'a' + expect(set).not_to match '&' + expect(set).not_to match 'b' + end + + specify('parse set intersection type') do + root = RP.parse('[a&&\\w]') + set = root[0] + ints = set[0] + + expect(set.count).to eq 1 + expect(ints).to be_instance_of(CharacterSet::Intersection) + expect(ints.count).to eq 2 + + seq1, seq2 = ints.expressions + expect(seq1).to be_instance_of(CharacterSet::IntersectedSequence) + expect(seq1.count).to eq 1 + expect(seq1.first.to_s).to eq 'a' + expect(seq1.first).to be_instance_of(Literal) + expect(seq2).to be_instance_of(CharacterSet::IntersectedSequence) + expect(seq2.count).to eq 1 + expect(seq2.first.to_s).to eq '\\w' + expect(seq2.first).to be_instance_of(CharacterType::Word) + + expect(set).to match 'a' + expect(set).not_to match '&' + expect(set).not_to match 'b' + end + + specify('parse set intersection multipart') do + root = RP.parse('[\\h&&\\w&&efg]') + set = root[0] + ints = set[0] + + expect(set.count).to eq 1 + expect(ints).to be_instance_of(CharacterSet::Intersection) + expect(ints.count).to eq 3 + + seq1, seq2, seq3 = ints.expressions + expect(seq1).to be_instance_of(CharacterSet::IntersectedSequence) + expect(seq1.count).to eq 1 + expect(seq1.first.to_s).to eq '\\h' + expect(seq2).to be_instance_of(CharacterSet::IntersectedSequence) + expect(seq2.count).to eq 1 + expect(seq2.first.to_s).to eq '\\w' + expect(seq3).to be_instance_of(CharacterSet::IntersectedSequence) + expect(seq3.count).to eq 3 + expect(seq3.to_s).to eq 'efg' + + expect(set).to match 'e' + expect(set).to match 'f' + expect(set).not_to match 'a' + expect(set).not_to match 'g' + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/set/ranges_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/set/ranges_spec.rb new file mode 100644 index 00000000..705acf58 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/set/ranges_spec.rb @@ -0,0 +1,111 @@ +require 'spec_helper' + +RSpec.describe('CharacterSet::Range parsing') do + specify('parse set range') do + root = RP.parse('[a-z]') + set = root[0] + range = set[0] + + expect(set.count).to eq 1 + expect(range).to be_instance_of(CharacterSet::Range) + expect(range.count).to eq 2 + expect(range.first.to_s).to eq 'a' + expect(range.first).to be_instance_of(Literal) + expect(range.last.to_s).to eq 'z' + expect(range.last).to be_instance_of(Literal) + expect(set).to match 'm' + end + + specify('parse set range hex') do + root = RP.parse('[\\x00-\\x99]') + set = root[0] + range = set[0] + + expect(set.count).to eq 1 + expect(range).to be_instance_of(CharacterSet::Range) + expect(range.count).to eq 2 + expect(range.first.to_s).to eq '\\x00' + expect(range.first).to be_instance_of(EscapeSequence::Hex) + expect(range.last.to_s).to eq '\\x99' + expect(range.last).to be_instance_of(EscapeSequence::Hex) + expect(set).to match '\\x50' + end + + specify('parse set range unicode') do + root = RP.parse('[\\u{40 42}-\\u1234]') + set = root[0] + range = set[0] + + expect(set.count).to eq 1 + expect(range).to be_instance_of(CharacterSet::Range) + expect(range.count).to eq 2 + expect(range.first.to_s).to eq '\\u{40 42}' + expect(range.first).to be_instance_of(EscapeSequence::CodepointList) + expect(range.last.to_s).to eq '\\u1234' + expect(range.last).to be_instance_of(EscapeSequence::Codepoint) + expect(set).to match '\\u600' + end + + specify('parse set range edge case leading dash') do + root = RP.parse('[--z]') + set = root[0] + range = set[0] + + expect(set.count).to eq 1 + expect(range.count).to eq 2 + expect(set).to match 'a' + end + + specify('parse set range edge case trailing dash') do + root = RP.parse('[!--]') + set = root[0] + range = set[0] + + expect(set.count).to eq 1 + expect(range.count).to eq 2 + expect(set).to match '$' + end + + specify('parse set range edge case leading negate') do + root = RP.parse('[^-z]') + set = root[0] + + expect(set.count).to eq 2 + expect(set).to match 'a' + expect(set).not_to match 'z' + end + + specify('parse set range edge case trailing negate') do + root = RP.parse('[!-^]') + set = root[0] + range = set[0] + + expect(set.count).to eq 1 + expect(range.count).to eq 2 + expect(set).to match '$' + end + + specify('parse set range edge case leading intersection') do + root = RP.parse('[[\\-ab]&&-bc]') + set = root[0] + + expect(set.count).to eq 1 + expect(set.first.last.to_s).to eq '-bc' + expect(set).to match '-' + expect(set).to match 'b' + expect(set).not_to match 'a' + expect(set).not_to match 'c' + end + + specify('parse set range edge case trailing intersection') do + root = RP.parse('[bc-&&[\\-ab]]') + set = root[0] + + expect(set.count).to eq 1 + expect(set.first.first.to_s).to eq 'bc-' + expect(set).to match '-' + expect(set).to match 'b' + expect(set).not_to match 'a' + expect(set).not_to match 'c' + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/sets_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/sets_spec.rb new file mode 100644 index 00000000..bf2cdd86 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/sets_spec.rb @@ -0,0 +1,178 @@ +require 'spec_helper' + +RSpec.describe('CharacterSet parsing') do + specify('parse set basic') do + root = RP.parse('[ab]+') + exp = root[0] + + expect(exp).to be_instance_of(CharacterSet) + expect(exp.count).to eq 2 + + expect(exp[0]).to be_instance_of(Literal) + expect(exp[0].text).to eq 'a' + expect(exp[1]).to be_instance_of(Literal) + expect(exp[1].text).to eq 'b' + + expect(exp).to be_quantified + expect(exp.quantifier.min).to eq 1 + expect(exp.quantifier.max).to eq(-1) + end + + specify('parse set char type') do + root = RP.parse('[a\\dc]') + exp = root[0] + + expect(exp).to be_instance_of(CharacterSet) + expect(exp.count).to eq 3 + + expect(exp[1]).to be_instance_of(CharacterType::Digit) + expect(exp[1].text).to eq '\\d' + end + + specify('parse set escape sequence backspace') do + root = RP.parse('[a\\bc]') + exp = root[0] + + expect(exp).to be_instance_of(CharacterSet) + expect(exp.count).to eq 3 + + expect(exp[1]).to be_instance_of(EscapeSequence::Backspace) + expect(exp[1].text).to eq '\\b' + + expect(exp).to match 'a' + expect(exp).to match "\b" + expect(exp).not_to match 'b' + expect(exp).to match 'c' + end + + specify('parse set escape sequence hex') do + root = RP.parse('[a\\x20c]', :any) + exp = root[0] + + expect(exp).to be_instance_of(CharacterSet) + expect(exp.count).to eq 3 + + expect(exp[1]).to be_instance_of(EscapeSequence::Hex) + expect(exp[1].text).to eq '\\x20' + end + + specify('parse set escape sequence codepoint') do + root = RP.parse('[a\\u0640]') + exp = root[0] + + expect(exp).to be_instance_of(CharacterSet) + expect(exp.count).to eq 2 + + expect(exp[1]).to be_instance_of(EscapeSequence::Codepoint) + expect(exp[1].text).to eq '\\u0640' + end + + specify('parse set escape sequence codepoint list') do + root = RP.parse('[a\\u{41 1F60D}]') + exp = root[0] + + expect(exp).to be_instance_of(CharacterSet) + expect(exp.count).to eq 2 + + expect(exp[1]).to be_instance_of(EscapeSequence::CodepointList) + expect(exp[1].text).to eq '\\u{41 1F60D}' + end + + specify('parse set posix class') do + root = RP.parse('[[:digit:][:^lower:]]+') + exp = root[0] + + expect(exp).to be_instance_of(CharacterSet) + expect(exp.count).to eq 2 + + expect(exp[0]).to be_instance_of(PosixClass) + expect(exp[0].text).to eq '[:digit:]' + expect(exp[1]).to be_instance_of(PosixClass) + expect(exp[1].text).to eq '[:^lower:]' + end + + specify('parse set nesting') do + root = RP.parse('[a[b[c]d]e]') + + exp = root[0] + expect(exp).to be_instance_of(CharacterSet) + expect(exp.count).to eq 3 + expect(exp[0]).to be_instance_of(Literal) + expect(exp[2]).to be_instance_of(Literal) + + subset1 = exp[1] + expect(subset1).to be_instance_of(CharacterSet) + expect(subset1.count).to eq 3 + expect(subset1[0]).to be_instance_of(Literal) + expect(subset1[2]).to be_instance_of(Literal) + + subset2 = subset1[1] + expect(subset2).to be_instance_of(CharacterSet) + expect(subset2.count).to eq 1 + expect(subset2[0]).to be_instance_of(Literal) + end + + specify('parse set nesting negative') do + root = RP.parse('[a[^b[c]]]') + exp = root[0] + + expect(exp).to be_instance_of(CharacterSet) + expect(exp.count).to eq 2 + expect(exp[0]).to be_instance_of(Literal) + expect(exp).not_to be_negative + + subset1 = exp[1] + expect(subset1).to be_instance_of(CharacterSet) + expect(subset1.count).to eq 2 + expect(subset1[0]).to be_instance_of(Literal) + expect(subset1).to be_negative + + subset2 = subset1[1] + expect(subset2).to be_instance_of(CharacterSet) + expect(subset2.count).to eq 1 + expect(subset2[0]).to be_instance_of(Literal) + expect(subset2).not_to be_negative + end + + specify('parse set nesting #to_s') do + pattern = '[a[b[^c]]]' + root = RP.parse(pattern) + + expect(root.to_s).to eq pattern + end + + specify('parse set literals are not merged') do + root = RP.parse("[#{('a' * 10)}]") + exp = root[0] + + expect(exp.count).to eq 10 + end + + specify('parse set whitespace is not merged') do + root = RP.parse("[#{(' ' * 10)}]") + exp = root[0] + + expect(exp.count).to eq 10 + end + + specify('parse set whitespace is not merged in x mode') do + root = RP.parse("(?x)[#{(' ' * 10)}]") + exp = root[1] + + expect(exp.count).to eq 10 + end + + specify('parse set collating sequence') do + root = RP.parse('[a[.span-ll.]h]', :any) + exp = root[0] + + expect(exp[1].to_s).to eq '[.span-ll.]' + end + + specify('parse set character equivalents') do + root = RP.parse('[a[=e=]h]', :any) + exp = root[0] + + expect(exp[1].to_s).to eq '[=e=]' + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/types_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/types_spec.rb new file mode 100644 index 00000000..9dc7ce7e --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/parser/types_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +RSpec.describe('CharacterType parsing') do + include_examples 'parse', /a\dc/, 1 => [:type, :digit, CharacterType::Digit] + include_examples 'parse', /a\Dc/, 1 => [:type, :nondigit, CharacterType::NonDigit] + + include_examples 'parse', /a\sc/, 1 => [:type, :space, CharacterType::Space] + include_examples 'parse', /a\Sc/, 1 => [:type, :nonspace, CharacterType::NonSpace] + + include_examples 'parse', /a\hc/, 1 => [:type, :hex, CharacterType::Hex] + include_examples 'parse', /a\Hc/, 1 => [:type, :nonhex, CharacterType::NonHex] + + include_examples 'parse', /a\wc/, 1 => [:type, :word, CharacterType::Word] + include_examples 'parse', /a\Wc/, 1 => [:type, :nonword, CharacterType::NonWord] + + include_examples 'parse', 'a\\Rc', 1 => [:type, :linebreak, CharacterType::Linebreak] + include_examples 'parse', 'a\\Xc', 1 => [:type, :xgrapheme, CharacterType::ExtendedGrapheme] +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/all_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/all_spec.rb new file mode 100644 index 00000000..31cf911b --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/all_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +RSpec.describe(Regexp::Scanner) do + specify('scanner returns an array') do + expect(RS.scan('abc')).to be_instance_of(Array) + end + + specify('scanner returns tokens as arrays') do + tokens = RS.scan('^abc+[^one]{2,3}\\b\\d\\\\C-C$') + expect(tokens).to all(be_a Array) + expect(tokens.map(&:length)).to all(eq 5) + end + + specify('scanner token count') do + re = /^(one|two){2,3}([^d\]efm-qz\,\-]*)(ghi)+$/i + expect(RS.scan(re).length).to eq 28 + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/anchors_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/anchors_spec.rb new file mode 100644 index 00000000..a13335f9 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/anchors_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +RSpec.describe('Anchor scanning') do + include_examples 'scan', '^abc', 0 => [:anchor, :bol, '^', 0, 1] + include_examples 'scan', 'abc$', 1 => [:anchor, :eol, '$', 3, 4] + + include_examples 'scan', '\Aabc', 0 => [:anchor, :bos, '\A', 0, 2] + include_examples 'scan', 'abc\z', 1 => [:anchor, :eos, '\z', 3, 5] + include_examples 'scan', 'abc\Z', 1 => [:anchor, :eos_ob_eol, '\Z', 3, 5] + + include_examples 'scan', 'a\bc', 1 => [:anchor, :word_boundary, '\b', 1, 3] + include_examples 'scan', 'a\Bc', 1 => [:anchor, :nonword_boundary, '\B', 1, 3] + + include_examples 'scan', 'a\Gc', 1 => [:anchor, :match_start, '\G', 1, 3] + + include_examples 'scan', "\\\\Ac", 0 => [:escape, :backslash, '\\\\', 0, 2] + include_examples 'scan', "a\\\\z", 1 => [:escape, :backslash, '\\\\', 1, 3] + include_examples 'scan', "a\\\\Z", 1 => [:escape, :backslash, '\\\\', 1, 3] + include_examples 'scan', "a\\\\bc", 1 => [:escape, :backslash, '\\\\', 1, 3] + include_examples 'scan', "a\\\\Bc", 1 => [:escape, :backslash, '\\\\', 1, 3] +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/conditionals_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/conditionals_spec.rb new file mode 100644 index 00000000..a39445e3 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/conditionals_spec.rb @@ -0,0 +1,128 @@ +require 'spec_helper' + +RSpec.describe('Conditional scanning') do + include_examples 'scan', /(a)(?(1)T|F)1/, 3 => [:conditional, :open, '(?', 3, 5] + include_examples 'scan', /(a)(?(1)T|F)2/, 4 => [:conditional, :condition_open, '(', 5, 6] + include_examples 'scan', /(a)(?(1)T|F)3/, 5 => [:conditional, :condition, '1', 6, 7] + include_examples 'scan', /(a)(?(1)T|F)4/, 6 => [:conditional, :condition_close, ')', 7, 8] + include_examples 'scan', /(a)(?(1)T|F)5/, 7 => [:literal, :literal, 'T', 8, 9] + include_examples 'scan', /(a)(?(1)T|F)6/, 8 => [:conditional, :separator, '|', 9, 10] + include_examples 'scan', /(a)(?(1)T|F)7/, 9 => [:literal, :literal, 'F', 10, 11] + include_examples 'scan', /(a)(?(1)T|F)8/, 10 => [:conditional, :close, ')', 11, 12] + include_examples 'scan', /(a)(?(1)TRUE)9/, 8 => [:conditional, :close, ')', 12, 13] + include_examples 'scan', /(a)(?(1)TRUE|)10/, 8 => [:conditional, :separator, '|', 12, 13] + include_examples 'scan', /(a)(?(1)TRUE|)11/, 9 => [:conditional, :close, ')', 13, 14] + include_examples 'scan', /(?A)(?()T|F)1/, 5 => [:conditional, :condition, '', 10, 13] + include_examples 'scan', /(?'N'A)(?('N')T|F)2/, 5 => [:conditional, :condition, "'N'", 10, 13] + + include_examples 'scan', /(a(b(c)))(?(1)(?(2)d|(?(3)e|f))|(?(2)(?(1)g|h)))/, + 0 => [:group, :capture, '(', 0, 1], + 1 => [:literal, :literal, 'a', 1, 2], + 2 => [:group, :capture, '(', 2, 3], + 3 => [:literal, :literal, 'b', 3, 4], + 4 => [:group, :capture, '(', 4, 5], + 5 => [:literal, :literal, 'c', 5, 6], + 6 => [:group, :close, ')', 6, 7], + 7 => [:group, :close, ')', 7, 8], + 8 => [:group, :close, ')', 8, 9], + 9 => [:conditional, :open, '(?', 9, 11], + 10 => [:conditional, :condition_open, '(', 11, 12], + 11 => [:conditional, :condition, '1', 12, 13], + 12 => [:conditional, :condition_close, ')', 13, 14], + 13 => [:conditional, :open, '(?', 14, 16], + 14 => [:conditional, :condition_open, '(', 16, 17], + 15 => [:conditional, :condition, '2', 17, 18], + 16 => [:conditional, :condition_close, ')', 18, 19], + 17 => [:literal, :literal, 'd', 19, 20], + 18 => [:conditional, :separator, '|', 20, 21], + 19 => [:conditional, :open, '(?', 21, 23], + 20 => [:conditional, :condition_open, '(', 23, 24], + 21 => [:conditional, :condition, '3', 24, 25], + 22 => [:conditional, :condition_close, ')', 25, 26], + 23 => [:literal, :literal, 'e', 26, 27], + 24 => [:conditional, :separator, '|', 27, 28], + 25 => [:literal, :literal, 'f', 28, 29], + 26 => [:conditional, :close, ')', 29, 30], + 27 => [:conditional, :close, ')', 30, 31], + 28 => [:conditional, :separator, '|', 31, 32], + 29 => [:conditional, :open, '(?', 32, 34], + 30 => [:conditional, :condition_open, '(', 34, 35], + 31 => [:conditional, :condition, '2', 35, 36], + 32 => [:conditional, :condition_close, ')', 36, 37], + 33 => [:conditional, :open, '(?', 37, 39], + 34 => [:conditional, :condition_open, '(', 39, 40], + 35 => [:conditional, :condition, '1', 40, 41], + 36 => [:conditional, :condition_close, ')', 41, 42], + 37 => [:literal, :literal, 'g', 42, 43], + 38 => [:conditional, :separator, '|', 43, 44], + 39 => [:literal, :literal, 'h', 44, 45], + 40 => [:conditional, :close, ')', 45, 46], + 41 => [:conditional, :close, ')', 46, 47], + 42 => [:conditional, :close, ')', 47, 48] + + include_examples 'scan', /((a)|(b)|((?(2)(c(d|e)+)?|(?(3)f|(?(4)(g|(h)(i)))))))/, + 0 => [:group, :capture, '(', 0, 1], + 1 => [:group, :capture, '(', 1, 2], + 2 => [:literal, :literal, 'a', 2, 3], + 3 => [:group, :close, ')', 3, 4], + 4 => [:meta, :alternation, '|', 4, 5], + 5 => [:group, :capture, '(', 5, 6], + 6 => [:literal, :literal, 'b', 6, 7], + 7 => [:group, :close, ')', 7, 8], + 8 => [:meta, :alternation, '|', 8, 9], + 9 => [:group, :capture, '(', 9, 10], + 10 => [:conditional, :open, '(?', 10, 12], + 11 => [:conditional, :condition_open, '(', 12, 13], + 12 => [:conditional, :condition, '2', 13, 14], + 13 => [:conditional, :condition_close, ')', 14, 15], + 14 => [:group, :capture, '(', 15, 16], + 15 => [:literal, :literal, 'c', 16, 17], + 16 => [:group, :capture, '(', 17, 18], + 17 => [:literal, :literal, 'd', 18, 19], + 18 => [:meta, :alternation, '|', 19, 20], + 19 => [:literal, :literal, 'e', 20, 21], + 20 => [:group, :close, ')', 21, 22], + 21 => [:quantifier, :one_or_more, '+', 22, 23], + 22 => [:group, :close, ')', 23, 24], + 23 => [:quantifier, :zero_or_one, '?', 24, 25], + 24 => [:conditional, :separator, '|', 25, 26], + 25 => [:conditional, :open, '(?', 26, 28], + 26 => [:conditional, :condition_open, '(', 28, 29], + 27 => [:conditional, :condition, '3', 29, 30], + 28 => [:conditional, :condition_close, ')', 30, 31], + 29 => [:literal, :literal, 'f', 31, 32], + 30 => [:conditional, :separator, '|', 32, 33], + 31 => [:conditional, :open, '(?', 33, 35], + 32 => [:conditional, :condition_open, '(', 35, 36], + 33 => [:conditional, :condition, '4', 36, 37], + 34 => [:conditional, :condition_close, ')', 37, 38], + 35 => [:group, :capture, '(', 38, 39], + 36 => [:literal, :literal, 'g', 39, 40], + 37 => [:meta, :alternation, '|', 40, 41], + 38 => [:group, :capture, '(', 41, 42], + 39 => [:literal, :literal, 'h', 42, 43], + 40 => [:group, :close, ')', 43, 44], + 41 => [:group, :capture, '(', 44, 45], + 42 => [:literal, :literal, 'i', 45, 46], + 43 => [:group, :close, ')', 46, 47], + 44 => [:group, :close, ')', 47, 48], + 45 => [:conditional, :close, ')', 48, 49], + 46 => [:conditional, :close, ')', 49, 50], + 47 => [:conditional, :close, ')', 50, 51], + 48 => [:group, :close, ')', 51, 52], + 49 => [:group, :close, ')', 52, 53] + + include_examples 'scan', /(a)(?(1)(b|c|d)|(e|f|g))(h)(?(2)(i|j|k)|(l|m|n))|o|p/, + 9 => [:meta, :alternation, '|', 10, 11], + 11 => [:meta, :alternation, '|', 12, 13], + 14 => [:conditional, :separator, '|', 15, 16], + 17 => [:meta, :alternation, '|', 18, 19], + 19 => [:meta, :alternation, '|', 20, 21], + 32 => [:meta, :alternation, '|', 34, 35], + 34 => [:meta, :alternation, '|', 36, 37], + 37 => [:conditional, :separator, '|', 39, 40], + 40 => [:meta, :alternation, '|', 42, 43], + 42 => [:meta, :alternation, '|', 44, 45], + 46 => [:meta, :alternation, '|', 48, 49], + 48 => [:meta, :alternation, '|', 50, 51] +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/errors_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/errors_spec.rb new file mode 100644 index 00000000..ff1e7168 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/errors_spec.rb @@ -0,0 +1,68 @@ +require 'spec_helper' + +RSpec.describe(Regexp::Scanner) do + RSpec.shared_examples 'scan error' do |error, issue, source| + it "raises #{error} for #{issue} `#{source}`" do + expect { RS.scan(source) }.to raise_error(error) + end + end + + include_examples 'scan error', RS::PrematureEndError, 'unbalanced set', '[a' + include_examples 'scan error', RS::PrematureEndError, 'unbalanced set', '[[:alpha:]' + include_examples 'scan error', RS::PrematureEndError, 'unbalanced group', '(abc' + include_examples 'scan error', RS::PrematureEndError, 'unbalanced interval', 'a{1,2' + include_examples 'scan error', RS::PrematureEndError, 'eof in property', '\p{asci' + include_examples 'scan error', RS::PrematureEndError, 'incomplete property', '\p{ascii abc' + include_examples 'scan error', RS::PrematureEndError, 'eof options', '(?mix' + include_examples 'scan error', RS::PrematureEndError, 'eof escape', '\\' + include_examples 'scan error', RS::PrematureEndError, 'eof in hex escape', '\x' + include_examples 'scan error', RS::PrematureEndError, 'eof in cp escape', '\u' + include_examples 'scan error', RS::PrematureEndError, 'eof in cp escape', '\u0' + include_examples 'scan error', RS::PrematureEndError, 'eof in cp escape', '\u00' + include_examples 'scan error', RS::PrematureEndError, 'eof in cp escape', '\u000' + include_examples 'scan error', RS::PrematureEndError, 'eof in cp escape', '\u{' + include_examples 'scan error', RS::PrematureEndError, 'eof in cp escape', '\u{00' + include_examples 'scan error', RS::PrematureEndError, 'eof in cp escape', '\u{0000' + include_examples 'scan error', RS::PrematureEndError, 'eof in cp escape', '\u{0000 ' + include_examples 'scan error', RS::PrematureEndError, 'eof in cp escape', '\u{0000 0000' + include_examples 'scan error', RS::PrematureEndError, 'eof in c-seq', '\c' + include_examples 'scan error', RS::PrematureEndError, 'eof in c-seq', '\c\M' + include_examples 'scan error', RS::PrematureEndError, 'eof in c-seq', '\c\M-' + include_examples 'scan error', RS::PrematureEndError, 'eof in c-seq', '\C' + include_examples 'scan error', RS::PrematureEndError, 'eof in c-seq', '\C-' + include_examples 'scan error', RS::PrematureEndError, 'eof in c-seq', '\C-\M' + include_examples 'scan error', RS::PrematureEndError, 'eof in c-seq', '\C-\M-' + include_examples 'scan error', RS::PrematureEndError, 'eof in m-seq', '\M' + include_examples 'scan error', RS::PrematureEndError, 'eof in m-seq', '\M-' + include_examples 'scan error', RS::PrematureEndError, 'eof in m-seq', '\M-\\' + include_examples 'scan error', RS::PrematureEndError, 'eof in m-seq', '\M-\c' + include_examples 'scan error', RS::PrematureEndError, 'eof in m-seq', '\M-\C' + include_examples 'scan error', RS::PrematureEndError, 'eof in m-seq', '\M-\C-' + include_examples 'scan error', RS::InvalidSequenceError, 'invalid hex', '\xZ' + include_examples 'scan error', RS::InvalidSequenceError, 'invalid hex', '\xZ0' + include_examples 'scan error', RS::InvalidSequenceError, 'invalid c-seq', '\cü' + include_examples 'scan error', RS::InvalidSequenceError, 'invalid c-seq', '\c\M-ü' + include_examples 'scan error', RS::InvalidSequenceError, 'invalid c-seq', '\C-ü' + include_examples 'scan error', RS::InvalidSequenceError, 'invalid c-seq', '\C-\M-ü' + include_examples 'scan error', RS::InvalidSequenceError, 'invalid m-seq', '\M-ü' + include_examples 'scan error', RS::InvalidSequenceError, 'invalid m-seq', '\M-\cü' + include_examples 'scan error', RS::InvalidSequenceError, 'invalid m-seq', '\M-\C-ü' + include_examples 'scan error', RS::ScannerError, 'invalid c-seq', '\Ca' + include_examples 'scan error', RS::ScannerError, 'invalid m-seq', '\Ma' + include_examples 'scan error', RS::InvalidGroupError, 'invalid group', "(?'')" + include_examples 'scan error', RS::InvalidGroupError, 'invalid group', "(?''empty-name)" + include_examples 'scan error', RS::InvalidGroupError, 'invalid group', '(?<>)' + include_examples 'scan error', RS::InvalidGroupError, 'invalid group', '(?<>empty-name)' + include_examples 'scan error', RS::InvalidGroupOption, 'invalid option', '(?foo)' + include_examples 'scan error', RS::InvalidGroupOption, 'invalid option', '(?mix abc)' + include_examples 'scan error', RS::InvalidGroupOption, 'invalid option', '(?mix^bc' + include_examples 'scan error', RS::InvalidGroupOption, 'invalid option', '(?)' + include_examples 'scan error', RS::InvalidGroupOption, 'invalid neg option', '(?-foo)' + include_examples 'scan error', RS::InvalidGroupOption, 'invalid neg option', '(?-u)' + include_examples 'scan error', RS::InvalidGroupOption, 'invalid neg option', '(?-mixu)' + include_examples 'scan error', RS::InvalidBackrefError, 'empty backref', '\k<>' + include_examples 'scan error', RS::InvalidBackrefError, 'empty backref', '\k\'\'' + include_examples 'scan error', RS::InvalidBackrefError, 'empty refcall', '\g<>' + include_examples 'scan error', RS::InvalidBackrefError, 'empty refcall', '\g\'\'' + include_examples 'scan error', RS::UnknownUnicodePropertyError, 'unknown property', '\p{foobar}' +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/escapes_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/escapes_spec.rb new file mode 100644 index 00000000..8579c2ca --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/escapes_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +RSpec.describe('Escape scanning') do + include_examples 'scan', /c\at/, 1 => [:escape, :bell, '\a', 1, 3] + + # not an escape outside a character set + include_examples 'scan', /c\bt/, 1 => [:anchor, :word_boundary, '\b', 1, 3] + + include_examples 'scan', /c\ft/, 1 => [:escape, :form_feed, '\f', 1, 3] + include_examples 'scan', /c\nt/, 1 => [:escape, :newline, '\n', 1, 3] + include_examples 'scan', /c\tt/, 1 => [:escape, :tab, '\t', 1, 3] + include_examples 'scan', /c\vt/, 1 => [:escape, :vertical_tab, '\v', 1, 3] + + include_examples 'scan', 'c\qt', 1 => [:escape, :literal, '\q', 1, 3] + + include_examples 'scan', 'a\012c', 1 => [:escape, :octal, '\012', 1, 5] + include_examples 'scan', 'a\0124', 1 => [:escape, :octal, '\012', 1, 5] + include_examples 'scan', '\712+7', 0 => [:escape, :octal, '\712', 0, 4] + + include_examples 'scan', 'a\x24c', 1 => [:escape, :hex, '\x24', 1, 5] + include_examples 'scan', 'a\x0640c', 1 => [:escape, :hex, '\x06', 1, 5] + + include_examples 'scan', 'a\u0640c', 1 => [:escape, :codepoint, '\u0640', 1, 7] + include_examples 'scan', 'a\u{640 0641}c', 1 => [:escape, :codepoint_list, '\u{640 0641}', 1, 13] + include_examples 'scan', 'a\u{10FFFF}c', 1 => [:escape, :codepoint_list, '\u{10FFFF}', 1, 11] + + include_examples 'scan', /a\cBc/, 1 => [:escape, :control, '\cB', 1, 4] + include_examples 'scan', /a\c^c/, 1 => [:escape, :control, '\c^', 1, 4] + include_examples 'scan', /a\c\n/, 1 => [:escape, :control, '\c\n', 1, 5] + include_examples 'scan', /a\c\\b/, 1 => [:escape, :control, '\c\\\\', 1, 5] + include_examples 'scan', /a\C-bc/, 1 => [:escape, :control, '\C-b', 1, 5] + include_examples 'scan', /a\C-^b/, 1 => [:escape, :control, '\C-^', 1, 5] + include_examples 'scan', /a\C-\nb/, 1 => [:escape, :control, '\C-\n', 1, 6] + include_examples 'scan', /a\C-\\b/, 1 => [:escape, :control, '\C-\\\\', 1, 6] + include_examples 'scan', /a\c\M-Bc/n, 1 => [:escape, :control, '\c\M-B', 1, 7] + include_examples 'scan', /a\C-\M-Bc/n, 1 => [:escape, :control, '\C-\M-B', 1, 8] + + include_examples 'scan', /a\M-Bc/n, 1 => [:escape, :meta_sequence, '\M-B', 1, 5] + include_examples 'scan', /a\M-\cBc/n, 1 => [:escape, :meta_sequence, '\M-\cB', 1, 7] + include_examples 'scan', /a\M-\c^/n, 1 => [:escape, :meta_sequence, '\M-\c^', 1, 7] + include_examples 'scan', /a\M-\c\n/n, 1 => [:escape, :meta_sequence, '\M-\c\n', 1, 8] + include_examples 'scan', /a\M-\c\\/n, 1 => [:escape, :meta_sequence, '\M-\c\\\\', 1, 8] + include_examples 'scan', /a\M-\C-Bc/n, 1 => [:escape, :meta_sequence, '\M-\C-B', 1, 8] + include_examples 'scan', /a\M-\C-\\/n, 1 => [:escape, :meta_sequence, '\M-\C-\\\\', 1, 9] + + include_examples 'scan', 'ab\\\xcd', 1 => [:escape, :backslash, '\\\\', 2, 4] + include_examples 'scan', 'ab\\\0cd', 1 => [:escape, :backslash, '\\\\', 2, 4] + include_examples 'scan', 'ab\\\Kcd', 1 => [:escape, :backslash, '\\\\', 2, 4] + + include_examples 'scan', 'ab\^cd', 1 => [:escape, :bol, '\^', 2, 4] + include_examples 'scan', 'ab\$cd', 1 => [:escape, :eol, '\$', 2, 4] + include_examples 'scan', 'ab\[cd', 1 => [:escape, :set_open, '\[', 2, 4] +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/free_space_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/free_space_spec.rb new file mode 100644 index 00000000..41c8ec7b --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/free_space_spec.rb @@ -0,0 +1,133 @@ +require 'spec_helper' + +RSpec.describe('FreeSpace scanning') do + describe('scan free space tokens') do + let(:tokens) { RS.scan(/ + a + b ? c * + d {2,3} + e + | f + + /x) } + + 0.upto(24).select(&:even?).each do |i| + it "scans #{i} as free space" do + expect(tokens[i][0]).to eq :free_space + expect(tokens[i][1]).to eq :whitespace + end + end + 0.upto(24).reject(&:even?).each do |i| + it "does not scan #{i} as free space" do + expect(tokens[i][0]).not_to eq :free_space + expect(tokens[i][1]).not_to eq :whitespace + end + end + + it 'sets the correct text' do + [0, 2, 10, 14].each { |i| expect(tokens[i][2]).to eq "\n " } + [4, 6, 8, 12].each { |i| expect(tokens[i][2]).to eq ' ' } + end + end + + describe('scan free space comments') do + include_examples 'scan', / + a + # A + comment + b ? # B ? comment + c {2,3} # C {2,3} comment + d + | e + # D|E comment + /x, + 5 => [:free_space, :comment, "# A + comment\n", 11, 25], + 11 => [:free_space, :comment, "# B ? comment\n", 37, 51], + 17 => [:free_space, :comment, "# C {2,3} comment\n", 66, 84], + 29 => [:free_space, :comment, "# D|E comment\n", 100, 114] + end + + describe('scan free space inlined') do + include_examples 'scan', /a b(?x:c d e)f g/, + 0 => [:literal, :literal, 'a b', 0, 3], + 1 => [:group, :options, '(?x:', 3, 7], + 2 => [:literal, :literal, 'c', 7, 8], + 3 => [:free_space, :whitespace, ' ', 8, 9], + 4 => [:literal, :literal, 'd', 9, 10], + 5 => [:free_space, :whitespace, ' ', 10, 11], + 6 => [:literal, :literal, 'e', 11, 12], + 7 => [:group, :close, ')', 12, 13], + 8 => [:literal, :literal, 'f g', 13, 16] + end + + describe('scan free space nested') do + include_examples 'scan', /a b(?x:c d(?-x:e f)g h)i j/, + 0 => [:literal, :literal, 'a b', 0, 3], + 1 => [:group, :options, '(?x:', 3, 7], + 2 => [:literal, :literal, 'c', 7, 8], + 3 => [:free_space, :whitespace, ' ', 8, 9], + 4 => [:literal, :literal, 'd', 9, 10], + 5 => [:group, :options, '(?-x:', 10, 15], + 6 => [:literal, :literal, 'e f', 15, 18], + 7 => [:group, :close, ')', 18, 19], + 8 => [:literal, :literal, 'g', 19, 20], + 9 => [:free_space, :whitespace, ' ', 20, 21], + 10 => [:literal, :literal, 'h', 21, 22], + 11 => [:group, :close, ')', 22, 23], + 12 => [:literal, :literal, 'i j', 23, 26] + end + + describe('scan free space nested groups') do + include_examples 'scan', /(a (b(?x: (c d) (?-x:(e f) )g) h)i j)/, + 0 => [:group, :capture, '(', 0, 1], + 1 => [:literal, :literal, 'a ', 1, 3], + 2 => [:group, :capture, '(', 3, 4], + 3 => [:literal, :literal, 'b', 4, 5], + 4 => [:group, :options, '(?x:', 5, 9], + 5 => [:free_space, :whitespace, ' ', 9, 10], + 6 => [:group, :capture, '(', 10, 11], + 7 => [:literal, :literal, 'c', 11, 12], + 8 => [:free_space, :whitespace, ' ', 12, 13], + 9 => [:literal, :literal, 'd', 13, 14], + 10 => [:group, :close, ')', 14, 15], + 11 => [:free_space, :whitespace, ' ', 15, 16], + 12 => [:group, :options, '(?-x:', 16, 21], + 13 => [:group, :capture, '(', 21, 22], + 14 => [:literal, :literal, 'e f', 22, 25], + 15 => [:group, :close, ')', 25, 26], + 16 => [:literal, :literal, ' ', 26, 27], + 17 => [:group, :close, ')', 27, 28], + 18 => [:literal, :literal, 'g', 28, 29], + 19 => [:group, :close, ')', 29, 30], + 20 => [:literal, :literal, ' h', 30, 32], + 21 => [:group, :close, ')', 32, 33], + 22 => [:literal, :literal, 'i j', 33, 36], + 23 => [:group, :close, ')', 36, 37] + end + + describe('scan free space switch groups') do + include_examples 'scan', /(a (b((?x) (c d) ((?-x)(e f) )g) h)i j)/, + 0 => [:group, :capture, '(', 0, 1], + 1 => [:literal, :literal, 'a ', 1, 3], + 2 => [:group, :capture, '(', 3, 4], + 3 => [:literal, :literal, 'b', 4, 5], + 4 => [:group, :capture, '(', 5, 6], + 5 => [:group, :options_switch, '(?x', 6, 9], + 6 => [:group, :close, ')', 9, 10], + 7 => [:free_space, :whitespace, ' ', 10, 11], + 8 => [:group, :capture, '(', 11, 12], + 9 => [:literal, :literal, 'c', 12, 13], + 10 => [:free_space, :whitespace, ' ', 13, 14], + 11 => [:literal, :literal, 'd', 14, 15], + 12 => [:group, :close, ')', 15, 16], + 13 => [:free_space, :whitespace, ' ', 16, 17], + 14 => [:group, :capture, '(', 17, 18], + 15 => [:group, :options_switch, '(?-x', 18, 22], + 16 => [:group, :close, ')', 22, 23], + 17 => [:group, :capture, '(', 23, 24], + 18 => [:literal, :literal, 'e f', 24, 27], + 19 => [:group, :close, ')', 27, 28], + 20 => [:literal, :literal, ' ', 28, 29], + 21 => [:group, :close, ')', 29, 30], + 22 => [:literal, :literal, 'g', 30, 31], + 23 => [:group, :close, ')', 31, 32], + 24 => [:literal, :literal, ' h', 32, 34], + 25 => [:group, :close, ')', 34, 35], + 26 => [:literal, :literal, 'i j', 35, 38], + 27 => [:group, :close, ')', 38, 39] + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/groups_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/groups_spec.rb new file mode 100644 index 00000000..853a165a --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/groups_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +RSpec.describe('Group scanning') do + # Group types + include_examples 'scan', '(?>abc)', 0 => [:group, :atomic, '(?>', 0, 3] + include_examples 'scan', '(abc)', 0 => [:group, :capture, '(', 0, 1] + + include_examples 'scan', '(?abc)', 0 => [:group, :named_ab, '(?', 0, 8] + include_examples 'scan', "(?'name'abc)", 0 => [:group, :named_sq, "(?'name'", 0, 8] + + include_examples 'scan', '(?abc)', 0 => [:group, :named_ab, '(?', 0,10] + include_examples 'scan', "(?'name_1'abc)", 0 => [:group, :named_sq, "(?'name_1'", 0,10] + + include_examples 'scan', '(?:abc)', 0 => [:group, :passive, '(?:', 0, 3] + include_examples 'scan', '(?:)', 0 => [:group, :passive, '(?:', 0, 3] + include_examples 'scan', '(?::)', 0 => [:group, :passive, '(?:', 0, 3] + + # Comments + include_examples 'scan', '(?#abc)', 0 => [:group, :comment, '(?#abc)', 0, 7] + include_examples 'scan', '(?#)', 0 => [:group, :comment, '(?#)', 0, 4] + + # Assertions + include_examples 'scan', '(?=abc)', 0 => [:assertion, :lookahead, '(?=', 0, 3] + include_examples 'scan', '(?!abc)', 0 => [:assertion, :nlookahead, '(?!', 0, 3] + include_examples 'scan', '(?<=abc)', 0 => [:assertion, :lookbehind, '(?<=', 0, 4] + include_examples 'scan', '(? [:assertion, :nlookbehind, '(? [:group, :options, '(?-mix:', 0, 7] + include_examples 'scan', '(?m-ix:abc)', 0 => [:group, :options, '(?m-ix:', 0, 7] + include_examples 'scan', '(?mi-x:abc)', 0 => [:group, :options, '(?mi-x:', 0, 7] + include_examples 'scan', '(?mix:abc)', 0 => [:group, :options, '(?mix:', 0, 6] + include_examples 'scan', '(?m:)', 0 => [:group, :options, '(?m:', 0, 4] + include_examples 'scan', '(?i:)', 0 => [:group, :options, '(?i:', 0, 4] + include_examples 'scan', '(?x:)', 0 => [:group, :options, '(?x:', 0, 4] + include_examples 'scan', '(?mix)', 0 => [:group, :options_switch, '(?mix', 0, 5] + include_examples 'scan', '(?d-mix:abc)', 0 => [:group, :options, '(?d-mix:', 0, 8] + include_examples 'scan', '(?a-mix:abc)', 0 => [:group, :options, '(?a-mix:', 0, 8] + include_examples 'scan', '(?u-mix:abc)', 0 => [:group, :options, '(?u-mix:', 0, 8] + include_examples 'scan', '(?da-m:abc)', 0 => [:group, :options, '(?da-m:', 0, 7] + include_examples 'scan', '(?du-x:abc)', 0 => [:group, :options, '(?du-x:', 0, 7] + include_examples 'scan', '(?dau-i:abc)', 0 => [:group, :options, '(?dau-i:', 0, 8] + include_examples 'scan', '(?dau:abc)', 0 => [:group, :options, '(?dau:', 0, 6] + include_examples 'scan', '(?d:)', 0 => [:group, :options, '(?d:', 0, 4] + include_examples 'scan', '(?a:)', 0 => [:group, :options, '(?a:', 0, 4] + include_examples 'scan', '(?u:)', 0 => [:group, :options, '(?u:', 0, 4] + include_examples 'scan', '(?dau)', 0 => [:group, :options_switch, '(?dau', 0, 5] + + if ruby_version_at_least('2.4.1') + include_examples 'scan', '(?~abc)', 0 => [:group, :absence, '(?~', 0, 3] + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/keep_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/keep_spec.rb new file mode 100644 index 00000000..724bfa21 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/keep_spec.rb @@ -0,0 +1,10 @@ +require 'spec_helper' + +RSpec.describe('Keep scanning') do + include_examples 'scan', /ab\Kcd/, + 1 => [:keep, :mark, '\K', 2, 4] + + include_examples 'scan', /(a\Kb)|(c\\\Kd)ef/, + 2 => [:keep, :mark, '\K', 2, 4], + 9 => [:keep, :mark, '\K', 11, 13] +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/literals_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/literals_spec.rb new file mode 100644 index 00000000..19aacc7f --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/literals_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +RSpec.describe('UTF8 scanning') do + # ascii, single byte characters + include_examples 'scan', 'a', 0 => [:literal, :literal, 'a', 0, 1] + + include_examples 'scan', 'ab+', 0 => [:literal, :literal, 'ab', 0, 2] + include_examples 'scan', 'ab+', 1 => [:quantifier, :one_or_more, '+', 2, 3] + + # 2 byte wide characters, Arabic + include_examples 'scan', 'aاbبcت', 0 => [:literal, :literal, 'aاbبcت', 0, 9] + + include_examples 'scan', 'aاbبت?', 0 => [:literal, :literal, 'aاbبت', 0, 8] + include_examples 'scan', 'aاbبت?', 1 => [:quantifier, :zero_or_one, '?', 8, 9] + + include_examples 'scan', 'aا?bبcت+', 0 => [:literal, :literal, 'aا', 0, 3] + include_examples 'scan', 'aا?bبcت+', 1 => [:quantifier, :zero_or_one, '?', 3, 4] + include_examples 'scan', 'aا?bبcت+', 2 => [:literal, :literal, 'bبcت', 4, 10] + include_examples 'scan', 'aا?bبcت+', 3 => [:quantifier, :one_or_more, '+', 10, 11] + + include_examples 'scan', 'a(اbب+)cت?', 0 => [:literal, :literal, 'a', 0, 1] + include_examples 'scan', 'a(اbب+)cت?', 1 => [:group, :capture, '(', 1, 2] + include_examples 'scan', 'a(اbب+)cت?', 2 => [:literal, :literal, 'اbب', 2, 7] + include_examples 'scan', 'a(اbب+)cت?', 3 => [:quantifier, :one_or_more, '+', 7, 8] + include_examples 'scan', 'a(اbب+)cت?', 4 => [:group, :close, ')', 8, 9] + include_examples 'scan', 'a(اbب+)cت?', 5 => [:literal, :literal, 'cت', 9, 12] + include_examples 'scan', 'a(اbب+)cت?', 6 => [:quantifier, :zero_or_one, '?', 12, 13] + + # 3 byte wide characters, Japanese + include_examples 'scan', 'ab?れます+cd', 0 => [:literal, :literal, 'ab', 0, 2] + include_examples 'scan', 'ab?れます+cd', 1 => [:quantifier, :zero_or_one, '?', 2, 3] + include_examples 'scan', 'ab?れます+cd', 2 => [:literal, :literal, 'れます', 3, 12] + include_examples 'scan', 'ab?れます+cd', 3 => [:quantifier, :one_or_more, '+', 12, 13] + include_examples 'scan', 'ab?れます+cd', 4 => [:literal, :literal, 'cd', 13, 15] + + # 4 byte wide characters, Osmanya + include_examples 'scan', '𐒀𐒁?𐒂ab+𐒃', 0 => [:literal, :literal, '𐒀𐒁', 0, 8] + include_examples 'scan', '𐒀𐒁?𐒂ab+𐒃', 1 => [:quantifier, :zero_or_one, '?', 8, 9] + include_examples 'scan', '𐒀𐒁?𐒂ab+𐒃', 2 => [:literal, :literal, '𐒂ab', 9, 15] + include_examples 'scan', '𐒀𐒁?𐒂ab+𐒃', 3 => [:quantifier, :one_or_more, '+', 15, 16] + include_examples 'scan', '𐒀𐒁?𐒂ab+𐒃', 4 => [:literal, :literal, '𐒃', 16, 20] + + include_examples 'scan', 'mu𝄞?si*𝄫c+', 0 => [:literal, :literal, 'mu𝄞', 0, 6] + include_examples 'scan', 'mu𝄞?si*𝄫c+', 1 => [:quantifier, :zero_or_one, '?', 6, 7] + include_examples 'scan', 'mu𝄞?si*𝄫c+', 2 => [:literal, :literal, 'si', 7, 9] + include_examples 'scan', 'mu𝄞?si*𝄫c+', 3 => [:quantifier, :zero_or_more, '*', 9, 10] + include_examples 'scan', 'mu𝄞?si*𝄫c+', 4 => [:literal, :literal, '𝄫c', 10, 15] + include_examples 'scan', 'mu𝄞?si*𝄫c+', 5 => [:quantifier, :one_or_more, '+', 15, 16] +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/meta_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/meta_spec.rb new file mode 100644 index 00000000..9cd586cd --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/meta_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +RSpec.describe('Meta scanning') do + include_examples 'scan', /abc??|def*+|ghi+/, + 0 => [:literal, :literal, 'abc', 0, 3], + 1 => [:quantifier, :zero_or_one_reluctant, '??', 3, 5], + 2 => [:meta, :alternation, '|', 5, 6], + 3 => [:literal, :literal, 'def', 6, 9], + 4 => [:quantifier, :zero_or_more_possessive, '*+', 9, 11], + 5 => [:meta, :alternation, '|', 11, 12] + + include_examples 'scan', /(a\|b)|(c|d)\|(e[|]f)/, + 2 => [:escape, :alternation, '\|', 2, 4], + 5 => [:meta, :alternation, '|', 6, 7], + 8 => [:meta, :alternation, '|', 9, 10], + 11 => [:escape, :alternation, '\|', 12, 14], + 15 => [:literal, :literal, '|', 17, 18] +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/properties_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/properties_spec.rb new file mode 100644 index 00000000..1a5e256a --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/properties_spec.rb @@ -0,0 +1,64 @@ +require 'spec_helper' + +RSpec.describe('Property scanning') do + RSpec.shared_examples 'scan property' do |text, token| + it("scans \\p{#{text}} as property #{token}") do + result = RS.scan("\\p{#{text}}")[0] + expect(result[0..1]).to eq [:property, token] + end + + it("scans \\P{#{text}} as nonproperty #{token}") do + result = RS.scan("\\P{#{text}}")[0] + expect(result[0..1]).to eq [:nonproperty, token] + end + + it("scans \\p{^#{text}} as nonproperty #{token}") do + result = RS.scan("\\p{^#{text}}")[0] + expect(result[0..1]).to eq [:nonproperty, token] + end + + it("scans double-negated \\P{^#{text}} as property #{token}") do + result = RS.scan("\\P{^#{text}}")[0] + expect(result[0..1]).to eq [:property, token] + end + end + + include_examples 'scan property', 'Alnum', :alnum + + include_examples 'scan property', 'XPosixPunct', :xposixpunct + + include_examples 'scan property', 'Newline', :newline + + include_examples 'scan property', 'Any', :any + + include_examples 'scan property', 'Assigned', :assigned + + include_examples 'scan property', 'Age=1.1', :'age=1.1' + include_examples 'scan property', 'Age=10.0', :'age=10.0' + + include_examples 'scan property', 'ahex', :ascii_hex_digit + include_examples 'scan property', 'ASCII_Hex_Digit', :ascii_hex_digit # test underscore + + include_examples 'scan property', 'sd', :soft_dotted + include_examples 'scan property', 'Soft-Dotted', :soft_dotted # test dash + + include_examples 'scan property', 'Egyp', :egyptian_hieroglyphs + include_examples 'scan property', 'Egyptian Hieroglyphs', :egyptian_hieroglyphs # test whitespace + + include_examples 'scan property', 'Linb', :linear_b + include_examples 'scan property', 'Linear-B', :linear_b # test dash + + include_examples 'scan property', 'InArabic', :in_arabic # test block + include_examples 'scan property', 'in Arabic', :in_arabic # test block w. whitespace + include_examples 'scan property', 'In_Arabic', :in_arabic # test block w. underscore + + include_examples 'scan property', 'Yiii', :yi + include_examples 'scan property', 'Yi', :yi + + include_examples 'scan property', 'Zinh', :inherited + include_examples 'scan property', 'Inherited', :inherited + include_examples 'scan property', 'Qaai', :inherited + + include_examples 'scan property', 'Zzzz', :unknown + include_examples 'scan property', 'Unknown', :unknown +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/quantifiers_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/quantifiers_spec.rb new file mode 100644 index 00000000..78e677a1 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/quantifiers_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +RSpec.describe('Quantifier scanning') do + include_examples 'scan', 'a?', 1 => [:quantifier, :zero_or_one, '?', 1, 2] + include_examples 'scan', 'a??', 1 => [:quantifier, :zero_or_one_reluctant, '??', 1, 3] + include_examples 'scan', 'a?+', 1 => [:quantifier, :zero_or_one_possessive, '?+', 1, 3] + + include_examples 'scan', 'a*', 1 => [:quantifier, :zero_or_more, '*', 1, 2] + include_examples 'scan', 'a*?', 1 => [:quantifier, :zero_or_more_reluctant, '*?', 1, 3] + include_examples 'scan', 'a*+', 1 => [:quantifier, :zero_or_more_possessive, '*+', 1, 3] + + include_examples 'scan', 'a+', 1 => [:quantifier, :one_or_more, '+', 1, 2] + include_examples 'scan', 'a+?', 1 => [:quantifier, :one_or_more_reluctant, '+?', 1, 3] + include_examples 'scan', 'a++', 1 => [:quantifier, :one_or_more_possessive, '++', 1, 3] + + include_examples 'scan', 'a{2}', 1 => [:quantifier, :interval, '{2}', 1, 4] + include_examples 'scan', 'a{2,}', 1 => [:quantifier, :interval, '{2,}', 1, 5] + include_examples 'scan', 'a{,2}', 1 => [:quantifier, :interval, '{,2}', 1, 5] + include_examples 'scan', 'a{2,4}', 1 => [:quantifier, :interval, '{2,4}', 1, 6] +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/refcalls_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/refcalls_spec.rb new file mode 100644 index 00000000..cd3f568e --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/refcalls_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +RSpec.describe('RefCall scanning') do + # Traditional numerical group back-reference + include_examples 'scan', '(abc)\1' , 3 => [:backref, :number, '\1', 5, 7] + + # Group back-references, named, numbered, and relative + include_examples 'scan', '(?abc)\k', 3 => [:backref, :name_ref_ab, '\k', 9, 14] + include_examples 'scan', "(?abc)\\k'X'", 3 => [:backref, :name_ref_sq, "\\k'X'", 9, 14] + + include_examples 'scan', '(abc)\k<1>', 3 => [:backref, :number_ref_ab, '\k<1>', 5, 10] + include_examples 'scan', "(abc)\\k'1'", 3 => [:backref, :number_ref_sq, "\\k'1'", 5, 10] + + include_examples 'scan', '(abc)\k<-1>', 3 => [:backref, :number_rel_ref_ab, '\k<-1>', 5, 11] + include_examples 'scan', "(abc)\\k'-1'", 3 => [:backref, :number_rel_ref_sq, "\\k'-1'", 5, 11] + + # Sub-expression invocation, named, numbered, and relative + include_examples 'scan', '(?abc)\g', 3 => [:backref, :name_call_ab, '\g', 9, 14] + include_examples 'scan', "(?abc)\\g'X'", 3 => [:backref, :name_call_sq, "\\g'X'", 9, 14] + + include_examples 'scan', '(abc)\g<1>', 3 => [:backref, :number_call_ab, '\g<1>', 5, 10] + include_examples 'scan', "(abc)\\g'1'", 3 => [:backref, :number_call_sq, "\\g'1'", 5, 10] + + include_examples 'scan', '(abc)\g<-1>', 3 => [:backref, :number_rel_call_ab, '\g<-1>', 5, 11] + include_examples 'scan', "(abc)\\g'-1'", 3 => [:backref, :number_rel_call_sq, "\\g'-1'", 5, 11] + + include_examples 'scan', '\g<+1>(abc)', 0 => [:backref, :number_rel_call_ab, '\g<+1>', 0, 6] + include_examples 'scan', "\\g'+1'(abc)", 0 => [:backref, :number_rel_call_sq, "\\g'+1'", 0, 6] + + # Group back-references, with recursion level + include_examples 'scan', '(?abc)\k', 3 => [:backref, :name_recursion_ref_ab, '\k', 9, 16] + include_examples 'scan', "(?abc)\\k'X-0'", 3 => [:backref, :name_recursion_ref_sq, "\\k'X-0'", 9, 16] + + include_examples 'scan', '(abc)\k<1-0>', 3 => [:backref, :number_recursion_ref_ab, '\k<1-0>', 5, 12] + include_examples 'scan', "(abc)\\k'1-0'", 3 => [:backref, :number_recursion_ref_sq, "\\k'1-0'", 5, 12] +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/sets_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/sets_spec.rb new file mode 100644 index 00000000..5ec52209 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/sets_spec.rb @@ -0,0 +1,102 @@ +require 'spec_helper' + +RSpec.describe('Set scanning') do + include_examples 'scan', /[a]/, 0 => [:set, :open, '[', 0, 1] + include_examples 'scan', /[b]/, 2 => [:set, :close, ']', 2, 3] + include_examples 'scan', /[^n]/, 1 => [:set, :negate, '^', 1, 2] + + include_examples 'scan', /[c]/, 1 => [:literal, :literal, 'c', 1, 2] + include_examples 'scan', /[\b]/, 1 => [:escape, :backspace, '\b', 1, 3] + include_examples 'scan', /[A\bX]/, 2 => [:escape, :backspace, '\b', 2, 4] + + include_examples 'scan', /[.]/, 1 => [:literal, :literal, '.', 1, 2] + include_examples 'scan', /[?]/, 1 => [:literal, :literal, '?', 1, 2] + include_examples 'scan', /[*]/, 1 => [:literal, :literal, '*', 1, 2] + include_examples 'scan', /[+]/, 1 => [:literal, :literal, '+', 1, 2] + include_examples 'scan', /[{]/, 1 => [:literal, :literal, '{', 1, 2] + include_examples 'scan', /[}]/, 1 => [:literal, :literal, '}', 1, 2] + include_examples 'scan', /[<]/, 1 => [:literal, :literal, '<', 1, 2] + include_examples 'scan', /[>]/, 1 => [:literal, :literal, '>', 1, 2] + + include_examples 'scan', /[äöü]/, 2 => [:literal, :literal, 'ö', 3, 5] + + include_examples 'scan', /[\x20]/, 1 => [:escape, :hex, '\x20', 1, 5] + + include_examples 'scan', '[\.]', 1 => [:escape, :dot, '\.', 1, 3] + include_examples 'scan', '[\!]', 1 => [:escape, :literal, '\!', 1, 3] + include_examples 'scan', '[\#]', 1 => [:escape, :literal, '\#', 1, 3] + include_examples 'scan', '[\\]]', 1 => [:escape, :set_close, '\]', 1, 3] + include_examples 'scan', '[\\\\]', 1 => [:escape, :backslash, '\\\\', 1, 3] + include_examples 'scan', '[\A]', 1 => [:escape, :literal, '\A', 1, 3] + include_examples 'scan', '[\z]', 1 => [:escape, :literal, '\z', 1, 3] + include_examples 'scan', '[\g]', 1 => [:escape, :literal, '\g', 1, 3] + include_examples 'scan', '[\K]', 1 => [:escape, :literal, '\K', 1, 3] + include_examples 'scan', '[\R]', 1 => [:escape, :literal, '\R', 1, 3] + include_examples 'scan', '[\X]', 1 => [:escape, :literal, '\X', 1, 3] + include_examples 'scan', '[\c2]', 1 => [:escape, :literal, '\c', 1, 3] + include_examples 'scan', '[\B]', 1 => [:escape, :literal, '\B', 1, 3] + include_examples 'scan', '[a\-c]', 2 => [:escape, :literal, '\-', 2, 4] + + include_examples 'scan', /[\d]/, 1 => [:type, :digit, '\d', 1, 3] + include_examples 'scan', /[\da-z]/, 1 => [:type, :digit, '\d', 1, 3] + include_examples 'scan', /[\D]/, 1 => [:type, :nondigit, '\D', 1, 3] + + include_examples 'scan', /[\h]/, 1 => [:type, :hex, '\h', 1, 3] + include_examples 'scan', /[\H]/, 1 => [:type, :nonhex, '\H', 1, 3] + + include_examples 'scan', /[\s]/, 1 => [:type, :space, '\s', 1, 3] + include_examples 'scan', /[\S]/, 1 => [:type, :nonspace, '\S', 1, 3] + + include_examples 'scan', /[\w]/, 1 => [:type, :word, '\w', 1, 3] + include_examples 'scan', /[\W]/, 1 => [:type, :nonword, '\W', 1, 3] + + include_examples 'scan', /[a-b]/, 1 => [:literal, :literal, 'a', 1, 2] + include_examples 'scan', /[a-c]/, 2 => [:set, :range, '-', 2, 3] + include_examples 'scan', /[a-d]/, 3 => [:literal, :literal, 'd', 3, 4] + include_examples 'scan', /[a-b-]/, 4 => [:literal, :literal, '-', 4, 5] + include_examples 'scan', /[-a]/, 1 => [:literal, :literal, '-', 1, 2] + include_examples 'scan', /[a-c^]/, 4 => [:literal, :literal, '^', 4, 5] + include_examples 'scan', /[a-bd-f]/, 2 => [:set, :range, '-', 2, 3] + include_examples 'scan', /[a-cd-f]/, 5 => [:set, :range, '-', 5, 6] + + include_examples 'scan', /[a[:digit:]c]/, 2 => [:posixclass, :digit, '[:digit:]', 2, 11] + include_examples 'scan', /[[:digit:][:space:]]/, 2 => [:posixclass, :space, '[:space:]', 10, 19] + include_examples 'scan', /[[:^digit:]]/, 1 => [:nonposixclass, :digit, '[:^digit:]', 1, 11] + + include_examples 'scan', /[a[.a-b.]c]/, 2 => [:set, :collation, '[.a-b.]', 2, 9] + include_examples 'scan', /[a[=e=]c]/, 2 => [:set, :equivalent, '[=e=]', 2, 7] + + include_examples 'scan', /[a-d&&g-h]/, 4 => [:set, :intersection, '&&', 4, 6] + include_examples 'scan', /[a&&]/, 2 => [:set, :intersection, '&&', 2, 4] + include_examples 'scan', /[&&z]/, 1 => [:set, :intersection, '&&', 1, 3] + + include_examples 'scan', /[a\p{digit}c]/, 2 => [:property, :digit, '\p{digit}', 2, 11] + include_examples 'scan', /[a\P{digit}c]/, 2 => [:nonproperty, :digit, '\P{digit}', 2, 11] + include_examples 'scan', /[a\p{^digit}c]/, 2 => [:nonproperty, :digit, '\p{^digit}', 2, 12] + include_examples 'scan', /[a\P{^digit}c]/, 2 => [:property, :digit, '\P{^digit}', 2, 12] + + include_examples 'scan', /[a\p{ALPHA}c]/, 2 => [:property, :alpha, '\p{ALPHA}', 2, 11] + include_examples 'scan', /[a\p{P}c]/, 2 => [:property, :punctuation,'\p{P}', 2, 7] + include_examples 'scan', /[a\p{P}\P{P}c]/, 3 => [:nonproperty, :punctuation,'\P{P}', 7, 12] + + include_examples 'scan', /[\x20-\x27]/, + 1 => [:escape, :hex, '\x20', 1, 5], + 2 => [:set, :range, '-', 5, 6], + 3 => [:escape, :hex, '\x27', 6, 10] + + include_examples 'scan', /[a-w&&[^c-g]z]/, + 5 => [:set, :open, '[', 6, 7], + 6 => [:set, :negate, '^', 7, 8], + 8 => [:set, :range, '-', 9, 10], + 10=> [:set, :close, ']', 11, 12] + + specify('set literal encoding') do + text = RS.scan('[a]')[1][2].to_s + expect(text).to eq 'a' + expect(text.encoding.to_s).to eq 'UTF-8' + + text = RS.scan("[\u{1F632}]")[1][2].to_s + expect(text).to eq "\u{1F632}" + expect(text.encoding.to_s).to eq 'UTF-8' + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/types_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/types_spec.rb new file mode 100644 index 00000000..b67ccced --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/scanner/types_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +RSpec.describe('Type scanning') do + include_examples 'scan', 'a\\dc', 1 => [:type, :digit, '\\d', 1, 3] + include_examples 'scan', 'a\\Dc', 1 => [:type, :nondigit, '\\D', 1, 3] + include_examples 'scan', 'a\\hc', 1 => [:type, :hex, '\\h', 1, 3] + include_examples 'scan', 'a\\Hc', 1 => [:type, :nonhex, '\\H', 1, 3] + include_examples 'scan', 'a\\sc', 1 => [:type, :space, '\\s', 1, 3] + include_examples 'scan', 'a\\Sc', 1 => [:type, :nonspace, '\\S', 1, 3] + include_examples 'scan', 'a\\wc', 1 => [:type, :word, '\\w', 1, 3] + include_examples 'scan', 'a\\Wc', 1 => [:type, :nonword, '\\W', 1, 3] + include_examples 'scan', 'a\\Rc', 1 => [:type, :linebreak, '\\R', 1, 3] + include_examples 'scan', 'a\\Xc', 1 => [:type, :xgrapheme, '\\X', 1, 3] +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/spec_helper.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/spec_helper.rb new file mode 100644 index 00000000..763f1bdc --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/spec_helper.rb @@ -0,0 +1,15 @@ +require 'regexp_parser' +require 'regexp_property_values' +require_relative 'support/shared_examples' + +RS = Regexp::Scanner +RL = Regexp::Lexer +RP = Regexp::Parser +RE = Regexp::Expression +T = Regexp::Syntax::Token + +include Regexp::Expression + +def ruby_version_at_least(version) + Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new(version) +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/support/runner.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/support/runner.rb new file mode 100644 index 00000000..39b9a37a --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/support/runner.rb @@ -0,0 +1,42 @@ +require 'pathname' +require 'rspec' + +module RegexpParserSpec + class Runner + def initialize(arguments, warning_whitelist) + @arguments = arguments + @warning_whitelist = warning_whitelist + end + + def run + spec_status = nil + + Warning::Filter.new(warning_whitelist).assert_expected_warnings_only do + setup + spec_status = run_rspec + end + + spec_status + end + + private + + def setup + $VERBOSE = true + + spec_files.each(&method(:require)) + end + + def run_rspec + RSpec::Core::Runner.run([]) + end + + def spec_files + arguments + .map { |path| Pathname.new(path).expand_path.freeze } + .select(&:file?) + end + + attr_reader :arguments, :warning_whitelist + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/support/shared_examples.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/support/shared_examples.rb new file mode 100644 index 00000000..25fd6d31 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/support/shared_examples.rb @@ -0,0 +1,77 @@ +RSpec.shared_examples 'syntax' do |klass, opts| + opts[:implements].each do |type, tokens| + tokens.each do |token| + it("implements #{token} #{type}") do + expect(klass.implements?(type, token)).to be true + end + end + end + + opts[:excludes] && opts[:excludes].each do |type, tokens| + tokens.each do |token| + it("does not implement #{token} #{type}") do + expect(klass.implements?(type, token)).to be false + end + end + end +end + +RSpec.shared_examples 'scan' do |pattern, checks| + context "given the pattern #{pattern}" do + before(:all) { @tokens = Regexp::Scanner.scan(pattern) } + + checks.each do |index, (type, token, text, ts, te)| + it "scans token #{index} as #{token} #{type} at #{ts}..#{te}" do + result = @tokens.at(index) + + expect(result[0]).to eq type + expect(result[1]).to eq token + expect(result[2]).to eq text + expect(result[3]).to eq ts + expect(result[4]).to eq te + end + end + end +end + +RSpec.shared_examples 'lex' do |pattern, checks| + context "given the pattern #{pattern}" do + before(:all) { @tokens = Regexp::Lexer.lex(pattern) } + + checks.each do |index, (type, token, text, ts, te, lvl, set_lvl, cond_lvl)| + it "lexes token #{index} as #{token} #{type} at #{lvl}, #{set_lvl}, #{cond_lvl}" do + struct = @tokens.at(index) + + expect(struct.type).to eq type + expect(struct.token).to eq token + expect(struct.text).to eq text + expect(struct.ts).to eq ts + expect(struct.te).to eq te + expect(struct.level).to eq lvl + expect(struct.set_level).to eq set_lvl + expect(struct.conditional_level).to eq cond_lvl + end + end + end +end + +RSpec.shared_examples 'parse' do |pattern, checks| + context "given the pattern #{pattern}" do + before(:all) { @root = Regexp::Parser.parse(pattern, '*') } + + checks.each do |path, (type, token, klass, attributes)| + it "parses expression at #{path} as #{klass}" do + exp = @root.dig(*path) + + expect(exp).to be_instance_of(klass) + expect(exp.type).to eq type + expect(exp.token).to eq token + + attributes && attributes.each do |method, value| + expect(exp.send(method)).to eq(value), + "expected expression at #{path} to have #{method} #{value}" + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/support/warning_extractor.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/support/warning_extractor.rb new file mode 100644 index 00000000..51f6f920 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/support/warning_extractor.rb @@ -0,0 +1,60 @@ +require 'set' +require 'delegate' + +module RegexpParserSpec + class Warning + class UnexpectedWarnings < StandardError + MSG = 'Unexpected warnings: %s'.freeze + + def initialize(warnings) + super(MSG % warnings.join("\n")) + end + end + + class Filter + def initialize(whitelist) + @whitelist = whitelist + end + + def assert_expected_warnings_only + original = $stderr + $stderr = Extractor.new(original, @whitelist) + + yield + + assert_no_warnings($stderr.warnings) + ensure + $stderr = original + end + + private + + def assert_no_warnings(warnings) + raise UnexpectedWarnings, warnings.to_a if warnings.any? + end + end + + class Extractor < DelegateClass(IO) + PATTERN = /\A(?:.+):(?:\d+): warning: (?:.+)\n\z/ + + def initialize(io, whitelist) + @whitelist = whitelist + @warnings = Set.new + super(io) + end + + def write(message) + return super if PATTERN !~ message + + warning = message.chomp + @warnings << warning if @whitelist.none?(&warning.method(:include?)) + + self + end + + def warnings + @warnings.dup.freeze + end + end + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/syntax/syntax_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/syntax/syntax_spec.rb new file mode 100644 index 00000000..9994f3f9 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/syntax/syntax_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +RSpec.describe(Regexp::Syntax) do + specify('unknown name') do + expect { Regexp::Syntax.new('ruby/1.0') }.to raise_error(Regexp::Syntax::UnknownSyntaxNameError) + end + + specify('new') do + expect(Regexp::Syntax.new('ruby/1.9.3')).to be_instance_of(Regexp::Syntax::V1_9_3) + end + + specify('new any') do + expect(Regexp::Syntax.new('any')).to be_instance_of(Regexp::Syntax::Any) + expect(Regexp::Syntax.new('*')).to be_instance_of(Regexp::Syntax::Any) + end + + specify('not implemented') do + expect { RP.parse('\\p{alpha}', 'ruby/1.8') }.to raise_error(Regexp::Syntax::NotImplementedError) + end + + specify('supported?') do + expect(Regexp::Syntax.supported?('ruby/1.1.1')).to be false + expect(Regexp::Syntax.supported?('ruby/2.4.3')).to be true + expect(Regexp::Syntax.supported?('ruby/2.5')).to be true + end + + specify('invalid version') do + expect { Regexp::Syntax.version_class('2.0.0') }.to raise_error(Regexp::Syntax::InvalidVersionNameError) + + expect { Regexp::Syntax.version_class('ruby/20') }.to raise_error(Regexp::Syntax::InvalidVersionNameError) + end + + specify('version class tiny version') do + expect(Regexp::Syntax.version_class('ruby/1.9.3')).to eq Regexp::Syntax::V1_9_3 + + expect(Regexp::Syntax.version_class('ruby/2.3.1')).to eq Regexp::Syntax::V2_3_1 + end + + specify('version class minor version') do + expect(Regexp::Syntax.version_class('ruby/1.9')).to eq Regexp::Syntax::V1_9 + + expect(Regexp::Syntax.version_class('ruby/2.3')).to eq Regexp::Syntax::V2_3 + end + + specify('raises for unknown constant lookups') do + expect { Regexp::Syntax::V1 }.to raise_error(/V1/) + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/syntax/syntax_token_map_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/syntax/syntax_token_map_spec.rb new file mode 100644 index 00000000..20c9f625 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/syntax/syntax_token_map_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +RSpec.describe(Regexp::Syntax::Token::Map) do + let(:map) { Regexp::Syntax::Token::Map } + + specify('is complete') do + latest_syntax = Regexp::Syntax.new('ruby/2.9') + + latest_syntax.features.each do |type, tokens| + tokens.each { |token| expect(map[type]).to include(token) } + end + end + + specify('contains no duplicate type/token combinations') do + combinations = map.flat_map do |type, tokens| + tokens.map { |token| "#{type} #{token}" } + end + + non_uniq = combinations.group_by { |str| str }.select { |_, v| v.count > 1 } + + expect(non_uniq.keys).to be_empty + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/syntax/versions/1.8.6_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/syntax/versions/1.8.6_spec.rb new file mode 100644 index 00000000..ead31a9c --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/syntax/versions/1.8.6_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +RSpec.describe(Regexp::Syntax::V1_8_6) do + include_examples 'syntax', Regexp::Syntax.new('ruby/1.8.6'), + implements: { + assertion: T::Assertion::Lookahead, + backref: [:number], + escape: T::Escape::Basic + T::Escape::ASCII + T::Escape::Meta + T::Escape::Control, + group: T::Group::V1_8_6, + quantifier: T::Quantifier::Greedy + T::Quantifier::Reluctant + T::Quantifier::Interval + T::Quantifier::IntervalReluctant + }, + excludes: { + assertion: T::Assertion::Lookbehind, + backref: T::Backreference::All - [:number] + T::SubexpressionCall::All, + quantifier: T::Quantifier::Possessive + } +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/syntax/versions/1.9.1_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/syntax/versions/1.9.1_spec.rb new file mode 100644 index 00000000..ab869cd7 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/syntax/versions/1.9.1_spec.rb @@ -0,0 +1,10 @@ +require 'spec_helper' + +RSpec.describe(Regexp::Syntax::V1_9_1) do + include_examples 'syntax', Regexp::Syntax.new('ruby/1.9.1'), + implements: { + escape: T::Escape::Hex + T::Escape::Octal + T::Escape::Unicode, + type: T::CharacterType::Hex, + quantifier: T::Quantifier::Greedy + T::Quantifier::Reluctant + T::Quantifier::Possessive + } +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/syntax/versions/1.9.3_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/syntax/versions/1.9.3_spec.rb new file mode 100644 index 00000000..d40ff008 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/syntax/versions/1.9.3_spec.rb @@ -0,0 +1,9 @@ +require 'spec_helper' + +RSpec.describe(Regexp::Syntax::V1_9_3) do + include_examples 'syntax', Regexp::Syntax.new('ruby/1.9.3'), + implements: { + property: T::UnicodeProperty::Script_V1_9_3 + T::UnicodeProperty::Age_V1_9_3, + nonproperty: T::UnicodeProperty::Script_V1_9_3 + T::UnicodeProperty::Age_V1_9_3 + } +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/syntax/versions/2.0.0_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/syntax/versions/2.0.0_spec.rb new file mode 100644 index 00000000..a3d73989 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/syntax/versions/2.0.0_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +RSpec.describe(Regexp::Syntax::V2_0_0) do + include_examples 'syntax', Regexp::Syntax.new('ruby/2.0.0'), + implements: { + property: T::UnicodeProperty::Age_V2_0_0, + nonproperty: T::UnicodeProperty::Age_V2_0_0 + }, + excludes: { + property: [:newline], + nonproperty: [:newline] + } +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/syntax/versions/2.2.0_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/syntax/versions/2.2.0_spec.rb new file mode 100644 index 00000000..d0cc5e00 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/syntax/versions/2.2.0_spec.rb @@ -0,0 +1,9 @@ +require 'spec_helper' + +RSpec.describe(Regexp::Syntax::V2_2_0) do + include_examples 'syntax', Regexp::Syntax.new('ruby/2.2.0'), + implements: { + property: T::UnicodeProperty::Script_V2_2_0 + T::UnicodeProperty::Age_V2_2_0, + nonproperty: T::UnicodeProperty::Script_V2_2_0 + T::UnicodeProperty::Age_V2_2_0 + } +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/syntax/versions/aliases_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/syntax/versions/aliases_spec.rb new file mode 100644 index 00000000..0492c784 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/syntax/versions/aliases_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +RSpec.describe(Regexp::Syntax) do + RSpec.shared_examples 'syntax alias' do |string, klass| + it "aliases #{string} to #{klass}" do + syntax = Regexp::Syntax.new(string) + expect(syntax).to be_a klass + end + end + + include_examples 'syntax alias', 'ruby/1.8.6', Regexp::Syntax::V1_8_6 + include_examples 'syntax alias', 'ruby/1.8', Regexp::Syntax::V1_8_6 + include_examples 'syntax alias', 'ruby/1.9.1', Regexp::Syntax::V1_9_1 + include_examples 'syntax alias', 'ruby/1.9', Regexp::Syntax::V1_9_3 + include_examples 'syntax alias', 'ruby/2.0.0', Regexp::Syntax::V1_9 + include_examples 'syntax alias', 'ruby/2.0', Regexp::Syntax::V2_0_0 + include_examples 'syntax alias', 'ruby/2.1', Regexp::Syntax::V2_0_0 + include_examples 'syntax alias', 'ruby/2.2.0', Regexp::Syntax::V2_0_0 + include_examples 'syntax alias', 'ruby/2.2.10', Regexp::Syntax::V2_0_0 + include_examples 'syntax alias', 'ruby/2.2', Regexp::Syntax::V2_0_0 + include_examples 'syntax alias', 'ruby/2.3.0', Regexp::Syntax::V2_3_0 + include_examples 'syntax alias', 'ruby/2.3', Regexp::Syntax::V2_3_0 + include_examples 'syntax alias', 'ruby/2.4.0', Regexp::Syntax::V2_4_0 + include_examples 'syntax alias', 'ruby/2.4.1', Regexp::Syntax::V2_4_1 + include_examples 'syntax alias', 'ruby/2.5.0', Regexp::Syntax::V2_4_1 + include_examples 'syntax alias', 'ruby/2.5', Regexp::Syntax::V2_5_0 + include_examples 'syntax alias', 'ruby/2.6.0', Regexp::Syntax::V2_5_0 + include_examples 'syntax alias', 'ruby/2.6.2', Regexp::Syntax::V2_6_2 + include_examples 'syntax alias', 'ruby/2.6.3', Regexp::Syntax::V2_6_3 + include_examples 'syntax alias', 'ruby/2.6', Regexp::Syntax::V2_6_3 + + specify('future alias warning') do + expect { Regexp::Syntax.new('ruby/5.0') } + .to output(/This library .* but you are running .* \(feature set of .*\)/) + .to_stderr + end +end diff --git a/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/token/token_spec.rb b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/token/token_spec.rb new file mode 100644 index 00000000..d17c9198 --- /dev/null +++ b/path/ruby/2.6.0/gems/regexp_parser-1.6.0/spec/token/token_spec.rb @@ -0,0 +1,85 @@ +require 'spec_helper' + +RSpec.describe(Regexp::Token) do + specify('#offset') do + regexp = /ab?cd/ + tokens = RL.lex(regexp) + + expect(tokens[1].text).to eq 'b' + expect(tokens[1].offset).to eq [1, 2] + + expect(tokens[2].text).to eq '?' + expect(tokens[2].offset).to eq [2, 3] + + expect(tokens[3].text).to eq 'cd' + expect(tokens[3].offset).to eq [3, 5] + end + + specify('#length') do + regexp = /abc?def/ + tokens = RL.lex(regexp) + + expect(tokens[0].text).to eq 'ab' + expect(tokens[0].length).to eq 2 + + expect(tokens[1].text).to eq 'c' + expect(tokens[1].length).to eq 1 + + expect(tokens[2].text).to eq '?' + expect(tokens[2].length).to eq 1 + + expect(tokens[3].text).to eq 'def' + expect(tokens[3].length).to eq 3 + end + + specify('#to_h') do + regexp = /abc?def/ + tokens = RL.lex(regexp) + + expect(tokens[0].text).to eq 'ab' + expect(tokens[0].to_h).to eq type: :literal, token: :literal, text: 'ab', ts: 0, te: 2, level: 0, set_level: 0, conditional_level: 0 + + expect(tokens[2].text).to eq '?' + expect(tokens[2].to_h).to eq type: :quantifier, token: :zero_or_one, text: '?', ts: 3, te: 4, level: 0, set_level: 0, conditional_level: 0 + end + + specify('#next') do + regexp = /a+b?c*d{2,3}/ + tokens = RL.lex(regexp) + + a = tokens.first + expect(a.text).to eq 'a' + + plus = a.next + expect(plus.text).to eq '+' + + b = plus.next + expect(b.text).to eq 'b' + + interval = tokens.last + expect(interval.text).to eq '{2,3}' + + expect(interval.next).to be_nil + end + + specify('#previous') do + regexp = /a+b?c*d{2,3}/ + tokens = RL.lex(regexp) + + interval = tokens.last + expect(interval.text).to eq '{2,3}' + + d = interval.previous + expect(d.text).to eq 'd' + + star = d.previous + expect(star.text).to eq '*' + + c = star.previous + expect(c.text).to eq 'c' + + a = tokens.first + expect(a.text).to eq 'a' + expect(a.previous).to be_nil + end +end diff --git a/path/ruby/2.6.0/gems/responders-3.0.0/CHANGELOG.md b/path/ruby/2.6.0/gems/responders-3.0.0/CHANGELOG.md new file mode 100644 index 00000000..0ab78ef3 --- /dev/null +++ b/path/ruby/2.6.0/gems/responders-3.0.0/CHANGELOG.md @@ -0,0 +1,117 @@ +## 3.0.0 + +* Remove support for Rails 4.2 +* Remove support for Ruby < 2.4 + +## 2.4.1 + +* Add support for Rails 6 beta + +## 2.4.0 + +* `respond_with` now accepts a new kwargs called `:render` which goes straight to the `render` + call after an unsuccessful post request. Usefull if for example you need to render a template + which is outside of controller's path eg: + + `respond_with resource, render: { template: 'path/to/template' }` + +## 2.3.0 + +* `verify_request_format!` is aliased to `verify_requested_format!` now. +* Implementing the `interpolation_options` method on your controller is deprecated + in favor of naming it `flash_interpolation_options` instead. + +## 2.2.0 + +* Added the `verify_request_format!` method, that can be used as a `before_action` + callback to prevent your actions from being invoked when the controller does + not respond to the request mime type, preventing the execution of complex + queries or creating/deleting records from your app. + +## 2.1.2 + +* Fix rendering when using `ActionController::API`. (by @eLod) +* Added API controller template for the controller generator. (by @vestimir) + +## 2.1.1 + +* Added support for Rails 5. + +## 2.1.0 + +* No longer automatically set the responders generator as many projects may use this gem as a dependency. When upgrading, users will need to add `config.app_generators.scaffold_controller :responders_controller` to their application. The `responders:install` generator has been updated to automatically insert it in new applications + +## 2.0.1 + +* Require `rails/railtie` explicitly before using it +* Require `action_controller` explicitly before using it +* Remove unnecessary and limiting `resourceful?` check that required models to implement `to_#{format}` (such checks are responsibility of the rendering layer) + +## 2.0.0 + +* Import `respond_with` and class-level `respond_to` from Rails +* Support only Rails ~> 4.2 +* `Responders::LocationResponder` is now included by the default responder (and therefore deprecated) + +## 1.1.0 + +* Support Rails 4.1. +* Allow callable objects as the location. + +## 1.0.0 + +* Improve controller generator to work closer to the Rails 4 one, and make it + compatible with strong parameters. +* Drop support for Rails 3.1 and Ruby 1.8, keep support for Rails 3.2 +* Support for Rails 4.0 onward +* Fix flash message on destroy failure. Fixes #61 + +## 0.9.3 + +* Fix url generation for namespaced models + +## 0.9.2 + +* Properly inflect custom responders names + +## 0.9.1 + +* Fix bug with namespace lookup + +## 0.9.0 + +* Disable namespace lookup by default + +## 0.8 + +* Allow embedded HTML in flash messages + +## 0.7 + +* Support Rails 3.1 onward +* Support namespaced engines + +## 0.6 + +* Allow engine detection in generators +* HTTP Cache is no longer triggered for collections +* `:js` now sets the `flash.now` by default, instead of `flash` +* Renamed `responders_install` generator to `responders:install` +* Added `CollectionResponder` which allows you to always redirect to the collection path + (index action) after POST/PUT/DELETE + +## 0.5 + +* Added Railtie and better Rails 3 support +* Added `:flash_now` as option + +## 0.4 + +* Added `Responders::FlashResponder.flash_keys` and default to `[ :notice, :alert ]` +* Added support to `respond_with(@resource, :notice => "Yes!", :alert => "No!")`` + +## 0.1 + +* Added `FlashResponder` +* Added `HttpCacheResponder` +* Added responders generators diff --git a/path/ruby/2.6.0/gems/responders-3.0.0/MIT-LICENSE b/path/ruby/2.6.0/gems/responders-3.0.0/MIT-LICENSE new file mode 100644 index 00000000..d452d3d4 --- /dev/null +++ b/path/ruby/2.6.0/gems/responders-3.0.0/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright 2009-2019 Plataformatec. http://plataformatec.com.br + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/path/ruby/2.6.0/gems/responders-3.0.0/README.md b/path/ruby/2.6.0/gems/responders-3.0.0/README.md new file mode 100644 index 00000000..10c45a47 --- /dev/null +++ b/path/ruby/2.6.0/gems/responders-3.0.0/README.md @@ -0,0 +1,256 @@ +# Responders + +[![Gem Version](https://fury-badge.herokuapp.com/rb/responders.svg)](http://badge.fury.io/rb/responders) +[![Build Status](https://api.travis-ci.org/plataformatec/responders.svg?branch=master)](http://travis-ci.org/plataformatec/responders) +[![Code Climate](https://codeclimate.com/github/plataformatec/responders.svg)](https://codeclimate.com/github/plataformatec/responders) + +A set of responders modules to dry up your Rails 4.2+ app. + +## Installation + +Add the responders gem to your Gemfile: + + gem "responders" + +Update your bundle and run the install generator: + + $ bundle install + $ rails g responders:install + +If you are including this gem to support backwards compatibilty for responders in previous releases of Rails, you only need to include the gem and bundle. + + $ bundle install + +## Responders Types + +### FlashResponder + +Sets the flash based on the controller action and resource status. +For instance, if you do: `respond_with(@post)` on a POST request and the resource `@post` +does not contain errors, it will automatically set the flash message to +`"Post was successfully created"` as long as you configure your I18n file: + +```yaml + flash: + actions: + create: + notice: "%{resource_name} was successfully created." + update: + notice: "%{resource_name} was successfully updated." + destroy: + notice: "%{resource_name} was successfully destroyed." + alert: "%{resource_name} could not be destroyed." +``` + +In case the resource contains errors, you should use the failure key on I18n. This is +useful to dry up flash messages from your controllers. Note: by default alerts for `update` +and `destroy` actions are commented in generated I18n file. If you need a specific message +for a controller, let's say, for `PostsController`, you can also do: + +```yaml + flash: + posts: + create: + notice: "Your post was created and will be published soon" +``` + +This responder is activated in all non get requests. By default it will use the keys +`:notice` and `:alert`, but they can be changed in your application: + +```ruby +config.responders.flash_keys = [ :success, :failure ] +``` + +You can also have embedded HTML. Just create a `_html` scope. + +```yaml + flash: + actions: + create: + alert_html: "OH NOES! You did it wrong!" + posts: + create: + notice_html: "Yay! You did it!" +``` + +See also the `namespace_lookup` option to search the full hierarchy of possible keys. + +### HttpCacheResponder + +Automatically adds Last-Modified headers to API requests. This +allows clients to easily query the server if a resource changed and if the client tries +to retrieve a resource that has not been modified, it returns not_modified status. + +### CollectionResponder + +Makes your create and update action redirect to the collection on success. + +### LocationResponder + +This responder allows you to use callable objects as the redirect location. +Useful when you want to use the `respond_with` method with +a custom route that requires persisted objects, but the validation may fail. + +Note: this responder is included by default, and doesn't need to be included +on the top of your controller (including it will issue a deprecation warning). + +```ruby +class ThingsController < ApplicationController + respond_to :html + + def create + @thing = Thing.create(params[:thing]) + respond_with @thing, location: -> { thing_path(@thing) } + end +end +``` + +**Dealing with namespaced routes** + +In order for the LocationResponder to find the correct route helper for namespaced routes you need to pass the namespaces to `respond_with`: + +```ruby +class Api::V1::ThingsController < ApplicationController + respond_to :json + + # POST /api/v1/things + def create + @thing = Thing.create(thing_params) + respond_with :api, :v1, @thing + end +end +``` + +## Configuring your own responder + +Responders only provides a set of modules and to use them you have to create your own +responder. After you run the install command, the following responder will be +generated in your application: + +```ruby +# lib/application_responder.rb +class ApplicationResponder < ActionController::Responder + include Responders::FlashResponder + include Responders::HttpCacheResponder +end +``` + +Your application also needs to be configured to use it: + +```ruby +# app/controllers/application_controller.rb +require "application_responder" + +class ApplicationController < ActionController::Base + self.responder = ApplicationResponder + respond_to :html +end +``` + +## Controller method + +This gem also includes the controller method `responders`, which allows you to cherry-pick which +responders you want included in your controller. + +```ruby +class InvitationsController < ApplicationController + responders :flash, :http_cache +end +``` + +## Interpolation Options + +You can pass in extra interpolation options for the translation by adding an `flash_interpolation_options` method to your controller: + +```ruby +class InvitationsController < ApplicationController + responders :flash, :http_cache + + def create + @invitation = Invitation.create(params[:invitation]) + respond_with @invitation + end + + private + + def flash_interpolation_options + { resource_name: @invitation.email } + end +end +``` + +Now you would see the message "name@example.com was successfully created" instead of the default "Invitation was successfully created." + +## Generator + +This gem also includes a responders controller generator, so your scaffold can be customized +to use `respond_with` instead of default `respond_to` blocks. From 2.1, you need to explicitly opt-in to use this generator by adding the following to your `config/application.rb`: + + config.app_generators.scaffold_controller :responders_controller + +## Failure handling + +Responders don't use `valid?` to check for errors in models to figure out if +the request was successfull or not, and relies on your controllers to call +`save` or `create` to trigger the validations. + +```ruby +def create + @widget = Widget.new(widget_params) + # @widget will be a valid record for responders, as we haven't called `save` + # on it, and will always redirect to the `widgets_path`. + respond_with @widget, location: -> { widgets_path } +end +``` + +Responders will check if the `errors` object in your model is empty or not. Take +this in consideration when implementing different actions or writing test +assertions on this behavior for your controllers. + +```ruby +def create + @widget = Widget.new(widget_params) + @widget.errors.add(:base, :invalid) + # `respond_with` will render the `new` template again. + respond_with @widget +end +``` + +## Verifying request formats + +`respond_with` will raise an `ActionController::UnknownFormat` if the request +mime type was not configured through the class level `respond_to`, but the +action will still be executed and any side effects (like creating a new record) +will still occur. To raise the `UnknownFormat` exception before your action +is invoked you can set the `verify_requested_format!` method as a `before_action` +on your controller. + +```ruby +class WidgetsController < ApplicationController + respond_to :json + before_action :verify_requested_format! + + # POST /widgets.html won't reach the `create` action. + def create + widget = Widget.create(widget_params) + respond_with widget + end +end + +``` + +## Examples + +Want more examples ? Check out these blog posts: + +* [Embracing REST with mind, body and soul](http://blog.plataformatec.com.br/2009/08/embracing-rest-with-mind-body-and-soul/) +* [Three reasons to love ActionController::Responder](http://weblog.rubyonrails.org/2009/8/31/three-reasons-love-responder/) +* [My five favorite things about Rails 3](http://www.engineyard.com/blog/2009/my-five-favorite-things-about-rails-3) + +## Bugs and Feedback + +If you discover any bugs or want to drop a line, feel free to create an issue on GitHub. + +http://github.com/plataformatec/responders/issues + +MIT License. Copyright 2009-2019 Plataformatec. http://plataformatec.com.br diff --git a/path/ruby/2.6.0/gems/responders-3.0.0/lib/action_controller/respond_with.rb b/path/ruby/2.6.0/gems/responders-3.0.0/lib/action_controller/respond_with.rb new file mode 100644 index 00000000..acbadf50 --- /dev/null +++ b/path/ruby/2.6.0/gems/responders-3.0.0/lib/action_controller/respond_with.rb @@ -0,0 +1,258 @@ +# frozen_string_literal: true + +require "active_support/core_ext/array/extract_options" +require "action_controller/metal/mime_responds" + +module ActionController #:nodoc: + module RespondWith + extend ActiveSupport::Concern + + included do + class_attribute :responder, :mimes_for_respond_to + self.responder = ActionController::Responder + clear_respond_to + end + + module ClassMethods + # Defines mime types that are rendered by default when invoking + # respond_with. + # + # respond_to :html, :xml, :json + # + # Specifies that all actions in the controller respond to requests + # for :html, :xml and :json. + # + # To specify on per-action basis, use :only and + # :except with an array of actions or a single action: + # + # respond_to :html + # respond_to :xml, :json, except: [ :edit ] + # + # This specifies that all actions respond to :html + # and all actions except :edit respond to :xml and + # :json. + # + # respond_to :json, only: :create + # + # This specifies that the :create action and no other responds + # to :json. + def respond_to(*mimes) + options = mimes.extract_options! + + only_actions = Array(options.delete(:only)).map(&:to_sym) + except_actions = Array(options.delete(:except)).map(&:to_sym) + + hash = mimes_for_respond_to.dup + mimes.each do |mime| + mime = mime.to_sym + hash[mime] = {} + hash[mime][:only] = only_actions unless only_actions.empty? + hash[mime][:except] = except_actions unless except_actions.empty? + end + self.mimes_for_respond_to = hash.freeze + end + + # Clear all mime types in respond_to. + # + def clear_respond_to + self.mimes_for_respond_to = Hash.new.freeze + end + end + + # For a given controller action, respond_with generates an appropriate + # response based on the mime-type requested by the client. + # + # If the method is called with just a resource, as in this example - + # + # class PeopleController < ApplicationController + # respond_to :html, :xml, :json + # + # def index + # @people = Person.all + # respond_with @people + # end + # end + # + # then the mime-type of the response is typically selected based on the + # request's Accept header and the set of available formats declared + # by previous calls to the controller's class method +respond_to+. Alternatively + # the mime-type can be selected by explicitly setting request.format in + # the controller. + # + # If an acceptable format is not identified, the application returns a + # '406 - not acceptable' status. Otherwise, the default response is to render + # a template named after the current action and the selected format, + # e.g. index.html.erb. If no template is available, the behavior + # depends on the selected format: + # + # * for an html response - if the request method is +get+, an exception + # is raised but for other requests such as +post+ the response + # depends on whether the resource has any validation errors (i.e. + # assuming that an attempt has been made to save the resource, + # e.g. by a +create+ action) - + # 1. If there are no errors, i.e. the resource + # was saved successfully, the response +redirect+'s to the resource + # i.e. its +show+ action. + # 2. If there are validation errors, the response + # renders a default action, which is :new for a + # +post+ request or :edit for +patch+ or +put+. + # Thus an example like this - + # + # respond_to :html, :xml + # + # def create + # @user = User.new(params[:user]) + # flash[:notice] = 'User was successfully created.' if @user.save + # respond_with(@user) + # end + # + # is equivalent, in the absence of create.html.erb, to - + # + # def create + # @user = User.new(params[:user]) + # respond_to do |format| + # if @user.save + # flash[:notice] = 'User was successfully created.' + # format.html { redirect_to(@user) } + # format.xml { render xml: @user } + # else + # format.html { render action: "new" } + # format.xml { render xml: @user } + # end + # end + # end + # + # * for a JavaScript request - if the template isn't found, an exception is + # raised. + # * for other requests - i.e. data formats such as xml, json, csv etc, if + # the resource passed to +respond_with+ responds to to_, + # the method attempts to render the resource in the requested format + # directly, e.g. for an xml request, the response is equivalent to calling + # render xml: resource. + # + # === Nested resources + # + # As outlined above, the +resources+ argument passed to +respond_with+ + # can play two roles. It can be used to generate the redirect url + # for successful html requests (e.g. for +create+ actions when + # no template exists), while for formats other than html and JavaScript + # it is the object that gets rendered, by being converted directly to the + # required format (again assuming no template exists). + # + # For redirecting successful html requests, +respond_with+ also supports + # the use of nested resources, which are supplied in the same way as + # in form_for and polymorphic_url. For example - + # + # def create + # @project = Project.find(params[:project_id]) + # @task = @project.comments.build(params[:task]) + # flash[:notice] = 'Task was successfully created.' if @task.save + # respond_with(@project, @task) + # end + # + # This would cause +respond_with+ to redirect to project_task_url + # instead of task_url. For request formats other than html or + # JavaScript, if multiple resources are passed in this way, it is the last + # one specified that is rendered. + # + # === Customizing response behavior + # + # Like +respond_to+, +respond_with+ may also be called with a block that + # can be used to overwrite any of the default responses, e.g. - + # + # def create + # @user = User.new(params[:user]) + # flash[:notice] = "User was successfully created." if @user.save + # + # respond_with(@user) do |format| + # format.html { render } + # end + # end + # + # The argument passed to the block is an ActionController::MimeResponds::Collector + # object which stores the responses for the formats defined within the + # block. Note that formats with responses defined explicitly in this way + # do not have to first be declared using the class method +respond_to+. + # + # Also, a hash passed to +respond_with+ immediately after the specified + # resource(s) is interpreted as a set of options relevant to all + # formats. Any option accepted by +render+ can be used, e.g. + # + # respond_with @people, status: 200 + # + # However, note that these options are ignored after an unsuccessful attempt + # to save a resource, e.g. when automatically rendering :new + # after a post request. + # + # Three additional options are relevant specifically to +respond_with+ - + # 1. :location - overwrites the default redirect location used after + # a successful html +post+ request. + # 2. :action - overwrites the default render action used after an + # unsuccessful html +post+ request. + # 3. :render - allows to pass any options directly to the :render + # call after unsuccessful html +post+ request. Usefull if for example you + # need to render a template which is outside of controller's path or you + # want to override the default http :status code, e.g. + # + # respond_with(resource, render: { template: 'path/to/template', status: 422 }) + def respond_with(*resources, &block) + if self.class.mimes_for_respond_to.empty? + raise "In order to use respond_with, first you need to declare the " \ + "formats your controller responds to in the class level." + end + + mimes = collect_mimes_from_class_level + collector = ActionController::MimeResponds::Collector.new(mimes, request.variant) + block.call(collector) if block_given? + + if format = collector.negotiate_format(request) + _process_format(format) + options = resources.size == 1 ? {} : resources.extract_options! + options = options.clone + options[:default_response] = collector.response + (options.delete(:responder) || self.class.responder).call(self, resources, options) + else + raise ActionController::UnknownFormat + end + end + + protected + + # Before action callback that can be used to prevent requests that do not + # match the mime types defined through respond_to from being executed. + # + # class PeopleController < ApplicationController + # respond_to :html, :xml, :json + # + # before_action :verify_requested_format! + # end + def verify_requested_format! + mimes = collect_mimes_from_class_level + collector = ActionController::MimeResponds::Collector.new(mimes, request.variant) + + unless collector.negotiate_format(request) + raise ActionController::UnknownFormat + end + end + + alias :verify_request_format! :verify_requested_format! + + # Collect mimes declared in the class method respond_to valid for the + # current action. + def collect_mimes_from_class_level #:nodoc: + action = action_name.to_sym + + self.class.mimes_for_respond_to.keys.select do |mime| + config = self.class.mimes_for_respond_to[mime] + + if config[:except] + !config[:except].include?(action) + elsif config[:only] + config[:only].include?(action) + else + true + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/responders-3.0.0/lib/action_controller/responder.rb b/path/ruby/2.6.0/gems/responders-3.0.0/lib/action_controller/responder.rb new file mode 100644 index 00000000..7d914655 --- /dev/null +++ b/path/ruby/2.6.0/gems/responders-3.0.0/lib/action_controller/responder.rb @@ -0,0 +1,311 @@ +# frozen_string_literal: true + +require "active_support/json" + +module ActionController #:nodoc: + # Responsible for exposing a resource to different mime requests, + # usually depending on the HTTP verb. The responder is triggered when + # respond_with is called. The simplest case to study is a GET request: + # + # class PeopleController < ApplicationController + # respond_to :html, :xml, :json + # + # def index + # @people = Person.all + # respond_with(@people) + # end + # end + # + # When a request comes in, for example for an XML response, three steps happen: + # + # 1) the responder searches for a template at people/index.xml; + # + # 2) if the template is not available, it will invoke #to_xml on the given resource; + # + # 3) if the responder does not respond_to :to_xml, call #to_format on it. + # + # === Built-in HTTP verb semantics + # + # The default \Rails responder holds semantics for each HTTP verb. Depending on the + # content type, verb and the resource status, it will behave differently. + # + # Using \Rails default responder, a POST request for creating an object could + # be written as: + # + # def create + # @user = User.new(params[:user]) + # flash[:notice] = 'User was successfully created.' if @user.save + # respond_with(@user) + # end + # + # Which is exactly the same as: + # + # def create + # @user = User.new(params[:user]) + # + # respond_to do |format| + # if @user.save + # flash[:notice] = 'User was successfully created.' + # format.html { redirect_to(@user) } + # format.xml { render xml: @user, status: :created, location: @user } + # else + # format.html { render action: "new" } + # format.xml { render xml: @user.errors, status: :unprocessable_entity } + # end + # end + # end + # + # The same happens for PATCH/PUT and DELETE requests. + # + # === Nested resources + # + # You can supply nested resources as you do in form_for and polymorphic_url. + # Consider the project has many tasks example. The create action for + # TasksController would be like: + # + # def create + # @project = Project.find(params[:project_id]) + # @task = @project.tasks.build(params[:task]) + # flash[:notice] = 'Task was successfully created.' if @task.save + # respond_with(@project, @task) + # end + # + # Giving several resources ensures that the responder will redirect to + # project_task_url instead of task_url. + # + # Namespaced and singleton resources require a symbol to be given, as in + # polymorphic urls. If a project has one manager which has many tasks, it + # should be invoked as: + # + # respond_with(@project, :manager, @task) + # + # Note that if you give an array, it will be treated as a collection, + # so the following is not equivalent: + # + # respond_with [@project, :manager, @task] + # + # === Custom options + # + # respond_with also allows you to pass options that are forwarded + # to the underlying render call. Those options are only applied for success + # scenarios. For instance, you can do the following in the create method above: + # + # def create + # @project = Project.find(params[:project_id]) + # @task = @project.tasks.build(params[:task]) + # flash[:notice] = 'Task was successfully created.' if @task.save + # respond_with(@project, @task, status: 201) + # end + # + # This will return status 201 if the task was saved successfully. If not, + # it will simply ignore the given options and return status 422 and the + # resource errors. You can also override the location to redirect to: + # + # respond_with(@project, location: root_path) + # + # To customize the failure scenario, you can pass a block to + # respond_with: + # + # def create + # @project = Project.find(params[:project_id]) + # @task = @project.tasks.build(params[:task]) + # respond_with(@project, @task, status: 201) do |format| + # if @task.save + # flash[:notice] = 'Task was successfully created.' + # else + # format.html { render "some_special_template" } + # end + # end + # end + # + # Using respond_with with a block follows the same syntax as respond_to. + class Responder + attr_reader :controller, :request, :format, :resource, :resources, :options + + DEFAULT_ACTIONS_FOR_VERBS = { + post: :new, + patch: :edit, + put: :edit + } + + def initialize(controller, resources, options = {}) + @controller = controller + @request = @controller.request + @format = @controller.formats.first + @resource = resources.last + @resources = resources + @options = options + @action = options.delete(:action) + @default_response = options.delete(:default_response) + + if options[:location].respond_to?(:call) + location = options.delete(:location) + options[:location] = location.call unless has_errors? + end + end + + delegate :head, :render, :redirect_to, to: :controller + delegate :get?, :post?, :patch?, :put?, :delete?, to: :request + + # Undefine :to_json and :to_yaml since it's defined on Object + undef_method(:to_json) if method_defined?(:to_json) + undef_method(:to_yaml) if method_defined?(:to_yaml) + + # Initializes a new responder and invokes the proper format. If the format is + # not defined, call to_format. + # + def self.call(*args) + new(*args).respond + end + + # Main entry point for responder responsible to dispatch to the proper format. + # + def respond + method = "to_#{format}" + respond_to?(method) ? send(method) : to_format + end + + # HTML format does not render the resource, it always attempt to render a + # template. + # + def to_html + default_render + rescue ActionView::MissingTemplate => e + navigation_behavior(e) + end + + # to_js simply tries to render a template. If no template is found, raises the error. + def to_js + default_render + end + + # All other formats follow the procedure below. First we try to render a + # template, if the template is not available, we verify if the resource + # responds to :to_format and display it. + # + def to_format + if !get? && has_errors? && !response_overridden? + display_errors + elsif has_view_rendering? || response_overridden? + default_render + else + api_behavior + end + rescue ActionView::MissingTemplate + api_behavior + end + + protected + + # This is the common behavior for formats associated with browsing, like :html, :iphone and so forth. + def navigation_behavior(error) + if get? + raise error + elsif has_errors? && default_action + render rendering_options + else + redirect_to navigation_location + end + end + + # This is the common behavior for formats associated with APIs, such as :xml and :json. + def api_behavior + raise MissingRenderer.new(format) unless has_renderer? + + if get? + display resource + elsif post? + display resource, status: :created, location: api_location + else + head :no_content + end + end + + # Returns the resource location by retrieving it from the options or + # returning the resources array. + # + def resource_location + options[:location] || resources + end + alias :navigation_location :resource_location + alias :api_location :resource_location + + # If a response block was given, use it, otherwise call render on + # controller. + # + def default_render + if @default_response + @default_response.call(options) + else + controller.render(options) + end + end + + # Display is just a shortcut to render a resource with the current format. + # + # display @user, status: :ok + # + # For XML requests it's equivalent to: + # + # render xml: @user, status: :ok + # + # Options sent by the user are also used: + # + # respond_with(@user, status: :created) + # display(@user, status: :ok) + # + # Results in: + # + # render xml: @user, status: :created + # + def display(resource, given_options = {}) + controller.render given_options.merge!(options).merge!(format => resource) + end + + def display_errors + controller.render format => resource_errors, :status => :unprocessable_entity + end + + # Check whether the resource has errors. + # + def has_errors? + resource.respond_to?(:errors) && !resource.errors.empty? + end + + # Check whether the necessary Renderer is available + def has_renderer? + Renderers::RENDERERS.include?(format) + end + + def has_view_rendering? + controller.class.include? ActionView::Rendering + end + + # By default, render the :edit action for HTML requests with errors, unless + # the verb was POST. + # + def default_action + @action ||= DEFAULT_ACTIONS_FOR_VERBS[request.request_method_symbol] + end + + def resource_errors + respond_to?("#{format}_resource_errors", true) ? send("#{format}_resource_errors") : resource.errors + end + + def json_resource_errors + { errors: resource.errors } + end + + def response_overridden? + @default_response.present? + end + + def rendering_options + if options[:render] + options[:render] + else + { action: default_action } + end + end + end +end diff --git a/path/ruby/2.6.0/gems/responders-3.0.0/lib/generators/rails/USAGE b/path/ruby/2.6.0/gems/responders-3.0.0/lib/generators/rails/USAGE new file mode 100644 index 00000000..38987084 --- /dev/null +++ b/path/ruby/2.6.0/gems/responders-3.0.0/lib/generators/rails/USAGE @@ -0,0 +1,11 @@ +Description: + Stubs out a scaffolded controller and its views. Different from rails + scaffold_controller, it uses respond_with instead of respond_to blocks. + Pass the model name, either CamelCased or under_scored. The controller + name is retrieved as a pluralized version of the model name. + + To create a controller within a module, specify the model name as a + path like 'parent_module/controller_name'. + + This generates a controller class in app/controllers and invokes helper, + template engine and test framework generators. diff --git a/path/ruby/2.6.0/gems/responders-3.0.0/lib/generators/rails/responders_controller_generator.rb b/path/ruby/2.6.0/gems/responders-3.0.0/lib/generators/rails/responders_controller_generator.rb new file mode 100644 index 00000000..34f23821 --- /dev/null +++ b/path/ruby/2.6.0/gems/responders-3.0.0/lib/generators/rails/responders_controller_generator.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require "rails/generators/rails/scaffold_controller/scaffold_controller_generator" + +module Rails + module Generators + class RespondersControllerGenerator < ScaffoldControllerGenerator + source_root File.expand_path("../templates", __FILE__) + + protected + + def flash? + if defined?(ApplicationController) + !ApplicationController.responder.ancestors.include?(Responders::FlashResponder) + else + Rails.application.config.responders.flash_keys.blank? + end + end + + def attributes_params + "#{singular_table_name}_params" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/responders-3.0.0/lib/generators/rails/templates/api_controller.rb.tt b/path/ruby/2.6.0/gems/responders-3.0.0/lib/generators/rails/templates/api_controller.rb.tt new file mode 100644 index 00000000..ba9f3fd3 --- /dev/null +++ b/path/ruby/2.6.0/gems/responders-3.0.0/lib/generators/rails/templates/api_controller.rb.tt @@ -0,0 +1,51 @@ +<% if namespaced? -%> +require_dependency "<%= namespaced_file_path %>/application_controller" + +<% end -%> +<% module_namespacing do -%> +class <%= controller_class_name %>Controller < ApplicationController + before_action :set_<%= singular_table_name %>, only: [:show, :update, :destroy] + + respond_to :json + +<% unless options[:singleton] -%> + def index + @<%= plural_table_name %> = <%= orm_class.all(class_name) %> + respond_with(@<%= plural_table_name %>) + end +<% end -%> + + def show + respond_with(@<%= singular_table_name %>) + end + + def create + @<%= singular_table_name %> = <%= orm_class.build(class_name, attributes_params) %> + <%= "flash[:notice] = '#{class_name} was successfully created.' if " if flash? %>@<%= orm_instance.save %> + respond_with(@<%= singular_table_name %>) + end + + def update + <%= "flash[:notice] = '#{class_name} was successfully updated.' if " if flash? %>@<%= orm_instance.update(attributes_params) %> + respond_with(@<%= singular_table_name %>) + end + + def destroy + @<%= orm_instance.destroy %> + respond_with(@<%= singular_table_name %>) + end + + private + def set_<%= singular_table_name %> + @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %> + end + + def <%= "#{singular_table_name}_params" %> + <%- if attributes_names.empty? -%> + params[:<%= singular_table_name %>] + <%- else -%> + params.require(:<%= singular_table_name %>).permit(<%= attributes_names.map { |name| ":#{name}" }.join(', ') %>) + <%- end -%> + end +end +<% end -%> diff --git a/path/ruby/2.6.0/gems/responders-3.0.0/lib/generators/rails/templates/controller.rb.tt b/path/ruby/2.6.0/gems/responders-3.0.0/lib/generators/rails/templates/controller.rb.tt new file mode 100644 index 00000000..2fed65ba --- /dev/null +++ b/path/ruby/2.6.0/gems/responders-3.0.0/lib/generators/rails/templates/controller.rb.tt @@ -0,0 +1,59 @@ +<% if namespaced? -%> +require_dependency "<%= namespaced_file_path %>/application_controller" + +<% end -%> +<% module_namespacing do -%> +class <%= controller_class_name %>Controller < ApplicationController + before_action :set_<%= singular_table_name %>, only: [:show, :edit, :update, :destroy] + + respond_to :html + +<% unless options[:singleton] -%> + def index + @<%= plural_table_name %> = <%= orm_class.all(class_name) %> + respond_with(@<%= plural_table_name %>) + end +<% end -%> + + def show + respond_with(@<%= singular_table_name %>) + end + + def new + @<%= singular_table_name %> = <%= orm_class.build(class_name) %> + respond_with(@<%= singular_table_name %>) + end + + def edit + end + + def create + @<%= singular_table_name %> = <%= orm_class.build(class_name, attributes_params) %> + <%= "flash[:notice] = '#{class_name} was successfully created.' if " if flash? %>@<%= orm_instance.save %> + respond_with(@<%= singular_table_name %>) + end + + def update + <%= "flash[:notice] = '#{class_name} was successfully updated.' if " if flash? %>@<%= orm_instance.update(attributes_params) %> + respond_with(@<%= singular_table_name %>) + end + + def destroy + @<%= orm_instance.destroy %> + respond_with(@<%= singular_table_name %>) + end + + private + def set_<%= singular_table_name %> + @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %> + end + + def <%= "#{singular_table_name}_params" %> + <%- if attributes_names.empty? -%> + params[:<%= singular_table_name %>] + <%- else -%> + params.require(:<%= singular_table_name %>).permit(<%= attributes_names.map { |name| ":#{name}" }.join(', ') %>) + <%- end -%> + end +end +<% end -%> diff --git a/path/ruby/2.6.0/gems/responders-3.0.0/lib/generators/responders/install_generator.rb b/path/ruby/2.6.0/gems/responders-3.0.0/lib/generators/responders/install_generator.rb new file mode 100644 index 00000000..d493591c --- /dev/null +++ b/path/ruby/2.6.0/gems/responders-3.0.0/lib/generators/responders/install_generator.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Responders + module Generators + class InstallGenerator < Rails::Generators::Base + source_root File.expand_path("..", __FILE__) + + desc "Creates an initializer with default responder configuration and copy locale file" + + def create_responder_file + create_file "lib/application_responder.rb", <<-RUBY +class ApplicationResponder < ActionController::Responder + include Responders::FlashResponder + include Responders::HttpCacheResponder + + # Redirects resources to the collection path (index action) instead + # of the resource path (show action) for POST/PUT/DELETE requests. + # include Responders::CollectionResponder +end + RUBY + end + + def update_application + inject_into_class "config/application.rb", "Application", <<-RUBY + # Use the responders controller from the responders gem + config.app_generators.scaffold_controller :responders_controller + + RUBY + end + + def update_application_controller + prepend_file "app/controllers/application_controller.rb", %{require "application_responder"\n\n} + inject_into_class "app/controllers/application_controller.rb", "ApplicationController", <<-RUBY + self.responder = ApplicationResponder + respond_to :html + + RUBY + end + + def copy_locale + copy_file "../../responders/locales/en.yml", "config/locales/responders.en.yml" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/responders-3.0.0/lib/responders.rb b/path/ruby/2.6.0/gems/responders-3.0.0/lib/responders.rb new file mode 100644 index 00000000..f3d1ec15 --- /dev/null +++ b/path/ruby/2.6.0/gems/responders-3.0.0/lib/responders.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require "action_controller" +require "rails/railtie" + +module ActionController + autoload :Responder, "action_controller/responder" + autoload :RespondWith, "action_controller/respond_with" +end + +module Responders + autoload :FlashResponder, "responders/flash_responder" + autoload :HttpCacheResponder, "responders/http_cache_responder" + autoload :CollectionResponder, "responders/collection_responder" + autoload :LocationResponder, "responders/location_responder" + + require "responders/controller_method" + + class Railtie < ::Rails::Railtie + config.responders = ActiveSupport::OrderedOptions.new + config.responders.flash_keys = [:notice, :alert] + config.responders.namespace_lookup = false + + # Add load paths straight to I18n, so engines and application can overwrite it. + require "active_support/i18n" + I18n.load_path << File.expand_path("../responders/locales/en.yml", __FILE__) + + initializer "responders.flash_responder" do |app| + Responders::FlashResponder.flash_keys = app.config.responders.flash_keys + Responders::FlashResponder.namespace_lookup = app.config.responders.namespace_lookup + end + end +end + +ActiveSupport.on_load(:action_controller) do + include ActionController::RespondWith +end diff --git a/path/ruby/2.6.0/gems/responders-3.0.0/lib/responders/collection_responder.rb b/path/ruby/2.6.0/gems/responders-3.0.0/lib/responders/collection_responder.rb new file mode 100644 index 00000000..891ea023 --- /dev/null +++ b/path/ruby/2.6.0/gems/responders-3.0.0/lib/responders/collection_responder.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Responders + # This responder modifies your current responder to redirect + # to the collection page on POST/PUT/DELETE. + module CollectionResponder + protected + + # Returns the collection location for redirecting after POST/PUT/DELETE. + # This method, converts the following resources array to the following: + # + # [:admin, @post] #=> [:admin, :posts] + # [@user, @post] #=> [@user, :posts] + # + # When these new arrays are given to redirect_to, it will generate the + # proper URL pointing to the index action. + # + # [:admin, @post] #=> admin_posts_url + # [@user, @post] #=> user_posts_url(@user.to_param) + # + def navigation_location + return options[:location] if options[:location] + klass = resources.last.class + + if klass.respond_to?(:model_name) + resources[0...-1] << klass.model_name.route_key.to_sym + else + resources + end + end + end +end diff --git a/path/ruby/2.6.0/gems/responders-3.0.0/lib/responders/controller_method.rb b/path/ruby/2.6.0/gems/responders-3.0.0/lib/responders/controller_method.rb new file mode 100644 index 00000000..36835a74 --- /dev/null +++ b/path/ruby/2.6.0/gems/responders-3.0.0/lib/responders/controller_method.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module Responders + module ControllerMethod + # Adds the given responders to the current controller's responder, allowing you to cherry-pick + # which responders you want per controller. + # + # class InvitationsController < ApplicationController + # responders :flash, :http_cache + # end + # + # Takes symbols and strings and translates them to VariableResponder (eg. :flash becomes FlashResponder). + # Also allows passing in the responders modules in directly, so you could do: + # + # responders FlashResponder, HttpCacheResponder + # + # Or a mix of both methods: + # + # responders :flash, MyCustomResponder + # + def responders(*responders) + self.responder = responders.inject(Class.new(responder)) do |klass, responder| + responder = \ + case responder + when Module + responder + when String, Symbol + Responders.const_get("#{responder.to_s.camelize}Responder") + else + raise "responder has to be a string, a symbol or a module" + end + + klass.send(:include, responder) + klass + end + end + end +end + +ActiveSupport.on_load(:action_controller) do + ActionController::Base.extend Responders::ControllerMethod +end diff --git a/path/ruby/2.6.0/gems/responders-3.0.0/lib/responders/flash_responder.rb b/path/ruby/2.6.0/gems/responders-3.0.0/lib/responders/flash_responder.rb new file mode 100644 index 00000000..6039c295 --- /dev/null +++ b/path/ruby/2.6.0/gems/responders-3.0.0/lib/responders/flash_responder.rb @@ -0,0 +1,206 @@ +# frozen_string_literal: true + +module Responders + # Responder to automatically set flash messages based on I18n API. It checks for + # message based on the current action, but also allows defaults to be set, using + # the following order: + # + # flash.controller_name.action_name.status + # flash.actions.action_name.status + # + # So, if you have a CarsController, create action, it will check for: + # + # flash.cars.create.status + # flash.actions.create.status + # + # The statuses by default are :notice (when the object can be created, updated + # or destroyed with success) and :alert (when the object cannot be created + # or updated). + # + # On I18n, the resource_name given is available as interpolation option, + # this means you can set: + # + # flash: + # actions: + # create: + # notice: "Hooray! %{resource_name} was successfully created!" + # + # But sometimes, flash messages are not that simple. Going back + # to cars example, you might want to say the brand of the car when it's + # updated. Well, that's easy also: + # + # flash: + # cars: + # update: + # notice: "Hooray! You just tuned your %{car_brand}!" + # + # Since :car_name is not available for interpolation by default, you have + # to overwrite `flash_interpolation_options` in your controller. + # + # def flash_interpolation_options + # { :car_brand => @car.brand } + # end + # + # Then you will finally have: + # + # 'Hooray! You just tuned your Aston Martin!' + # + # If your controller is namespaced, for example Admin::CarsController, + # the messages will be checked in the following order: + # + # flash.admin.cars.create.status + # flash.admin.actions.create.status + # flash.cars.create.status + # flash.actions.create.status + # + # You can also have flash messages with embedded HTML. Just create a scope that + # ends with _html as the scopes below: + # + # flash.actions.create.notice_html + # flash.cars.create.notice_html + # + # == Options + # + # FlashResponder also accepts some options through respond_with API. + # + # * :flash - When set to false, no flash message is set. + # + # respond_with(@user, :flash => true) + # + # * :notice - Supply the message to be set if the record has no errors. + # * :alert - Supply the message to be set if the record has errors. + # + # respond_with(@user, :notice => "Hooray! Welcome!", :alert => "Woot! You failed.") + # + # * :flash_now - Sets the flash message using flash.now. Accepts true, :on_failure or :on_sucess. + # + # == Configure status keys + # + # As said previously, FlashResponder by default use :notice and :alert + # keys. You can change that by setting the status_keys: + # + # Responders::FlashResponder.flash_keys = [ :success, :failure ] + # + # However, the options :notice and :alert to respond_with are kept :notice + # and :alert. + # + module FlashResponder + class << self + attr_accessor :flash_keys, :namespace_lookup, :helper + end + + self.flash_keys = [ :notice, :alert ] + self.namespace_lookup = false + self.helper = Object.new.extend( + ActionView::Helpers::TranslationHelper, + ActionView::Helpers::TagHelper + ) + + def initialize(controller, resources, options = {}) + super + @flash = options.delete(:flash) + @notice = options.delete(:notice) + @alert = options.delete(:alert) + @flash_now = options.delete(:flash_now) { :on_failure } + end + + def to_html + set_flash_message! if set_flash_message? + super + end + + def to_js + set_flash_message! if set_flash_message? + defined?(super) ? super : to_format + end + + protected + + def set_flash_message! + if has_errors? + status = Responders::FlashResponder.flash_keys.last + set_flash(status, @alert) + else + status = Responders::FlashResponder.flash_keys.first + set_flash(status, @notice) + end + + return if controller.flash[status].present? + + options = mount_i18n_options(status) + message = Responders::FlashResponder.helper.t options[:default].shift, options + set_flash(status, message) + end + + def set_flash(key, value) + return if value.blank? + flash = controller.flash + flash = flash.now if set_flash_now? + flash[key] ||= value + end + + def set_flash_now? + @flash_now == true || format == :js || + (default_action && (has_errors? ? @flash_now == :on_failure : @flash_now == :on_success)) + end + + def set_flash_message? #:nodoc: + !get? && @flash != false + end + + def mount_i18n_options(status) #:nodoc: + options = { + default: flash_defaults_by_namespace(status), + resource_name: resource_name, + downcase_resource_name: resource_name.downcase + } + + controller_options = controller_interpolation_options + if controller_options + options.merge!(controller_options) + end + + options + end + + def controller_interpolation_options + if controller.respond_to?(:flash_interpolation_options, true) + controller.send(:flash_interpolation_options) + elsif controller.respond_to?(:interpolation_options, true) + ActiveSupport::Deprecation.warn("[responders] `#{controller.class}#interpolation_options` is deprecated, please rename it to `flash_interpolation_options`.") + controller.send(:interpolation_options) + end + end + + def resource_name + if resource.class.respond_to?(:model_name) + resource.class.model_name.human + else + resource.class.name.underscore.humanize + end + end + + def flash_defaults_by_namespace(status) #:nodoc: + defaults = [] + slices = controller.controller_path.split("/") + lookup = Responders::FlashResponder.namespace_lookup + + begin + controller_scope = :"flash.#{slices.fill(controller.controller_name, -1).join(".")}.#{controller.action_name}.#{status}" + + actions_scope = lookup ? slices.fill("actions", -1).join(".") : :actions + actions_scope = :"flash.#{actions_scope}.#{controller.action_name}.#{status}" + + defaults << :"#{controller_scope}_html" + defaults << controller_scope + + defaults << :"#{actions_scope}_html" + defaults << actions_scope + + slices.shift + end while slices.size > 0 && lookup + + defaults << "" + end + end +end diff --git a/path/ruby/2.6.0/gems/responders-3.0.0/lib/responders/http_cache_responder.rb b/path/ruby/2.6.0/gems/responders-3.0.0/lib/responders/http_cache_responder.rb new file mode 100644 index 00000000..d9f10442 --- /dev/null +++ b/path/ruby/2.6.0/gems/responders-3.0.0/lib/responders/http_cache_responder.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module Responders + # Set HTTP Last-Modified headers based on the given resource. It's used only + # on API behavior (to_format) and is useful for a client to check in the server + # if a resource changed after a specific date or not. + # + # This is not usually not used in html requests because pages contains a lot + # information besides the resource information, as current_user, flash messages, + # widgets... that are better handled with other strategies, as fragment caches and + # the digest of the body. + # + module HttpCacheResponder + def initialize(controller, resources, options = {}) + super + @http_cache = options.delete(:http_cache) + end + + def to_format + return if do_http_cache? && do_http_cache! + super + end + + protected + + def do_http_cache! + timestamp = resources.map do |resource| + resource.updated_at.try(:utc) if resource.respond_to?(:updated_at) + end.compact.max + + controller.response.last_modified ||= timestamp if timestamp + + head :not_modified if fresh = request.fresh?(controller.response) + fresh + end + + def do_http_cache? + get? && @http_cache != false && ActionController::Base.perform_caching && + persisted? && resource.respond_to?(:updated_at) + end + + def persisted? + resource.respond_to?(:persisted?) ? resource.persisted? : true + end + end +end diff --git a/path/ruby/2.6.0/gems/responders-3.0.0/lib/responders/locales/en.yml b/path/ruby/2.6.0/gems/responders-3.0.0/lib/responders/locales/en.yml new file mode 100644 index 00000000..c3e147ab --- /dev/null +++ b/path/ruby/2.6.0/gems/responders-3.0.0/lib/responders/locales/en.yml @@ -0,0 +1,12 @@ +en: + flash: + actions: + create: + notice: '%{resource_name} was successfully created.' + # alert: '%{resource_name} could not be created.' + update: + notice: '%{resource_name} was successfully updated.' + # alert: '%{resource_name} could not be updated.' + destroy: + notice: '%{resource_name} was successfully destroyed.' + alert: '%{resource_name} could not be destroyed.' diff --git a/path/ruby/2.6.0/gems/responders-3.0.0/lib/responders/location_responder.rb b/path/ruby/2.6.0/gems/responders-3.0.0/lib/responders/location_responder.rb new file mode 100644 index 00000000..5a654f30 --- /dev/null +++ b/path/ruby/2.6.0/gems/responders-3.0.0/lib/responders/location_responder.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Responders + module LocationResponder + def self.included(_base) + ActiveSupport::Deprecation.warn "Responders::LocationResponder is enabled by default, " \ + "no need to include it", caller + end + end +end diff --git a/path/ruby/2.6.0/gems/responders-3.0.0/lib/responders/version.rb b/path/ruby/2.6.0/gems/responders-3.0.0/lib/responders/version.rb new file mode 100644 index 00000000..d6843203 --- /dev/null +++ b/path/ruby/2.6.0/gems/responders-3.0.0/lib/responders/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module Responders + VERSION = "3.0.0" +end diff --git a/path/ruby/2.6.0/gems/ruby_dep-1.5.0/.gitignore b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/.gitignore new file mode 100644 index 00000000..e47bde0c --- /dev/null +++ b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/.gitignore @@ -0,0 +1,10 @@ +/.bundle/ +/.yardoc +/Gemfile.lock +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ +spec/examples.txt diff --git a/path/ruby/2.6.0/gems/ruby_dep-1.5.0/.rspec b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/.rspec new file mode 100644 index 00000000..34c5164d --- /dev/null +++ b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/.rspec @@ -0,0 +1,3 @@ +--format documentation +--color +--require spec_helper diff --git a/path/ruby/2.6.0/gems/ruby_dep-1.5.0/.rubocop.yml b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/.rubocop.yml new file mode 100644 index 00000000..7c9ecf66 --- /dev/null +++ b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/.rubocop.yml @@ -0,0 +1,3 @@ +Style/Documentation: + Description: Document classes and non-namespace modules. + Enabled: false diff --git a/path/ruby/2.6.0/gems/ruby_dep-1.5.0/.travis.yml b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/.travis.yml new file mode 100644 index 00000000..67acebd8 --- /dev/null +++ b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/.travis.yml @@ -0,0 +1,11 @@ +sudo: false +language: ruby +bundler_args: --without development +env: JRUBY_OPTS='--server -Xcompile.invokedynamic=false' +rvm: + - 2.2.5 + - 2.3.1 + - jruby-9.1.2.0 + +before_install: gem install bundler -v 1.12.5 +cache: bundler diff --git a/path/ruby/2.6.0/gems/ruby_dep-1.5.0/LICENSE.txt b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/LICENSE.txt new file mode 100644 index 00000000..c2c0cc19 --- /dev/null +++ b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Cezary Baginski + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/path/ruby/2.6.0/gems/ruby_dep-1.5.0/README.md b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/README.md new file mode 100644 index 00000000..ebfe525f --- /dev/null +++ b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/README.md @@ -0,0 +1,197 @@ +# RubyDep + +[![Gem Version](https://img.shields.io/gem/v/ruby_dep.svg?style=flat)](https://rubygems.org/gems/ruby_dep) [![Build Status](https://travis-ci.org/e2/ruby_dep.svg)](https://travis-ci.org/e2/ruby_dep) + +Avoid incompatible, slower, buggier and insecure Ruby versions. + +## IMPORTANT!! : HOW TO CORRECTLY SOLVE ISSUES + +If you're here because you're having issues, try the following: + +## 1. Upgrade Ruby. + +Ideally to the latest stable possible. It may be a little (or very!) inconvenient, but it helps everyone in the long run. + +Show the awesome Ruby Core Team your support for their work by letting them focus on newer and better Rubies. + +## 2. Upgrade Ruby anyway. + +If you can't upgrade Ruby because of the environment, work out how to do so anyway. + +E.g. if you can't install Ruby 2.2.5 on OSX due to RVM issues (even though Ruby 2.2.5 has been released over 5 months ago), then obviously the RVM project may need help or support of some kind. Help the RVM maintainers out - they're awesome! Or, fork the project and heroically take things into your own hands. + +If Apple (or Amazon or whatever hosting service or company) doesn't provide the latest recommended, supported version of Ruby, use Homebrew (or build from sources) or complain to those companies to provide support. It's unfair for them to prevent users from getting better/faster Rubies. + +## 3. Upgrade Bundler (but even the most recent Bundler may not be enough!) + +Upgrade to a Bundler version that can automatically downgrade the gems for you. If that doesn't help, try this workaround: https://github.com/guard/listen/wiki/Ruby-version-requirements + +Work on this "downgrading" feature in Bundler is ongoing, so the best version of Bundler for the job may still be an unreleased version (beta, release candidate, etc.). + +Help the Bundler team out if you can - they're awesome! + +## 4. If all else fails, learn SemVer and USE IT! + +Often, there are older versions of gems that support the Ruby version you need. See http://semver.org/ on how to set version constraints. Then, check out the release notes of the gems you need to know what you're getting (or missing out on). + +E.g. You can downgrade to RubyDep 1.3.1 (`gem 'ruby_dep', '~> 1.3.1'`) which allows using Ruby 2.2.4 +E.g. Or, You can use Listen 3.0.x (`gem 'listen', '~> 3.0.8'`) to avoid dealing with RubyDep and Listen. + +If those gem versions are lacking for any reason (e.g. bugs in Listen 3.0.x fixed in 3.1.x), then e.g. open a request for backporting changes to the 3.0.x branch. + +The idea: if you don't need the latest Ruby ... then you probably don't need the latest of every gem either. + +## 5. If all that isn't possible or it doesn't work ... + +Let me know about it (open an issue), because I'm likely confused about how all the above steps failed. + +Or it's a bug I don't know about. Please report it - just in case... + + +## Description + +RubyDep does 2 things right now: + +1. Helps end users avoid incompatible, buggy and insecure Ruby versions. +2. Helps gem owners manage their gem's `required_ruby_version` gemspec field based on `.travis.yml`. + +## Quick info + +- if you want to know how to disable the warnings, see here: https://github.com/e2/ruby_dep/wiki/Disabling-warnings +- for a list of Ruby versions that can be used to install ruby_dep, see here: https://travis-ci.org/e2/ruby_dep +- if your version of Ruby is not supported, open a new issue and explain your situation/problem +- when in doubt, open a new issue or [read the FAQ on the Wiki](https://github.com/e2/ruby_dep/wiki/FAQ). +- gems using RubyDep are designed to not be installable on a given Ruby version, unless it's specifically declared supported by those gems - but it's ok to ask for supporting your Ruby if you're stuck on an older version (for whatever reason) +- discussions about Ruby versions can get complex and frustrating - please be patient and constructive, and open-minded about solutions - especially if you're having problems + + +## Supported Ruby versions: + +NOTE: RubyDep uses it's own approach on itself. This means it can only be installed on Ruby versions tested here: [check out the Travis build status](https://travis-ci.org/e2/ruby_dep). If you need support for an different/older version of Ruby, open an issue with "backport" in the title and provide a compelling case for supporting the version of Ruby you need. + +## Problem 1: "Which version of Ruby does your project support?" + +Your gem shouldn't (and likely doesn't) support all possible Ruby versions. + +So you have to tell users which versions your gem supports. + +But, there are at least 3 places where you list the Rubies you support: + +1. Your gemspec +2. Your README +3. Your .travis.yml file + +That breaks the principle of single responsibility. + +Is it possible to just list the supported Rubies in just one place? + +Yes. That's what RubyDep helps with. + +## Solution to problem 1 + +Since Travis doesn't allow generated `.travis.yml` files, option 3 is the only choice. + +With RubyDep, your gemspec's `required_ruby_version` can be automatically set based on which Rubies you test your gem on. + +What about the README? Well, just insert a link to your Travis build status page! + +Example: do you want to know which Ruby versions RubyDep can be installed on? Just look here: https://travis-ci.org/e2/ruby_dep + +If you're running Travis builds on a Ruby you support (and it's not in the "allow failures" section), it means you support that version of Ruby, right? + +RubyDep intelligently creates a version constraint to encompass Rubies listed in your `.travis.yml`. + +## Usage (to solve Problem 1) + +### E.g. in your gemspec file: + +```ruby + begin + require "ruby_dep/travis" + s.required_ruby_version = RubyDep::Travis.new.version_constraint + rescue LoadError + abort "Install 'ruby_dep' gem before building this gem" + end + + s.add_development_dependency 'ruby_dep', '~> 1.1' +``` + +### In your `README.md`: + +Replace your mentions of "supported Ruby versions" and just insert a link to your Travis build status page. + +If users see their Ruby version "green" on Travis, they'll see those are the versions you support and test, right? + +(Or, you can link to your project's rubygems.org page where the required Ruby version is listed). + +### In your `.travis.yml`: + +To add a "supported Ruby", simply add it to the Travis build. + +To test a Ruby version, but not treat it as "supported", simply add that version to the `allowed_failures` section. + + +## Problem 2: Users don't know they're using an obsolete/buggy/insecure version of Ruby + +Users don't track news updates on https://ruby-lang.org, so they may not know their ruby has known bugs or even serious security vulnerabilities. + +And sometimes, that outdated/insecure Ruby is bundled by their operation system to begin with! + +## The solution to problem 2 + +RubyDep has a small "database" of Ruby versions with information about which are buggy and insecure. + +If you like, your gem can use RubyDep to show those warnings - to encourage users to upgrade and protect them from nasty bugs or bad security holes. + +This way, when most of the Ruby community has switched to newer versions, everyone can be more productive by having faster, more stable and more feature-rich tools. And less time will be wasted supporting obsolete versions that users simply don't know are worth upgrading. + +This also helps users understand that they should nudge their hosting providers, managers and package maintainers to provided up-to-date versions of Ruby to that everyone can benefit. + +### Usage (to solve Problem 2) + +In your gemspec: + +```ruby +s.add_runtime_dependency 'ruby_dep', '~> 1.1' +``` + +Somewhere in your library: + +```ruby +require 'ruby_dep/warnings' +RubyDep::Warning.show_warnings +ENV['RUBY_DEP_GEM_SILENCE_WARNINGS'] = '1' # to ignore repeating the warning if other gems use `ruby_dep` too +``` + +That way, as soon as there's a severe vulnerability discovered in Ruby (and RubyDep is updated), users will be notified quickly. + + +## Tips + +1. To disable warnings, just set the following environment variable: `RUBY_DEP_GEM_SILENCE_WARNINGS=1` +2. If you want to support a newer version of Ruby, just add it to your `.travis.yml` (e.g. ruby-2.3.1) +3. To support an earlier version of Ruby, add it to your `.travis.yml` and release a new gem version. +4. If you want to support a range of Rubies, include the whole range without gaps in minor version numbers (e.g. 2.0, 2.1, 2.2, 2.3) and ruby_dep will use the whole range. (If there's a gap, older versions will be considered "unsupported"). +5. If you want to drop support for a Ruby, remove it from the `.travis.yml` and just bump your gem's minor number (Yes! Bumping just the minor if fine according to SemVer). +5. If you just want to test a Ruby version (but not actually support it), put it into the `allow failures` part of your Travis build matrix. (ruby_dep ignores versions there). + +When in doubt, open an issue and just ask. + +## Roadmap + +Pull Requests are welcome. + +Plans include: reading supported Ruby from `.rubocop.yml` (`TargetRubyVersion` field). + + +## Development + +Use `rake` to run tests. + +## Contributing + +Bug reports and pull requests are welcome on GitHub at https://github.com/e2/ruby_dep. + +## License + +The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). diff --git a/path/ruby/2.6.0/gems/ruby_dep-1.5.0/lib/ruby_dep.rb b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/lib/ruby_dep.rb new file mode 100644 index 00000000..0e204983 --- /dev/null +++ b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/lib/ruby_dep.rb @@ -0,0 +1,2 @@ +require 'ruby_dep/version' +require 'ruby_dep/travis' diff --git a/path/ruby/2.6.0/gems/ruby_dep-1.5.0/lib/ruby_dep/logger.rb b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/lib/ruby_dep/logger.rb new file mode 100644 index 00000000..4bfa14f1 --- /dev/null +++ b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/lib/ruby_dep/logger.rb @@ -0,0 +1,48 @@ +require 'logger' + +module RubyDep + def self.logger + @logger ||= stderr_logger + end + + def self.logger=(new_logger) + @logger = new_logger.nil? ? NullLogger.new : new_logger + end + + def self.stderr_logger + ::Logger.new(STDERR).tap do |logger| + logger.formatter = proc { |_,_,_,msg| "#{msg}\n" } + end + end + + # Shamelessly stolen from https://github.com/karafka/null-logger + class NullLogger + LOG_LEVELS = %w(unknown fatal error warn info debug).freeze + + def respond_to_missing?(method_name, include_private = false) + LOG_LEVELS.include?(method_name.to_s) || super + end + + def method_missing(method_name, *args, &block) + LOG_LEVELS.include?(method_name.to_s) ? nil : super + end + end + + # TODO: not used, but kept for the sake of SemVer + # TODO: remove in next major version + class Logger + def initialize(device, prefix) + @device = device + @prefix = prefix + ::RubyDep.logger.warn("The RubyDep::Logger class is deprecated") + end + + def warning(msg) + @device.puts @prefix + msg + end + + def notice(msg) + @device.puts @prefix + msg + end + end +end diff --git a/path/ruby/2.6.0/gems/ruby_dep-1.5.0/lib/ruby_dep/quiet.rb b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/lib/ruby_dep/quiet.rb new file mode 100644 index 00000000..8445a832 --- /dev/null +++ b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/lib/ruby_dep/quiet.rb @@ -0,0 +1,3 @@ +require 'ruby_dep/warning' + +RubyDep::Warning.new.silence! diff --git a/path/ruby/2.6.0/gems/ruby_dep-1.5.0/lib/ruby_dep/ruby_version.rb b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/lib/ruby_dep/ruby_version.rb new file mode 100644 index 00000000..da9acbcb --- /dev/null +++ b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/lib/ruby_dep/ruby_version.rb @@ -0,0 +1,58 @@ + +module RubyDep + class RubyVersion + attr_reader :status # NOTE: monkey-patched by acceptance tests + attr_reader :version + attr_reader :engine + + def initialize(ruby_version, engine) + @engine = engine + @version = Gem::Version.new(ruby_version) + @status = detect_status + end + + def recognized? + info.any? + end + + def recommended(status) + current = Gem::Version.new(@version) + info.select do |key, value| + value == status && Gem::Version.new(key) > current + end.keys.reverse + end + + private + + VERSION_INFO = { + 'ruby' => { + '2.3.1' => :unknown, + '2.3.0' => :buggy, + '2.2.5' => :unknown, + '2.2.4' => :buggy, + '2.2.0' => :insecure, + '2.1.9' => :buggy, + '2.0.0' => :insecure + }, + + 'jruby' => { + '2.3.0' => :unknown, # jruby-9.1.2.0, jruby-9.1.0.0 + '2.2.3' => :buggy, # jruby-9.0.5.0 + '2.2.0' => :insecure + } + }.freeze + + def info + @info ||= VERSION_INFO[@engine] || {} + end + + def detect_status + return :untracked unless recognized? + + info.each do |ruby, status| + return status if @version >= Gem::Version.new(ruby) + end + :insecure + end + end +end diff --git a/path/ruby/2.6.0/gems/ruby_dep-1.5.0/lib/ruby_dep/travis.rb b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/lib/ruby_dep/travis.rb new file mode 100644 index 00000000..5176a5ae --- /dev/null +++ b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/lib/ruby_dep/travis.rb @@ -0,0 +1,58 @@ +require 'yaml' + +require 'ruby_dep/travis/ruby_version' + +module RubyDep + class Travis + def version_constraint(filename = '.travis.yml') + yaml = YAML.load(IO.read(filename)) + versions = supported_versions(yaml) + + selected = versions_for_latest_major(versions) + lowest = lowest_supported(selected) + + ["~> #{lowest[0..1].join('.')}", ">= #{lowest.join('.')}"] + rescue RubyVersion::Error => ex + abort("RubyDep Error: #{ex.message}") + end + + private + + def versions_for_latest_major(versions) + by_major = versions.map do |x| + RubyVersion.new(x).segments[0..2] + end.group_by(&:first) + + last_supported_major = by_major.keys.sort.last + by_major[last_supported_major] + end + + def lowest_supported(versions) + selected = versions.sort.reverse! + grouped_by_minor = selected.group_by { |x| x[1] } + + lowest_minor = lowest_minor_without_skipping(grouped_by_minor) + grouped_by_minor[lowest_minor].sort.first + end + + def failable(yaml) + matrix = yaml.fetch('matrix', {}) + allowed = matrix.fetch('allow_failures', []) + allowed.map(&:values).flatten + end + + def supported_versions(yaml) + yaml['rvm'] - failable(yaml) + end + + def lowest_minor_without_skipping(grouped_by_minor) + minors = grouped_by_minor.keys.flatten + lowest = minors.shift + current = lowest + while (lower = minors.shift) + (current -= 1) == lower ? lowest = lower : break + end + lowest + end + end +end diff --git a/path/ruby/2.6.0/gems/ruby_dep-1.5.0/lib/ruby_dep/travis/ruby_version.rb b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/lib/ruby_dep/travis/ruby_version.rb new file mode 100644 index 00000000..275aec4e --- /dev/null +++ b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/lib/ruby_dep/travis/ruby_version.rb @@ -0,0 +1,59 @@ +module RubyDep + class Travis + class RubyVersion + REGEXP = /^ + (?: + (?ruby|jruby) + -)? + (?\d+\.\d+\.\d+(?:\.\d+)?) + (?:-p\d+)? + (?:-clang)? + $/x + + class Error < RuntimeError + class Unrecognized < Error + def initialize(invalid_version_string) + @invalid_version_string = invalid_version_string + end + + def message + "Unrecognized Ruby version: #{@invalid_version_string.inspect}" + end + + class JRubyVersion < Unrecognized + def message + "Unrecognized JRuby version: #{@invalid_version_string.inspect}" + end + end + end + end + + def initialize(travis_version_string) + ruby_version_string = version_for(travis_version_string) + @version = Gem::Version.new(ruby_version_string) + end + + def segments + @version.segments + end + + private + + def version_for(travis_version_string) + match = REGEXP.match(travis_version_string) + raise Error::Unrecognized, travis_version_string unless match + return match[:version] unless match[:engine] + return jruby_version(match[:version]) if match[:engine] == 'jruby' + match[:version] # if match[:engine] == 'ruby' + end + + def jruby_version(version) + return '2.3.0' if version == '9.1.2.0' + return '2.3.0' if version == '9.1.0.0' + return '2.2.3' if version == '9.0.5.0' + return '2.2.2' if version == '9.0.4.0' + raise Error::Unrecognized::JRubyVersion, version + end + end + end +end diff --git a/path/ruby/2.6.0/gems/ruby_dep-1.5.0/lib/ruby_dep/version.rb b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/lib/ruby_dep/version.rb new file mode 100644 index 00000000..e1fc03d6 --- /dev/null +++ b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/lib/ruby_dep/version.rb @@ -0,0 +1,3 @@ +module RubyDep + VERSION = '1.5.0'.freeze +end diff --git a/path/ruby/2.6.0/gems/ruby_dep-1.5.0/lib/ruby_dep/warning.rb b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/lib/ruby_dep/warning.rb new file mode 100644 index 00000000..37883b28 --- /dev/null +++ b/path/ruby/2.6.0/gems/ruby_dep-1.5.0/lib/ruby_dep/warning.rb @@ -0,0 +1,98 @@ +require 'ruby_dep/logger' +require 'ruby_dep/ruby_version' + +module RubyDep + PROJECT_URL = 'http://github.com/e2/ruby_dep'.freeze + + class Warning + DISABLING_ENVIRONMENT_VAR = 'RUBY_DEP_GEM_SILENCE_WARNINGS'.freeze + PREFIX = 'RubyDep: WARNING: '.freeze + + WARNING = { + insecure: 'Your Ruby has security vulnerabilities!'.freeze, + buggy: 'Your Ruby is outdated/buggy.'.freeze, + untracked: 'Your Ruby may not be supported.'.freeze + }.freeze + + NOTICE_RECOMMENDATION = 'Your Ruby is: %s (%s).'\ + ' Recommendation: upgrade to %s.'.freeze + + NOTICE_BUGGY_ALTERNATIVE = '(Or, at least to %s)'.freeze + + NOTICE_HOW_TO_DISABLE = '(To disable warnings, see:'\ + "#{PROJECT_URL}/wiki/Disabling-warnings )".freeze + + NOTICE_OPEN_ISSUE = 'If you need this version supported,'\ + " please open an issue at #{PROJECT_URL}".freeze + + def initialize + @version = RubyVersion.new(RUBY_VERSION, RUBY_ENGINE) + end + + def show_warnings + return if silenced? + return warn_ruby(WARNING[status]) if WARNING.key?(status) + return if status == :unknown + raise "Unknown problem type: #{problem.inspect}" + end + + def silence! + ENV[DISABLING_ENVIRONMENT_VAR] = '1' + end + + private + + def silenced? + value = ENV[DISABLING_ENVIRONMENT_VAR] + (value || '0') !~ /^0|false|no|n$/ + end + + def status + @version.status + end + + def warn_ruby(msg) + RubyDep.logger.tap do |logger| + logger.warn(PREFIX + msg) + logger.info(PREFIX + recommendation) + logger.info(PREFIX + NOTICE_HOW_TO_DISABLE) + end + end + + def recommendation + return unrecognized_msg unless @version.recognized? + return recommendation_msg unless status == :insecure + [recommendation_msg, safer_alternatives_msg].join(' ') + end + + def unrecognized_msg + format( + "Your Ruby is: %s '%s' (unrecognized). %s", + @version.version, + @version.engine, + NOTICE_OPEN_ISSUE + ) + end + + def recommended_versions + @version.recommended(:unknown) + end + + def buggy_alternatives + @version.recommended(:buggy) + end + + def recommendation_msg + format( + NOTICE_RECOMMENDATION, + @version.version, + status, + recommended_versions.join(' or ') + ) + end + + def safer_alternatives_msg + format(NOTICE_BUGGY_ALTERNATIVE, buggy_alternatives.join(' or ')) + end + end +end diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/README.md b/path/ruby/2.6.0/gems/rubyzip-2.0.0/README.md new file mode 100644 index 00000000..059f22d1 --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/README.md @@ -0,0 +1,354 @@ +# rubyzip + +[![Gem Version](https://badge.fury.io/rb/rubyzip.svg)](http://badge.fury.io/rb/rubyzip) +[![Build Status](https://secure.travis-ci.org/rubyzip/rubyzip.svg)](http://travis-ci.org/rubyzip/rubyzip) +[![Code Climate](https://codeclimate.com/github/rubyzip/rubyzip.svg)](https://codeclimate.com/github/rubyzip/rubyzip) +[![Coverage Status](https://img.shields.io/coveralls/rubyzip/rubyzip.svg)](https://coveralls.io/r/rubyzip/rubyzip?branch=master) + +Rubyzip is a ruby library for reading and writing zip files. + +## Important note + +The Rubyzip interface has changed!!! No need to do `require "zip/zip"` and `Zip` prefix in class names removed. + +If you have issues with any third-party gems that require an old version of rubyzip, you can use this workaround: + +```ruby +gem 'rubyzip', '>= 1.0.0' # will load new rubyzip version +gem 'zip-zip' # will load compatibility for old rubyzip API. +``` + +## Requirements + +- Ruby 2.4 or greater (for rubyzip 2.0; use 1.x for older rubies) + +## Installation + +Rubyzip is available on RubyGems: + +``` +gem install rubyzip +``` + +Or in your Gemfile: + +```ruby +gem 'rubyzip' +``` + +## Usage + +### Basic zip archive creation + +```ruby +require 'rubygems' +require 'zip' + +folder = "Users/me/Desktop/stuff_to_zip" +input_filenames = ['image.jpg', 'description.txt', 'stats.csv'] + +zipfile_name = "/Users/me/Desktop/archive.zip" + +Zip::File.open(zipfile_name, Zip::File::CREATE) do |zipfile| + input_filenames.each do |filename| + # Two arguments: + # - The name of the file as it will appear in the archive + # - The original file, including the path to find it + zipfile.add(filename, File.join(folder, filename)) + end + zipfile.get_output_stream("myFile") { |f| f.write "myFile contains just this" } +end +``` + +### Zipping a directory recursively + +Copy from [here](https://github.com/rubyzip/rubyzip/blob/9d891f7353e66052283562d3e252fe380bb4b199/samples/example_recursive.rb) + +```ruby +require 'zip' + +# This is a simple example which uses rubyzip to +# recursively generate a zip file from the contents of +# a specified directory. The directory itself is not +# included in the archive, rather just its contents. +# +# Usage: +# directory_to_zip = "/tmp/input" +# output_file = "/tmp/out.zip" +# zf = ZipFileGenerator.new(directory_to_zip, output_file) +# zf.write() +class ZipFileGenerator + # Initialize with the directory to zip and the location of the output archive. + def initialize(input_dir, output_file) + @input_dir = input_dir + @output_file = output_file + end + + # Zip the input directory. + def write + entries = Dir.entries(@input_dir) - %w[. ..] + + ::Zip::File.open(@output_file, ::Zip::File::CREATE) do |zipfile| + write_entries entries, '', zipfile + end + end + + private + + # A helper method to make the recursion work. + def write_entries(entries, path, zipfile) + entries.each do |e| + zipfile_path = path == '' ? e : File.join(path, e) + disk_file_path = File.join(@input_dir, zipfile_path) + + if File.directory? disk_file_path + recursively_deflate_directory(disk_file_path, zipfile, zipfile_path) + else + put_into_archive(disk_file_path, zipfile, zipfile_path) + end + end + end + + def recursively_deflate_directory(disk_file_path, zipfile, zipfile_path) + zipfile.mkdir zipfile_path + subdir = Dir.entries(disk_file_path) - %w[. ..] + write_entries subdir, zipfile_path, zipfile + end + + def put_into_archive(disk_file_path, zipfile, zipfile_path) + zipfile.add(zipfile_path, disk_file_path) + end +end +``` + +### Save zip archive entries in sorted by name state + +To save zip archives in sorted order like below, you need to set `::Zip.sort_entries` to `true` + +``` +Vegetable/ +Vegetable/bean +Vegetable/carrot +Vegetable/celery +fruit/ +fruit/apple +fruit/kiwi +fruit/mango +fruit/orange +``` + +After this, entries in the zip archive will be saved in ordered state. + +### Default permissions of zip archives + +On Posix file systems the default file permissions applied to a new archive +are (0666 - umask), which mimics the behavior of standard tools such as `touch`. + +On Windows the default file permissions are set to 0644 as suggested by the +[Ruby File documentation](http://ruby-doc.org/core-2.2.2/File.html). + +When modifying a zip archive the file permissions of the archive are preserved. + +### Reading a Zip file + +```ruby +MAX_SIZE = 1024**2 # 1MiB (but of course you can increase this) +Zip::File.open('foo.zip') do |zip_file| + # Handle entries one by one + zip_file.each do |entry| + puts "Extracting #{entry.name}" + raise 'File too large when extracted' if entry.size > MAX_SIZE + + # Extract to file or directory based on name in the archive + entry.extract + + # Read into memory + content = entry.get_input_stream.read + end + + # Find specific entry + entry = zip_file.glob('*.csv').first + raise 'File too large when extracted' if entry.size > MAX_SIZE + puts entry.get_input_stream.read +end +``` + +#### Notice about ::Zip::InputStream + +`::Zip::InputStream` usable for fast reading zip file content because it not read Central directory. + +But there is one exception when it is not working - General Purpose Flag Bit 3. + +> If bit 3 (0x08) of the general-purpose flags field is set, then the CRC-32 and file sizes are not known when the header is written. The fields in the local header are filled with zero, and the CRC-32 and size are appended in a 12-byte structure (optionally preceded by a 4-byte signature) immediately after the compressed data + +If `::Zip::InputStream` finds such entry in the zip archive it will raise an exception. + +### Password Protection (Experimental) + +Rubyzip supports reading/writing zip files with traditional zip encryption (a.k.a. "ZipCrypto"). AES encryption is not yet supported. It can be used with buffer streams, e.g.: + +```ruby +Zip::OutputStream.write_buffer(::StringIO.new(''), Zip::TraditionalEncrypter.new('password')) do |out| + out.put_next_entry("my_file.txt") + out.write my_data +end.string +``` + +This is an experimental feature and the interface for encryption may change in future versions. + +## Known issues + +### Modify docx file with rubyzip + +Use `write_buffer` instead `open`. Thanks to @jondruse + +```ruby +buffer = Zip::OutputStream.write_buffer do |out| + @zip_file.entries.each do |e| + unless [DOCUMENT_FILE_PATH, RELS_FILE_PATH].include?(e.name) + out.put_next_entry(e.name) + out.write e.get_input_stream.read + end + end + + out.put_next_entry(DOCUMENT_FILE_PATH) + out.write xml_doc.to_xml(:indent => 0).gsub("\n","") + + out.put_next_entry(RELS_FILE_PATH) + out.write rels.to_xml(:indent => 0).gsub("\n","") +end + +File.open(new_path, "wb") {|f| f.write(buffer.string) } +``` + +## Configuration + +### Existing Files + +By default, rubyzip will not overwrite files if they already exist inside of the extracted path. To change this behavior, you may specify a configuration option like so: + +```ruby +Zip.on_exists_proc = true +``` + +If you're using rubyzip with rails, consider placing this snippet of code in an initializer file such as `config/initializers/rubyzip.rb` + +Additionally, if you want to configure rubyzip to overwrite existing files while creating a .zip file, you can do so with the following: + +```ruby +Zip.continue_on_exists_proc = true +``` + +### Non-ASCII Names + +If you want to store non-english names and want to open them on Windows(pre 7) you need to set this option: + +```ruby +Zip.unicode_names = true +``` + +Sometimes file names inside zip contain non-ASCII characters. If you can assume which encoding was used for such names and want to be able to find such entries using `find_entry` then you can force assumed encoding like so: + +```ruby +Zip.force_entry_names_encoding = 'UTF-8' +``` + +Allowed encoding names are the same as accepted by `String#force_encoding` + +### Date Validation + +Some zip files might have an invalid date format, which will raise a warning. You can hide this warning with the following setting: + +```ruby +Zip.warn_invalid_date = false +``` + +### Size Validation + +By default (in rubyzip >= 2.0), rubyzip's `extract` method checks that an entry's reported uncompressed size is not (significantly) smaller than its actual size. This is to help you protect your application against [zip bombs](https://en.wikipedia.org/wiki/Zip_bomb). Before `extract`ing an entry, you should check that its size is in the range you expect. For example, if your application supports processing up to 100 files at once, each up to 10MiB, your zip extraction code might look like: + +```ruby +MAX_FILE_SIZE = 10 * 1024**2 # 10MiB +MAX_FILES = 100 +Zip::File.open('foo.zip') do |zip_file| + num_files = 0 + zip_file.each do |entry| + num_files += 1 if entry.file? + raise 'Too many extracted files' if num_files > MAX_FILES + raise 'File too large when extracted' if entry.size > MAX_FILE_SIZE + entry.extract + end +end +``` + +If you need to extract zip files that report incorrect uncompressed sizes and you really trust them not too be too large, you can disable this setting with +```ruby +Zip.validate_entry_sizes = false +``` + +Note that if you use the lower level `Zip::InputStream` interface, `rubyzip` does *not* check the entry `size`s. In this case, the caller is responsible for making sure it does not read more data than expected from the input stream. + +### Default Compression + +You can set the default compression level like so: + +```ruby +Zip.default_compression = Zlib::DEFAULT_COMPRESSION +``` + +It defaults to `Zlib::DEFAULT_COMPRESSION`. Possible values are `Zlib::BEST_COMPRESSION`, `Zlib::DEFAULT_COMPRESSION` and `Zlib::NO_COMPRESSION` + +### Zip64 Support + +By default, Zip64 support is disabled for writing. To enable it do this: + +```ruby +Zip.write_zip64_support = true +``` + +_NOTE_: If you will enable Zip64 writing then you will need zip extractor with Zip64 support to extract archive. + +### Block Form + +You can set multiple settings at the same time by using a block: + +```ruby + Zip.setup do |c| + c.on_exists_proc = true + c.continue_on_exists_proc = true + c.unicode_names = true + c.default_compression = Zlib::BEST_COMPRESSION + end +``` + +## Developing + +To run the test you need to do this: + +``` +bundle install +rake +``` + +## Website and Project Home + +http://github.com/rubyzip/rubyzip + +http://rdoc.info/github/rubyzip/rubyzip/master/frames + +## Authors + +Alexander Simonov ( alex at simonov.me) + +Alan Harper ( alan at aussiegeek.net) + +Thomas Sondergaard (thomas at sondergaard.cc) + +Technorama Ltd. (oss-ruby-zip at technorama.net) + +extra-field support contributed by Tatsuki Sugiura (sugi at nemui.org) + +## License + +Rubyzip is distributed under the same license as ruby. See +http://www.ruby-lang.org/en/LICENSE.txt diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/Rakefile b/path/ruby/2.6.0/gems/rubyzip-2.0.0/Rakefile new file mode 100644 index 00000000..44a9b287 --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/Rakefile @@ -0,0 +1,18 @@ +require 'bundler/gem_tasks' +require 'rake/testtask' + +task default: :test + +Rake::TestTask.new(:test) do |test| + test.libs << 'lib' + test.libs << 'test' + test.pattern = 'test/**/*_test.rb' + test.verbose = true +end + +# Rake::TestTask.new(:zip64_full_test) do |test| +# test.libs << File.join(File.dirname(__FILE__), 'lib') +# test.libs << File.join(File.dirname(__FILE__), 'test') +# test.pattern = File.join(File.dirname(__FILE__), 'test/zip64_full_test.rb') +# test.verbose = true +# end diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/TODO b/path/ruby/2.6.0/gems/rubyzip-2.0.0/TODO new file mode 100644 index 00000000..16b9a2e7 --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/TODO @@ -0,0 +1,15 @@ + +* ZipInputStream: Support zip-files with trailing data descriptors +* Adjust rdoc stylesheet to advertise inherited methods if possible +* Suggestion: Add ZipFile/ZipInputStream example that demonstrates extracting all entries. +* Suggestion: ZipFile#extract destination should default to "." +* Suggestion: ZipEntry should have extract(), get_input_stream() methods etc +* (is buffering used anywhere with write?) +* Inflater.sysread should pass the buffer to produce_input. +* Implement ZipFsDir.glob +* ZipFile.checkIntegrity method +* non-MSDOS permission attributes +** See mail from Ned Konz to ruby-talk subj. "Re: SV: [ANN] Archive 0.2" +* Packager version, required unpacker version in zip headers +** See mail from Ned Konz to ruby-talk subj. "Re: SV: [ANN] Archive 0.2" +* implement storing attributes and ownership information diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip.rb new file mode 100644 index 00000000..c3a6ed5e --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip.rb @@ -0,0 +1,71 @@ +require 'delegate' +require 'singleton' +require 'tempfile' +require 'tmpdir' +require 'fileutils' +require 'stringio' +require 'zlib' +require 'zip/dos_time' +require 'zip/ioextras' +require 'rbconfig' +require 'zip/entry' +require 'zip/extra_field' +require 'zip/entry_set' +require 'zip/central_directory' +require 'zip/file' +require 'zip/input_stream' +require 'zip/output_stream' +require 'zip/decompressor' +require 'zip/compressor' +require 'zip/null_decompressor' +require 'zip/null_compressor' +require 'zip/null_input_stream' +require 'zip/pass_thru_compressor' +require 'zip/pass_thru_decompressor' +require 'zip/crypto/encryption' +require 'zip/crypto/null_encryption' +require 'zip/crypto/traditional_encryption' +require 'zip/inflater' +require 'zip/deflater' +require 'zip/streamable_stream' +require 'zip/streamable_directory' +require 'zip/constants' +require 'zip/errors' + +module Zip + extend self + attr_accessor :unicode_names, + :on_exists_proc, + :continue_on_exists_proc, + :sort_entries, + :default_compression, + :write_zip64_support, + :warn_invalid_date, + :case_insensitive_match, + :force_entry_names_encoding, + :validate_entry_sizes + + def reset! + @_ran_once = false + @unicode_names = false + @on_exists_proc = false + @continue_on_exists_proc = false + @sort_entries = false + @default_compression = ::Zlib::DEFAULT_COMPRESSION + @write_zip64_support = false + @warn_invalid_date = true + @case_insensitive_match = false + @validate_entry_sizes = true + end + + def setup + yield self unless @_ran_once + @_ran_once = true + end + + reset! +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/central_directory.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/central_directory.rb new file mode 100644 index 00000000..0b6874ef --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/central_directory.rb @@ -0,0 +1,208 @@ +module Zip + class CentralDirectory + include Enumerable + + END_OF_CDS = 0x06054b50 + ZIP64_END_OF_CDS = 0x06064b50 + ZIP64_EOCD_LOCATOR = 0x07064b50 + MAX_END_OF_CDS_SIZE = 65_536 + 18 + STATIC_EOCD_SIZE = 22 + + attr_reader :comment + + # Returns an Enumerable containing the entries. + def entries + @entry_set.entries + end + + def initialize(entries = EntrySet.new, comment = '') #:nodoc: + super() + @entry_set = entries.kind_of?(EntrySet) ? entries : EntrySet.new(entries) + @comment = comment + end + + def write_to_stream(io) #:nodoc: + cdir_offset = io.tell + @entry_set.each { |entry| entry.write_c_dir_entry(io) } + eocd_offset = io.tell + cdir_size = eocd_offset - cdir_offset + if ::Zip.write_zip64_support + need_zip64_eocd = cdir_offset > 0xFFFFFFFF || cdir_size > 0xFFFFFFFF || @entry_set.size > 0xFFFF + need_zip64_eocd ||= @entry_set.any? { |entry| entry.extra['Zip64'] } + if need_zip64_eocd + write_64_e_o_c_d(io, cdir_offset, cdir_size) + write_64_eocd_locator(io, eocd_offset) + end + end + write_e_o_c_d(io, cdir_offset, cdir_size) + end + + def write_e_o_c_d(io, offset, cdir_size) #:nodoc: + tmp = [ + END_OF_CDS, + 0, # @numberOfThisDisk + 0, # @numberOfDiskWithStartOfCDir + @entry_set ? [@entry_set.size, 0xFFFF].min : 0, + @entry_set ? [@entry_set.size, 0xFFFF].min : 0, + [cdir_size, 0xFFFFFFFF].min, + [offset, 0xFFFFFFFF].min, + @comment ? @comment.bytesize : 0 + ] + io << tmp.pack('VvvvvVVv') + io << @comment + end + + private :write_e_o_c_d + + def write_64_e_o_c_d(io, offset, cdir_size) #:nodoc: + tmp = [ + ZIP64_END_OF_CDS, + 44, # size of zip64 end of central directory record (excludes signature and field itself) + VERSION_MADE_BY, + VERSION_NEEDED_TO_EXTRACT_ZIP64, + 0, # @numberOfThisDisk + 0, # @numberOfDiskWithStartOfCDir + @entry_set ? @entry_set.size : 0, # number of entries on this disk + @entry_set ? @entry_set.size : 0, # number of entries total + cdir_size, # size of central directory + offset, # offset of start of central directory in its disk + ] + io << tmp.pack('VQ 'FAT'.freeze, + FSTYPE_AMIGA => 'Amiga'.freeze, + FSTYPE_VMS => 'VMS (Vax or Alpha AXP)'.freeze, + FSTYPE_UNIX => 'Unix'.freeze, + FSTYPE_VM_CMS => 'VM/CMS'.freeze, + FSTYPE_ATARI => 'Atari ST'.freeze, + FSTYPE_HPFS => 'OS/2 or NT HPFS'.freeze, + FSTYPE_MAC => 'Macintosh'.freeze, + FSTYPE_Z_SYSTEM => 'Z-System'.freeze, + FSTYPE_CPM => 'CP/M'.freeze, + FSTYPE_TOPS20 => 'TOPS-20'.freeze, + FSTYPE_NTFS => 'NTFS'.freeze, + FSTYPE_QDOS => 'SMS/QDOS'.freeze, + FSTYPE_ACORN => 'Acorn RISC OS'.freeze, + FSTYPE_VFAT => 'Win32 VFAT'.freeze, + FSTYPE_MVS => 'MVS'.freeze, + FSTYPE_BEOS => 'BeOS'.freeze, + FSTYPE_TANDEM => 'Tandem NSK'.freeze, + FSTYPE_THEOS => 'Theos'.freeze, + FSTYPE_MAC_OSX => 'Mac OS/X (Darwin)'.freeze, + FSTYPE_ATHEOS => 'AtheOS'.freeze + }.freeze +end diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/crypto/encryption.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/crypto/encryption.rb new file mode 100644 index 00000000..4351be1c --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/crypto/encryption.rb @@ -0,0 +1,11 @@ +module Zip + class Encrypter #:nodoc:all + end + + class Decrypter + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/crypto/null_encryption.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/crypto/null_encryption.rb new file mode 100644 index 00000000..a93f707c --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/crypto/null_encryption.rb @@ -0,0 +1,43 @@ +module Zip + module NullEncryption + def header_bytesize + 0 + end + + def gp_flags + 0 + end + end + + class NullEncrypter < Encrypter + include NullEncryption + + def header(_mtime) + '' + end + + def encrypt(data) + data + end + + def data_descriptor(_crc32, _compressed_size, _uncomprssed_size) + '' + end + + def reset!; end + end + + class NullDecrypter < Decrypter + include NullEncryption + + def decrypt(data) + data + end + + def reset!(_header); end + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/crypto/traditional_encryption.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/crypto/traditional_encryption.rb new file mode 100644 index 00000000..91e6ce16 --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/crypto/traditional_encryption.rb @@ -0,0 +1,99 @@ +module Zip + module TraditionalEncryption + def initialize(password) + @password = password + reset_keys! + end + + def header_bytesize + 12 + end + + def gp_flags + 0x0001 | 0x0008 + end + + protected + + def reset_keys! + @key0 = 0x12345678 + @key1 = 0x23456789 + @key2 = 0x34567890 + @password.each_byte do |byte| + update_keys(byte.chr) + end + end + + def update_keys(n) + @key0 = ~Zlib.crc32(n, ~@key0) + @key1 = ((@key1 + (@key0 & 0xff)) * 134_775_813 + 1) & 0xffffffff + @key2 = ~Zlib.crc32((@key1 >> 24).chr, ~@key2) + end + + def decrypt_byte + temp = (@key2 & 0xffff) | 2 + ((temp * (temp ^ 1)) >> 8) & 0xff + end + end + + class TraditionalEncrypter < Encrypter + include TraditionalEncryption + + def header(mtime) + [].tap do |header| + (header_bytesize - 2).times do + header << Random.rand(0..255) + end + header << (mtime.to_binary_dos_time & 0xff) + header << (mtime.to_binary_dos_time >> 8) + end.map { |x| encode x }.pack('C*') + end + + def encrypt(data) + data.unpack('C*').map { |x| encode x }.pack('C*') + end + + def data_descriptor(crc32, compressed_size, uncomprssed_size) + [0x08074b50, crc32, compressed_size, uncomprssed_size].pack('VVVV') + end + + def reset! + reset_keys! + end + + private + + def encode(n) + t = decrypt_byte + update_keys(n.chr) + t ^ n + end + end + + class TraditionalDecrypter < Decrypter + include TraditionalEncryption + + def decrypt(data) + data.unpack('C*').map { |x| decode x }.pack('C*') + end + + def reset!(header) + reset_keys! + header.each_byte do |x| + decode x + end + end + + private + + def decode(n) + n ^= decrypt_byte + update_keys(n.chr) + n + end + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/decompressor.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/decompressor.rb new file mode 100644 index 00000000..047ed5e7 --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/decompressor.rb @@ -0,0 +1,13 @@ +module Zip + class Decompressor #:nodoc:all + CHUNK_SIZE = 32_768 + def initialize(input_stream) + super() + @input_stream = input_stream + end + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/deflater.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/deflater.rb new file mode 100644 index 00000000..8509cf47 --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/deflater.rb @@ -0,0 +1,34 @@ +module Zip + class Deflater < Compressor #:nodoc:all + def initialize(output_stream, level = Zip.default_compression, encrypter = NullEncrypter.new) + super() + @output_stream = output_stream + @zlib_deflater = ::Zlib::Deflate.new(level, -::Zlib::MAX_WBITS) + @size = 0 + @crc = ::Zlib.crc32 + @encrypter = encrypter + end + + def <<(data) + val = data.to_s + @crc = Zlib.crc32(val, @crc) + @size += val.bytesize + buffer = @zlib_deflater.deflate(data) + if buffer.empty? + @output_stream + else + @output_stream << @encrypter.encrypt(buffer) + end + end + + def finish + @output_stream << @encrypter.encrypt(@zlib_deflater.finish) until @zlib_deflater.finished? + end + + attr_reader :size, :crc + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/dos_time.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/dos_time.rb new file mode 100644 index 00000000..bf0cb7e0 --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/dos_time.rb @@ -0,0 +1,48 @@ +module Zip + class DOSTime < Time #:nodoc:all + # MS-DOS File Date and Time format as used in Interrupt 21H Function 57H: + + # Register CX, the Time: + # Bits 0-4 2 second increments (0-29) + # Bits 5-10 minutes (0-59) + # bits 11-15 hours (0-24) + + # Register DX, the Date: + # Bits 0-4 day (1-31) + # bits 5-8 month (1-12) + # bits 9-15 year (four digit year minus 1980) + + def to_binary_dos_time + (sec / 2) + + (min << 5) + + (hour << 11) + end + + def to_binary_dos_date + day + + (month << 5) + + ((year - 1980) << 9) + end + + # Dos time is only stored with two seconds accuracy + def dos_equals(other) + to_i / 2 == other.to_i / 2 + end + + def self.parse_binary_dos_format(binaryDosDate, binaryDosTime) + second = 2 * (0b11111 & binaryDosTime) + minute = (0b11111100000 & binaryDosTime) >> 5 + hour = (0b1111100000000000 & binaryDosTime) >> 11 + day = (0b11111 & binaryDosDate) + month = (0b111100000 & binaryDosDate) >> 5 + year = ((0b1111111000000000 & binaryDosDate) >> 9) + 1980 + begin + local(year, month, day, hour, minute, second) + end + end + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/entry.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/entry.rb new file mode 100644 index 00000000..677e49ef --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/entry.rb @@ -0,0 +1,700 @@ +require 'pathname' +module Zip + class Entry + STORED = 0 + DEFLATED = 8 + # Language encoding flag (EFS) bit + EFS = 0b100000000000 + + attr_accessor :comment, :compressed_size, :crc, :extra, :compression_method, + :name, :size, :local_header_offset, :zipfile, :fstype, :external_file_attributes, + :internal_file_attributes, + :gp_flags, :header_signature, :follow_symlinks, + :restore_times, :restore_permissions, :restore_ownership, + :unix_uid, :unix_gid, :unix_perms, + :dirty + attr_reader :ftype, :filepath # :nodoc: + + def set_default_vars_values + @local_header_offset = 0 + @local_header_size = nil # not known until local entry is created or read + @internal_file_attributes = 1 + @external_file_attributes = 0 + @header_signature = ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE + + @version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT + @version = VERSION_MADE_BY + + @ftype = nil # unspecified or unknown + @filepath = nil + @gp_flags = 0 + if ::Zip.unicode_names + @gp_flags |= EFS + @version = 63 + end + @follow_symlinks = false + + @restore_times = true + @restore_permissions = false + @restore_ownership = false + # BUG: need an extra field to support uid/gid's + @unix_uid = nil + @unix_gid = nil + @unix_perms = nil + # @posix_acl = nil + # @ntfs_acl = nil + @dirty = false + end + + def check_name(name) + return unless name.start_with?('/') + raise ::Zip::EntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /" + end + + def initialize(*args) + name = args[1] || '' + check_name(name) + + set_default_vars_values + @fstype = ::Zip::RUNNING_ON_WINDOWS ? ::Zip::FSTYPE_FAT : ::Zip::FSTYPE_UNIX + + @zipfile = args[0] || '' + @name = name + @comment = args[2] || '' + @extra = args[3] || '' + @compressed_size = args[4] || 0 + @crc = args[5] || 0 + @compression_method = args[6] || ::Zip::Entry::DEFLATED + @size = args[7] || 0 + @time = args[8] || ::Zip::DOSTime.now + + @ftype = name_is_directory? ? :directory : :file + @extra = ::Zip::ExtraField.new(@extra.to_s) unless @extra.is_a?(::Zip::ExtraField) + end + + def time + if @extra['UniversalTime'] + @extra['UniversalTime'].mtime + elsif @extra['NTFS'] + @extra['NTFS'].mtime + else + # Standard time field in central directory has local time + # under archive creator. Then, we can't get timezone. + @time + end + end + + alias mtime time + + def time=(value) + unless @extra.member?('UniversalTime') || @extra.member?('NTFS') + @extra.create('UniversalTime') + end + (@extra['UniversalTime'] || @extra['NTFS']).mtime = value + @time = value + end + + def file_type_is?(type) + raise InternalError, "current filetype is unknown: #{inspect}" unless @ftype + @ftype == type + end + + # Dynamic checkers + %w[directory file symlink].each do |k| + define_method "#{k}?" do + file_type_is?(k.to_sym) + end + end + + def name_is_directory? #:nodoc:all + @name.end_with?('/') + end + + # Is the name a relative path, free of `..` patterns that could lead to + # path traversal attacks? This does NOT handle symlinks; if the path + # contains symlinks, this check is NOT enough to guarantee safety. + def name_safe? + cleanpath = Pathname.new(@name).cleanpath + return false unless cleanpath.relative? + root = ::File::SEPARATOR + naive_expanded_path = ::File.join(root, cleanpath.to_s) + ::File.absolute_path(cleanpath.to_s, root) == naive_expanded_path + end + + def local_entry_offset #:nodoc:all + local_header_offset + @local_header_size + end + + def name_size + @name ? @name.bytesize : 0 + end + + def extra_size + @extra ? @extra.local_size : 0 + end + + def comment_size + @comment ? @comment.bytesize : 0 + end + + def calculate_local_header_size #:nodoc:all + LOCAL_ENTRY_STATIC_HEADER_LENGTH + name_size + extra_size + end + + # check before rewriting an entry (after file sizes are known) + # that we didn't change the header size (and thus clobber file data or something) + def verify_local_header_size! + return if @local_header_size.nil? + new_size = calculate_local_header_size + raise Error, "local header size changed (#{@local_header_size} -> #{new_size})" if @local_header_size != new_size + end + + def cdir_header_size #:nodoc:all + CDIR_ENTRY_STATIC_HEADER_LENGTH + name_size + + (@extra ? @extra.c_dir_size : 0) + comment_size + end + + def next_header_offset #:nodoc:all + local_entry_offset + compressed_size + data_descriptor_size + end + + # Extracts entry to file dest_path (defaults to @name). + # NB: The caller is responsible for making sure dest_path is safe, if it + # is passed. + def extract(dest_path = nil, &block) + if dest_path.nil? && !name_safe? + puts "WARNING: skipped #{@name} as unsafe" + return self + end + + dest_path ||= @name + block ||= proc { ::Zip.on_exists_proc } + + if directory? || file? || symlink? + __send__("create_#{@ftype}", dest_path, &block) + else + raise "unknown file type #{inspect}" + end + + self + end + + def to_s + @name + end + + class << self + def read_zip_short(io) # :nodoc: + io.read(2).unpack('v')[0] + end + + def read_zip_long(io) # :nodoc: + io.read(4).unpack('V')[0] + end + + def read_zip_64_long(io) # :nodoc: + io.read(8).unpack('Q<')[0] + end + + def read_c_dir_entry(io) #:nodoc:all + path = if io.respond_to?(:path) + io.path + else + io + end + entry = new(path) + entry.read_c_dir_entry(io) + entry + rescue Error + nil + end + + def read_local_entry(io) + entry = new(io) + entry.read_local_entry(io) + entry + rescue Error + nil + end + end + + public + + def unpack_local_entry(buf) + @header_signature, + @version, + @fstype, + @gp_flags, + @compression_method, + @last_mod_time, + @last_mod_date, + @crc, + @compressed_size, + @size, + @name_length, + @extra_length = buf.unpack('VCCvvvvVVVvv') + end + + def read_local_entry(io) #:nodoc:all + @local_header_offset = io.tell + + static_sized_fields_buf = io.read(::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH) || '' + + unless static_sized_fields_buf.bytesize == ::Zip::LOCAL_ENTRY_STATIC_HEADER_LENGTH + raise Error, 'Premature end of file. Not enough data for zip entry local header' + end + + unpack_local_entry(static_sized_fields_buf) + + unless @header_signature == ::Zip::LOCAL_ENTRY_SIGNATURE + raise ::Zip::Error, "Zip local header magic not found at location '#{local_header_offset}'" + end + set_time(@last_mod_date, @last_mod_time) + + @name = io.read(@name_length) + extra = io.read(@extra_length) + + @name.tr!('\\', '/') + if ::Zip.force_entry_names_encoding + @name.force_encoding(::Zip.force_entry_names_encoding) + end + + if extra && extra.bytesize != @extra_length + raise ::Zip::Error, 'Truncated local zip entry header' + else + if @extra.is_a?(::Zip::ExtraField) + @extra.merge(extra) if extra + else + @extra = ::Zip::ExtraField.new(extra) + end + end + parse_zip64_extra(true) + @local_header_size = calculate_local_header_size + end + + def pack_local_entry + zip64 = @extra['Zip64'] + [::Zip::LOCAL_ENTRY_SIGNATURE, + @version_needed_to_extract, # version needed to extract + @gp_flags, # @gp_flags + @compression_method, + @time.to_binary_dos_time, # @last_mod_time + @time.to_binary_dos_date, # @last_mod_date + @crc, + zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size, + zip64 && zip64.original_size ? 0xFFFFFFFF : @size, + name_size, + @extra ? @extra.local_size : 0].pack('VvvvvvVVVvv') + end + + def write_local_entry(io, rewrite = false) #:nodoc:all + prep_zip64_extra(true) + verify_local_header_size! if rewrite + @local_header_offset = io.tell + + io << pack_local_entry + + io << @name + io << @extra.to_local_bin if @extra + @local_header_size = io.tell - @local_header_offset + end + + def unpack_c_dir_entry(buf) + @header_signature, + @version, # version of encoding software + @fstype, # filesystem type + @version_needed_to_extract, + @gp_flags, + @compression_method, + @last_mod_time, + @last_mod_date, + @crc, + @compressed_size, + @size, + @name_length, + @extra_length, + @comment_length, + _, # diskNumberStart + @internal_file_attributes, + @external_file_attributes, + @local_header_offset, + @name, + @extra, + @comment = buf.unpack('VCCvvvvvVVVvvvvvVV') + end + + def set_ftype_from_c_dir_entry + @ftype = case @fstype + when ::Zip::FSTYPE_UNIX + @unix_perms = (@external_file_attributes >> 16) & 0o7777 + case (@external_file_attributes >> 28) + when ::Zip::FILE_TYPE_DIR + :directory + when ::Zip::FILE_TYPE_FILE + :file + when ::Zip::FILE_TYPE_SYMLINK + :symlink + else + # best case guess for whether it is a file or not + # Otherwise this would be set to unknown and that entry would never be able to extracted + if name_is_directory? + :directory + else + :file + end + end + else + if name_is_directory? + :directory + else + :file + end + end + end + + def check_c_dir_entry_static_header_length(buf) + return if buf.bytesize == ::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH + raise Error, 'Premature end of file. Not enough data for zip cdir entry header' + end + + def check_c_dir_entry_signature + return if header_signature == ::Zip::CENTRAL_DIRECTORY_ENTRY_SIGNATURE + raise Error, "Zip local header magic not found at location '#{local_header_offset}'" + end + + def check_c_dir_entry_comment_size + return if @comment && @comment.bytesize == @comment_length + raise ::Zip::Error, 'Truncated cdir zip entry header' + end + + def read_c_dir_extra_field(io) + if @extra.is_a?(::Zip::ExtraField) + @extra.merge(io.read(@extra_length)) + else + @extra = ::Zip::ExtraField.new(io.read(@extra_length)) + end + end + + def read_c_dir_entry(io) #:nodoc:all + static_sized_fields_buf = io.read(::Zip::CDIR_ENTRY_STATIC_HEADER_LENGTH) + check_c_dir_entry_static_header_length(static_sized_fields_buf) + unpack_c_dir_entry(static_sized_fields_buf) + check_c_dir_entry_signature + set_time(@last_mod_date, @last_mod_time) + @name = io.read(@name_length) + if ::Zip.force_entry_names_encoding + @name.force_encoding(::Zip.force_entry_names_encoding) + end + read_c_dir_extra_field(io) + @comment = io.read(@comment_length) + check_c_dir_entry_comment_size + set_ftype_from_c_dir_entry + parse_zip64_extra(false) + end + + def file_stat(path) # :nodoc: + if @follow_symlinks + ::File.stat(path) + else + ::File.lstat(path) + end + end + + def get_extra_attributes_from_path(path) # :nodoc: + return if Zip::RUNNING_ON_WINDOWS + stat = file_stat(path) + @unix_uid = stat.uid + @unix_gid = stat.gid + @unix_perms = stat.mode & 0o7777 + end + + def set_unix_permissions_on_path(dest_path) + # BUG: does not update timestamps into account + # ignore setuid/setgid bits by default. honor if @restore_ownership + unix_perms_mask = 0o1777 + unix_perms_mask = 0o7777 if @restore_ownership + ::FileUtils.chmod(@unix_perms & unix_perms_mask, dest_path) if @restore_permissions && @unix_perms + ::FileUtils.chown(@unix_uid, @unix_gid, dest_path) if @restore_ownership && @unix_uid && @unix_gid && ::Process.egid == 0 + # File::utimes() + end + + def set_extra_attributes_on_path(dest_path) # :nodoc: + return unless file? || directory? + + case @fstype + when ::Zip::FSTYPE_UNIX + set_unix_permissions_on_path(dest_path) + end + end + + def pack_c_dir_entry + zip64 = @extra['Zip64'] + [ + @header_signature, + @version, # version of encoding software + @fstype, # filesystem type + @version_needed_to_extract, # @versionNeededToExtract + @gp_flags, # @gp_flags + @compression_method, + @time.to_binary_dos_time, # @last_mod_time + @time.to_binary_dos_date, # @last_mod_date + @crc, + zip64 && zip64.compressed_size ? 0xFFFFFFFF : @compressed_size, + zip64 && zip64.original_size ? 0xFFFFFFFF : @size, + name_size, + @extra ? @extra.c_dir_size : 0, + comment_size, + zip64 && zip64.disk_start_number ? 0xFFFF : 0, # disk number start + @internal_file_attributes, # file type (binary=0, text=1) + @external_file_attributes, # native filesystem attributes + zip64 && zip64.relative_header_offset ? 0xFFFFFFFF : @local_header_offset, + @name, + @extra, + @comment + ].pack('VCCvvvvvVVVvvvvvVV') + end + + def write_c_dir_entry(io) #:nodoc:all + prep_zip64_extra(false) + case @fstype + when ::Zip::FSTYPE_UNIX + ft = case @ftype + when :file + @unix_perms ||= 0o644 + ::Zip::FILE_TYPE_FILE + when :directory + @unix_perms ||= 0o755 + ::Zip::FILE_TYPE_DIR + when :symlink + @unix_perms ||= 0o755 + ::Zip::FILE_TYPE_SYMLINK + end + + unless ft.nil? + @external_file_attributes = (ft << 12 | (@unix_perms & 0o7777)) << 16 + end + end + + io << pack_c_dir_entry + + io << @name + io << (@extra ? @extra.to_c_dir_bin : '') + io << @comment + end + + def ==(other) + return false unless other.class == self.class + # Compares contents of local entry and exposed fields + keys_equal = %w[compression_method crc compressed_size size name extra filepath].all? do |k| + other.__send__(k.to_sym) == __send__(k.to_sym) + end + keys_equal && time.dos_equals(other.time) + end + + def <=>(other) + to_s <=> other.to_s + end + + # Returns an IO like object for the given ZipEntry. + # Warning: may behave weird with symlinks. + def get_input_stream(&block) + if @ftype == :directory + yield ::Zip::NullInputStream if block_given? + ::Zip::NullInputStream + elsif @filepath + case @ftype + when :file + ::File.open(@filepath, 'rb', &block) + when :symlink + linkpath = ::File.readlink(@filepath) + stringio = ::StringIO.new(linkpath) + yield(stringio) if block_given? + stringio + else + raise "unknown @file_type #{@ftype}" + end + else + zis = ::Zip::InputStream.new(@zipfile, local_header_offset) + zis.instance_variable_set(:@complete_entry, self) + zis.get_next_entry + if block_given? + begin + yield(zis) + ensure + zis.close + end + else + zis + end + end + end + + def gather_fileinfo_from_srcpath(src_path) # :nodoc: + stat = file_stat(src_path) + @ftype = case stat.ftype + when 'file' + if name_is_directory? + raise ArgumentError, + "entry name '#{newEntry}' indicates directory entry, but " \ + "'#{src_path}' is not a directory" + end + :file + when 'directory' + @name += '/' unless name_is_directory? + :directory + when 'link' + if name_is_directory? + raise ArgumentError, + "entry name '#{newEntry}' indicates directory entry, but " \ + "'#{src_path}' is not a directory" + end + :symlink + else + raise "unknown file type: #{src_path.inspect} #{stat.inspect}" + end + + @filepath = src_path + get_extra_attributes_from_path(@filepath) + end + + def write_to_zip_output_stream(zip_output_stream) #:nodoc:all + if @ftype == :directory + zip_output_stream.put_next_entry(self, nil, nil, ::Zip::Entry::STORED) + elsif @filepath + zip_output_stream.put_next_entry(self, nil, nil, compression_method || ::Zip::Entry::DEFLATED) + get_input_stream { |is| ::Zip::IOExtras.copy_stream(zip_output_stream, is) } + else + zip_output_stream.copy_raw_entry(self) + end + end + + def parent_as_string + entry_name = name.chomp('/') + slash_index = entry_name.rindex('/') + slash_index ? entry_name.slice(0, slash_index + 1) : nil + end + + def get_raw_input_stream(&block) + if @zipfile.respond_to?(:seek) && @zipfile.respond_to?(:read) + yield @zipfile + else + ::File.open(@zipfile, 'rb', &block) + end + end + + def clean_up + # By default, do nothing + end + + private + + def set_time(binary_dos_date, binary_dos_time) + @time = ::Zip::DOSTime.parse_binary_dos_format(binary_dos_date, binary_dos_time) + rescue ArgumentError + warn 'Invalid date/time in zip entry' if ::Zip.warn_invalid_date + end + + def create_file(dest_path, _continue_on_exists_proc = proc { Zip.continue_on_exists_proc }) + if ::File.exist?(dest_path) && !yield(self, dest_path) + raise ::Zip::DestinationFileExistsError, + "Destination '#{dest_path}' already exists" + end + ::File.open(dest_path, 'wb') do |os| + get_input_stream do |is| + set_extra_attributes_on_path(dest_path) + + bytes_written = 0 + warned = false + buf = ''.dup + while (buf = is.sysread(::Zip::Decompressor::CHUNK_SIZE, buf)) + os << buf + bytes_written += buf.bytesize + if bytes_written > size && !warned + message = "Entry #{name} should be #{size}B but is larger when inflated" + if ::Zip.validate_entry_sizes + raise ::Zip::EntrySizeError, message + else + puts "WARNING: #{message}" + warned = true + end + end + end + end + end + end + + def create_directory(dest_path) + return if ::File.directory?(dest_path) + if ::File.exist?(dest_path) + if block_given? && yield(self, dest_path) + ::FileUtils.rm_f dest_path + else + raise ::Zip::DestinationFileExistsError, + "Cannot create directory '#{dest_path}'. " \ + 'A file already exists with that name' + end + end + ::FileUtils.mkdir_p(dest_path) + set_extra_attributes_on_path(dest_path) + end + + # BUG: create_symlink() does not use &block + def create_symlink(dest_path) + # TODO: Symlinks pose security challenges. Symlink support temporarily + # removed in view of https://github.com/rubyzip/rubyzip/issues/369 . + puts "WARNING: skipped symlink #{dest_path}" + end + + # apply missing data from the zip64 extra information field, if present + # (required when file sizes exceed 2**32, but can be used for all files) + def parse_zip64_extra(for_local_header) #:nodoc:all + return if @extra['Zip64'].nil? + if for_local_header + @size, @compressed_size = @extra['Zip64'].parse(@size, @compressed_size) + else + @size, @compressed_size, @local_header_offset = @extra['Zip64'].parse(@size, @compressed_size, @local_header_offset) + end + end + + def data_descriptor_size + (@gp_flags & 0x0008) > 0 ? 16 : 0 + end + + # create a zip64 extra information field if we need one + def prep_zip64_extra(for_local_header) #:nodoc:all + return unless ::Zip.write_zip64_support + need_zip64 = @size >= 0xFFFFFFFF || @compressed_size >= 0xFFFFFFFF + need_zip64 ||= @local_header_offset >= 0xFFFFFFFF unless for_local_header + if need_zip64 + @version_needed_to_extract = VERSION_NEEDED_TO_EXTRACT_ZIP64 + @extra.delete('Zip64Placeholder') + zip64 = @extra.create('Zip64') + if for_local_header + # local header always includes size and compressed size + zip64.original_size = @size + zip64.compressed_size = @compressed_size + else + # central directory entry entries include whichever fields are necessary + zip64.original_size = @size if @size >= 0xFFFFFFFF + zip64.compressed_size = @compressed_size if @compressed_size >= 0xFFFFFFFF + zip64.relative_header_offset = @local_header_offset if @local_header_offset >= 0xFFFFFFFF + end + else + @extra.delete('Zip64') + + # if this is a local header entry, create a placeholder + # so we have room to write a zip64 extra field afterward + # (we won't know if it's needed until the file data is written) + if for_local_header + @extra.create('Zip64Placeholder') + else + @extra.delete('Zip64Placeholder') + end + end + end + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/entry_set.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/entry_set.rb new file mode 100644 index 00000000..3272b2a4 --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/entry_set.rb @@ -0,0 +1,86 @@ +module Zip + class EntrySet #:nodoc:all + include Enumerable + attr_accessor :entry_set, :entry_order + + def initialize(an_enumerable = []) + super() + @entry_set = {} + an_enumerable.each { |o| push(o) } + end + + def include?(entry) + @entry_set.include?(to_key(entry)) + end + + def find_entry(entry) + @entry_set[to_key(entry)] + end + + def <<(entry) + @entry_set[to_key(entry)] = entry if entry + end + + alias push << + + def size + @entry_set.size + end + + alias length size + + def delete(entry) + entry if @entry_set.delete(to_key(entry)) + end + + def each + @entry_set = sorted_entries.dup.each do |_, value| + yield(value) + end + end + + def entries + sorted_entries.values + end + + # deep clone + def dup + EntrySet.new(@entry_set.values.map(&:dup)) + end + + def ==(other) + return false unless other.kind_of?(EntrySet) + @entry_set.values == other.entry_set.values + end + + def parent(entry) + @entry_set[to_key(entry.parent_as_string)] + end + + def glob(pattern, flags = ::File::FNM_PATHNAME | ::File::FNM_DOTMATCH | ::File::FNM_EXTGLOB) + entries.map do |entry| + next nil unless ::File.fnmatch(pattern, entry.name.chomp('/'), flags) + yield(entry) if block_given? + entry + end.compact + end + + protected + + def sorted_entries + ::Zip.sort_entries ? Hash[@entry_set.sort] : @entry_set + end + + private + + def to_key(entry) + k = entry.to_s.chomp('/') + k.downcase! if ::Zip.case_insensitive_match + k + end + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/errors.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/errors.rb new file mode 100644 index 00000000..364c6eee --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/errors.rb @@ -0,0 +1,18 @@ +module Zip + class Error < StandardError; end + class EntryExistsError < Error; end + class DestinationFileExistsError < Error; end + class CompressionMethodError < Error; end + class EntryNameError < Error; end + class EntrySizeError < Error; end + class InternalError < Error; end + class GPFBit3Error < Error; end + + # Backwards compatibility with v1 (delete in v2) + ZipError = Error + ZipEntryExistsError = EntryExistsError + ZipDestinationFileExistsError = DestinationFileExistsError + ZipCompressionMethodError = CompressionMethodError + ZipEntryNameError = EntryNameError + ZipInternalError = InternalError +end diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/extra_field.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/extra_field.rb new file mode 100644 index 00000000..72c36764 --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/extra_field.rb @@ -0,0 +1,101 @@ +module Zip + class ExtraField < Hash + ID_MAP = {} + + def initialize(binstr = nil) + merge(binstr) if binstr + end + + def extra_field_type_exist(binstr, id, len, i) + field_name = ID_MAP[id].name + if member?(field_name) + self[field_name].merge(binstr[i, len + 4]) + else + field_obj = ID_MAP[id].new(binstr[i, len + 4]) + self[field_name] = field_obj + end + end + + def extra_field_type_unknown(binstr, len, i) + create_unknown_item unless self['Unknown'] + if !len || len + 4 > binstr[i..-1].bytesize + self['Unknown'] << binstr[i..-1] + return + end + self['Unknown'] << binstr[i, len + 4] + end + + def create_unknown_item + s = ''.dup + class << s + alias_method :to_c_dir_bin, :to_s + alias_method :to_local_bin, :to_s + end + self['Unknown'] = s + end + + def merge(binstr) + return if binstr.empty? + i = 0 + while i < binstr.bytesize + id = binstr[i, 2] + len = binstr[i + 2, 2].to_s.unpack('v').first + if id && ID_MAP.member?(id) + extra_field_type_exist(binstr, id, len, i) + elsif id + create_unknown_item unless self['Unknown'] + break unless extra_field_type_unknown(binstr, len, i) + end + i += len + 4 + end + end + + def create(name) + unless (field_class = ID_MAP.values.find { |k| k.name == name }) + raise Error, "Unknown extra field '#{name}'" + end + self[name] = field_class.new + end + + # place Unknown last, so "extra" data that is missing the proper signature/size + # does not prevent known fields from being read back in + def ordered_values + result = [] + each { |k, v| k == 'Unknown' ? result.push(v) : result.unshift(v) } + result + end + + def to_local_bin + ordered_values.map! { |v| v.to_local_bin.force_encoding('BINARY') }.join + end + + alias to_s to_local_bin + + def to_c_dir_bin + ordered_values.map! { |v| v.to_c_dir_bin.force_encoding('BINARY') }.join + end + + def c_dir_size + to_c_dir_bin.bytesize + end + + def local_size + to_local_bin.bytesize + end + + alias length local_size + alias size local_size + end +end + +require 'zip/extra_field/generic' +require 'zip/extra_field/universal_time' +require 'zip/extra_field/old_unix' +require 'zip/extra_field/unix' +require 'zip/extra_field/zip64' +require 'zip/extra_field/zip64_placeholder' +require 'zip/extra_field/ntfs' + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/extra_field/generic.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/extra_field/generic.rb new file mode 100644 index 00000000..5931b5c2 --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/extra_field/generic.rb @@ -0,0 +1,43 @@ +module Zip + class ExtraField::Generic + def self.register_map + if const_defined?(:HEADER_ID) + ::Zip::ExtraField::ID_MAP[const_get(:HEADER_ID)] = self + end + end + + def self.name + @name ||= to_s.split('::')[-1] + end + + # return field [size, content] or false + def initial_parse(binstr) + if !binstr + # If nil, start with empty. + return false + elsif binstr[0, 2] != self.class.const_get(:HEADER_ID) + $stderr.puts 'Warning: weired extra feild header ID. skip parsing' + return false + end + [binstr[2, 2].unpack('v')[0], binstr[4..-1]] + end + + def ==(other) + return false if self.class != other.class + each do |k, v| + return false if v != other[k] + end + true + end + + def to_local_bin + s = pack_for_local + self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v') << s + end + + def to_c_dir_bin + s = pack_for_c_dir + self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v') << s + end + end +end diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/extra_field/ntfs.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/extra_field/ntfs.rb new file mode 100644 index 00000000..687704d8 --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/extra_field/ntfs.rb @@ -0,0 +1,90 @@ +module Zip + # PKWARE NTFS Extra Field (0x000a) + # Only Tag 0x0001 is supported + class ExtraField::NTFS < ExtraField::Generic + HEADER_ID = [0x000A].pack('v') + register_map + + WINDOWS_TICK = 10_000_000.0 + SEC_TO_UNIX_EPOCH = 11_644_473_600 + + def initialize(binstr = nil) + @ctime = nil + @mtime = nil + @atime = nil + binstr && merge(binstr) + end + + attr_accessor :atime, :ctime, :mtime + + def merge(binstr) + return if binstr.empty? + size, content = initial_parse(binstr) + (size && content) || return + + content = content[4..-1] + tags = parse_tags(content) + + tag1 = tags[1] + return unless tag1 + ntfs_mtime, ntfs_atime, ntfs_ctime = tag1.unpack('Qmy.zip + # (creating it if it doesn't exist) and adds an entry + # first.txt and a directory entry a_dir + # to it. + # + # require 'zip' + # + # Zip::File.open("my.zip", Zip::File::CREATE) { + # |zipfile| + # zipfile.get_output_stream("first.txt") { |f| f.puts "Hello from ZipFile" } + # zipfile.mkdir("a_dir") + # } + # + # The next example reopens my.zip writes the contents of + # first.txt to standard out and deletes the entry from + # the archive. + # + # require 'zip' + # + # Zip::File.open("my.zip", Zip::File::CREATE) { + # |zipfile| + # puts zipfile.read("first.txt") + # zipfile.remove("first.txt") + # } + # + # ZipFileSystem offers an alternative API that emulates ruby's + # interface for accessing the filesystem, ie. the File and Dir classes. + + class File < CentralDirectory + CREATE = true + SPLIT_SIGNATURE = 0x08074b50 + ZIP64_EOCD_SIGNATURE = 0x06064b50 + MAX_SEGMENT_SIZE = 3_221_225_472 + MIN_SEGMENT_SIZE = 65_536 + DATA_BUFFER_SIZE = 8192 + IO_METHODS = [:tell, :seek, :read, :close] + + attr_reader :name + + # default -> false + attr_accessor :restore_ownership + # default -> false + attr_accessor :restore_permissions + # default -> true + attr_accessor :restore_times + # Returns the zip files comment, if it has one + attr_accessor :comment + + # Opens a zip archive. Pass true as the second parameter to create + # a new archive if it doesn't exist already. + def initialize(path_or_io, create = false, buffer = false, options = {}) + super() + @name = path_or_io.respond_to?(:path) ? path_or_io.path : path_or_io + @comment = '' + @create = create ? true : false # allow any truthy value to mean true + + if ::File.size?(@name.to_s) + # There is a file, which exists, that is associated with this zip. + @create = false + @file_permissions = ::File.stat(@name).mode + + if buffer + read_from_stream(path_or_io) + else + ::File.open(@name, 'rb') do |f| + read_from_stream(f) + end + end + elsif buffer && path_or_io.size > 0 + # This zip is probably a non-empty StringIO. + read_from_stream(path_or_io) + elsif @create + # This zip is completely new/empty and is to be created. + @entry_set = EntrySet.new + elsif ::File.zero?(@name) + # A file exists, but it is empty. + raise Error, "File #{@name} has zero size. Did you mean to pass the create flag?" + else + # Everything is wrong. + raise Error, "File #{@name} not found" + end + + @stored_entries = @entry_set.dup + @stored_comment = @comment + @restore_ownership = options[:restore_ownership] || false + @restore_permissions = options[:restore_permissions] || true + @restore_times = options[:restore_times] || true + end + + class << self + # Same as #new. If a block is passed the ZipFile object is passed + # to the block and is automatically closed afterwards just as with + # ruby's builtin File.open method. + def open(file_name, create = false) + zf = ::Zip::File.new(file_name, create) + return zf unless block_given? + begin + yield zf + ensure + zf.close + end + end + + # Same as #open. But outputs data to a buffer instead of a file + def add_buffer + io = ::StringIO.new('') + zf = ::Zip::File.new(io, true, true) + yield zf + zf.write_buffer(io) + end + + # Like #open, but reads zip archive contents from a String or open IO + # stream, and outputs data to a buffer. + # (This can be used to extract data from a + # downloaded zip archive without first saving it to disk.) + def open_buffer(io, options = {}) + unless IO_METHODS.map { |method| io.respond_to?(method) }.all? || io.is_a?(String) + raise "Zip::File.open_buffer expects a String or IO-like argument (responds to #{IO_METHODS.join(', ')}). Found: #{io.class}" + end + + io = ::StringIO.new(io) if io.is_a?(::String) + + # https://github.com/rubyzip/rubyzip/issues/119 + io.binmode if io.respond_to?(:binmode) + + zf = ::Zip::File.new(io, true, true, options) + return zf unless block_given? + yield zf + + begin + zf.write_buffer(io) + rescue IOError => e + raise unless e.message == 'not opened for writing' + end + end + + # Iterates over the contents of the ZipFile. This is more efficient + # than using a ZipInputStream since this methods simply iterates + # through the entries in the central directory structure in the archive + # whereas ZipInputStream jumps through the entire archive accessing the + # local entry headers (which contain the same information as the + # central directory). + def foreach(aZipFileName, &block) + open(aZipFileName) do |zipFile| + zipFile.each(&block) + end + end + + def get_segment_size_for_split(segment_size) + if MIN_SEGMENT_SIZE > segment_size + MIN_SEGMENT_SIZE + elsif MAX_SEGMENT_SIZE < segment_size + MAX_SEGMENT_SIZE + else + segment_size + end + end + + def get_partial_zip_file_name(zip_file_name, partial_zip_file_name) + unless partial_zip_file_name.nil? + partial_zip_file_name = zip_file_name.sub(/#{::File.basename(zip_file_name)}\z/, + partial_zip_file_name + ::File.extname(zip_file_name)) + end + partial_zip_file_name ||= zip_file_name + partial_zip_file_name + end + + def get_segment_count_for_split(zip_file_size, segment_size) + (zip_file_size / segment_size).to_i + (zip_file_size % segment_size == 0 ? 0 : 1) + end + + def put_split_signature(szip_file, segment_size) + signature_packed = [SPLIT_SIGNATURE].pack('V') + szip_file << signature_packed + segment_size - signature_packed.size + end + + # + # TODO: Make the code more understandable + # + def save_splited_part(zip_file, partial_zip_file_name, zip_file_size, szip_file_index, segment_size, segment_count) + ssegment_size = zip_file_size - zip_file.pos + ssegment_size = segment_size if ssegment_size > segment_size + szip_file_name = "#{partial_zip_file_name}.#{format('%03d', szip_file_index)}" + ::File.open(szip_file_name, 'wb') do |szip_file| + if szip_file_index == 1 + ssegment_size = put_split_signature(szip_file, segment_size) + end + chunk_bytes = 0 + until ssegment_size == chunk_bytes || zip_file.eof? + segment_bytes_left = ssegment_size - chunk_bytes + buffer_size = segment_bytes_left < DATA_BUFFER_SIZE ? segment_bytes_left : DATA_BUFFER_SIZE + chunk = zip_file.read(buffer_size) + chunk_bytes += buffer_size + szip_file << chunk + # Info for track splitting + yield segment_count, szip_file_index, chunk_bytes, ssegment_size if block_given? + end + end + end + + # Splits an archive into parts with segment size + def split(zip_file_name, segment_size = MAX_SEGMENT_SIZE, delete_zip_file = true, partial_zip_file_name = nil) + raise Error, "File #{zip_file_name} not found" unless ::File.exist?(zip_file_name) + raise Errno::ENOENT, zip_file_name unless ::File.readable?(zip_file_name) + zip_file_size = ::File.size(zip_file_name) + segment_size = get_segment_size_for_split(segment_size) + return if zip_file_size <= segment_size + segment_count = get_segment_count_for_split(zip_file_size, segment_size) + # Checking for correct zip structure + open(zip_file_name) {} + partial_zip_file_name = get_partial_zip_file_name(zip_file_name, partial_zip_file_name) + szip_file_index = 0 + ::File.open(zip_file_name, 'rb') do |zip_file| + until zip_file.eof? + szip_file_index += 1 + save_splited_part(zip_file, partial_zip_file_name, zip_file_size, szip_file_index, segment_size, segment_count) + end + end + ::File.delete(zip_file_name) if delete_zip_file + szip_file_index + end + end + + # Returns an input stream to the specified entry. If a block is passed + # the stream object is passed to the block and the stream is automatically + # closed afterwards just as with ruby's builtin File.open method. + def get_input_stream(entry, &aProc) + get_entry(entry).get_input_stream(&aProc) + end + + # Returns an output stream to the specified entry. If entry is not an instance + # of Zip::Entry, a new Zip::Entry will be initialized using the arguments + # specified. If a block is passed the stream object is passed to the block and + # the stream is automatically closed afterwards just as with ruby's builtin + # File.open method. + def get_output_stream(entry, permission_int = nil, comment = nil, extra = nil, compressed_size = nil, crc = nil, compression_method = nil, size = nil, time = nil, &aProc) + new_entry = + if entry.kind_of?(Entry) + entry + else + Entry.new(@name, entry.to_s, comment, extra, compressed_size, crc, compression_method, size, time) + end + if new_entry.directory? + raise ArgumentError, + "cannot open stream to directory entry - '#{new_entry}'" + end + new_entry.unix_perms = permission_int + zip_streamable_entry = StreamableStream.new(new_entry) + @entry_set << zip_streamable_entry + zip_streamable_entry.get_output_stream(&aProc) + end + + # Returns the name of the zip archive + def to_s + @name + end + + # Returns a string containing the contents of the specified entry + def read(entry) + get_input_stream(entry) { |is| is.read } + end + + # Convenience method for adding the contents of a file to the archive + def add(entry, src_path, &continue_on_exists_proc) + continue_on_exists_proc ||= proc { ::Zip.continue_on_exists_proc } + check_entry_exists(entry, continue_on_exists_proc, 'add') + new_entry = entry.kind_of?(::Zip::Entry) ? entry : ::Zip::Entry.new(@name, entry.to_s) + new_entry.gather_fileinfo_from_srcpath(src_path) + new_entry.dirty = true + @entry_set << new_entry + end + + # Convenience method for adding the contents of a file to the archive + # in Stored format (uncompressed) + def add_stored(entry, src_path, &continue_on_exists_proc) + entry = ::Zip::Entry.new(@name, entry.to_s, nil, nil, nil, nil, ::Zip::Entry::STORED) + add(entry, src_path, &continue_on_exists_proc) + end + + # Removes the specified entry. + def remove(entry) + @entry_set.delete(get_entry(entry)) + end + + # Renames the specified entry. + def rename(entry, new_name, &continue_on_exists_proc) + foundEntry = get_entry(entry) + check_entry_exists(new_name, continue_on_exists_proc, 'rename') + @entry_set.delete(foundEntry) + foundEntry.name = new_name + @entry_set << foundEntry + end + + # Replaces the specified entry with the contents of srcPath (from + # the file system). + def replace(entry, srcPath) + check_file(srcPath) + remove(entry) + add(entry, srcPath) + end + + # Extracts entry to file dest_path. + def extract(entry, dest_path, &block) + block ||= proc { ::Zip.on_exists_proc } + found_entry = get_entry(entry) + found_entry.extract(dest_path, &block) + end + + # Commits changes that has been made since the previous commit to + # the zip archive. + def commit + return if name.is_a?(StringIO) || !commit_required? + on_success_replace do |tmp_file| + ::Zip::OutputStream.open(tmp_file) do |zos| + @entry_set.each do |e| + e.write_to_zip_output_stream(zos) + e.dirty = false + e.clean_up + end + zos.comment = comment + end + true + end + initialize(name) + end + + # Write buffer write changes to buffer and return + def write_buffer(io = ::StringIO.new('')) + ::Zip::OutputStream.write_buffer(io) do |zos| + @entry_set.each { |e| e.write_to_zip_output_stream(zos) } + zos.comment = comment + end + end + + # Closes the zip file committing any changes that has been made. + def close + commit + end + + # Returns true if any changes has been made to this archive since + # the previous commit + def commit_required? + @entry_set.each do |e| + return true if e.dirty + end + @comment != @stored_comment || @entry_set != @stored_entries || @create + end + + # Searches for entry with the specified name. Returns nil if + # no entry is found. See also get_entry + def find_entry(entry_name) + @entry_set.find_entry(entry_name) + end + + # Searches for entries given a glob + def glob(*args, &block) + @entry_set.glob(*args, &block) + end + + # Searches for an entry just as find_entry, but throws Errno::ENOENT + # if no entry is found. + def get_entry(entry) + selected_entry = find_entry(entry) + raise Errno::ENOENT, entry unless selected_entry + selected_entry.restore_ownership = @restore_ownership + selected_entry.restore_permissions = @restore_permissions + selected_entry.restore_times = @restore_times + selected_entry + end + + # Creates a directory + def mkdir(entryName, permissionInt = 0o755) + raise Errno::EEXIST, "File exists - #{entryName}" if find_entry(entryName) + entryName = entryName.dup.to_s + entryName << '/' unless entryName.end_with?('/') + @entry_set << ::Zip::StreamableDirectory.new(@name, entryName, nil, permissionInt) + end + + private + + def directory?(newEntry, srcPath) + srcPathIsDirectory = ::File.directory?(srcPath) + if newEntry.directory? && !srcPathIsDirectory + raise ArgumentError, + "entry name '#{newEntry}' indicates directory entry, but " \ + "'#{srcPath}' is not a directory" + elsif !newEntry.directory? && srcPathIsDirectory + newEntry.name += '/' + end + newEntry.directory? && srcPathIsDirectory + end + + def check_entry_exists(entryName, continue_on_exists_proc, procedureName) + continue_on_exists_proc ||= proc { Zip.continue_on_exists_proc } + return unless @entry_set.include?(entryName) + if continue_on_exists_proc.call + remove get_entry(entryName) + else + raise ::Zip::EntryExistsError, + procedureName + " failed. Entry #{entryName} already exists" + end + end + + def check_file(path) + raise Errno::ENOENT, path unless ::File.readable?(path) + end + + def on_success_replace + dirname, basename = ::File.split(name) + ::Dir::Tmpname.create(basename, dirname) do |tmp_filename| + begin + if yield tmp_filename + ::File.rename(tmp_filename, name) + ::File.chmod(@file_permissions, name) unless @create + end + ensure + ::File.unlink(tmp_filename) if ::File.exist?(tmp_filename) + end + end + end + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/filesystem.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/filesystem.rb new file mode 100644 index 00000000..81ad1a18 --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/filesystem.rb @@ -0,0 +1,627 @@ +require 'zip' + +module Zip + # The ZipFileSystem API provides an API for accessing entries in + # a zip archive that is similar to ruby's builtin File and Dir + # classes. + # + # Requiring 'zip/filesystem' includes this module in Zip::File + # making the methods in this module available on Zip::File objects. + # + # Using this API the following example creates a new zip file + # my.zip containing a normal entry with the name + # first.txt, a directory entry named mydir + # and finally another normal entry named second.txt + # + # require 'zip/filesystem' + # + # Zip::File.open("my.zip", Zip::File::CREATE) { + # |zipfile| + # zipfile.file.open("first.txt", "w") { |f| f.puts "Hello world" } + # zipfile.dir.mkdir("mydir") + # zipfile.file.open("mydir/second.txt", "w") { |f| f.puts "Hello again" } + # } + # + # Reading is as easy as writing, as the following example shows. The + # example writes the contents of first.txt from zip archive + # my.zip to standard out. + # + # require 'zip/filesystem' + # + # Zip::File.open("my.zip") { + # |zipfile| + # puts zipfile.file.read("first.txt") + # } + + module FileSystem + def initialize # :nodoc: + mappedZip = ZipFileNameMapper.new(self) + @zipFsDir = ZipFsDir.new(mappedZip) + @zipFsFile = ZipFsFile.new(mappedZip) + @zipFsDir.file = @zipFsFile + @zipFsFile.dir = @zipFsDir + end + + # Returns a ZipFsDir which is much like ruby's builtin Dir (class) + # object, except it works on the Zip::File on which this method is + # invoked + def dir + @zipFsDir + end + + # Returns a ZipFsFile which is much like ruby's builtin File (class) + # object, except it works on the Zip::File on which this method is + # invoked + def file + @zipFsFile + end + + # Instances of this class are normally accessed via the accessor + # Zip::File::file. An instance of ZipFsFile behaves like ruby's + # builtin File (class) object, except it works on Zip::File entries. + # + # The individual methods are not documented due to their + # similarity with the methods in File + class ZipFsFile + attr_writer :dir + # protected :dir + + class ZipFsStat + class << self + def delegate_to_fs_file(*methods) + methods.each do |method| + class_eval <<-end_eval, __FILE__, __LINE__ + 1 + def #{method} # def file? + @zipFsFile.#{method}(@entryName) # @zipFsFile.file?(@entryName) + end # end + end_eval + end + end + end + + def initialize(zipFsFile, entryName) + @zipFsFile = zipFsFile + @entryName = entryName + end + + def kind_of?(t) + super || t == ::File::Stat + end + + delegate_to_fs_file :file?, :directory?, :pipe?, :chardev?, :symlink?, + :socket?, :blockdev?, :readable?, :readable_real?, :writable?, :ctime, + :writable_real?, :executable?, :executable_real?, :sticky?, :owned?, + :grpowned?, :setuid?, :setgid?, :zero?, :size, :size?, :mtime, :atime + + def blocks + nil + end + + def get_entry + @zipFsFile.__send__(:get_entry, @entryName) + end + private :get_entry + + def gid + e = get_entry + if e.extra.member? 'IUnix' + e.extra['IUnix'].gid || 0 + else + 0 + end + end + + def uid + e = get_entry + if e.extra.member? 'IUnix' + e.extra['IUnix'].uid || 0 + else + 0 + end + end + + def ino + 0 + end + + def dev + 0 + end + + def rdev + 0 + end + + def rdev_major + 0 + end + + def rdev_minor + 0 + end + + def ftype + if file? + 'file' + elsif directory? + 'directory' + else + raise StandardError, 'Unknown file type' + end + end + + def nlink + 1 + end + + def blksize + nil + end + + def mode + e = get_entry + if e.fstype == 3 + e.external_file_attributes >> 16 + else + 33_206 # 33206 is equivalent to -rw-rw-rw- + end + end + end + + def initialize(mappedZip) + @mappedZip = mappedZip + end + + def get_entry(fileName) + unless exists?(fileName) + raise Errno::ENOENT, "No such file or directory - #{fileName}" + end + @mappedZip.find_entry(fileName) + end + private :get_entry + + def unix_mode_cmp(fileName, mode) + e = get_entry(fileName) + e.fstype == 3 && ((e.external_file_attributes >> 16) & mode) != 0 + rescue Errno::ENOENT + false + end + private :unix_mode_cmp + + def exists?(fileName) + expand_path(fileName) == '/' || !@mappedZip.find_entry(fileName).nil? + end + alias exist? exists? + + # Permissions not implemented, so if the file exists it is accessible + alias owned? exists? + alias grpowned? exists? + + def readable?(fileName) + unix_mode_cmp(fileName, 0o444) + end + alias readable_real? readable? + + def writable?(fileName) + unix_mode_cmp(fileName, 0o222) + end + alias writable_real? writable? + + def executable?(fileName) + unix_mode_cmp(fileName, 0o111) + end + alias executable_real? executable? + + def setuid?(fileName) + unix_mode_cmp(fileName, 0o4000) + end + + def setgid?(fileName) + unix_mode_cmp(fileName, 0o2000) + end + + def sticky?(fileName) + unix_mode_cmp(fileName, 0o1000) + end + + def umask(*args) + ::File.umask(*args) + end + + def truncate(_fileName, _len) + raise StandardError, 'truncate not supported' + end + + def directory?(fileName) + entry = @mappedZip.find_entry(fileName) + expand_path(fileName) == '/' || (!entry.nil? && entry.directory?) + end + + def open(fileName, openMode = 'r', permissionInt = 0o644, &block) + openMode.delete!('b') # ignore b option + case openMode + when 'r' + @mappedZip.get_input_stream(fileName, &block) + when 'w' + @mappedZip.get_output_stream(fileName, permissionInt, &block) + else + raise StandardError, "openmode '#{openMode} not supported" unless openMode == 'r' + end + end + + def new(fileName, openMode = 'r') + open(fileName, openMode) + end + + def size(fileName) + @mappedZip.get_entry(fileName).size + end + + # Returns nil for not found and nil for directories + def size?(fileName) + entry = @mappedZip.find_entry(fileName) + entry.nil? || entry.directory? ? nil : entry.size + end + + def chown(ownerInt, groupInt, *filenames) + filenames.each do |fileName| + e = get_entry(fileName) + e.extra.create('IUnix') unless e.extra.member?('IUnix') + e.extra['IUnix'].uid = ownerInt + e.extra['IUnix'].gid = groupInt + end + filenames.size + end + + def chmod(modeInt, *filenames) + filenames.each do |fileName| + e = get_entry(fileName) + e.fstype = 3 # force convertion filesystem type to unix + e.unix_perms = modeInt + e.external_file_attributes = modeInt << 16 + e.dirty = true + end + filenames.size + end + + def zero?(fileName) + sz = size(fileName) + sz.nil? || sz == 0 + rescue Errno::ENOENT + false + end + + def file?(fileName) + entry = @mappedZip.find_entry(fileName) + !entry.nil? && entry.file? + end + + def dirname(fileName) + ::File.dirname(fileName) + end + + def basename(fileName) + ::File.basename(fileName) + end + + def split(fileName) + ::File.split(fileName) + end + + def join(*fragments) + ::File.join(*fragments) + end + + def utime(modifiedTime, *fileNames) + fileNames.each do |fileName| + get_entry(fileName).time = modifiedTime + end + end + + def mtime(fileName) + @mappedZip.get_entry(fileName).mtime + end + + def atime(fileName) + e = get_entry(fileName) + if e.extra.member? 'UniversalTime' + e.extra['UniversalTime'].atime + elsif e.extra.member? 'NTFS' + e.extra['NTFS'].atime + end + end + + def ctime(fileName) + e = get_entry(fileName) + if e.extra.member? 'UniversalTime' + e.extra['UniversalTime'].ctime + elsif e.extra.member? 'NTFS' + e.extra['NTFS'].ctime + end + end + + def pipe?(_filename) + false + end + + def blockdev?(_filename) + false + end + + def chardev?(_filename) + false + end + + def symlink?(_fileName) + false + end + + def socket?(_fileName) + false + end + + def ftype(fileName) + @mappedZip.get_entry(fileName).directory? ? 'directory' : 'file' + end + + def readlink(_fileName) + raise NotImplementedError, 'The readlink() function is not implemented' + end + + def symlink(_fileName, _symlinkName) + raise NotImplementedError, 'The symlink() function is not implemented' + end + + def link(_fileName, _symlinkName) + raise NotImplementedError, 'The link() function is not implemented' + end + + def pipe + raise NotImplementedError, 'The pipe() function is not implemented' + end + + def stat(fileName) + raise Errno::ENOENT, fileName unless exists?(fileName) + ZipFsStat.new(self, fileName) + end + + alias lstat stat + + def readlines(fileName) + open(fileName) { |is| is.readlines } + end + + def read(fileName) + @mappedZip.read(fileName) + end + + def popen(*args, &aProc) + ::File.popen(*args, &aProc) + end + + def foreach(fileName, aSep = $/, &aProc) + open(fileName) { |is| is.each_line(aSep, &aProc) } + end + + def delete(*args) + args.each do |fileName| + if directory?(fileName) + raise Errno::EISDIR, "Is a directory - \"#{fileName}\"" + end + @mappedZip.remove(fileName) + end + end + + def rename(fileToRename, newName) + @mappedZip.rename(fileToRename, newName) { true } + end + + alias unlink delete + + def expand_path(aPath) + @mappedZip.expand_path(aPath) + end + end + + # Instances of this class are normally accessed via the accessor + # ZipFile::dir. An instance of ZipFsDir behaves like ruby's + # builtin Dir (class) object, except it works on ZipFile entries. + # + # The individual methods are not documented due to their + # similarity with the methods in Dir + class ZipFsDir + def initialize(mappedZip) + @mappedZip = mappedZip + end + + attr_writer :file + + def new(aDirectoryName) + ZipFsDirIterator.new(entries(aDirectoryName)) + end + + def open(aDirectoryName) + dirIt = new(aDirectoryName) + if block_given? + begin + yield(dirIt) + return nil + ensure + dirIt.close + end + end + dirIt + end + + def pwd + @mappedZip.pwd + end + alias getwd pwd + + def chdir(aDirectoryName) + unless @file.stat(aDirectoryName).directory? + raise Errno::EINVAL, "Invalid argument - #{aDirectoryName}" + end + @mappedZip.pwd = @file.expand_path(aDirectoryName) + end + + def entries(aDirectoryName) + entries = [] + foreach(aDirectoryName) { |e| entries << e } + entries + end + + def glob(*args, &block) + @mappedZip.glob(*args, &block) + end + + def foreach(aDirectoryName) + unless @file.stat(aDirectoryName).directory? + raise Errno::ENOTDIR, aDirectoryName + end + path = @file.expand_path(aDirectoryName) + path << '/' unless path.end_with?('/') + path = Regexp.escape(path) + subDirEntriesRegex = Regexp.new("^#{path}([^/]+)$") + @mappedZip.each do |fileName| + match = subDirEntriesRegex.match(fileName) + yield(match[1]) unless match.nil? + end + end + + def delete(entryName) + unless @file.stat(entryName).directory? + raise Errno::EINVAL, "Invalid argument - #{entryName}" + end + @mappedZip.remove(entryName) + end + alias rmdir delete + alias unlink delete + + def mkdir(entryName, permissionInt = 0o755) + @mappedZip.mkdir(entryName, permissionInt) + end + + def chroot(*_args) + raise NotImplementedError, 'The chroot() function is not implemented' + end + end + + class ZipFsDirIterator # :nodoc:all + include Enumerable + + def initialize(arrayOfFileNames) + @fileNames = arrayOfFileNames + @index = 0 + end + + def close + @fileNames = nil + end + + def each(&aProc) + raise IOError, 'closed directory' if @fileNames.nil? + @fileNames.each(&aProc) + end + + def read + raise IOError, 'closed directory' if @fileNames.nil? + @fileNames[(@index += 1) - 1] + end + + def rewind + raise IOError, 'closed directory' if @fileNames.nil? + @index = 0 + end + + def seek(anIntegerPosition) + raise IOError, 'closed directory' if @fileNames.nil? + @index = anIntegerPosition + end + + def tell + raise IOError, 'closed directory' if @fileNames.nil? + @index + end + end + + # All access to Zip::File from ZipFsFile and ZipFsDir goes through a + # ZipFileNameMapper, which has one responsibility: ensure + class ZipFileNameMapper # :nodoc:all + include Enumerable + + def initialize(zipFile) + @zipFile = zipFile + @pwd = '/' + end + + attr_accessor :pwd + + def find_entry(fileName) + @zipFile.find_entry(expand_to_entry(fileName)) + end + + def get_entry(fileName) + @zipFile.get_entry(expand_to_entry(fileName)) + end + + def get_input_stream(fileName, &aProc) + @zipFile.get_input_stream(expand_to_entry(fileName), &aProc) + end + + def get_output_stream(fileName, permissionInt = nil, &aProc) + @zipFile.get_output_stream(expand_to_entry(fileName), permissionInt, &aProc) + end + + def glob(pattern, *flags, &block) + @zipFile.glob(expand_to_entry(pattern), *flags, &block) + end + + def read(fileName) + @zipFile.read(expand_to_entry(fileName)) + end + + def remove(fileName) + @zipFile.remove(expand_to_entry(fileName)) + end + + def rename(fileName, newName, &continueOnExistsProc) + @zipFile.rename(expand_to_entry(fileName), expand_to_entry(newName), + &continueOnExistsProc) + end + + def mkdir(fileName, permissionInt = 0o755) + @zipFile.mkdir(expand_to_entry(fileName), permissionInt) + end + + # Turns entries into strings and adds leading / + # and removes trailing slash on directories + def each + @zipFile.each do |e| + yield('/' + e.to_s.chomp('/')) + end + end + + def expand_path(aPath) + expanded = aPath.start_with?('/') ? aPath : ::File.join(@pwd, aPath) + expanded.gsub!(/\/\.(\/|$)/, '') + expanded.gsub!(/[^\/]+\/\.\.(\/|$)/, '') + expanded.empty? ? '/' : expanded + end + + private + + def expand_to_entry(aPath) + expand_path(aPath)[1..-1] + end + end + end + + class File + include FileSystem + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/inflater.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/inflater.rb new file mode 100644 index 00000000..f1b26d45 --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/inflater.rb @@ -0,0 +1,66 @@ +module Zip + class Inflater < Decompressor #:nodoc:all + def initialize(input_stream, decrypter = NullDecrypter.new) + super(input_stream) + @zlib_inflater = ::Zlib::Inflate.new(-Zlib::MAX_WBITS) + @output_buffer = ''.dup + @has_returned_empty_string = false + @decrypter = decrypter + end + + def sysread(number_of_bytes = nil, buf = '') + readEverything = number_of_bytes.nil? + while readEverything || @output_buffer.bytesize < number_of_bytes + break if internal_input_finished? + @output_buffer << internal_produce_input(buf) + end + return value_when_finished if @output_buffer.bytesize == 0 && input_finished? + end_index = number_of_bytes.nil? ? @output_buffer.bytesize : number_of_bytes + @output_buffer.slice!(0...end_index) + end + + def produce_input + if @output_buffer.empty? + internal_produce_input + else + @output_buffer.slice!(0...(@output_buffer.length)) + end + end + + # to be used with produce_input, not read (as read may still have more data cached) + # is data cached anywhere other than @outputBuffer? the comment above may be wrong + def input_finished? + @output_buffer.empty? && internal_input_finished? + end + + alias :eof input_finished? + alias :eof? input_finished? + + private + + def internal_produce_input(buf = '') + retried = 0 + begin + @zlib_inflater.inflate(@decrypter.decrypt(@input_stream.read(Decompressor::CHUNK_SIZE, buf))) + rescue Zlib::BufError + raise if retried >= 5 # how many times should we retry? + retried += 1 + retry + end + end + + def internal_input_finished? + @zlib_inflater.finished? + end + + def value_when_finished # mimic behaviour of ruby File object. + return if @has_returned_empty_string + @has_returned_empty_string = true + '' + end + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/input_stream.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/input_stream.rb new file mode 100644 index 00000000..95fc3c16 --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/input_stream.rb @@ -0,0 +1,173 @@ +module Zip + # InputStream is the basic class for reading zip entries in a + # zip file. It is possible to create a InputStream object directly, + # passing the zip file name to the constructor, but more often than not + # the InputStream will be obtained from a File (perhaps using the + # ZipFileSystem interface) object for a particular entry in the zip + # archive. + # + # A InputStream inherits IOExtras::AbstractInputStream in order + # to provide an IO-like interface for reading from a single zip + # entry. Beyond methods for mimicking an IO-object it contains + # the method get_next_entry for iterating through the entries of + # an archive. get_next_entry returns a Entry object that describes + # the zip entry the InputStream is currently reading from. + # + # Example that creates a zip archive with ZipOutputStream and reads it + # back again with a InputStream. + # + # require 'zip' + # + # Zip::OutputStream.open("my.zip") do |io| + # + # io.put_next_entry("first_entry.txt") + # io.write "Hello world!" + # + # io.put_next_entry("adir/first_entry.txt") + # io.write "Hello again!" + # end + # + # + # Zip::InputStream.open("my.zip") do |io| + # + # while (entry = io.get_next_entry) + # puts "Contents of #{entry.name}: '#{io.read}'" + # end + # end + # + # java.util.zip.ZipInputStream is the original inspiration for this + # class. + + class InputStream + include ::Zip::IOExtras::AbstractInputStream + + # Opens the indicated zip file. An exception is thrown + # if the specified offset in the specified filename is + # not a local zip entry header. + # + # @param context [String||IO||StringIO] file path or IO/StringIO object + # @param offset [Integer] offset in the IO/StringIO + def initialize(context, offset = 0, decrypter = nil) + super() + @archive_io = get_io(context, offset) + @decompressor = ::Zip::NullDecompressor + @decrypter = decrypter || ::Zip::NullDecrypter.new + @current_entry = nil + end + + def close + @archive_io.close + end + + # Returns a Entry object. It is necessary to call this + # method on a newly created InputStream before reading from + # the first entry in the archive. Returns nil when there are + # no more entries. + def get_next_entry + @archive_io.seek(@current_entry.next_header_offset, IO::SEEK_SET) if @current_entry + open_entry + end + + # Rewinds the stream to the beginning of the current entry + def rewind + return if @current_entry.nil? + @lineno = 0 + @pos = 0 + @archive_io.seek(@current_entry.local_header_offset, IO::SEEK_SET) + open_entry + end + + # Modeled after IO.sysread + def sysread(number_of_bytes = nil, buf = nil) + @decompressor.sysread(number_of_bytes, buf) + end + + def eof + @output_buffer.empty? && @decompressor.eof + end + + alias :eof? eof + + class << self + # Same as #initialize but if a block is passed the opened + # stream is passed to the block and closed when the block + # returns. + def open(filename_or_io, offset = 0, decrypter = nil) + zio = new(filename_or_io, offset, decrypter) + return zio unless block_given? + begin + yield zio + ensure + zio.close if zio + end + end + + def open_buffer(filename_or_io, offset = 0) + puts 'open_buffer is deprecated!!! Use open instead!' + open(filename_or_io, offset) + end + end + + protected + + def get_io(io_or_file, offset = 0) + if io_or_file.respond_to?(:seek) + io = io_or_file.dup + io.seek(offset, ::IO::SEEK_SET) + io + else + file = ::File.open(io_or_file, 'rb') + file.seek(offset, ::IO::SEEK_SET) + file + end + end + + def open_entry + @current_entry = ::Zip::Entry.read_local_entry(@archive_io) + if @current_entry && @current_entry.gp_flags & 1 == 1 && @decrypter.is_a?(NullEncrypter) + raise Error, 'password required to decode zip file' + end + if @current_entry && @current_entry.gp_flags & 8 == 8 && @current_entry.crc == 0 \ + && @current_entry.compressed_size == 0 \ + && @current_entry.size == 0 && !@complete_entry + raise GPFBit3Error, + 'General purpose flag Bit 3 is set so not possible to get proper info from local header.' \ + 'Please use ::Zip::File instead of ::Zip::InputStream' + end + @decompressor = get_decompressor + flush + @current_entry + end + + def get_decompressor + if @current_entry.nil? + ::Zip::NullDecompressor + elsif @current_entry.compression_method == ::Zip::Entry::STORED + if @current_entry.gp_flags & 8 == 8 && @current_entry.crc == 0 && @current_entry.size == 0 && @complete_entry + ::Zip::PassThruDecompressor.new(@archive_io, @complete_entry.size) + else + ::Zip::PassThruDecompressor.new(@archive_io, @current_entry.size) + end + elsif @current_entry.compression_method == ::Zip::Entry::DEFLATED + header = @archive_io.read(@decrypter.header_bytesize) + @decrypter.reset!(header) + ::Zip::Inflater.new(@archive_io, @decrypter) + else + raise ::Zip::CompressionMethodError, + "Unsupported compression method #{@current_entry.compression_method}" + end + end + + def produce_input + @decompressor.produce_input + end + + def input_finished? + @decompressor.input_finished? + end + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/ioextras.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/ioextras.rb new file mode 100644 index 00000000..2412480b --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/ioextras.rb @@ -0,0 +1,36 @@ +module Zip + module IOExtras #:nodoc: + CHUNK_SIZE = 131_072 + + RANGE_ALL = 0..-1 + + class << self + def copy_stream(ostream, istream) + ostream.write(istream.read(CHUNK_SIZE, '')) until istream.eof? + end + + def copy_stream_n(ostream, istream, nbytes) + toread = nbytes + while toread > 0 && !istream.eof? + tr = toread > CHUNK_SIZE ? CHUNK_SIZE : toread + ostream.write(istream.read(tr, '')) + toread -= tr + end + end + end + + # Implements kind_of? in order to pretend to be an IO object + module FakeIO + def kind_of?(object) + object == IO || super + end + end + end # IOExtras namespace module +end + +require 'zip/ioextras/abstract_input_stream' +require 'zip/ioextras/abstract_output_stream' + +# Copyright (C) 2002-2004 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/ioextras/abstract_input_stream.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/ioextras/abstract_input_stream.rb new file mode 100644 index 00000000..7b7fd61d --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/ioextras/abstract_input_stream.rb @@ -0,0 +1,111 @@ +module Zip + module IOExtras + # Implements many of the convenience methods of IO + # such as gets, getc, readline and readlines + # depends on: input_finished?, produce_input and read + module AbstractInputStream + include Enumerable + include FakeIO + + def initialize + super + @lineno = 0 + @pos = 0 + @output_buffer = '' + end + + attr_accessor :lineno + attr_reader :pos + + def read(number_of_bytes = nil, buf = '') + tbuf = if @output_buffer.bytesize > 0 + if number_of_bytes <= @output_buffer.bytesize + @output_buffer.slice!(0, number_of_bytes) + else + number_of_bytes -= @output_buffer.bytesize if number_of_bytes + rbuf = sysread(number_of_bytes, buf) + out = @output_buffer + out << rbuf if rbuf + @output_buffer = '' + out + end + else + sysread(number_of_bytes, buf) + end + + if tbuf.nil? || tbuf.empty? + return nil if number_of_bytes + return '' + end + + @pos += tbuf.length + + if buf + buf.replace(tbuf) + else + buf = tbuf + end + buf + end + + def readlines(a_sep_string = $/) + ret_val = [] + each_line(a_sep_string) { |line| ret_val << line } + ret_val + end + + def gets(a_sep_string = $/, number_of_bytes = nil) + @lineno = @lineno.next + + if number_of_bytes.respond_to?(:to_int) + number_of_bytes = number_of_bytes.to_int + a_sep_string = a_sep_string.to_str if a_sep_string + elsif a_sep_string.respond_to?(:to_int) + number_of_bytes = a_sep_string.to_int + a_sep_string = $/ + else + number_of_bytes = nil + a_sep_string = a_sep_string.to_str if a_sep_string + end + + return read(number_of_bytes) if a_sep_string.nil? + a_sep_string = "#{$/}#{$/}" if a_sep_string.empty? + + buffer_index = 0 + over_limit = (number_of_bytes && @output_buffer.bytesize >= number_of_bytes) + while (match_index = @output_buffer.index(a_sep_string, buffer_index)).nil? && !over_limit + buffer_index = [buffer_index, @output_buffer.bytesize - a_sep_string.bytesize].max + return @output_buffer.empty? ? nil : flush if input_finished? + @output_buffer << produce_input + over_limit = (number_of_bytes && @output_buffer.bytesize >= number_of_bytes) + end + sep_index = [match_index + a_sep_string.bytesize, number_of_bytes || @output_buffer.bytesize].min + @pos += sep_index + @output_buffer.slice!(0...sep_index) + end + + def ungetc(byte) + @output_buffer = byte.chr + @output_buffer + end + + def flush + ret_val = @output_buffer + @output_buffer = '' + ret_val + end + + def readline(a_sep_string = $/) + ret_val = gets(a_sep_string) + raise EOFError unless ret_val + ret_val + end + + def each_line(a_sep_string = $/) + yield readline(a_sep_string) while true + rescue EOFError + end + + alias_method :each, :each_line + end + end +end diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/ioextras/abstract_output_stream.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/ioextras/abstract_output_stream.rb new file mode 100644 index 00000000..69d0cc7c --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/ioextras/abstract_output_stream.rb @@ -0,0 +1,43 @@ +module Zip + module IOExtras + # Implements many of the output convenience methods of IO. + # relies on << + module AbstractOutputStream + include FakeIO + + def write(data) + self << data + data.to_s.bytesize + end + + def print(*params) + self << params.join($,) << $\.to_s + end + + def printf(a_format_string, *params) + self << format(a_format_string, *params) + end + + def putc(an_object) + self << case an_object + when Integer + an_object.chr + when String + an_object + else + raise TypeError, 'putc: Only Integer and String supported' + end + an_object + end + + def puts(*params) + params << "\n" if params.empty? + params.flatten.each do |element| + val = element.to_s + self << val + self << "\n" unless val[-1, 1] == "\n" + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/null_compressor.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/null_compressor.rb new file mode 100644 index 00000000..70fd3294 --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/null_compressor.rb @@ -0,0 +1,15 @@ +module Zip + class NullCompressor < Compressor #:nodoc:all + include Singleton + + def <<(_data) + raise IOError, 'closed stream' + end + + attr_reader :size, :compressed_size + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/null_decompressor.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/null_decompressor.rb new file mode 100644 index 00000000..1560ef14 --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/null_decompressor.rb @@ -0,0 +1,27 @@ +module Zip + module NullDecompressor #:nodoc:all + module_function + + def sysread(_numberOfBytes = nil, _buf = nil) + nil + end + + def produce_input + nil + end + + def input_finished? + true + end + + def eof + true + end + + alias eof? eof + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/null_input_stream.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/null_input_stream.rb new file mode 100644 index 00000000..2cd36616 --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/null_input_stream.rb @@ -0,0 +1,10 @@ +module Zip + module NullInputStream #:nodoc:all + include ::Zip::NullDecompressor + include ::Zip::IOExtras::AbstractInputStream + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/output_stream.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/output_stream.rb new file mode 100644 index 00000000..d9bbc4df --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/output_stream.rb @@ -0,0 +1,189 @@ +module Zip + # ZipOutputStream is the basic class for writing zip files. It is + # possible to create a ZipOutputStream object directly, passing + # the zip file name to the constructor, but more often than not + # the ZipOutputStream will be obtained from a ZipFile (perhaps using the + # ZipFileSystem interface) object for a particular entry in the zip + # archive. + # + # A ZipOutputStream inherits IOExtras::AbstractOutputStream in order + # to provide an IO-like interface for writing to a single zip + # entry. Beyond methods for mimicking an IO-object it contains + # the method put_next_entry that closes the current entry + # and creates a new. + # + # Please refer to ZipInputStream for example code. + # + # java.util.zip.ZipOutputStream is the original inspiration for this + # class. + + class OutputStream + include ::Zip::IOExtras::AbstractOutputStream + + attr_accessor :comment + + # Opens the indicated zip file. If a file with that name already + # exists it will be overwritten. + def initialize(file_name, stream = false, encrypter = nil) + super() + @file_name = file_name + @output_stream = if stream + iostream = @file_name.dup + iostream.reopen(@file_name) + iostream.rewind + iostream + else + ::File.new(@file_name, 'wb') + end + @entry_set = ::Zip::EntrySet.new + @compressor = ::Zip::NullCompressor.instance + @encrypter = encrypter || ::Zip::NullEncrypter.new + @closed = false + @current_entry = nil + @comment = nil + end + + # Same as #initialize but if a block is passed the opened + # stream is passed to the block and closed when the block + # returns. + class << self + def open(file_name, encrypter = nil) + return new(file_name) unless block_given? + zos = new(file_name, false, encrypter) + yield zos + ensure + zos.close if zos + end + + # Same as #open but writes to a filestream instead + def write_buffer(io = ::StringIO.new(''), encrypter = nil) + zos = new(io, true, encrypter) + yield zos + zos.close_buffer + end + end + + # Closes the stream and writes the central directory to the zip file + def close + return if @closed + finalize_current_entry + update_local_headers + write_central_directory + @output_stream.close + @closed = true + end + + # Closes the stream and writes the central directory to the zip file + def close_buffer + return @output_stream if @closed + finalize_current_entry + update_local_headers + write_central_directory + @closed = true + @output_stream + end + + # Closes the current entry and opens a new for writing. + # +entry+ can be a ZipEntry object or a string. + def put_next_entry(entry_name, comment = nil, extra = nil, compression_method = Entry::DEFLATED, level = Zip.default_compression) + raise Error, 'zip stream is closed' if @closed + new_entry = if entry_name.kind_of?(Entry) + entry_name + else + Entry.new(@file_name, entry_name.to_s) + end + new_entry.comment = comment unless comment.nil? + unless extra.nil? + new_entry.extra = extra.is_a?(ExtraField) ? extra : ExtraField.new(extra.to_s) + end + new_entry.compression_method = compression_method unless compression_method.nil? + init_next_entry(new_entry, level) + @current_entry = new_entry + end + + def copy_raw_entry(entry) + entry = entry.dup + raise Error, 'zip stream is closed' if @closed + raise Error, 'entry is not a ZipEntry' unless entry.is_a?(Entry) + finalize_current_entry + @entry_set << entry + src_pos = entry.local_header_offset + entry.write_local_entry(@output_stream) + @compressor = NullCompressor.instance + entry.get_raw_input_stream do |is| + is.seek(src_pos, IO::SEEK_SET) + ::Zip::Entry.read_local_entry(is) + IOExtras.copy_stream_n(@output_stream, is, entry.compressed_size) + end + @compressor = NullCompressor.instance + @current_entry = nil + end + + private + + def finalize_current_entry + return unless @current_entry + finish + @current_entry.compressed_size = @output_stream.tell - @current_entry.local_header_offset - @current_entry.calculate_local_header_size + @current_entry.size = @compressor.size + @current_entry.crc = @compressor.crc + @output_stream << @encrypter.data_descriptor(@current_entry.crc, @current_entry.compressed_size, @current_entry.size) + @current_entry.gp_flags |= @encrypter.gp_flags + @current_entry = nil + @compressor = ::Zip::NullCompressor.instance + end + + def init_next_entry(entry, level = Zip.default_compression) + finalize_current_entry + @entry_set << entry + entry.write_local_entry(@output_stream) + @encrypter.reset! + @output_stream << @encrypter.header(entry.mtime) + @compressor = get_compressor(entry, level) + end + + def get_compressor(entry, level) + case entry.compression_method + when Entry::DEFLATED then + ::Zip::Deflater.new(@output_stream, level, @encrypter) + when Entry::STORED then + ::Zip::PassThruCompressor.new(@output_stream) + else + raise ::Zip::CompressionMethodError, + "Invalid compression method: '#{entry.compression_method}'" + end + end + + def update_local_headers + pos = @output_stream.pos + @entry_set.each do |entry| + @output_stream.pos = entry.local_header_offset + entry.write_local_entry(@output_stream, true) + end + @output_stream.pos = pos + end + + def write_central_directory + cdir = CentralDirectory.new(@entry_set, @comment) + cdir.write_to_stream(@output_stream) + end + + protected + + def finish + @compressor.finish + end + + public + + # Modeled after IO.<< + def <<(data) + @compressor << data + self + end + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/pass_thru_compressor.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/pass_thru_compressor.rb new file mode 100644 index 00000000..fdca2481 --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/pass_thru_compressor.rb @@ -0,0 +1,23 @@ +module Zip + class PassThruCompressor < Compressor #:nodoc:all + def initialize(outputStream) + super() + @output_stream = outputStream + @crc = Zlib.crc32 + @size = 0 + end + + def <<(data) + val = data.to_s + @crc = Zlib.crc32(val, @crc) + @size += val.bytesize + @output_stream << val + end + + attr_reader :size, :crc + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/pass_thru_decompressor.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/pass_thru_decompressor.rb new file mode 100644 index 00000000..485462c5 --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/pass_thru_decompressor.rb @@ -0,0 +1,40 @@ +module Zip + class PassThruDecompressor < Decompressor #:nodoc:all + def initialize(input_stream, chars_to_read) + super(input_stream) + @chars_to_read = chars_to_read + @read_so_far = 0 + @has_returned_empty_string = false + end + + def sysread(number_of_bytes = nil, buf = '') + if input_finished? + has_returned_empty_string_val = @has_returned_empty_string + @has_returned_empty_string = true + return '' unless has_returned_empty_string_val + return + end + + if number_of_bytes.nil? || @read_so_far + number_of_bytes > @chars_to_read + number_of_bytes = @chars_to_read - @read_so_far + end + @read_so_far += number_of_bytes + @input_stream.read(number_of_bytes, buf) + end + + def produce_input + sysread(::Zip::Decompressor::CHUNK_SIZE) + end + + def input_finished? + @read_so_far >= @chars_to_read + end + + alias eof input_finished? + alias eof? input_finished? + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/streamable_directory.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/streamable_directory.rb new file mode 100644 index 00000000..4560663c --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/streamable_directory.rb @@ -0,0 +1,15 @@ +module Zip + class StreamableDirectory < Entry + def initialize(zipfile, entry, srcPath = nil, permissionInt = nil) + super(zipfile, entry) + + @ftype = :directory + entry.get_extra_attributes_from_path(srcPath) if srcPath + @unix_perms = permissionInt if permissionInt + end + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/streamable_stream.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/streamable_stream.rb new file mode 100644 index 00000000..2a4bf507 --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/streamable_stream.rb @@ -0,0 +1,56 @@ +module Zip + class StreamableStream < DelegateClass(Entry) # nodoc:all + def initialize(entry) + super(entry) + dirname = if zipfile.is_a?(::String) + ::File.dirname(zipfile) + else + nil + end + @temp_file = Tempfile.new(::File.basename(name), dirname) + @temp_file.binmode + end + + def get_output_stream + if block_given? + begin + yield(@temp_file) + ensure + @temp_file.close + end + else + @temp_file + end + end + + def get_input_stream + unless @temp_file.closed? + raise StandardError, "cannot open entry for reading while its open for writing - #{name}" + end + @temp_file.open # reopens tempfile from top + @temp_file.binmode + if block_given? + begin + yield(@temp_file) + ensure + @temp_file.close + end + else + @temp_file + end + end + + def write_to_zip_output_stream(aZipOutputStream) + aZipOutputStream.put_next_entry(self) + get_input_stream { |is| ::Zip::IOExtras.copy_stream(aZipOutputStream, is) } + end + + def clean_up + @temp_file.unlink + end + end +end + +# Copyright (C) 2002, 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/version.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/version.rb new file mode 100644 index 00000000..eb9cfa9b --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/lib/zip/version.rb @@ -0,0 +1,3 @@ +module Zip + VERSION = '2.0.0' +end diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/samples/example.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/samples/example.rb new file mode 100755 index 00000000..224d4f1c --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/samples/example.rb @@ -0,0 +1,81 @@ +#!/usr/bin/env ruby + +$: << '../lib' +system('zip example.zip example.rb gtk_ruby_zip.rb') + +require 'zip' + +####### Using ZipInputStream alone: ####### + +Zip::InputStream.open('example.zip') do |zis| + entry = zis.get_next_entry + print "First line of '#{entry.name} (#{entry.size} bytes): " + puts "'#{zis.gets.chomp}'" + entry = zis.get_next_entry + print "First line of '#{entry.name} (#{entry.size} bytes): " + puts "'#{zis.gets.chomp}'" +end + +####### Using ZipFile to read the directory of a zip file: ####### + +zf = Zip::File.new('example.zip') +zf.each_with_index do |entry, index| + puts "entry #{index} is #{entry.name}, size = #{entry.size}, compressed size = #{entry.compressed_size}" + # use zf.get_input_stream(entry) to get a ZipInputStream for the entry + # entry can be the ZipEntry object or any object which has a to_s method that + # returns the name of the entry. +end + +####### Using ZipOutputStream to write a zip file: ####### + +Zip::OutputStream.open('exampleout.zip') do |zos| + zos.put_next_entry('the first little entry') + zos.puts 'Hello hello hello hello hello hello hello hello hello' + + zos.put_next_entry('the second little entry') + zos.puts 'Hello again' + + # Use rubyzip or your zip client of choice to verify + # the contents of exampleout.zip +end + +####### Using ZipFile to change a zip file: ####### + +Zip::File.open('exampleout.zip') do |zip_file| + zip_file.add('thisFile.rb', 'example.rb') + zip_file.rename('thisFile.rb', 'ILikeThisName.rb') + zip_file.add('Again', 'example.rb') +end + +# Lets check +Zip::File.open('exampleout.zip') do |zip_file| + puts "Changed zip file contains: #{zip_file.entries.join(', ')}" + zip_file.remove('Again') + puts "Without 'Again': #{zip_file.entries.join(', ')}" +end + +####### Using ZipFile to split a zip file: ####### + +# Creating large zip file for splitting +Zip::OutputStream.open('large_zip_file.zip') do |zos| + puts 'Creating zip file...' + 10.times do |i| + zos.put_next_entry("large_entry_#{i}.txt") + zos.puts 'Hello' * 104_857_600 + end +end + +# Splitting created large zip file +part_zips_count = Zip::File.split('large_zip_file.zip', 2_097_152, false) +puts "Zip file splitted in #{part_zips_count} parts" + +# Track splitting an archive +Zip::File.split('large_zip_file.zip', 1_048_576, true, 'part_zip_file') do |part_count, part_index, chunk_bytes, segment_bytes| + puts "#{part_index} of #{part_count} part splitting: #{(chunk_bytes.to_f / segment_bytes.to_f * 100).to_i}%" +end + +# For other examples, look at zip.rb and ziptest.rb + +# Copyright (C) 2002 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/samples/example_filesystem.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/samples/example_filesystem.rb new file mode 100755 index 00000000..f253a5e5 --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/samples/example_filesystem.rb @@ -0,0 +1,31 @@ +#!/usr/bin/env ruby + +$: << '../lib' + +require 'zip/filesystem' + +EXAMPLE_ZIP = 'filesystem.zip' + +File.delete(EXAMPLE_ZIP) if File.exist?(EXAMPLE_ZIP) + +Zip::File.open(EXAMPLE_ZIP, Zip::File::CREATE) do |zf| + zf.file.open('file1.txt', 'w') { |os| os.write 'first file1.txt' } + zf.dir.mkdir('dir1') + zf.dir.chdir('dir1') + zf.file.open('file1.txt', 'w') { |os| os.write 'second file1.txt' } + puts zf.file.read('file1.txt') + puts zf.file.read('../file1.txt') + zf.dir.chdir('..') + zf.file.open('file2.txt', 'w') { |os| os.write 'first file2.txt' } + puts "Entries: #{zf.entries.join(', ')}" +end + +Zip::File.open(EXAMPLE_ZIP) do |zf| + puts "Entries from reloaded zip: #{zf.entries.join(', ')}" +end + +# For other examples, look at zip.rb and ziptest.rb + +# Copyright (C) 2003 Thomas Sondergaard +# rubyzip is free software; you can redistribute it and/or +# modify it under the terms of the ruby license. diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/samples/example_recursive.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/samples/example_recursive.rb new file mode 100644 index 00000000..56a5cc7c --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/samples/example_recursive.rb @@ -0,0 +1,54 @@ +require 'zip' + +# This is a simple example which uses rubyzip to +# recursively generate a zip file from the contents of +# a specified directory. The directory itself is not +# included in the archive, rather just its contents. +# +# Usage: +# directory_to_zip = "/tmp/input" +# output_file = "/tmp/out.zip" +# zf = ZipFileGenerator.new(directory_to_zip, output_file) +# zf.write() +class ZipFileGenerator + # Initialize with the directory to zip and the location of the output archive. + def initialize(input_dir, output_file) + @input_dir = input_dir + @output_file = output_file + end + + # Zip the input directory. + def write + entries = Dir.entries(@input_dir) - %w[. ..] + + ::Zip::File.open(@output_file, ::Zip::File::CREATE) do |zipfile| + write_entries entries, '', zipfile + end + end + + private + + # A helper method to make the recursion work. + def write_entries(entries, path, zipfile) + entries.each do |e| + zipfile_path = path == '' ? e : File.join(path, e) + disk_file_path = File.join(@input_dir, zipfile_path) + + if File.directory? disk_file_path + recursively_deflate_directory(disk_file_path, zipfile, zipfile_path) + else + put_into_archive(disk_file_path, zipfile, zipfile_path) + end + end + end + + def recursively_deflate_directory(disk_file_path, zipfile, zipfile_path) + zipfile.mkdir zipfile_path + subdir = Dir.entries(disk_file_path) - %w[. ..] + write_entries subdir, zipfile_path, zipfile + end + + def put_into_archive(disk_file_path, zipfile, zipfile_path) + zipfile.add(zipfile_path, disk_file_path) + end +end diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/samples/gtk_ruby_zip.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/samples/gtk_ruby_zip.rb new file mode 100755 index 00000000..62f005a5 --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/samples/gtk_ruby_zip.rb @@ -0,0 +1,84 @@ +#!/usr/bin/env ruby + +$: << '../lib' + +$VERBOSE = true + +require 'gtk' +require 'zip' + +class MainApp < Gtk::Window + def initialize + super() + set_usize(400, 256) + set_title('rubyzip') + signal_connect(Gtk::Window::SIGNAL_DESTROY) { Gtk.main_quit } + + box = Gtk::VBox.new(false, 0) + add(box) + + @zipfile = nil + @buttonPanel = ButtonPanel.new + @buttonPanel.openButton.signal_connect(Gtk::Button::SIGNAL_CLICKED) do + show_file_selector + end + @buttonPanel.extractButton.signal_connect(Gtk::Button::SIGNAL_CLICKED) do + puts 'Not implemented!' + end + box.pack_start(@buttonPanel, false, false, 0) + + sw = Gtk::ScrolledWindow.new + sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC) + box.pack_start(sw, true, true, 0) + + @clist = Gtk::CList.new(%w[Name Size Compression]) + @clist.set_selection_mode(Gtk::SELECTION_BROWSE) + @clist.set_column_width(0, 120) + @clist.set_column_width(1, 120) + @clist.signal_connect(Gtk::CList::SIGNAL_SELECT_ROW) do |_w, row, _column, _event| + @selected_row = row + end + sw.add(@clist) + end + + class ButtonPanel < Gtk::HButtonBox + attr_reader :openButton, :extractButton + def initialize + super + set_layout(Gtk::BUTTONBOX_START) + set_spacing(0) + @openButton = Gtk::Button.new('Open archive') + @extractButton = Gtk::Button.new('Extract entry') + pack_start(@openButton) + pack_start(@extractButton) + end + end + + def show_file_selector + @fileSelector = Gtk::FileSelection.new('Open zip file') + @fileSelector.show + @fileSelector.ok_button.signal_connect(Gtk::Button::SIGNAL_CLICKED) do + open_zip(@fileSelector.filename) + @fileSelector.destroy + end + @fileSelector.cancel_button.signal_connect(Gtk::Button::SIGNAL_CLICKED) do + @fileSelector.destroy + end + end + + def open_zip(filename) + @zipfile = Zip::File.open(filename) + @clist.clear + @zipfile.each do |entry| + @clist.append([entry.name, + entry.size.to_s, + (100.0 * entry.compressedSize / entry.size).to_s + '%']) + end + end +end + +mainApp = MainApp.new + +mainApp.show_all + +Gtk.main diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/samples/qtzip.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/samples/qtzip.rb new file mode 100755 index 00000000..1d450a78 --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/samples/qtzip.rb @@ -0,0 +1,92 @@ +#!/usr/bin/env ruby + +$VERBOSE = true + +$: << '../lib' + +require 'Qt' +system('rbuic -o zipdialogui.rb zipdialogui.ui') +require 'zipdialogui.rb' +require 'zip' + +a = Qt::Application.new(ARGV) + +class ZipDialog < ZipDialogUI + def initialize + super() + connect(child('add_button'), SIGNAL('clicked()'), + self, SLOT('add_files()')) + connect(child('extract_button'), SIGNAL('clicked()'), + self, SLOT('extract_files()')) + end + + def zipfile(&proc) + Zip::File.open(@zip_filename, &proc) + end + + def each(&proc) + Zip::File.foreach(@zip_filename, &proc) + end + + def refresh + lv = child('entry_list_view') + lv.clear + each do |e| + lv.insert_item(Qt::ListViewItem.new(lv, e.name, e.size.to_s)) + end + end + + def load(zipfile) + @zip_filename = zipfile + refresh + end + + def add_files + l = Qt::FileDialog.getOpenFileNames(nil, nil, self) + zipfile do |zf| + l.each do |path| + zf.add(File.basename(path), path) + end + end + refresh + end + + def extract_files + selected_items = [] + unselected_items = [] + lv_item = entry_list_view.first_child + while lv_item + if entry_list_view.is_selected(lv_item) + selected_items << lv_item.text(0) + else + unselected_items << lv_item.text(0) + end + lv_item = lv_item.next_sibling + end + puts "selected_items.size = #{selected_items.size}" + puts "unselected_items.size = #{unselected_items.size}" + items = !selected_items.empty? ? selected_items : unselected_items + puts "items.size = #{items.size}" + + d = Qt::FileDialog.get_existing_directory(nil, self) + if !d + puts 'No directory chosen' + else + zipfile { |zf| items.each { |e| zf.extract(e, File.join(d, e)) } } + end + end + + slots 'add_files()', 'extract_files()' +end + +unless ARGV[0] + puts "usage: #{$0} zipname" + exit +end + +zd = ZipDialog.new +zd.load(ARGV[0]) + +a.mainWidget = zd +zd.show +a.exec diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/samples/write_simple.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/samples/write_simple.rb new file mode 100755 index 00000000..be2a9704 --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/samples/write_simple.rb @@ -0,0 +1,12 @@ +#!/usr/bin/env ruby + +$: << '../lib' + +require 'zip' + +include Zip + +OutputStream.open('simple.zip') do |zos| + zos.put_next_entry 'entry.txt' + zos.puts 'Hello world' +end diff --git a/path/ruby/2.6.0/gems/rubyzip-2.0.0/samples/zipfind.rb b/path/ruby/2.6.0/gems/rubyzip-2.0.0/samples/zipfind.rb new file mode 100755 index 00000000..400e0a69 --- /dev/null +++ b/path/ruby/2.6.0/gems/rubyzip-2.0.0/samples/zipfind.rb @@ -0,0 +1,66 @@ +#!/usr/bin/env ruby + +$VERBOSE = true + +$: << '../lib' + +require 'zip' +require 'find' + +module Zip + module ZipFind + def self.find(path, zipFilePattern = /\.zip$/i) + Find.find(path) do |fileName| + yield(fileName) + next unless zipFilePattern.match(fileName) && File.file?(fileName) + begin + Zip::File.foreach(fileName) do |zipEntry| + yield(fileName + File::SEPARATOR + zipEntry.to_s) + end + rescue Errno::EACCES => ex + puts ex + end + end + end + + def self.find_file(path, fileNamePattern, zipFilePattern = /\.zip$/i) + find(path, zipFilePattern) do |fileName| + yield(fileName) if fileNamePattern.match(fileName) + end + end + end +end + +if $0 == __FILE__ + module ZipFindConsoleRunner + PATH_ARG_INDEX = 0 + FILENAME_PATTERN_ARG_INDEX = 1 + ZIPFILE_PATTERN_ARG_INDEX = 2 + + def self.run(args) + check_args(args) + Zip::ZipFind.find_file(args[PATH_ARG_INDEX], + args[FILENAME_PATTERN_ARG_INDEX], + args[ZIPFILE_PATTERN_ARG_INDEX]) do |fileName| + report_entry_found fileName + end + end + + def self.check_args(args) + if args.size != 3 + usage + exit + end + end + + def self.usage + puts "Usage: #{$0} PATH ZIPFILENAME_PATTERN FILNAME_PATTERN" + end + + def self.report_entry_found(fileName) + puts fileName + end + end + + ZipFindConsoleRunner.run(ARGV) +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/.yardopts b/path/ruby/2.6.0/gems/sass-3.7.4/.yardopts new file mode 100644 index 00000000..a380440c --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/.yardopts @@ -0,0 +1,13 @@ +--readme README.md +--markup markdown +--markup-provider redcarpet +--default-return "" +--title "Sass Documentation" +--query 'object.type != :classvariable' +--query 'object.type != :constant || @api && @api.text == "public"' +--hide-void-return +--protected +--no-private +--no-highlight +--tag comment +--hide-tag comment diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/CODE_OF_CONDUCT.md b/path/ruby/2.6.0/gems/sass-3.7.4/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..c4164af1 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/CODE_OF_CONDUCT.md @@ -0,0 +1,10 @@ +Sass is more than a technology; Sass is driven by the community of individuals +that power its development and use every day. As a community, we want to embrace +the very differences that have made our collaboration so powerful, and work +together to provide the best environment for learning, growing, and sharing of +ideas. It is imperative that we keep Sass a fun, welcoming, challenging, and +fair place to play. + +[The full community guidelines can be found on the Sass website.][link] + +[link]: https://sass-lang.com/community-guidelines diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/CONTRIBUTING.md b/path/ruby/2.6.0/gems/sass-3.7.4/CONTRIBUTING.md new file mode 100644 index 00000000..a4e671d9 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/CONTRIBUTING.md @@ -0,0 +1,148 @@ +Contributions are welcomed. Please see the following site for guidelines: + +[https://sass-lang.com/community#Contribute](https://sass-lang.com/community#Contribute) + +* [Branches](#main-development-branches) + * [Feature Branches](#feature-branches) + * [Experimental Branches](#experimental-branches) + * [Old Stable Branches](#old-stable-branches) +* [Versioning](#versioning) + * [Making Breaking Changes](#making-breaking-changes) + * [Exceptional Breakages](#exceptional-breakages) + +## Branches + +The Sass repository has three primary development branches, each of which tracks +a different line of releases (see [versioning](#versioning) below). Each branch +is regularly merged into the one below: `stable` into `next`, `next` into +`master`. + +* The `stable` branch is the default—it's what GitHub shows if you go to + [sass/ruby-sass](https://github.com/sass/ruby-sass), and it's the default place for pull + requests to go. This branch is where we work on the next patch release. Bug + fixes and documentation improvements belong here, but not new features. + +* The `next` branch is where we work on the next minor release. It's where most + new features go, as long as they're not breaking changes. Very occasionally + breaking changes will go here as well—see + [exceptional breakages](#exceptional-breakages) below for details. + +* The `master` branch is where we work on the next major release. It's where + breaking changes go. We also occasionally decide that a non-breaking feature + is big enough to warrant saving until the next major release, in which case it + will also be developed here. + +Ideally, pull requests would be made against the appropriate +branch, but don't worry about it too much; if you make a request against the +wrong branch, the maintainer will take responsibility for rebasing it before +merging. + +### Testing + +Tests for changes to the Sass language go in +[sass-spec](https://github.com/sass/sass-spec) so that other +implementations (E.g. libSass) can be tested against the same test +suite. The sass-spec repo follows a "trunk development" model in that +the tests there test against different version of the Sass language (as +opposed to having branches that track different Sass versions). When +contributing changes to Sass, update the Gemfile to use sass-spec from a +branch or fork that has the new tests. When the feature lands in Sass, +the committer will also merge the corresponding sass-spec changes. + +The [documentation of +sass-spec](https://github.com/sass/sass-spec/blob/master/README.md) +explains how to run sass-spec and contribute changes. In development, +Change the Gemfile(s) to use the `:path` option against the sass-spec gem +to link your local checkout of sass and sass-spec together in one or +both directions. + +Changes to Sass internals or Ruby Sass specific features (E.g. +the `sass-convert` tool) should always have tests in the Sass `test` +directory following the conventions you see there. + +### Feature Branches + +Sometimes it won't be possible to merge a new feature into `next` or `master` +immediately. It may require longer-term work before it's complete, or we may not +want to release it as part of any alpha releases of the branch in question. +Branches like this are labeled `feature.#{name}` and stay on GitHub until +they're ready to be merged. + +### Experimental Branches + +Not all features pan out, and not all code is a good fit for merging into the +main codebase. Usually when this happens the code is just discarded, but every +so often it's interesting or promising enough that it's worth keeping around. +This is what experimental branches (labeled `experimental.#{name}`) are for. +While they're not currently in use, they contain code that might be useful in +the future. + +### Old Stable Branches + +Usually Sass doesn't have the development time to do long-term maintenance of +old release. But occasionally, very rarely, it becomes necessary. In cases like +that, a branch named `stable_#{version}` will be created, starting from the last +tag in that version series. + +## Versioning + +Starting with version 3.5.0, Sass uses [semantic versioning](http://semver.org/) +to indicate the evolution of its language semantics as much as possible. This +means that patch releases (such as 3.5.3) contain only bug fixes, minor releases +(such as 3.6.0) contain backwards-compatible features, and only major releases +(such as 4.0.0) are allowed to have backwards-incompatible behavior. There are +[exceptions](#exceptional-breakages), but we try to follow this rule as closely +as possible. + +Note, however, that the semantic versioning applies only to the language's +semantics, not to the Ruby APIs. Although we try hard to keep widely-used APIs +like [`Sass::Engine`][Sass::Engine] stable, we don't have a strong distinction +between public and private APIs and we need to be able to freely refactor our +code. + +[Sass::Engine]: https://sass-lang.com/documentation/Sass/Engine.html + +### Making Breaking Changes + +Sometimes the old way of doing something just isn't going to work anymore, and +the new way just can't be made backwards-compatible. In that case, a breaking +change is necessary. These changes are rarely pleasant, but they contribute to +making the language better in the long term. + +Our breaking change process tries to make such changes as clear to users and as +easy to adapt to as possible. We want to ensure that there's a clear path +forward for users using functionality that will no longer exist, and that they +are able to understand what's changing and what they need to do. We've developed +the following process for this: + +1. Deprecate the old behavior [in `stable`](#branches). At minimum, deprecating + some behavior involves printing a warning when that behavior is used + explaining that it's going to go away in the future. Ideally, this message + will also include code that will do the same thing in a non-deprecated way. + If there's a thorough prose explanation of the change available online, the + message should link to that as well. + +2. If possible, make `sass-convert` (also in `stable`) convert the deprecated + behavior into a non-deprecated form. This allows users to run `sass-convert + -R -i` to automatically update their stylesheets. + +3. Implement the new behavior in `master`. The sooner this happens, the better: + it may be unclear exactly what needs to be deprecated until the new + implementation exists. + +4. Release an alpha version of `master` that includes the new behavior. This + allows users who are dissatisfied with the workaround to use the new + behavior early. Normally a maintainer will take care of this. + +### Exceptional Breakages + +Because Sass's syntax and semantics are closely tied to those of CSS, there are +occasionally times when CSS syntax is introduced that overlaps with +previously-valid Sass. In this case in particular, we may introduce a breaking +change in a minor version to get back to CSS compatibility as soon as possible. + +Exceptional breakages still require the full deprecation process; the only +change is that the new behavior is implemented in `next` rather than `master`. +Because there are no minor releases between the deprecation and the removal of +the old behavior, the deprecation warning should be introduced soon as it +becomes clear that an exceptional breakage is necessary. diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/MIT-LICENSE b/path/ruby/2.6.0/gems/sass-3.7.4/MIT-LICENSE new file mode 100644 index 00000000..5c184eba --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2006-2016 Hampton Catlin, Natalie Weizenbaum, and Chris Eppstein + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/README.md b/path/ruby/2.6.0/gems/sass-3.7.4/README.md new file mode 100644 index 00000000..4ae836e6 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/README.md @@ -0,0 +1,235 @@ +## Ruby Sass Has Reached End-of-Life + +Ruby Sass should no longer be used, and will no longer be receiving any updates. +See [the Sass blog][], and consider switching to the [`sassc` gem]. + +[the Sass blog]: https://sass-lang.com/blog/posts/7828841 +[`sassc` gem]: https://rubygems.org/gems/sassc + +# Sass [![Travis Build Status](https://travis-ci.org/sass/ruby-sass.svg?branch=next)](https://travis-ci.org/sass/ruby-sass) [![Gem Version](https://badge.fury.io/rb/sass.svg)](http://badge.fury.io/rb/sass) [![Inline docs](http://inch-ci.org/github/sass/sass.svg)](http://inch-ci.org/github/sass/sass) + +**Sass makes CSS fun again**. Sass is an extension of CSS, +adding nested rules, variables, mixins, selector inheritance, and more. +It's translated to well-formatted, standard CSS +using the command line tool or a web-framework plugin. + +Sass has two syntaxes. The new main syntax (as of Sass 3) +is known as "SCSS" (for "Sassy CSS"), +and is a superset of CSS's syntax. +This means that every valid CSS stylesheet is valid SCSS as well. +SCSS files use the extension `.scss`. + +The second, older syntax is known as the indented syntax (or just "Sass"). +Inspired by Haml's terseness, it's intended for people +who prefer conciseness over similarity to CSS. +Instead of brackets and semicolons, +it uses the indentation of lines to specify blocks. +Although no longer the primary syntax, +the indented syntax will continue to be supported. +Files in the indented syntax use the extension `.sass`. + +## Using + +Sass can be used from the command line +or as part of a web framework. +The first step is to install the gem: + + gem install sass + +After you convert some CSS to Sass, you can run + + sass style.scss + +to compile it back to CSS. +For more information on these commands, check out + + sass --help + +To install Sass in Rails 2, +just add `config.gem "sass"` to `config/environment.rb`. +In Rails 3, add `gem "sass"` to your Gemfile instead. +`.sass` or `.scss` files should be placed in `public/stylesheets/sass`, +where they'll be automatically compiled +to corresponding CSS files in `public/stylesheets` when needed +(the Sass template directory is customizable... +see [the Sass reference](https://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#template_location-option) for details). + +Sass can also be used with any Rack-enabled web framework. +To do so, just add + +```ruby +require 'sass/plugin/rack' +use Sass::Plugin::Rack +``` + +to `config.ru`. +Then any Sass files in `public/stylesheets/sass` +will be compiled into CSS files in `public/stylesheets` on every request. + +To use Sass programmatically, +check out the [YARD documentation](https://sass-lang.com/documentation/file.SASS_REFERENCE.html#using_sass). + +## Formatting + +Sass is an extension of CSS +that adds power and elegance to the basic language. +It allows you to use [variables][vars], [nested rules][nested], +[mixins][mixins], [inline imports][imports], +and more, all with a fully CSS-compatible syntax. +Sass helps keep large stylesheets well-organized, +and get small stylesheets up and running quickly, +particularly with the help of +[the Compass style library](http://compass-style.org). + +[vars]: https://sass-lang.com/documentation/file.SASS_REFERENCE.html#variables_ +[nested]: https://sass-lang.com/documentation/file.SASS_REFERENCE.html#nested_rules +[mixins]: https://sass-lang.com/documentation/file.SASS_REFERENCE.html#mixins +[imports]: https://sass-lang.com/documentation/file.SASS_REFERENCE.html#import + +Sass has two syntaxes. +The one presented here, known as "SCSS" (for "Sassy CSS"), +is fully CSS-compatible. +The other (older) syntax, known as the indented syntax or just "Sass", +is whitespace-sensitive and indentation-based. +For more information, see the [reference documentation][syntax]. + +[syntax]: https://sass-lang.com/documentation/file.SASS_REFERENCE.html#syntax + +To run the following examples and see the CSS they produce, +put them in a file called `test.scss` and run `sass test.scss`. + +### Nesting + +Sass avoids repetition by nesting selectors within one another. +The same thing works for properties. + +```scss +table.hl { + margin: 2em 0; + td.ln { text-align: right; } +} + +li { + font: { + family: serif; + weight: bold; + size: 1.2em; + } +} +``` + +### Variables + +Use the same color all over the place? +Need to do some math with height and width and text size? +Sass supports variables, math operations, and many useful functions. + +```scss +$blue: #3bbfce; +$margin: 16px; + +.content_navigation { + border-color: $blue; + color: darken($blue, 10%); +} + +.border { + padding: $margin / 2; + margin: $margin / 2; + border-color: $blue; +} +``` + +### Mixins + +Even more powerful than variables, +mixins allow you to re-use whole chunks of CSS, +properties or selectors. +You can even give them arguments. + +```scss +@mixin table-scaffolding { + th { + text-align: center; + font-weight: bold; + } + td, th { padding: 2px; } +} + +@mixin left($dist) { + float: left; + margin-left: $dist; +} + +#data { + @include left(10px); + @include table-scaffolding; +} +``` + +A comprehensive list of features is available +in the [Sass reference](https://sass-lang.com/documentation/file.SASS_REFERENCE.html). + +## Executables + +The Sass gem includes several executables that are useful +for dealing with Sass from the command line. + +### `sass` + +The `sass` executable transforms a source Sass file into CSS. +See `sass --help` for further information and options. + +### `sass-convert` + +The `sass-convert` executable converts between CSS, Sass, and SCSS. +When converting from CSS to Sass or SCSS, +nesting is applied where appropriate. +See `sass-convert --help` for further information and options. + +### Running locally + +To run the Sass executables from a source checkout instead of from rubygems: + +``` +$ cd sass +$ bundle +$ bundle exec sass ... +$ bundle exec scss ... +$ bundle exec sass-convert ... +``` + +## Authors + +Sass was envisioned by [Hampton Catlin](http://www.hamptoncatlin.com) +(@hcatlin). However, Hampton doesn't even know his way around the code anymore +and now occasionally consults on the language issues. Hampton lives in San +Francisco, California and works as VP of Technology +at [Moovweb](http://www.moovweb.com/). + +[Natalie Weizenbaum](https://twitter.com/nex3) is the primary developer and +architect of Sass. Her hard work has kept the project alive by endlessly +answering forum posts, fixing bugs, refactoring, finding speed improvements, +writing documentation, implementing new features, and designing the language. +Natalie lives in Seattle, Washington and works on [Dart](http://dartlang.org) +application libraries at Google. + +[Chris Eppstein](http://twitter.com/chriseppstein) is a core contributor to +Sass and the creator of [Compass](http://compass-style.org/), the first Sass-based framework, and +[Eyeglass](http://github.com/sass-eyeglass/eyeglass), a node-sass plugin ecosystem for NPM. Chris focuses +on making Sass more powerful, easy to use, and on ways to speed its adoption +through the web development community. Chris lives in San Jose, California with +his wife and two children. He is an Engineer for +[LinkedIn.com](http://linkedin.com), where his primary responsibility is to +maintain Sass and many other Sass-related open source projects. + +If you use this software, we'd be truly honored if you'd make a +tax-deductible donation to a non-profit organization and then +[let us know on twitter](http://twitter.com/SassCSS), so that we can +thank you. Here's a few that we endorse: + +* [Trans Justice Funding Project](http://www.transjusticefundingproject.org/) +* [United Mitochondrial Disease Foundation](http://umdf.org/compass) +* [Girl Develop It](https://www.girldevelopit.com/donate) + +Sass is licensed under the MIT License. diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/REVISION b/path/ruby/2.6.0/gems/sass-3.7.4/REVISION new file mode 100644 index 00000000..54798d67 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/REVISION @@ -0,0 +1 @@ +(release) diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/VERSION b/path/ruby/2.6.0/gems/sass-3.7.4/VERSION new file mode 100644 index 00000000..0833a98f --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/VERSION @@ -0,0 +1 @@ +3.7.4 diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/VERSION_DATE b/path/ruby/2.6.0/gems/sass-3.7.4/VERSION_DATE new file mode 100644 index 00000000..ad0300cc --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/VERSION_DATE @@ -0,0 +1 @@ +04 April 2019 00:49:58 UTC diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/VERSION_NAME b/path/ruby/2.6.0/gems/sass-3.7.4/VERSION_NAME new file mode 100644 index 00000000..ee5e396d --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/VERSION_NAME @@ -0,0 +1 @@ +Bleeding Edge diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/bin/sass b/path/ruby/2.6.0/gems/sass-3.7.4/bin/sass new file mode 100755 index 00000000..62d6d0c9 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/bin/sass @@ -0,0 +1,13 @@ +#!/usr/bin/env ruby +# The command line Sass parser. + +THIS_FILE = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__ +begin + require File.dirname(THIS_FILE) + '/../lib/sass' +rescue LoadError + require 'sass' +end +require 'sass/exec' + +opts = Sass::Exec::SassScss.new(ARGV, :sass) +opts.parse! diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/bin/sass-convert b/path/ruby/2.6.0/gems/sass-3.7.4/bin/sass-convert new file mode 100755 index 00000000..b2762531 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/bin/sass-convert @@ -0,0 +1,12 @@ +#!/usr/bin/env ruby + +THIS_FILE = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__ +begin + require File.dirname(THIS_FILE) + '/../lib/sass' +rescue LoadError + require 'sass' +end +require 'sass/exec' + +opts = Sass::Exec::SassConvert.new(ARGV) +opts.parse! diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/bin/scss b/path/ruby/2.6.0/gems/sass-3.7.4/bin/scss new file mode 100755 index 00000000..ce3c4ad0 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/bin/scss @@ -0,0 +1,13 @@ +#!/usr/bin/env ruby +# The command line Sass parser. + +THIS_FILE = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__ +begin + require File.dirname(THIS_FILE) + '/../lib/sass' +rescue LoadError + require 'sass' +end +require 'sass/exec' + +opts = Sass::Exec::SassScss.new(ARGV, :scss) +opts.parse! diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/extra/sass-spec-ref.sh b/path/ruby/2.6.0/gems/sass-3.7.4/extra/sass-spec-ref.sh new file mode 100755 index 00000000..5e0f885d --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/extra/sass-spec-ref.sh @@ -0,0 +1,40 @@ +#!/bin/bash -e +# Copyright 2016 Google Inc. Use of this source code is governed by an MIT-style +# license that can be found in the LICENSE file or at +# https://opensource.org/licenses/MIT. + +# Echoes the sass-spec Git ref that should be checked out for the current Travis +# run. If we're running specs for a pull request which refers to a sass-spec +# pull request, we'll run against the latter rather than sass-spec master. + +default=master + +if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then + >&2 echo "TRAVIS_PULL_REQUEST: $TRAVIS_PULL_REQUEST." + >&2 echo "Ref: $default." + echo "$default" + exit 0 +fi + +>&2 echo "Fetching pull request $TRAVIS_PULL_REQUEST..." + +url=https://api.github.com/repos/sass/ruby-sass/pulls/$TRAVIS_PULL_REQUEST +if [ -z "$GITHUB_AUTH" ]; then + >&2 echo "Fetching pull request info without authentication" + JSON=$(curl -L -sS $url) +else + >&2 echo "Fetching pull request info as sassbot" + JSON=$(curl -u "sassbot:$GITHUB_AUTH" -L -sS $url) +fi +>&2 echo "$JSON" + +RE_SPEC_PR="sass\/sass-spec(#|\/pull\/)([0-9]+)" + +if [[ $JSON =~ $RE_SPEC_PR ]]; then + ref="pull/${BASH_REMATCH[2]}/head" + >&2 echo "Ref: $ref." + echo "$ref" +else + >&2 echo "Ref: $default." + echo "$default" +fi diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/extra/update_watch.rb b/path/ruby/2.6.0/gems/sass-3.7.4/extra/update_watch.rb new file mode 100644 index 00000000..dc90685b --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/extra/update_watch.rb @@ -0,0 +1,13 @@ +require 'rubygems' +require 'sinatra' +require 'json' +set :port, 3124 +set :environment, :production +enable :lock +Dir.chdir(File.dirname(__FILE__) + "/..") + +post "/" do + puts "Received payload!" + puts "Rev: #{`git name-rev HEAD`.strip}" + system %{rake handle_update --trace REF=#{JSON.parse(params["payload"])["ref"].inspect}} +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/init.rb b/path/ruby/2.6.0/gems/sass-3.7.4/init.rb new file mode 100644 index 00000000..5a3bceb4 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/init.rb @@ -0,0 +1,18 @@ +begin + require File.join(File.dirname(__FILE__), 'lib', 'sass') # From here +rescue LoadError + begin + require 'sass' # From gem + rescue LoadError => e + # gems:install may be run to install Haml with the skeleton plugin + # but not the gem itself installed. + # Don't die if this is the case. + raise e unless defined?(Rake) && + (Rake.application.top_level_tasks.include?('gems') || + Rake.application.top_level_tasks.include?('gems:install')) + end +end + +# Load Sass. +# Sass may be undefined if we're running gems:install. +require 'sass/plugin' if defined?(Sass) diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass.rb new file mode 100644 index 00000000..a569bc3b --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass.rb @@ -0,0 +1,102 @@ +dir = File.dirname(__FILE__) +$LOAD_PATH.unshift dir unless $LOAD_PATH.include?(dir) + +require 'sass/version' + +# The module that contains everything Sass-related: +# +# * {Sass::Engine} is the class used to render Sass/SCSS within Ruby code. +# * {Sass::Plugin} is interfaces with web frameworks (Rails and Merb in particular). +# * {Sass::SyntaxError} is raised when Sass encounters an error. +# * {Sass::CSS} handles conversion of CSS to Sass. +# +# Also see the {file:SASS_REFERENCE.md full Sass reference}. +module Sass + class << self + # @private + attr_accessor :tests_running + end + + # The global load paths for Sass files. This is meant for plugins and + # libraries to register the paths to their Sass stylesheets to that they may + # be `@imported`. This load path is used by every instance of {Sass::Engine}. + # They are lower-precedence than any load paths passed in via the + # {file:SASS_REFERENCE.md#load_paths-option `:load_paths` option}. + # + # If the `SASS_PATH` environment variable is set, + # the initial value of `load_paths` will be initialized based on that. + # The variable should be a colon-separated list of path names + # (semicolon-separated on Windows). + # + # Note that files on the global load path are never compiled to CSS + # themselves, even if they aren't partials. They exist only to be imported. + # + # @example + # Sass.load_paths << File.dirname(__FILE__ + '/sass') + # @return [Array] + def self.load_paths + @load_paths ||= if ENV['SASS_PATH'] + ENV['SASS_PATH'].split(Sass::Util.windows? ? ';' : ':') + else + [] + end + end + + # Compile a Sass or SCSS string to CSS. + # Defaults to SCSS. + # + # @param contents [String] The contents of the Sass file. + # @param options [{Symbol => Object}] An options hash; + # see {file:SASS_REFERENCE.md#Options the Sass options documentation} + # @raise [Sass::SyntaxError] if there's an error in the document + # @raise [Encoding::UndefinedConversionError] if the source encoding + # cannot be converted to UTF-8 + # @raise [ArgumentError] if the document uses an unknown encoding with `@charset` + def self.compile(contents, options = {}) + options[:syntax] ||= :scss + Engine.new(contents, options).to_css + end + + # Compile a file on disk to CSS. + # + # @raise [Sass::SyntaxError] if there's an error in the document + # @raise [Encoding::UndefinedConversionError] if the source encoding + # cannot be converted to UTF-8 + # @raise [ArgumentError] if the document uses an unknown encoding with `@charset` + # + # @overload compile_file(filename, options = {}) + # Return the compiled CSS rather than writing it to a file. + # + # @param filename [String] The path to the Sass, SCSS, or CSS file on disk. + # @param options [{Symbol => Object}] An options hash; + # see {file:SASS_REFERENCE.md#Options the Sass options documentation} + # @return [String] The compiled CSS. + # + # @overload compile_file(filename, css_filename, options = {}) + # Write the compiled CSS to a file. + # + # @param filename [String] The path to the Sass, SCSS, or CSS file on disk. + # @param options [{Symbol => Object}] An options hash; + # see {file:SASS_REFERENCE.md#Options the Sass options documentation} + # @param css_filename [String] The location to which to write the compiled CSS. + def self.compile_file(filename, *args) + options = args.last.is_a?(Hash) ? args.pop : {} + css_filename = args.shift + result = Sass::Engine.for_file(filename, options).render + if css_filename + options[:css_filename] ||= css_filename + open(css_filename, "w") {|css_file| css_file.write(result)} + nil + else + result + end + end +end + +require 'sass/logger' +require 'sass/util' + +require 'sass/engine' +require 'sass/plugin' if defined?(Merb::Plugins) +require 'sass/railtie' +require 'sass/features' diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/cache_stores.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/cache_stores.rb new file mode 100644 index 00000000..62259b32 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/cache_stores.rb @@ -0,0 +1,15 @@ +require 'stringio' + +module Sass + # Sass cache stores are in charge of storing cached information, + # especially parse trees for Sass documents. + # + # User-created importers must inherit from {CacheStores::Base}. + module CacheStores + end +end + +require 'sass/cache_stores/base' +require 'sass/cache_stores/filesystem' +require 'sass/cache_stores/memory' +require 'sass/cache_stores/chain' diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/cache_stores/base.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/cache_stores/base.rb new file mode 100644 index 00000000..e239666d --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/cache_stores/base.rb @@ -0,0 +1,88 @@ +module Sass + module CacheStores + # An abstract base class for backends for the Sass cache. + # Any key-value store can act as such a backend; + # it just needs to implement the + # \{#_store} and \{#_retrieve} methods. + # + # To use a cache store with Sass, + # use the {file:SASS_REFERENCE.md#cache_store-option `:cache_store` option}. + # + # @abstract + class Base + # Store cached contents for later retrieval + # Must be implemented by all CacheStore subclasses + # + # Note: cache contents contain binary data. + # + # @param key [String] The key to store the contents under + # @param version [String] The current sass version. + # Cached contents must not be retrieved across different versions of sass. + # @param sha [String] The sha of the sass source. + # Cached contents must not be retrieved if the sha has changed. + # @param contents [String] The contents to store. + def _store(key, version, sha, contents) + raise "#{self.class} must implement #_store." + end + + # Retrieved cached contents. + # Must be implemented by all subclasses. + # + # Note: if the key exists but the sha or version have changed, + # then the key may be deleted by the cache store, if it wants to do so. + # + # @param key [String] The key to retrieve + # @param version [String] The current sass version. + # Cached contents must not be retrieved across different versions of sass. + # @param sha [String] The sha of the sass source. + # Cached contents must not be retrieved if the sha has changed. + # @return [String] The contents that were previously stored. + # @return [NilClass] when the cache key is not found or the version or sha have changed. + def _retrieve(key, version, sha) + raise "#{self.class} must implement #_retrieve." + end + + # Store a {Sass::Tree::RootNode}. + # + # @param key [String] The key to store it under. + # @param sha [String] The checksum for the contents that are being stored. + # @param root [Object] The root node to cache. + def store(key, sha, root) + _store(key, Sass::VERSION, sha, Marshal.dump(root)) + rescue TypeError, LoadError => e + Sass::Util.sass_warn "Warning. Error encountered while saving cache #{path_to(key)}: #{e}" + nil + end + + # Retrieve a {Sass::Tree::RootNode}. + # + # @param key [String] The key the root element was stored under. + # @param sha [String] The checksum of the root element's content. + # @return [Object] The cached object. + def retrieve(key, sha) + contents = _retrieve(key, Sass::VERSION, sha) + Marshal.load(contents) if contents + rescue EOFError, TypeError, ArgumentError, LoadError => e + Sass::Util.sass_warn "Warning. Error encountered while reading cache #{path_to(key)}: #{e}" + nil + end + + # Return the key for the sass file. + # + # The `(sass_dirname, sass_basename)` pair + # should uniquely identify the Sass document, + # but otherwise there are no restrictions on their content. + # + # @param sass_dirname [String] + # The fully-expanded location of the Sass file. + # This corresponds to the directory name on a filesystem. + # @param sass_basename [String] The name of the Sass file that is being referenced. + # This corresponds to the basename on a filesystem. + def key(sass_dirname, sass_basename) + dir = Digest::SHA1.hexdigest(sass_dirname) + filename = "#{sass_basename}c" + "#{dir}/#{filename}" + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/cache_stores/chain.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/cache_stores/chain.rb new file mode 100644 index 00000000..914c1117 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/cache_stores/chain.rb @@ -0,0 +1,34 @@ +module Sass + module CacheStores + # A meta-cache that chains multiple caches together. + # Specifically: + # + # * All `#store`s are passed to all caches. + # * `#retrieve`s are passed to each cache until one has a hit. + # * When one cache has a hit, the value is `#store`d in all earlier caches. + class Chain < Base + # Create a new cache chaining the given caches. + # + # @param caches [Array] The caches to chain. + def initialize(*caches) + @caches = caches + end + + # @see Base#store + def store(key, sha, obj) + @caches.each {|c| c.store(key, sha, obj)} + end + + # @see Base#retrieve + def retrieve(key, sha) + @caches.each_with_index do |c, i| + obj = c.retrieve(key, sha) + next unless obj + @caches[0...i].each {|prev| prev.store(key, sha, obj)} + return obj + end + nil + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/cache_stores/filesystem.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/cache_stores/filesystem.rb new file mode 100644 index 00000000..140f5f1c --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/cache_stores/filesystem.rb @@ -0,0 +1,60 @@ +require 'fileutils' + +module Sass + module CacheStores + # A backend for the Sass cache using the filesystem. + class Filesystem < Base + # The directory where the cached files will be stored. + # + # @return [String] + attr_accessor :cache_location + + # @param cache_location [String] see \{#cache\_location} + def initialize(cache_location) + @cache_location = cache_location + end + + # @see Base#\_retrieve + def _retrieve(key, version, sha) + return unless File.readable?(path_to(key)) + begin + File.open(path_to(key), "rb") do |f| + if f.readline("\n").strip == version && f.readline("\n").strip == sha + return f.read + end + end + File.unlink path_to(key) + rescue Errno::ENOENT + # Already deleted. Race condition? + end + nil + rescue EOFError, TypeError, ArgumentError => e + Sass::Util.sass_warn "Warning. Error encountered while reading cache #{path_to(key)}: #{e}" + end + + # @see Base#\_store + def _store(key, version, sha, contents) + compiled_filename = path_to(key) + FileUtils.mkdir_p(File.dirname(compiled_filename)) + Sass::Util.atomic_create_and_write_file(compiled_filename) do |f| + f.puts(version) + f.puts(sha) + f.write(contents) + end + rescue Errno::EACCES + # pass + end + + private + + # Returns the path to a file for the given key. + # + # @param key [String] + # @return [String] The path to the cache file. + def path_to(key) + key = key.gsub(/[<>:\\|?*%]/) {|c| "%%%03d" % c.ord} + File.join(cache_location, key) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/cache_stores/memory.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/cache_stores/memory.rb new file mode 100644 index 00000000..ccf64bea --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/cache_stores/memory.rb @@ -0,0 +1,46 @@ +module Sass + module CacheStores + # A backend for the Sass cache using in-process memory. + class Memory < Base + # Since the {Memory} store is stored in the Sass tree's options hash, + # when the options get serialized as part of serializing the tree, + # you get crazy exponential growth in the size of the cached objects + # unless you don't dump the cache. + # + # @private + def _dump(depth) + "" + end + + # If we deserialize this class, just make a new empty one. + # + # @private + def self._load(repr) + Memory.new + end + + # Create a new, empty cache store. + def initialize + @contents = {} + end + + # @see Base#retrieve + def retrieve(key, sha) + return unless @contents.has_key?(key) + return unless @contents[key][:sha] == sha + obj = @contents[key][:obj] + obj.respond_to?(:deep_copy) ? obj.deep_copy : obj.dup + end + + # @see Base#store + def store(key, sha, obj) + @contents[key] = {:sha => sha, :obj => obj} + end + + # Destructively clear the cache. + def reset! + @contents = {} + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/cache_stores/null.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/cache_stores/null.rb new file mode 100644 index 00000000..f14f4c7e --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/cache_stores/null.rb @@ -0,0 +1,25 @@ +module Sass + module CacheStores + # Doesn't store anything, but records what things it should have stored. + # This doesn't currently have any use except for testing and debugging. + # + # @private + class Null < Base + def initialize + @keys = {} + end + + def _retrieve(key, version, sha) + nil + end + + def _store(key, version, sha, contents) + @keys[key] = true + end + + def was_set?(key) + @keys[key] + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/callbacks.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/callbacks.rb new file mode 100644 index 00000000..a33a5090 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/callbacks.rb @@ -0,0 +1,67 @@ +module Sass + # A lightweight infrastructure for defining and running callbacks. + # Callbacks are defined using \{#define\_callback\} at the class level, + # and called using `run_#{name}` at the instance level. + # + # Clients can add callbacks by calling the generated `on_#{name}` method, + # and passing in a block that's run when the callback is activated. + # + # @example Define a callback + # class Munger + # extend Sass::Callbacks + # define_callback :string_munged + # + # def munge(str) + # res = str.gsub(/[a-z]/, '\1\1') + # run_string_munged str, res + # res + # end + # end + # + # @example Use a callback + # m = Munger.new + # m.on_string_munged {|str, res| puts "#{str} was munged into #{res}!"} + # m.munge "bar" #=> bar was munged into bbaarr! + module Callbacks + # Automatically includes {InstanceMethods} + # when something extends this module. + # + # @param base [Module] + def self.extended(base) + base.send(:include, InstanceMethods) + end + + protected + + module InstanceMethods + # Removes all callbacks registered against this object. + def clear_callbacks! + @_sass_callbacks = {} + end + end + + # Define a callback with the given name. + # This will define an `on_#{name}` method + # that registers a block, + # and a `run_#{name}` method that runs that block + # (optionall with some arguments). + # + # @param name [Symbol] The name of the callback + # @return [void] + def define_callback(name) + class_eval < "p\n color: blue" + # Sass::CSS.new("p { color: blue }").render(:scss) #=> "p {\n color: blue; }" + class CSS + # @param template [String] The CSS stylesheet. + # This stylesheet can be encoded using any encoding + # that can be converted to Unicode. + # If the stylesheet contains an `@charset` declaration, + # that overrides the Ruby encoding + # (see {file:SASS_REFERENCE.md#Encodings the encoding documentation}) + # @option options :old [Boolean] (false) + # Whether or not to output old property syntax + # (`:color blue` as opposed to `color: blue`). + # This is only meaningful when generating Sass code, + # rather than SCSS. + # @option options :indent [String] (" ") + # The string to use for indenting each line. Defaults to two spaces. + def initialize(template, options = {}) + if template.is_a? IO + template = template.read + end + + @options = options.merge(:_convert => true) + # Backwards compatibility + @options[:old] = true if @options[:alternate] == false + @template = template + @checked_encoding = false + end + + # Converts the CSS template into Sass or SCSS code. + # + # @param fmt [Symbol] `:sass` or `:scss`, designating the format to return. + # @return [String] The resulting Sass or SCSS code + # @raise [Sass::SyntaxError] if there's an error parsing the CSS template + def render(fmt = :sass) + check_encoding! + build_tree.send("to_#{fmt}", @options).strip + "\n" + rescue Sass::SyntaxError => err + err.modify_backtrace(:filename => @options[:filename] || '(css)') + raise err + end + + # Returns the original encoding of the document. + # + # @return [Encoding, nil] + # @raise [Encoding::UndefinedConversionError] if the source encoding + # cannot be converted to UTF-8 + # @raise [ArgumentError] if the document uses an unknown encoding with `@charset` + def source_encoding + check_encoding! + @original_encoding + end + + private + + def check_encoding! + return if @checked_encoding + @checked_encoding = true + @template, @original_encoding = Sass::Util.check_sass_encoding(@template) + end + + # Parses the CSS template and applies various transformations + # + # @return [Tree::Node] The root node of the parsed tree + def build_tree + root = Sass::SCSS::CssParser.new(@template, @options[:filename], nil).parse + parse_selectors(root) + expand_commas(root) + nest_seqs(root) + parent_ref_rules(root) + flatten_rules(root) + bubble_subject(root) + fold_commas(root) + dump_selectors(root) + root + end + + # Parse all the selectors in the document and assign them to + # {Sass::Tree::RuleNode#parsed_rules}. + # + # @param root [Tree::Node] The parent node + def parse_selectors(root) + root.children.each do |child| + next parse_selectors(child) if child.is_a?(Tree::DirectiveNode) + next unless child.is_a?(Tree::RuleNode) + parser = Sass::SCSS::CssParser.new(child.rule.first, child.filename, nil, child.line) + child.parsed_rules = parser.parse_selector + end + end + + # Transform + # + # foo, bar, baz + # color: blue + # + # into + # + # foo + # color: blue + # bar + # color: blue + # baz + # color: blue + # + # @param root [Tree::Node] The parent node + def expand_commas(root) + root.children.map! do |child| + # child.parsed_rules.members.size > 1 iff the rule contains a comma + unless child.is_a?(Tree::RuleNode) && child.parsed_rules.members.size > 1 + expand_commas(child) if child.is_a?(Tree::DirectiveNode) + next child + end + child.parsed_rules.members.map do |seq| + node = Tree::RuleNode.new([]) + node.parsed_rules = make_cseq(seq) + node.children = child.children + node + end + end + root.children.flatten! + end + + # Make rules use nesting so that + # + # foo + # color: green + # foo bar + # color: red + # foo baz + # color: blue + # + # becomes + # + # foo + # color: green + # bar + # color: red + # baz + # color: blue + # + # @param root [Tree::Node] The parent node + def nest_seqs(root) + current_rule = nil + root.children.map! do |child| + unless child.is_a?(Tree::RuleNode) + nest_seqs(child) if child.is_a?(Tree::DirectiveNode) + next child + end + + seq = first_seq(child) + seq.members.reject! {|sseq| sseq == "\n"} + first, rest = seq.members.first, seq.members[1..-1] + + if current_rule.nil? || first_sseq(current_rule) != first + current_rule = Tree::RuleNode.new([]) + current_rule.parsed_rules = make_seq(first) + end + + if rest.empty? + current_rule.children += child.children + else + child.parsed_rules = make_seq(*rest) + current_rule << child + end + + current_rule + end + root.children.compact! + root.children.uniq! + + root.children.each {|v| nest_seqs(v)} + end + + # Make rules use parent refs so that + # + # foo + # color: green + # foo.bar + # color: blue + # + # becomes + # + # foo + # color: green + # &.bar + # color: blue + # + # @param root [Tree::Node] The parent node + def parent_ref_rules(root) + current_rule = nil + root.children.map! do |child| + unless child.is_a?(Tree::RuleNode) + parent_ref_rules(child) if child.is_a?(Tree::DirectiveNode) + next child + end + + sseq = first_sseq(child) + next child unless sseq.is_a?(Sass::Selector::SimpleSequence) + + firsts, rest = [sseq.members.first], sseq.members[1..-1] + firsts.push rest.shift if firsts.first.is_a?(Sass::Selector::Parent) + + last_simple_subject = rest.empty? && sseq.subject? + if current_rule.nil? || first_sseq(current_rule).members != firsts || + !!first_sseq(current_rule).subject? != !!last_simple_subject + current_rule = Tree::RuleNode.new([]) + current_rule.parsed_rules = make_sseq(last_simple_subject, *firsts) + end + + if rest.empty? + current_rule.children += child.children + else + rest.unshift Sass::Selector::Parent.new + child.parsed_rules = make_sseq(sseq.subject?, *rest) + current_rule << child + end + + current_rule + end + root.children.compact! + root.children.uniq! + + root.children.each {|v| parent_ref_rules(v)} + end + + # Flatten rules so that + # + # foo + # bar + # color: red + # + # becomes + # + # foo bar + # color: red + # + # and + # + # foo + # &.bar + # color: blue + # + # becomes + # + # foo.bar + # color: blue + # + # @param root [Tree::Node] The parent node + def flatten_rules(root) + root.children.each do |child| + case child + when Tree::RuleNode + flatten_rule(child) + when Tree::DirectiveNode + flatten_rules(child) + end + end + end + + # Flattens a single rule. + # + # @param rule [Tree::RuleNode] The candidate for flattening + # @see #flatten_rules + def flatten_rule(rule) + while rule.children.size == 1 && rule.children.first.is_a?(Tree::RuleNode) + child = rule.children.first + + if first_simple_sel(child).is_a?(Sass::Selector::Parent) + rule.parsed_rules = child.parsed_rules.resolve_parent_refs(rule.parsed_rules) + else + rule.parsed_rules = make_seq(*(first_seq(rule).members + first_seq(child).members)) + end + + rule.children = child.children + end + + flatten_rules(rule) + end + + def bubble_subject(root) + root.children.each do |child| + bubble_subject(child) if child.is_a?(Tree::RuleNode) || child.is_a?(Tree::DirectiveNode) + next unless child.is_a?(Tree::RuleNode) && !child.children.empty? + next unless child.children.all? do |c| + next unless c.is_a?(Tree::RuleNode) + first_simple_sel(c).is_a?(Sass::Selector::Parent) && first_sseq(c).subject? + end + first_sseq(child).subject = true + child.children.each {|c| first_sseq(c).subject = false} + end + end + + # Transform + # + # foo + # bar + # color: blue + # baz + # color: blue + # + # into + # + # foo + # bar, baz + # color: blue + # + # @param root [Tree::Node] The parent node + def fold_commas(root) + prev_rule = nil + root.children.map! do |child| + unless child.is_a?(Tree::RuleNode) + fold_commas(child) if child.is_a?(Tree::DirectiveNode) + next child + end + + if prev_rule && prev_rule.children.map {|c| c.to_sass} == child.children.map {|c| c.to_sass} + prev_rule.parsed_rules.members << first_seq(child) + next nil + end + + fold_commas(child) + prev_rule = child + child + end + root.children.compact! + end + + # Dump all the parsed {Sass::Tree::RuleNode} selectors to strings. + # + # @param root [Tree::Node] The parent node + def dump_selectors(root) + root.children.each do |child| + next dump_selectors(child) if child.is_a?(Tree::DirectiveNode) + next unless child.is_a?(Tree::RuleNode) + child.rule = [child.parsed_rules.to_s] + dump_selectors(child) + end + end + + # Create a {Sass::Selector::CommaSequence}. + # + # @param seqs [Array] + # @return [Sass::Selector::CommaSequence] + def make_cseq(*seqs) + Sass::Selector::CommaSequence.new(seqs) + end + + # Create a {Sass::Selector::CommaSequence} containing only a single + # {Sass::Selector::Sequence}. + # + # @param sseqs [Array] + # @return [Sass::Selector::CommaSequence] + def make_seq(*sseqs) + make_cseq(Sass::Selector::Sequence.new(sseqs)) + end + + # Create a {Sass::Selector::CommaSequence} containing only a single + # {Sass::Selector::Sequence} which in turn contains only a single + # {Sass::Selector::SimpleSequence}. + # + # @param subject [Boolean] Whether this is a subject selector + # @param sseqs [Array] + # @return [Sass::Selector::CommaSequence] + def make_sseq(subject, *sseqs) + make_seq(Sass::Selector::SimpleSequence.new(sseqs, subject)) + end + + # Return the first {Sass::Selector::Sequence} in a {Sass::Tree::RuleNode}. + # + # @param rule [Sass::Tree::RuleNode] + # @return [Sass::Selector::Sequence] + def first_seq(rule) + rule.parsed_rules.members.first + end + + # Return the first {Sass::Selector::SimpleSequence} in a + # {Sass::Tree::RuleNode}. + # + # @param rule [Sass::Tree::RuleNode] + # @return [Sass::Selector::SimpleSequence, String] + def first_sseq(rule) + first_seq(rule).members.first + end + + # Return the first {Sass::Selector::Simple} in a {Sass::Tree::RuleNode}, + # unless the rule begins with a combinator. + # + # @param rule [Sass::Tree::RuleNode] + # @return [Sass::Selector::Simple?] + def first_simple_sel(rule) + sseq = first_sseq(rule) + return unless sseq.is_a?(Sass::Selector::SimpleSequence) + sseq.members.first + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/deprecation.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/deprecation.rb new file mode 100644 index 00000000..16ccacab --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/deprecation.rb @@ -0,0 +1,55 @@ +module Sass + # A deprecation warning that should only be printed once for a given line in a + # given file. + # + # A global Deprecation instance should be created for each type of deprecation + # warning, and `warn` should be called each time a warning is needed. + class Deprecation + @@allow_double_warnings = false + + # Runs a block in which double deprecation warnings for the same location + # are allowed. + def self.allow_double_warnings + old_allow_double_warnings = @@allow_double_warnings + @@allow_double_warnings = true + yield + ensure + @@allow_double_warnings = old_allow_double_warnings + end + + def initialize + # A set of filename, line pairs for which warnings have been emitted. + @seen = Set.new + end + + # Prints `message` as a deprecation warning associated with `filename`, + # `line`, and optionally `column`. + # + # This ensures that only one message will be printed for each line of a + # given file. + # + # @overload warn(filename, line, message) + # @param filename [String, nil] + # @param line [Number] + # @param message [String] + # @overload warn(filename, line, column, message) + # @param filename [String, nil] + # @param line [Number] + # @param column [Number] + # @param message [String] + def warn(filename, line, column_or_message, message = nil) + return if !@@allow_double_warnings && @seen.add?([filename, line]).nil? + if message + column = column_or_message + else + message = column_or_message + end + + location = "line #{line}" + location << ", column #{column}" if column + location << " of #{filename}" if filename + + Sass::Util.sass_warn("DEPRECATION WARNING on #{location}:\n#{message}") + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/engine.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/engine.rb new file mode 100644 index 00000000..b777e8ad --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/engine.rb @@ -0,0 +1,1236 @@ +require 'set' +require 'digest/sha1' +require 'sass/cache_stores' +require 'sass/deprecation' +require 'sass/source/position' +require 'sass/source/range' +require 'sass/source/map' +require 'sass/tree/node' +require 'sass/tree/root_node' +require 'sass/tree/rule_node' +require 'sass/tree/comment_node' +require 'sass/tree/prop_node' +require 'sass/tree/directive_node' +require 'sass/tree/media_node' +require 'sass/tree/supports_node' +require 'sass/tree/css_import_node' +require 'sass/tree/variable_node' +require 'sass/tree/mixin_def_node' +require 'sass/tree/mixin_node' +require 'sass/tree/trace_node' +require 'sass/tree/content_node' +require 'sass/tree/function_node' +require 'sass/tree/return_node' +require 'sass/tree/extend_node' +require 'sass/tree/if_node' +require 'sass/tree/while_node' +require 'sass/tree/for_node' +require 'sass/tree/each_node' +require 'sass/tree/debug_node' +require 'sass/tree/warn_node' +require 'sass/tree/import_node' +require 'sass/tree/charset_node' +require 'sass/tree/at_root_node' +require 'sass/tree/keyframe_rule_node' +require 'sass/tree/error_node' +require 'sass/tree/visitors/base' +require 'sass/tree/visitors/perform' +require 'sass/tree/visitors/cssize' +require 'sass/tree/visitors/extend' +require 'sass/tree/visitors/convert' +require 'sass/tree/visitors/to_css' +require 'sass/tree/visitors/deep_copy' +require 'sass/tree/visitors/set_options' +require 'sass/tree/visitors/check_nesting' +require 'sass/selector' +require 'sass/environment' +require 'sass/script' +require 'sass/scss' +require 'sass/stack' +require 'sass/error' +require 'sass/importers' +require 'sass/shared' +require 'sass/media' +require 'sass/supports' + +module Sass + # A Sass mixin or function. + # + # `name`: `String` + # : The name of the mixin/function. + # + # `args`: `Array<(Script::Tree::Node, Script::Tree::Node)>` + # : The arguments for the mixin/function. + # Each element is a tuple containing the variable node of the argument + # and the parse tree for the default value of the argument. + # + # `splat`: `Script::Tree::Node?` + # : The variable node of the splat argument for this callable, or null. + # + # `environment`: {Sass::Environment} + # : The environment in which the mixin/function was defined. + # This is captured so that the mixin/function can have access + # to local variables defined in its scope. + # + # `tree`: `Array` + # : The parse tree for the mixin/function. + # + # `has_content`: `Boolean` + # : Whether the callable accepts a content block. + # + # `type`: `String` + # : The user-friendly name of the type of the callable. + # + # `origin`: `Symbol` + # : From whence comes the callable: `:stylesheet`, `:builtin`, `:css` + # A callable with an origin of `:stylesheet` was defined in the stylesheet itself. + # A callable with an origin of `:builtin` was defined in ruby. + # A callable (function) with an origin of `:css` returns a function call with arguments to CSS. + Callable = Struct.new(:name, :args, :splat, :environment, :tree, :has_content, :type, :origin) + + # This class handles the parsing and compilation of the Sass template. + # Example usage: + # + # template = File.read('stylesheets/sassy.sass') + # sass_engine = Sass::Engine.new(template) + # output = sass_engine.render + # puts output + class Engine + @@old_property_deprecation = Deprecation.new + + # A line of Sass code. + # + # `text`: `String` + # : The text in the line, without any whitespace at the beginning or end. + # + # `tabs`: `Integer` + # : The level of indentation of the line. + # + # `index`: `Integer` + # : The line number in the original document. + # + # `offset`: `Integer` + # : The number of bytes in on the line that the text begins. + # This ends up being the number of bytes of leading whitespace. + # + # `filename`: `String` + # : The name of the file in which this line appeared. + # + # `children`: `Array` + # : The lines nested below this one. + # + # `comment_tab_str`: `String?` + # : The prefix indentation for this comment, if it is a comment. + class Line < Struct.new(:text, :tabs, :index, :offset, :filename, :children, :comment_tab_str) + def comment? + text[0] == COMMENT_CHAR && (text[1] == SASS_COMMENT_CHAR || text[1] == CSS_COMMENT_CHAR) + end + end + + # The character that begins a CSS property. + PROPERTY_CHAR = ?: + + # The character that designates the beginning of a comment, + # either Sass or CSS. + COMMENT_CHAR = ?/ + + # The character that follows the general COMMENT_CHAR and designates a Sass comment, + # which is not output as a CSS comment. + SASS_COMMENT_CHAR = ?/ + + # The character that indicates that a comment allows interpolation + # and should be preserved even in `:compressed` mode. + SASS_LOUD_COMMENT_CHAR = ?! + + # The character that follows the general COMMENT_CHAR and designates a CSS comment, + # which is embedded in the CSS document. + CSS_COMMENT_CHAR = ?* + + # The character used to denote a compiler directive. + DIRECTIVE_CHAR = ?@ + + # Designates a non-parsed rule. + ESCAPE_CHAR = ?\\ + + # Designates block as mixin definition rather than CSS rules to output + MIXIN_DEFINITION_CHAR = ?= + + # Includes named mixin declared using MIXIN_DEFINITION_CHAR + MIXIN_INCLUDE_CHAR = ?+ + + # The regex that matches and extracts data from + # properties of the form `:name prop`. + PROPERTY_OLD = /^:([^\s=:"]+)\s*(?:\s+|$)(.*)/ + + # The default options for Sass::Engine. + # @api public + DEFAULT_OPTIONS = { + :style => :nested, + :load_paths => [], + :cache => true, + :cache_location => './.sass-cache', + :syntax => :sass, + :filesystem_importer => Sass::Importers::Filesystem + }.freeze + + # Converts a Sass options hash into a standard form, filling in + # default values and resolving aliases. + # + # @param options [{Symbol => Object}] The options hash; + # see {file:SASS_REFERENCE.md#Options the Sass options documentation} + # @return [{Symbol => Object}] The normalized options hash. + # @private + def self.normalize_options(options) + options = DEFAULT_OPTIONS.merge(options.reject {|_k, v| v.nil?}) + + # If the `:filename` option is passed in without an importer, + # assume it's using the default filesystem importer. + options[:importer] ||= options[:filesystem_importer].new(".") if options[:filename] + + # Tracks the original filename of the top-level Sass file + options[:original_filename] ||= options[:filename] + + options[:cache_store] ||= Sass::CacheStores::Chain.new( + Sass::CacheStores::Memory.new, Sass::CacheStores::Filesystem.new(options[:cache_location])) + # Support both, because the docs said one and the other actually worked + # for quite a long time. + options[:line_comments] ||= options[:line_numbers] + + options[:load_paths] = (options[:load_paths] + Sass.load_paths).map do |p| + next p unless p.is_a?(String) || (defined?(Pathname) && p.is_a?(Pathname)) + options[:filesystem_importer].new(p.to_s) + end + + # Remove any deprecated importers if the location is imported explicitly + options[:load_paths].reject! do |importer| + importer.is_a?(Sass::Importers::DeprecatedPath) && + options[:load_paths].find do |other_importer| + other_importer.is_a?(Sass::Importers::Filesystem) && + other_importer != importer && + other_importer.root == importer.root + end + end + + # Backwards compatibility + options[:property_syntax] ||= options[:attribute_syntax] + case options[:property_syntax] + when :alternate; options[:property_syntax] = :new + when :normal; options[:property_syntax] = :old + end + options[:sourcemap] = :auto if options[:sourcemap] == true + options[:sourcemap] = :none if options[:sourcemap] == false + + options + end + + # Returns the {Sass::Engine} for the given file. + # This is preferable to Sass::Engine.new when reading from a file + # because it properly sets up the Engine's metadata, + # enables parse-tree caching, + # and infers the syntax from the filename. + # + # @param filename [String] The path to the Sass or SCSS file + # @param options [{Symbol => Object}] The options hash; + # See {file:SASS_REFERENCE.md#Options the Sass options documentation}. + # @return [Sass::Engine] The Engine for the given Sass or SCSS file. + # @raise [Sass::SyntaxError] if there's an error in the document. + def self.for_file(filename, options) + had_syntax = options[:syntax] + + if had_syntax + # Use what was explicitly specified + elsif filename =~ /\.scss$/ + options.merge!(:syntax => :scss) + elsif filename =~ /\.sass$/ + options.merge!(:syntax => :sass) + end + + Sass::Engine.new(File.read(filename), options.merge(:filename => filename)) + end + + # The options for the Sass engine. + # See {file:SASS_REFERENCE.md#Options the Sass options documentation}. + # + # @return [{Symbol => Object}] + attr_reader :options + + # Creates a new Engine. Note that Engine should only be used directly + # when compiling in-memory Sass code. + # If you're compiling a single Sass file from the filesystem, + # use \{Sass::Engine.for\_file}. + # If you're compiling multiple files from the filesystem, + # use {Sass::Plugin}. + # + # @param template [String] The Sass template. + # This template can be encoded using any encoding + # that can be converted to Unicode. + # If the template contains an `@charset` declaration, + # that overrides the Ruby encoding + # (see {file:SASS_REFERENCE.md#Encodings the encoding documentation}) + # @param options [{Symbol => Object}] An options hash. + # See {file:SASS_REFERENCE.md#Options the Sass options documentation}. + # @see {Sass::Engine.for_file} + # @see {Sass::Plugin} + def initialize(template, options = {}) + @options = self.class.normalize_options(options) + @template = template + @checked_encoding = false + @filename = nil + @line = nil + end + + # Render the template to CSS. + # + # @return [String] The CSS + # @raise [Sass::SyntaxError] if there's an error in the document + # @raise [Encoding::UndefinedConversionError] if the source encoding + # cannot be converted to UTF-8 + # @raise [ArgumentError] if the document uses an unknown encoding with `@charset` + def render + return _to_tree.render unless @options[:quiet] + Sass::Util.silence_sass_warnings {_to_tree.render} + end + + # Render the template to CSS and return the source map. + # + # @param sourcemap_uri [String] The sourcemap URI to use in the + # `@sourceMappingURL` comment. If this is relative, it should be relative + # to the location of the CSS file. + # @return [(String, Sass::Source::Map)] The rendered CSS and the associated + # source map + # @raise [Sass::SyntaxError] if there's an error in the document, or if the + # public URL for this document couldn't be determined. + # @raise [Encoding::UndefinedConversionError] if the source encoding + # cannot be converted to UTF-8 + # @raise [ArgumentError] if the document uses an unknown encoding with `@charset` + def render_with_sourcemap(sourcemap_uri) + return _render_with_sourcemap(sourcemap_uri) unless @options[:quiet] + Sass::Util.silence_sass_warnings {_render_with_sourcemap(sourcemap_uri)} + end + + alias_method :to_css, :render + + # Parses the document into its parse tree. Memoized. + # + # @return [Sass::Tree::Node] The root of the parse tree. + # @raise [Sass::SyntaxError] if there's an error in the document + def to_tree + @tree ||= if @options[:quiet] + Sass::Util.silence_sass_warnings {_to_tree} + else + _to_tree + end + end + + # Returns the original encoding of the document. + # + # @return [Encoding, nil] + # @raise [Encoding::UndefinedConversionError] if the source encoding + # cannot be converted to UTF-8 + # @raise [ArgumentError] if the document uses an unknown encoding with `@charset` + def source_encoding + check_encoding! + @source_encoding + end + + # Gets a set of all the documents + # that are (transitive) dependencies of this document, + # not including the document itself. + # + # @return [[Sass::Engine]] The dependency documents. + def dependencies + _dependencies(Set.new, engines = Set.new) + Sass::Util.array_minus(engines, [self]) + end + + # Helper for \{#dependencies}. + # + # @private + def _dependencies(seen, engines) + key = [@options[:filename], @options[:importer]] + return if seen.include?(key) + seen << key + engines << self + to_tree.grep(Tree::ImportNode) do |n| + next if n.css_import? + n.imported_file._dependencies(seen, engines) + end + end + + private + + def _render_with_sourcemap(sourcemap_uri) + filename = @options[:filename] + importer = @options[:importer] + sourcemap_dir = @options[:sourcemap_filename] && + File.dirname(File.expand_path(@options[:sourcemap_filename])) + if filename.nil? + raise Sass::SyntaxError.new(< e + e.modify_backtrace(:filename => @options[:filename], :line => @line) + e.sass_template = @template + raise e + end + + def sassc_key + @options[:cache_store].key(*@options[:importer].key(@options[:filename], @options)) + end + + def check_encoding! + return if @checked_encoding + @checked_encoding = true + @template, @source_encoding = Sass::Util.check_sass_encoding(@template) + end + + def tabulate(string) + tab_str = nil + comment_tab_str = nil + first = true + lines = [] + string.scan(/^[^\n]*?$/).each_with_index do |line, index| + index += (@options[:line] || 1) + if line.strip.empty? + lines.last.text << "\n" if lines.last && lines.last.comment? + next + end + + line_tab_str = line[/^\s*/] + unless line_tab_str.empty? + if tab_str.nil? + comment_tab_str ||= line_tab_str + next if try_comment(line, lines.last, "", comment_tab_str, index) + comment_tab_str = nil + end + + tab_str ||= line_tab_str + + raise SyntaxError.new("Indenting at the beginning of the document is illegal.", + :line => index) if first + + raise SyntaxError.new("Indentation can't use both tabs and spaces.", + :line => index) if tab_str.include?(?\s) && tab_str.include?(?\t) + end + first &&= !tab_str.nil? + if tab_str.nil? + lines << Line.new(line.strip, 0, index, 0, @options[:filename], []) + next + end + + comment_tab_str ||= line_tab_str + if try_comment(line, lines.last, tab_str * lines.last.tabs, comment_tab_str, index) + next + else + comment_tab_str = nil + end + + line_tabs = line_tab_str.scan(tab_str).size + if tab_str * line_tabs != line_tab_str + message = < index) + end + + lines << Line.new(line.strip, line_tabs, index, line_tab_str.size, @options[:filename], []) + end + lines + end + + def try_comment(line, last, tab_str, comment_tab_str, index) + return unless last && last.comment? + # Nested comment stuff must be at least one whitespace char deeper + # than the normal indentation + return unless line =~ /^#{tab_str}\s/ + unless line =~ /^(?:#{comment_tab_str})(.*)$/ + raise SyntaxError.new(< index) +Inconsistent indentation: +previous line was indented by #{Sass::Shared.human_indentation comment_tab_str}, +but this line was indented by #{Sass::Shared.human_indentation line[/^\s*/]}. +MSG + end + + last.comment_tab_str ||= comment_tab_str + last.text << "\n" << line + true + end + + def tree(arr, i = 0) + return [], i if arr[i].nil? + + base = arr[i].tabs + nodes = [] + while (line = arr[i]) && line.tabs >= base + if line.tabs > base + nodes.last.children, i = tree(arr, i) + else + nodes << line + i += 1 + end + end + return nodes, i + end + + def build_tree(parent, line, root = false) + @line = line.index + @offset = line.offset + node_or_nodes = parse_line(parent, line, root) + + Array(node_or_nodes).each do |node| + # Node is a symbol if it's non-outputting, like a variable assignment + next unless node.is_a? Tree::Node + + node.line = line.index + node.filename = line.filename + + append_children(node, line.children, false) + end + + node_or_nodes + end + + def append_children(parent, children, root) + continued_rule = nil + continued_comment = nil + children.each do |line| + child = build_tree(parent, line, root) + + if child.is_a?(Tree::RuleNode) + if child.continued? && child.children.empty? + if continued_rule + continued_rule.add_rules child + else + continued_rule = child + end + next + elsif continued_rule + continued_rule.add_rules child + continued_rule.children = child.children + continued_rule, child = nil, continued_rule + end + elsif continued_rule + continued_rule = nil + end + + if child.is_a?(Tree::CommentNode) && child.type == :silent + if continued_comment && + child.line == continued_comment.line + + continued_comment.lines + 1 + continued_comment.value.last.sub!(%r{ \*/\Z}, '') + child.value.first.gsub!(%r{\A/\*}, ' *') + continued_comment.value += ["\n"] + child.value + next + end + + continued_comment = child + end + + check_for_no_children(child) + validate_and_append_child(parent, child, line, root) + end + + parent + end + + def validate_and_append_child(parent, child, line, root) + case child + when Array + child.each {|c| validate_and_append_child(parent, c, line, root)} + when Tree::Node + parent << child + end + end + + def check_for_no_children(node) + return unless node.is_a?(Tree::RuleNode) && node.children.empty? + Sass::Util.sass_warn(< @line) if name.nil? || value.nil? + + @@old_property_deprecation.warn(@options[:filename], @line, < @line + 1) + end + + parser = Sass::SCSS::Parser.new(value, + @options[:filename], @options[:importer], + @line, to_parser_offset(@offset)) + parsed_value = parser.parse_declaration_value + end_offset = start_offset + value.length + elsif value.strip.empty? + parsed_value = [Sass::Script::Tree::Literal.new(Sass::Script::Value::String.new(""))] + end_offset = start_offset + else + expr = parse_script(value, :offset => to_parser_offset(start_offset)) + end_offset = expr.source_range.end_pos.offset - 1 + parsed_value = [expr] + end + node = Tree::PropNode.new(parse_interp(name), parsed_value, prop) + node.value_source_range = Sass::Source::Range.new( + Sass::Source::Position.new(line.index, to_parser_offset(start_offset)), + Sass::Source::Position.new(line.index, to_parser_offset(end_offset)), + @options[:filename], @options[:importer]) + if !node.custom_property? && value.strip.empty? && line.children.empty? + raise SyntaxError.new( + "Invalid property: \"#{node.declaration}\" (no value)." + + node.pseudo_class_selector_message) + end + + node + end + + def parse_variable(line) + name, value, flags = line.text.scan(Script::MATCH)[0] + raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath variable declarations.", + :line => @line + 1) unless line.children.empty? + raise SyntaxError.new("Invalid variable: \"#{line.text}\".", + :line => @line) unless name && value + flags = flags ? flags.split(/\s+/) : [] + if (invalid_flag = flags.find {|f| f != '!default' && f != '!global'}) + raise SyntaxError.new("Invalid flag \"#{invalid_flag}\".", :line => @line) + end + + # This workaround is needed for the case when the variable value is part of the identifier, + # otherwise we end up with the offset equal to the value index inside the name: + # $red_color: red; + var_lhs_length = 1 + name.length # 1 stands for '$' + index = line.text.index(value, line.offset + var_lhs_length) || 0 + expr = parse_script(value, :offset => to_parser_offset(line.offset + index)) + + Tree::VariableNode.new(name, expr, flags.include?('!default'), flags.include?('!global')) + end + + def parse_comment(line) + if line.text[1] == CSS_COMMENT_CHAR || line.text[1] == SASS_COMMENT_CHAR + silent = line.text[1] == SASS_COMMENT_CHAR + loud = !silent && line.text[2] == SASS_LOUD_COMMENT_CHAR + if silent + value = [line.text] + else + value = self.class.parse_interp( + line.text, line.index, to_parser_offset(line.offset), :filename => @filename) + end + value = Sass::Util.with_extracted_values(value) do |str| + str = str.gsub(/^#{line.comment_tab_str}/m, '')[2..-1] # get rid of // or /* + format_comment_text(str, silent) + end + type = if silent + :silent + elsif loud + :loud + else + :normal + end + comment = Tree::CommentNode.new(value, type) + comment.line = line.index + text = line.text.rstrip + if text.include?("\n") + end_offset = text.length - text.rindex("\n") + else + end_offset = to_parser_offset(line.offset + text.length) + end + comment.source_range = Sass::Source::Range.new( + Sass::Source::Position.new(@line, to_parser_offset(line.offset)), + Sass::Source::Position.new(@line + text.count("\n"), end_offset), + @options[:filename]) + comment + else + Tree::RuleNode.new(parse_interp(line.text), full_line_range(line)) + end + end + + DIRECTIVES = Set[:mixin, :include, :function, :return, :debug, :warn, :for, + :each, :while, :if, :else, :extend, :import, :media, :charset, :content, + :at_root, :error] + + def parse_directive(parent, line, root) + directive, whitespace, value = line.text[1..-1].split(/(\s+)/, 2) + raise SyntaxError.new("Invalid directive: '@'.") unless directive + offset = directive.size + whitespace.size + 1 if whitespace + + directive_name = directive.tr('-', '_').to_sym + if DIRECTIVES.include?(directive_name) + return send("parse_#{directive_name}_directive", parent, line, root, value, offset) + end + + unprefixed_directive = directive.gsub(/^-[a-z0-9]+-/i, '') + if unprefixed_directive == 'supports' + parser = Sass::SCSS::Parser.new(value, @options[:filename], @line) + return Tree::SupportsNode.new(directive, parser.parse_supports_condition) + end + + Tree::DirectiveNode.new( + value.nil? ? ["@#{directive}"] : ["@#{directive} "] + parse_interp(value, offset)) + end + + def parse_while_directive(parent, line, root, value, offset) + raise SyntaxError.new("Invalid while directive '@while': expected expression.") unless value + Tree::WhileNode.new(parse_script(value, :offset => offset)) + end + + def parse_if_directive(parent, line, root, value, offset) + raise SyntaxError.new("Invalid if directive '@if': expected expression.") unless value + Tree::IfNode.new(parse_script(value, :offset => offset)) + end + + def parse_debug_directive(parent, line, root, value, offset) + raise SyntaxError.new("Invalid debug directive '@debug': expected expression.") unless value + raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath debug directives.", + :line => @line + 1) unless line.children.empty? + offset = line.offset + line.text.index(value).to_i + Tree::DebugNode.new(parse_script(value, :offset => offset)) + end + + def parse_error_directive(parent, line, root, value, offset) + raise SyntaxError.new("Invalid error directive '@error': expected expression.") unless value + raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath error directives.", + :line => @line + 1) unless line.children.empty? + offset = line.offset + line.text.index(value).to_i + Tree::ErrorNode.new(parse_script(value, :offset => offset)) + end + + def parse_extend_directive(parent, line, root, value, offset) + raise SyntaxError.new("Invalid extend directive '@extend': expected expression.") unless value + raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath extend directives.", + :line => @line + 1) unless line.children.empty? + optional = !!value.gsub!(/\s+#{Sass::SCSS::RX::OPTIONAL}$/, '') + offset = line.offset + line.text.index(value).to_i + interp_parsed = parse_interp(value, offset) + selector_range = Sass::Source::Range.new( + Sass::Source::Position.new(@line, to_parser_offset(offset)), + Sass::Source::Position.new(@line, to_parser_offset(line.offset) + line.text.length), + @options[:filename], @options[:importer] + ) + Tree::ExtendNode.new(interp_parsed, optional, selector_range) + end + + def parse_warn_directive(parent, line, root, value, offset) + raise SyntaxError.new("Invalid warn directive '@warn': expected expression.") unless value + raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath warn directives.", + :line => @line + 1) unless line.children.empty? + offset = line.offset + line.text.index(value).to_i + Tree::WarnNode.new(parse_script(value, :offset => offset)) + end + + def parse_return_directive(parent, line, root, value, offset) + raise SyntaxError.new("Invalid @return: expected expression.") unless value + raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath return directives.", + :line => @line + 1) unless line.children.empty? + offset = line.offset + line.text.index(value).to_i + Tree::ReturnNode.new(parse_script(value, :offset => offset)) + end + + def parse_charset_directive(parent, line, root, value, offset) + name = value && value[/\A(["'])(.*)\1\Z/, 2] # " + raise SyntaxError.new("Invalid charset directive '@charset': expected string.") unless name + raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath charset directives.", + :line => @line + 1) unless line.children.empty? + Tree::CharsetNode.new(name) + end + + def parse_media_directive(parent, line, root, value, offset) + parser = Sass::SCSS::Parser.new(value, + @options[:filename], @options[:importer], + @line, to_parser_offset(@offset)) + offset = line.offset + line.text.index('media').to_i - 1 + parsed_media_query_list = parser.parse_media_query_list.to_a + node = Tree::MediaNode.new(parsed_media_query_list) + node.source_range = Sass::Source::Range.new( + Sass::Source::Position.new(@line, to_parser_offset(offset)), + Sass::Source::Position.new(@line, to_parser_offset(line.offset) + line.text.length), + @options[:filename], @options[:importer]) + node + end + + def parse_at_root_directive(parent, line, root, value, offset) + return Sass::Tree::AtRootNode.new unless value + + if value.start_with?('(') + parser = Sass::SCSS::Parser.new(value, + @options[:filename], @options[:importer], + @line, to_parser_offset(@offset)) + offset = line.offset + line.text.index('at-root').to_i - 1 + return Tree::AtRootNode.new(parser.parse_at_root_query) + end + + at_root_node = Tree::AtRootNode.new + parsed = parse_interp(value, offset) + rule_node = Tree::RuleNode.new(parsed, full_line_range(line)) + + # The caller expects to automatically add children to the returned node + # and we want it to add children to the rule node instead, so we + # manually handle the wiring here and return nil so the caller doesn't + # duplicate our efforts. + append_children(rule_node, line.children, false) + at_root_node << rule_node + parent << at_root_node + nil + end + + def parse_for_directive(parent, line, root, value, offset) + var, from_expr, to_name, to_expr = + value.scan(/^([^\s]+)\s+from\s+(.+)\s+(to|through)\s+(.+)$/).first + + if var.nil? # scan failed, try to figure out why for error message + if value !~ /^[^\s]+/ + expected = "variable name" + elsif value !~ /^[^\s]+\s+from\s+.+/ + expected = "'from '" + else + expected = "'to ' or 'through '" + end + raise SyntaxError.new("Invalid for directive '@for #{value}': expected #{expected}.") + end + raise SyntaxError.new("Invalid variable \"#{var}\".") unless var =~ Script::VALIDATE + + var = var[1..-1] + parsed_from = parse_script(from_expr, :offset => line.offset + line.text.index(from_expr)) + parsed_to = parse_script(to_expr, :offset => line.offset + line.text.index(to_expr)) + Tree::ForNode.new(var, parsed_from, parsed_to, to_name == 'to') + end + + def parse_each_directive(parent, line, root, value, offset) + vars, list_expr = value.scan(/^([^\s]+(?:\s*,\s*[^\s]+)*)\s+in\s+(.+)$/).first + + if vars.nil? # scan failed, try to figure out why for error message + if value !~ /^[^\s]+/ + expected = "variable name" + elsif value !~ /^[^\s]+(?:\s*,\s*[^\s]+)*[^\s]+\s+from\s+.+/ + expected = "'in '" + end + raise SyntaxError.new("Invalid each directive '@each #{value}': expected #{expected}.") + end + + vars = vars.split(',').map do |var| + var.strip! + raise SyntaxError.new("Invalid variable \"#{var}\".") unless var =~ Script::VALIDATE + var[1..-1] + end + + parsed_list = parse_script(list_expr, :offset => line.offset + line.text.index(list_expr)) + Tree::EachNode.new(vars, parsed_list) + end + + def parse_else_directive(parent, line, root, value, offset) + previous = parent.children.last + raise SyntaxError.new("@else must come after @if.") unless previous.is_a?(Tree::IfNode) + + if value + if value !~ /^if\s+(.+)/ + raise SyntaxError.new("Invalid else directive '@else #{value}': expected 'if '.") + end + expr = parse_script($1, :offset => line.offset + line.text.index($1)) + end + + node = Tree::IfNode.new(expr) + append_children(node, line.children, false) + previous.add_else node + nil + end + + def parse_import_directive(parent, line, root, value, offset) + raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath import directives.", + :line => @line + 1) unless line.children.empty? + + scanner = Sass::Util::MultibyteStringScanner.new(value) + values = [] + + loop do + unless (node = parse_import_arg(scanner, offset + scanner.pos)) + raise SyntaxError.new( + "Invalid @import: expected file to import, was #{scanner.rest.inspect}", + :line => @line) + end + values << node + break unless scanner.scan(/,\s*/) + end + + if scanner.scan(/;/) + raise SyntaxError.new("Invalid @import: expected end of line, was \";\".", + :line => @line) + end + + values + end + + def parse_import_arg(scanner, offset) + return if scanner.eos? + + if scanner.match?(/url\(/i) + script_parser = Sass::Script::Parser.new(scanner, @line, to_parser_offset(offset), @options) + str = script_parser.parse_string + + if scanner.eos? + end_pos = str.source_range.end_pos + node = Tree::CssImportNode.new(str) + else + supports_parser = Sass::SCSS::Parser.new(scanner, + @options[:filename], @options[:importer], + @line, str.source_range.end_pos.offset) + supports_condition = supports_parser.parse_supports_clause + + if scanner.eos? + node = Tree::CssImportNode.new(str, [], supports_condition) + else + media_parser = Sass::SCSS::Parser.new(scanner, + @options[:filename], @options[:importer], + @line, str.source_range.end_pos.offset) + media = media_parser.parse_media_query_list + end_pos = Sass::Source::Position.new(@line, media_parser.offset + 1) + node = Tree::CssImportNode.new(str, media.to_a, supports_condition) + end + end + + node.source_range = Sass::Source::Range.new( + str.source_range.start_pos, end_pos, + @options[:filename], @options[:importer]) + return node + end + + unless (quoted_val = scanner.scan(Sass::SCSS::RX::STRING)) + scanned = scanner.scan(/[^,;]+/) + node = Tree::ImportNode.new(scanned) + start_parser_offset = to_parser_offset(offset) + node.source_range = Sass::Source::Range.new( + Sass::Source::Position.new(@line, start_parser_offset), + Sass::Source::Position.new(@line, start_parser_offset + scanned.length), + @options[:filename], @options[:importer]) + return node + end + + start_offset = offset + offset += scanner.matched.length + val = Sass::Script::Value::String.value(scanner[1] || scanner[2]) + scanned = scanner.scan(/\s*/) + if !scanner.match?(/[,;]|$/) + offset += scanned.length if scanned + media_parser = Sass::SCSS::Parser.new(scanner, + @options[:filename], @options[:importer], @line, offset) + media = media_parser.parse_media_query_list + node = Tree::CssImportNode.new(quoted_val, media.to_a) + node.source_range = Sass::Source::Range.new( + Sass::Source::Position.new(@line, to_parser_offset(start_offset)), + Sass::Source::Position.new(@line, media_parser.offset), + @options[:filename], @options[:importer]) + elsif val =~ %r{^(https?:)?//} + node = Tree::CssImportNode.new(quoted_val) + node.source_range = Sass::Source::Range.new( + Sass::Source::Position.new(@line, to_parser_offset(start_offset)), + Sass::Source::Position.new(@line, to_parser_offset(offset)), + @options[:filename], @options[:importer]) + else + node = Tree::ImportNode.new(val) + node.source_range = Sass::Source::Range.new( + Sass::Source::Position.new(@line, to_parser_offset(start_offset)), + Sass::Source::Position.new(@line, to_parser_offset(offset)), + @options[:filename], @options[:importer]) + end + node + end + + def parse_mixin_directive(parent, line, root, value, offset) + parse_mixin_definition(line) + end + + MIXIN_DEF_RE = /^(?:=|@mixin)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/ + def parse_mixin_definition(line) + name, arg_string = line.text.scan(MIXIN_DEF_RE).first + raise SyntaxError.new("Invalid mixin \"#{line.text[1..-1]}\".") if name.nil? + + offset = line.offset + line.text.size - arg_string.size + args, splat = Script::Parser.new(arg_string.strip, @line, to_parser_offset(offset), @options). + parse_mixin_definition_arglist + Tree::MixinDefNode.new(name, args, splat) + end + + CONTENT_RE = /^@content\s*(.+)?$/ + def parse_content_directive(parent, line, root, value, offset) + trailing = line.text.scan(CONTENT_RE).first.first + unless trailing.nil? + raise SyntaxError.new( + "Invalid content directive. Trailing characters found: \"#{trailing}\".") + end + raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath @content directives.", + :line => line.index + 1) unless line.children.empty? + Tree::ContentNode.new + end + + def parse_include_directive(parent, line, root, value, offset) + parse_mixin_include(line, root) + end + + MIXIN_INCLUDE_RE = /^(?:\+|@include)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/ + def parse_mixin_include(line, root) + name, arg_string = line.text.scan(MIXIN_INCLUDE_RE).first + raise SyntaxError.new("Invalid mixin include \"#{line.text}\".") if name.nil? + + offset = line.offset + line.text.size - arg_string.size + args, keywords, splat, kwarg_splat = + Script::Parser.new(arg_string.strip, @line, to_parser_offset(offset), @options). + parse_mixin_include_arglist + Tree::MixinNode.new(name, args, keywords, splat, kwarg_splat) + end + + FUNCTION_RE = /^@function\s*(#{Sass::SCSS::RX::IDENT})(.*)$/ + def parse_function_directive(parent, line, root, value, offset) + name, arg_string = line.text.scan(FUNCTION_RE).first + raise SyntaxError.new("Invalid function definition \"#{line.text}\".") if name.nil? + + offset = line.offset + line.text.size - arg_string.size + args, splat = Script::Parser.new(arg_string.strip, @line, to_parser_offset(offset), @options). + parse_function_definition_arglist + Tree::FunctionNode.new(name, args, splat) + end + + def parse_script(script, options = {}) + line = options[:line] || @line + offset = options[:offset] || @offset + 1 + Script.parse(script, line, offset, @options) + end + + def format_comment_text(text, silent) + content = text.split("\n") + + if content.first && content.first.strip.empty? + removed_first = true + content.shift + end + + return "/* */" if content.empty? + content.last.gsub!(%r{ ?\*/ *$}, '') + first = content.shift unless removed_first + content.map! {|l| l.gsub!(/^\*( ?)/, '\1') || (l.empty? ? "" : " ") + l} + content.unshift first unless removed_first + if silent + "/*" + content.join("\n *") + " */" + else + # The #gsub fixes the case of a trailing */ + "/*" + content.join("\n *").gsub(/ \*\Z/, '') + " */" + end + end + + def parse_interp(text, offset = 0) + self.class.parse_interp(text, @line, offset, :filename => @filename) + end + + # Parser tracks 1-based line and offset, so our offset should be converted. + def to_parser_offset(offset) + offset + 1 + end + + def full_line_range(line) + Sass::Source::Range.new( + Sass::Source::Position.new(@line, to_parser_offset(line.offset)), + Sass::Source::Position.new(@line, to_parser_offset(line.offset) + line.text.length), + @options[:filename], @options[:importer]) + end + + # It's important that this have strings (at least) + # at the beginning, the end, and between each Script::Tree::Node. + # + # @private + def self.parse_interp(text, line, offset, options) + res = [] + rest = Sass::Shared.handle_interpolation text do |scan| + escapes = scan[2].size + res << scan.matched[0...-2 - escapes] + if escapes.odd? + res << "\\" * (escapes - 1) << '#{' + else + res << "\\" * [0, escapes - 1].max + if scan[1].include?("\n") + line += scan[1].count("\n") + offset = scan.matched_size - scan[1].rindex("\n") + else + offset += scan.matched_size + end + node = Script::Parser.new(scan, line, offset, options).parse_interpolated + offset = node.source_range.end_pos.offset + res << node + end + end + res << rest + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/environment.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/environment.rb new file mode 100644 index 00000000..04502a88 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/environment.rb @@ -0,0 +1,236 @@ +require 'set' + +module Sass + # The abstract base class for lexical environments for SassScript. + class BaseEnvironment + class << self + # Note: when updating this, + # update sass/yard/inherited_hash.rb as well. + def inherited_hash_accessor(name) + inherited_hash_reader(name) + inherited_hash_writer(name) + end + + def inherited_hash_reader(name) + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{name}(name) + _#{name}(name.tr('_', '-')) + end + + def _#{name}(name) + (@#{name}s && @#{name}s[name]) || @parent && @parent._#{name}(name) + end + protected :_#{name} + + def is_#{name}_global?(name) + return !@parent if @#{name}s && @#{name}s.has_key?(name) + @parent && @parent.is_#{name}_global?(name) + end + RUBY + end + + def inherited_hash_writer(name) + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def set_#{name}(name, value) + name = name.tr('_', '-') + @#{name}s[name] = value unless try_set_#{name}(name, value) + end + + def try_set_#{name}(name, value) + @#{name}s ||= {} + if @#{name}s.include?(name) + @#{name}s[name] = value + true + elsif @parent && !@parent.global? + @parent.try_set_#{name}(name, value) + else + false + end + end + protected :try_set_#{name} + + def set_local_#{name}(name, value) + @#{name}s ||= {} + @#{name}s[name.tr('_', '-')] = value + end + + def set_global_#{name}(name, value) + global_env.set_#{name}(name, value) + end + RUBY + end + end + + # The options passed to the Sass Engine. + attr_reader :options + + attr_writer :caller + attr_writer :content + attr_writer :selector + + # variable + # Script::Value + inherited_hash_reader :var + + # mixin + # Sass::Callable + inherited_hash_reader :mixin + + # function + # Sass::Callable + inherited_hash_reader :function + + # @param options [{Symbol => Object}] The options hash. See + # {file:SASS_REFERENCE.md#Options the Sass options documentation}. + # @param parent [Environment] See \{#parent} + def initialize(parent = nil, options = nil) + @parent = parent + @options = options || (parent && parent.options) || {} + @stack = @parent.nil? ? Sass::Stack.new : nil + @caller = nil + @content = nil + @filename = nil + @functions = nil + @mixins = nil + @selector = nil + @vars = nil + end + + # Returns whether this is the global environment. + # + # @return [Boolean] + def global? + @parent.nil? + end + + # The environment of the caller of this environment's mixin or function. + # @return {Environment?} + def caller + @caller || (@parent && @parent.caller) + end + + # The content passed to this environment. This is naturally only set + # for mixin body environments with content passed in. + # + # @return {[Array, Environment]?} The content nodes and + # the lexical environment of the content block. + def content + @content || (@parent && @parent.content) + end + + # The selector for the current CSS rule, or nil if there is no + # current CSS rule. + # + # @return [Selector::CommaSequence?] The current selector, with any + # nesting fully resolved. + def selector + @selector || (@caller && @caller.selector) || (@parent && @parent.selector) + end + + # The top-level Environment object. + # + # @return [Environment] + def global_env + @global_env ||= global? ? self : @parent.global_env + end + + # The import/mixin stack. + # + # @return [Sass::Stack] + def stack + @stack || global_env.stack + end + end + + # The lexical environment for SassScript. + # This keeps track of variable, mixin, and function definitions. + # + # A new environment is created for each level of Sass nesting. + # This allows variables to be lexically scoped. + # The new environment refers to the environment in the upper scope, + # so it has access to variables defined in enclosing scopes, + # but new variables are defined locally. + # + # Environment also keeps track of the {Engine} options + # so that they can be made available to {Sass::Script::Functions}. + class Environment < BaseEnvironment + # The enclosing environment, + # or nil if this is the global environment. + # + # @return [Environment] + attr_reader :parent + + # variable + # Script::Value + inherited_hash_writer :var + + # mixin + # Sass::Callable + inherited_hash_writer :mixin + + # function + # Sass::Callable + inherited_hash_writer :function + end + + # A read-only wrapper for a lexical environment for SassScript. + class ReadOnlyEnvironment < BaseEnvironment + def initialize(parent = nil, options = nil) + super + @content_cached = nil + end + # The read-only environment of the caller of this environment's mixin or function. + # + # @see BaseEnvironment#caller + # @return {ReadOnlyEnvironment} + def caller + return @caller if @caller + env = super + @caller ||= env.is_a?(ReadOnlyEnvironment) ? env : ReadOnlyEnvironment.new(env, env.options) + end + + # The content passed to this environment. If the content's environment isn't already + # read-only, it's made read-only. + # + # @see BaseEnvironment#content + # + # @return {[Array, ReadOnlyEnvironment]?} The content nodes and + # the lexical environment of the content block. + # Returns `nil` when there is no content in this environment. + def content + # Return the cached content from a previous invocation if any + return @content if @content_cached + # get the content with a read-write environment from the superclass + read_write_content = super + if read_write_content + tree, env = read_write_content + # make the content's environment read-only + if env && !env.is_a?(ReadOnlyEnvironment) + env = ReadOnlyEnvironment.new(env, env.options) + end + @content_cached = true + @content = [tree, env] + else + @content_cached = true + @content = nil + end + end + end + + # An environment that can write to in-scope global variables, but doesn't + # create new variables in the global scope. Useful for top-level control + # directives. + class SemiGlobalEnvironment < Environment + def try_set_var(name, value) + @vars ||= {} + if @vars.include?(name) + @vars[name] = value + true + elsif @parent + @parent.try_set_var(name, value) + else + false + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/error.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/error.rb new file mode 100644 index 00000000..7ee74603 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/error.rb @@ -0,0 +1,198 @@ +module Sass + # An exception class that keeps track of + # the line of the Sass template it was raised on + # and the Sass file that was being parsed (if applicable). + # + # All Sass errors are raised as {Sass::SyntaxError}s. + # + # When dealing with SyntaxErrors, + # it's important to provide filename and line number information. + # This will be used in various error reports to users, including backtraces; + # see \{#sass\_backtrace} for details. + # + # Some of this information is usually provided as part of the constructor. + # New backtrace entries can be added with \{#add\_backtrace}, + # which is called when an exception is raised between files (e.g. with `@import`). + # + # Often, a chunk of code will all have similar backtrace information - + # the same filename or even line. + # It may also be useful to have a default line number set. + # In those situations, the default values can be used + # by omitting the information on the original exception, + # and then calling \{#modify\_backtrace} in a wrapper `rescue`. + # When doing this, be sure that all exceptions ultimately end up + # with the information filled in. + class SyntaxError < StandardError + # The backtrace of the error within Sass files. + # This is an array of hashes containing information for a single entry. + # The hashes have the following keys: + # + # `:filename` + # : The name of the file in which the exception was raised, + # or `nil` if no filename is available. + # + # `:mixin` + # : The name of the mixin in which the exception was raised, + # or `nil` if it wasn't raised in a mixin. + # + # `:line` + # : The line of the file on which the error occurred. Never nil. + # + # This information is also included in standard backtrace format + # in the output of \{#backtrace}. + # + # @return [Aray<{Symbol => Object>}] + attr_accessor :sass_backtrace + + # The text of the template where this error was raised. + # + # @return [String] + attr_accessor :sass_template + + # @param msg [String] The error message + # @param attrs [{Symbol => Object}] The information in the backtrace entry. + # See \{#sass\_backtrace} + def initialize(msg, attrs = {}) + @message = msg + @sass_backtrace = [] + add_backtrace(attrs) + end + + # The name of the file in which the exception was raised. + # This could be `nil` if no filename is available. + # + # @return [String, nil] + def sass_filename + sass_backtrace.first[:filename] + end + + # The name of the mixin in which the error occurred. + # This could be `nil` if the error occurred outside a mixin. + # + # @return [String] + def sass_mixin + sass_backtrace.first[:mixin] + end + + # The line of the Sass template on which the error occurred. + # + # @return [Integer] + def sass_line + sass_backtrace.first[:line] + end + + # Adds an entry to the exception's Sass backtrace. + # + # @param attrs [{Symbol => Object}] The information in the backtrace entry. + # See \{#sass\_backtrace} + def add_backtrace(attrs) + sass_backtrace << attrs.reject {|_k, v| v.nil?} + end + + # Modify the top Sass backtrace entries + # (that is, the most deeply nested ones) + # to have the given attributes. + # + # Specifically, this goes through the backtrace entries + # from most deeply nested to least, + # setting the given attributes for each entry. + # If an entry already has one of the given attributes set, + # the pre-existing attribute takes precedence + # and is not used for less deeply-nested entries + # (even if they don't have that attribute set). + # + # @param attrs [{Symbol => Object}] The information to add to the backtrace entry. + # See \{#sass\_backtrace} + def modify_backtrace(attrs) + attrs = attrs.reject {|_k, v| v.nil?} + # Move backwards through the backtrace + (0...sass_backtrace.size).to_a.reverse_each do |i| + entry = sass_backtrace[i] + sass_backtrace[i] = attrs.merge(entry) + attrs.reject! {|k, _v| entry.include?(k)} + break if attrs.empty? + end + end + + # @return [String] The error message + def to_s + @message + end + + # Returns the standard exception backtrace, + # including the Sass backtrace. + # + # @return [Array] + def backtrace + return nil if super.nil? + return super if sass_backtrace.all? {|h| h.empty?} + sass_backtrace.map do |h| + "#{h[:filename] || '(sass)'}:#{h[:line]}" + + (h[:mixin] ? ":in `#{h[:mixin]}'" : "") + end + super + end + + # Returns a string representation of the Sass backtrace. + # + # @param default_filename [String] The filename to use for unknown files + # @see #sass_backtrace + # @return [String] + def sass_backtrace_str(default_filename = "an unknown file") + lines = message.split("\n") + msg = lines[0] + lines[1..-1]. + map {|l| "\n" + (" " * "Error: ".size) + l}.join + "Error: #{msg}" + + sass_backtrace.each_with_index.map do |entry, i| + "\n #{i == 0 ? 'on' : 'from'} line #{entry[:line]}" + + " of #{entry[:filename] || default_filename}" + + (entry[:mixin] ? ", in `#{entry[:mixin]}'" : "") + end.join + end + + class << self + # Returns an error report for an exception in CSS format. + # + # @param e [Exception] + # @param line_offset [Integer] The number of the first line of the Sass template. + # @return [String] The error report + # @raise [Exception] `e`, if the + # {file:SASS_REFERENCE.md#full_exception-option `:full_exception`} option + # is set to false. + def exception_to_css(e, line_offset = 1) + header = header_string(e, line_offset) + + <] The command-line arguments + def initialize(args) + @args = args + @options = {} + end + + # Parses the command-line arguments and runs the executable. + # Calls `Kernel#exit` at the end, so it never returns. + # + # @see #parse + def parse! + begin + parse + rescue Exception => e + # Exit code 65 indicates invalid data per + # http://www.freebsd.org/cgi/man.cgi?query=sysexits. Setting it via + # at_exit is a bit of a hack, but it allows us to rethrow when --trace + # is active and get both the built-in exception formatting and the + # correct exit code. + at_exit {exit Sass::Util.windows? ? 13 : 65} if e.is_a?(Sass::SyntaxError) + + raise e if @options[:trace] || e.is_a?(SystemExit) + + if e.is_a?(Sass::SyntaxError) + $stderr.puts e.sass_backtrace_str("standard input") + else + $stderr.print "#{e.class}: " unless e.class == RuntimeError + $stderr.puts e.message.to_s + end + $stderr.puts " Use --trace for backtrace." + + exit 1 + end + exit 0 + end + + # Parses the command-line arguments and runs the executable. + # This does not handle exceptions or exit the program. + # + # @see #parse! + def parse + @opts = OptionParser.new(&method(:set_opts)) + @opts.parse!(@args) + + process_result + + @options + end + + # @return [String] A description of the executable + def to_s + @opts.to_s + end + + protected + + # Finds the line of the source template + # on which an exception was raised. + # + # @param exception [Exception] The exception + # @return [String] The line number + def get_line(exception) + # SyntaxErrors have weird line reporting + # when there's trailing whitespace + if exception.is_a?(::SyntaxError) + return (exception.message.scan(/:(\d+)/).first || ["??"]).first + end + (exception.backtrace[0].scan(/:(\d+)/).first || ["??"]).first + end + + # Tells optparse how to parse the arguments + # available for all executables. + # + # This is meant to be overridden by subclasses + # so they can add their own options. + # + # @param opts [OptionParser] + def set_opts(opts) + Sass::Util.abstract(this) + end + + # Set an option for specifying `Encoding.default_external`. + # + # @param opts [OptionParser] + def encoding_option(opts) + encoding_desc = 'Specify the default encoding for input files.' + opts.on('-E', '--default-encoding ENCODING', encoding_desc) do |encoding| + Encoding.default_external = encoding + end + end + + # Processes the options set by the command-line arguments. In particular, + # sets `@options[:input]` and `@options[:output]` to appropriate IO streams. + # + # This is meant to be overridden by subclasses + # so they can run their respective programs. + def process_result + input, output = @options[:input], @options[:output] + args = @args.dup + input ||= + begin + filename = args.shift + @options[:filename] = filename + open_file(filename) || $stdin + end + @options[:output_filename] = args.shift + output ||= @options[:output_filename] || $stdout + @options[:input], @options[:output] = input, output + end + + COLORS = {:red => 31, :green => 32, :yellow => 33} + + # Prints a status message about performing the given action, + # colored using the given color (via terminal escapes) if possible. + # + # @param name [#to_s] A short name for the action being performed. + # Shouldn't be longer than 11 characters. + # @param color [Symbol] The name of the color to use for this action. + # Can be `:red`, `:green`, or `:yellow`. + def puts_action(name, color, arg) + return if @options[:for_engine][:quiet] + printf color(color, "%11s %s\n"), name, arg + STDOUT.flush + end + + # Same as `Kernel.puts`, but doesn't print anything if the `--quiet` option is set. + # + # @param args [Array] Passed on to `Kernel.puts` + def puts(*args) + return if @options[:for_engine][:quiet] + Kernel.puts(*args) + end + + # Wraps the given string in terminal escapes + # causing it to have the given color. + # If terminal escapes aren't supported on this platform, + # just returns the string instead. + # + # @param color [Symbol] The name of the color to use. + # Can be `:red`, `:green`, or `:yellow`. + # @param str [String] The string to wrap in the given color. + # @return [String] The wrapped string. + def color(color, str) + raise "[BUG] Unrecognized color #{color}" unless COLORS[color] + + # Almost any real Unix terminal will support color, + # so we just filter for Windows terms (which don't set TERM) + # and not-real terminals, which aren't ttys. + return str if ENV["TERM"].nil? || ENV["TERM"].empty? || !STDOUT.tty? + "\e[#{COLORS[color]}m#{str}\e[0m" + end + + def write_output(text, destination) + if destination.is_a?(String) + open_file(destination, 'w') {|file| file.write(text)} + else + destination.write(text) + end + end + + private + + def open_file(filename, flag = 'r') + return if filename.nil? + flag = 'wb' if @options[:unix_newlines] && flag == 'w' + file = File.open(filename, flag) + return file unless block_given? + yield file + file.close + end + + def handle_load_error(err) + dep = err.message[/^no such file to load -- (.*)/, 1] + raise err if @options[:trace] || dep.nil? || dep.empty? + $stderr.puts <] The command-line arguments + def initialize(args) + super + require 'sass' + @options[:for_tree] = {} + @options[:for_engine] = {:cache => false, :read_cache => true} + end + + # Tells optparse how to parse the arguments. + # + # @param opts [OptionParser] + def set_opts(opts) + opts.banner = < e + raise e if @options[:trace] + file = " of #{e.sass_filename}" if e.sass_filename + raise "Error on line #{e.sass_line}#{file}: #{e.message}\n Use --trace for backtrace" + rescue LoadError => err + handle_load_error(err) + end + + def path_for(file) + return file.path if file.is_a?(File) + return file if file.is_a?(String) + end + + def read(file) + if file.respond_to?(:read) + file.read + else + open(file, 'rb') {|f| f.read} + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/exec/sass_scss.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/exec/sass_scss.rb new file mode 100644 index 00000000..baea0a94 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/exec/sass_scss.rb @@ -0,0 +1,436 @@ +module Sass::Exec + # The `sass` and `scss` executables. + class SassScss < Base + attr_reader :default_syntax + + # @param args [Array] The command-line arguments + def initialize(args, default_syntax) + super(args) + @options[:sourcemap] = :auto + @options[:for_engine] = { + :load_paths => default_sass_path + } + @default_syntax = default_syntax + end + + protected + + # Tells optparse how to parse the arguments. + # + # @param opts [OptionParser] + def set_opts(opts) + opts.banner = <>> Sass is watching for changes. Press Ctrl-C to stop." + + Sass::Plugin.on_template_modified do |template| + puts ">>> Change detected to: #{template}" + STDOUT.flush + end + Sass::Plugin.on_template_created do |template| + puts ">>> New template detected: #{template}" + STDOUT.flush + end + Sass::Plugin.on_template_deleted do |template| + puts ">>> Deleted template detected: #{template}" + STDOUT.flush + end + + Sass::Plugin.watch(files) + end + + def run + input = @options[:input] + output = @options[:output] + + if input == $stdin + # See issue 1745 + (@options[:for_engine][:load_paths] ||= []) << ::Sass::Importers::DeprecatedPath.new(".") + end + + @options[:for_engine][:syntax] ||= :scss if input.is_a?(File) && input.path =~ /\.scss$/ + @options[:for_engine][:syntax] ||= @default_syntax + engine = + if input.is_a?(File) && !@options[:check_syntax] + Sass::Engine.for_file(input.path, @options[:for_engine]) + else + # We don't need to do any special handling of @options[:check_syntax] here, + # because the Sass syntax checking happens alongside evaluation + # and evaluation doesn't actually evaluate any code anyway. + Sass::Engine.new(input.read, @options[:for_engine]) + end + + input.close if input.is_a?(File) + + if @options[:sourcemap] != :none && @options[:sourcemap_filename] + relative_sourcemap_path = Sass::Util.relative_path_from( + @options[:sourcemap_filename], Sass::Util.pathname(@options[:output_filename]).dirname) + rendered, mapping = engine.render_with_sourcemap(relative_sourcemap_path.to_s) + write_output(rendered, output) + write_output( + mapping.to_json( + :type => @options[:sourcemap], + :css_path => @options[:output_filename], + :sourcemap_path => @options[:sourcemap_filename]) + "\n", + @options[:sourcemap_filename]) + else + write_output(engine.render, output) + end + rescue Sass::SyntaxError => e + write_output(Sass::SyntaxError.exception_to_css(e), output) if output.is_a?(String) + raise e + ensure + output.close if output.is_a? File + end + + def colon_path?(path) + !split_colon_path(path)[1].nil? + end + + def split_colon_path(path) + one, two = path.split(':', 2) + if one && two && Sass::Util.windows? && + one =~ /\A[A-Za-z]\Z/ && two =~ %r{\A[/\\]} + # If we're on Windows and we were passed a drive letter path, + # don't split on that colon. + one2, two = two.split(':', 2) + one = one + ':' + one2 + end + return one, two + end + + # Whether path is likely to be meant as the destination + # in a source:dest pair. + def probably_dest_dir?(path) + return false unless path + return false if colon_path?(path) + Sass::Util.glob(File.join(path, "*.s[ca]ss")).empty? + end + + def default_sass_path + return unless ENV['SASS_PATH'] + # The select here prevents errors when the environment's + # load paths specified do not exist. + ENV['SASS_PATH'].split(File::PATH_SEPARATOR).select {|d| File.directory?(d)} + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/features.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/features.rb new file mode 100644 index 00000000..78d32992 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/features.rb @@ -0,0 +1,48 @@ +require 'set' +module Sass + # Provides `Sass.has_feature?` which allows for simple feature detection + # by providing a feature name. + module Features + # This is the set of features that can be detected. + # + # When this is updated, the documentation of `feature-exists()` should be + # updated as well. + KNOWN_FEATURES = Set[*%w( + global-variable-shadowing + extend-selector-pseudoclass + units-level-3 + at-error + custom-property + )] + + # Check if a feature exists by name. This is used to implement + # the Sass function `feature-exists($feature)` + # + # @param feature_name [String] The case sensitive name of the feature to + # check if it exists in this version of Sass. + # @return [Boolean] whether the feature of that name exists. + def has_feature?(feature_name) + KNOWN_FEATURES.include?(feature_name) + end + + # Add a feature to Sass. Plugins can use this to easily expose their + # availability to end users. Plugins must prefix their feature + # names with a dash to distinguish them from official features. + # + # @example + # Sass.add_feature("-import-globbing") + # Sass.add_feature("-math-cos") + # + # + # @param feature_name [String] The case sensitive name of the feature to + # to add to Sass. Must begin with a dash. + def add_feature(feature_name) + unless feature_name[0] == ?- + raise ArgumentError.new("Plugin feature names must begin with a dash") + end + KNOWN_FEATURES << feature_name + end + end + + extend Features +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/importers.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/importers.rb new file mode 100644 index 00000000..6178b201 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/importers.rb @@ -0,0 +1,23 @@ +module Sass + # Sass importers are in charge of taking paths passed to `@import` + # and finding the appropriate Sass code for those paths. + # By default, this code is always loaded from the filesystem, + # but importers could be added to load from a database or over HTTP. + # + # Each importer is in charge of a single load path + # (or whatever the corresponding notion is for the backend). + # Importers can be placed in the {file:SASS_REFERENCE.md#load_paths-option `:load_paths` array} + # alongside normal filesystem paths. + # + # When resolving an `@import`, Sass will go through the load paths + # looking for an importer that successfully imports the path. + # Once one is found, the imported file is used. + # + # User-created importers must inherit from {Importers::Base}. + module Importers + end +end + +require 'sass/importers/base' +require 'sass/importers/filesystem' +require 'sass/importers/deprecated_path' diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/importers/base.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/importers/base.rb new file mode 100644 index 00000000..a634d34b --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/importers/base.rb @@ -0,0 +1,182 @@ +module Sass + module Importers + # The abstract base class for Sass importers. + # All importers should inherit from this. + # + # At the most basic level, an importer is given a string + # and must return a {Sass::Engine} containing some Sass code. + # This string can be interpreted however the importer wants; + # however, subclasses are encouraged to use the URI format + # for pathnames. + # + # Importers that have some notion of "relative imports" + # should take a single load path in their constructor, + # and interpret paths as relative to that. + # They should also implement the \{#find\_relative} method. + # + # Importers should be serializable via `Marshal.dump`. + # + # @abstract + class Base + # Find a Sass file relative to another file. + # Importers without a notion of "relative paths" + # should just return nil here. + # + # If the importer does have a notion of "relative paths", + # it should ignore its load path during this method. + # + # See \{#find} for important information on how this method should behave. + # + # The `:filename` option passed to the returned {Sass::Engine} + # should be of a format that could be passed to \{#find}. + # + # @param uri [String] The URI to import. This is not necessarily relative, + # but this method should only return true if it is. + # @param base [String] The base filename. If `uri` is relative, + # it should be interpreted as relative to `base`. + # `base` is guaranteed to be in a format importable by this importer. + # @param options [{Symbol => Object}] Options for the Sass file + # containing the `@import` that's currently being resolved. + # @return [Sass::Engine, nil] An Engine containing the imported file, + # or nil if it couldn't be found or was in the wrong format. + def find_relative(uri, base, options) + Sass::Util.abstract(self) + end + + # Find a Sass file, if it exists. + # + # This is the primary entry point of the Importer. + # It corresponds directly to an `@import` statement in Sass. + # It should do three basic things: + # + # * Determine if the URI is in this importer's format. + # If not, return nil. + # * Determine if the file indicated by the URI actually exists and is readable. + # If not, return nil. + # * Read the file and place the contents in a {Sass::Engine}. + # Return that engine. + # + # If this importer's format allows for file extensions, + # it should treat them the same way as the default {Filesystem} importer. + # If the URI explicitly has a `.sass` or `.scss` filename, + # the importer should look for that exact file + # and import it as the syntax indicated. + # If it doesn't exist, the importer should return nil. + # + # If the URI doesn't have either of these extensions, + # the importer should look for files with the extensions. + # If no such files exist, it should return nil. + # + # The {Sass::Engine} to be returned should be passed `options`, + # with a few modifications. `:syntax` should be set appropriately, + # `:filename` should be set to `uri`, + # and `:importer` should be set to this importer. + # + # @param uri [String] The URI to import. + # @param options [{Symbol => Object}] Options for the Sass file + # containing the `@import` that's currently being resolved. + # This is safe for subclasses to modify destructively. + # Callers should only pass in a value they don't mind being destructively modified. + # @return [Sass::Engine, nil] An Engine containing the imported file, + # or nil if it couldn't be found or was in the wrong format. + def find(uri, options) + Sass::Util.abstract(self) + end + + # Returns the time the given Sass file was last modified. + # + # If the given file has been deleted or the time can't be accessed + # for some other reason, this should return nil. + # + # @param uri [String] The URI of the file to check. + # Comes from a `:filename` option set on an engine returned by this importer. + # @param options [{Symbol => Object}] Options for the Sass file + # containing the `@import` currently being checked. + # @return [Time, nil] + def mtime(uri, options) + Sass::Util.abstract(self) + end + + # Get the cache key pair for the given Sass URI. + # The URI need not be checked for validity. + # + # The only strict requirement is that the returned pair of strings + # uniquely identify the file at the given URI. + # However, the first component generally corresponds roughly to the directory, + # and the second to the basename, of the URI. + # + # Note that keys must be unique *across importers*. + # Thus it's probably a good idea to include the importer name + # at the beginning of the first component. + # + # @param uri [String] A URI known to be valid for this importer. + # @param options [{Symbol => Object}] Options for the Sass file + # containing the `@import` currently being checked. + # @return [(String, String)] The key pair which uniquely identifies + # the file at the given URI. + def key(uri, options) + Sass::Util.abstract(self) + end + + # Get the publicly-visible URL for an imported file. This URL is used by + # source maps to link to the source stylesheet. This may return `nil` to + # indicate that no public URL is available; however, this will cause + # sourcemap generation to fail if any CSS is generated from files imported + # from this importer. + # + # If an absolute "file:" URI can be produced for an imported file, that + # should be preferred to returning `nil`. However, a URL relative to + # `sourcemap_directory` should be preferred over an absolute "file:" URI. + # + # @param uri [String] A URI known to be valid for this importer. + # @param sourcemap_directory [String, NilClass] The absolute path to a + # directory on disk where the sourcemap will be saved. If uri refers to + # a file on disk that's accessible relative to sourcemap_directory, this + # may return a relative URL. This may be `nil` if the sourcemap's + # eventual location is unknown. + # @return [String?] The publicly-visible URL for this file, or `nil` + # indicating that no publicly-visible URL exists. This should be + # appropriately URL-escaped. + def public_url(uri, sourcemap_directory) + return if @public_url_warning_issued + @public_url_warning_issued = true + Sass::Util.sass_warn <] List of absolute paths of directories to watch + def directories_to_watch + [] + end + + # If this importer is based on files on the local filesystem This method + # should return true if the file, when changed, should trigger a + # recompile. + # + # It is acceptable for non-sass files to be watched and trigger a recompile. + # + # @param filename [String] The absolute filename for a file that has changed. + # @return [Boolean] When the file changed should cause a recompile. + def watched_file?(filename) + false + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/importers/deprecated_path.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/importers/deprecated_path.rb new file mode 100644 index 00000000..d817c9de --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/importers/deprecated_path.rb @@ -0,0 +1,51 @@ +module Sass + module Importers + # This importer emits a deprecation warning the first time it is used to + # import a file. It is used to deprecate the current working + # directory from the list of automatic sass load paths. + class DeprecatedPath < Filesystem + # @param root [String] The absolute, expanded path to the folder that is deprecated. + def initialize(root) + @specified_root = root + @warning_given = false + super + end + + # @see Sass::Importers::Base#find + def find(*args) + found = super + if found && !@warning_given + @warning_given = true + Sass::Util.sass_warn deprecation_warning + end + found + end + + # @see Base#directories_to_watch + def directories_to_watch + # The current working directory was not watched in Sass 3.2, + # so we continue not to watch it while it's deprecated. + [] + end + + # @see Sass::Importers::Base#to_s + def to_s + "#{@root} (DEPRECATED)" + end + + protected + + # @return [String] The deprecation warning that will be printed the first + # time an import occurs. + def deprecation_warning + path = @specified_root == "." ? "the current working directory" : @specified_root + < Symbol}] + def extensions + {'sass' => :sass, 'scss' => :scss} + end + + # Given an `@import`ed path, returns an array of possible + # on-disk filenames and their corresponding syntaxes for that path. + # + # @param name [String] The filename. + # @return [Array(String, Symbol)] An array of pairs. + # The first element of each pair is a filename to look for; + # the second element is the syntax that file would be in (`:sass` or `:scss`). + def possible_files(name) + name = escape_glob_characters(name) + dirname, basename, extname = split(name) + sorted_exts = extensions.sort + syntax = extensions[extname] + + if syntax + ret = [["#{dirname}/{_,}#{basename}.#{extensions.invert[syntax]}", syntax]] + else + ret = sorted_exts.map {|ext, syn| ["#{dirname}/{_,}#{basename}.#{ext}", syn]} + end + + # JRuby chokes when trying to import files from JARs when the path starts with './'. + ret.map {|f, s| [f.sub(%r{^\./}, ''), s]} + end + + def escape_glob_characters(name) + name.gsub(/[\*\[\]\{\}\?]/) do |char| + "\\#{char}" + end + end + + REDUNDANT_DIRECTORY = /#{Regexp.escape(File::SEPARATOR)}\.#{Regexp.escape(File::SEPARATOR)}/ + # Given a base directory and an `@import`ed name, + # finds an existent file that matches the name. + # + # @param dir [String] The directory relative to which to search. + # @param name [String] The filename to search for. + # @return [(String, Symbol)] A filename-syntax pair. + def find_real_file(dir, name, options) + # On windows 'dir' or 'name' can be in native File::ALT_SEPARATOR form. + dir = dir.gsub(File::ALT_SEPARATOR, File::SEPARATOR) unless File::ALT_SEPARATOR.nil? + name = name.gsub(File::ALT_SEPARATOR, File::SEPARATOR) unless File::ALT_SEPARATOR.nil? + + found = possible_files(remove_root(name)).map do |f, s| + path = if dir == "." || Sass::Util.pathname(f).absolute? + f + else + "#{escape_glob_characters(dir)}/#{f}" + end + Dir[path].map do |full_path| + full_path.gsub!(REDUNDANT_DIRECTORY, File::SEPARATOR) + [Sass::Util.cleanpath(full_path).to_s, s] + end + end.flatten(1) + if found.empty? && split(name)[2].nil? && File.directory?("#{dir}/#{name}") + return find_real_file("#{dir}/#{name}", "index", options) + end + + if found.size > 1 && !@same_name_warnings.include?(found.first.first) + found.each {|(f, _)| @same_name_warnings << f} + relative_to = Sass::Util.pathname(dir) + if options[:_from_import_node] + # If _line exists, we're here due to an actual import in an + # import_node and we want to print a warning for a user writing an + # ambiguous import. + candidates = found.map do |(f, _)| + " " + Sass::Util.pathname(f).relative_path_from(relative_to).to_s + end.join("\n") + raise Sass::SyntaxError.new(<= log_levels[min_level] + end + + def log_level(name, options = {}) + if options[:prepend] + level = log_levels.values.min + level = level.nil? ? 0 : level - 1 + else + level = log_levels.values.max + level = level.nil? ? 0 : level + 1 + end + log_levels.update(name => level) + define_logger(name) + end + + def define_logger(name, options = {}) + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{name}(message) + #{options.fetch(:to, :log)}(#{name.inspect}, message) + end + RUBY + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/media.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/media.rb new file mode 100644 index 00000000..dc4542d4 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/media.rb @@ -0,0 +1,210 @@ +# A namespace for the `@media` query parse tree. +module Sass::Media + # A comma-separated list of queries. + # + # media_query [ ',' S* media_query ]* + class QueryList + # The queries contained in this list. + # + # @return [Array] + attr_accessor :queries + + # @param queries [Array] See \{#queries} + def initialize(queries) + @queries = queries + end + + # Merges this query list with another. The returned query list + # queries for the intersection between the two inputs. + # + # Both query lists should be resolved. + # + # @param other [QueryList] + # @return [QueryList?] The merged list, or nil if there is no intersection. + def merge(other) + new_queries = queries.map {|q1| other.queries.map {|q2| q1.merge(q2)}}.flatten.compact + return if new_queries.empty? + QueryList.new(new_queries) + end + + # Returns the CSS for the media query list. + # + # @return [String] + def to_css + queries.map {|q| q.to_css}.join(', ') + end + + # Returns the Sass/SCSS code for the media query list. + # + # @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}). + # @return [String] + def to_src(options) + queries.map {|q| q.to_src(options)}.join(', ') + end + + # Returns a representation of the query as an array of strings and + # potentially {Sass::Script::Tree::Node}s (if there's interpolation in it). + # When the interpolation is resolved and the strings are joined together, + # this will be the string representation of this query. + # + # @return [Array] + def to_a + Sass::Util.intersperse(queries.map {|q| q.to_a}, ', ').flatten + end + + # Returns a deep copy of this query list and all its children. + # + # @return [QueryList] + def deep_copy + QueryList.new(queries.map {|q| q.deep_copy}) + end + end + + # A single media query. + # + # [ [ONLY | NOT]? S* media_type S* | expression ] [ AND S* expression ]* + class Query + # The modifier for the query. + # + # When parsed as Sass code, this contains strings and SassScript nodes. When + # parsed as CSS, it contains a single string (accessible via + # \{#resolved_modifier}). + # + # @return [Array] + attr_accessor :modifier + + # The type of the query (e.g. `"screen"` or `"print"`). + # + # When parsed as Sass code, this contains strings and SassScript nodes. When + # parsed as CSS, it contains a single string (accessible via + # \{#resolved_type}). + # + # @return [Array] + attr_accessor :type + + # The trailing expressions in the query. + # + # When parsed as Sass code, each expression contains strings and SassScript + # nodes. When parsed as CSS, each one contains a single string. + # + # @return [Array>] + attr_accessor :expressions + + # @param modifier [Array] See \{#modifier} + # @param type [Array] See \{#type} + # @param expressions [Array>] See \{#expressions} + def initialize(modifier, type, expressions) + @modifier = modifier + @type = type + @expressions = expressions + end + + # See \{#modifier}. + # @return [String] + def resolved_modifier + # modifier should contain only a single string + modifier.first || '' + end + + # See \{#type}. + # @return [String] + def resolved_type + # type should contain only a single string + type.first || '' + end + + # Merges this query with another. The returned query queries for + # the intersection between the two inputs. + # + # Both queries should be resolved. + # + # @param other [Query] + # @return [Query?] The merged query, or nil if there is no intersection. + def merge(other) + m1, t1 = resolved_modifier.downcase, resolved_type.downcase + m2, t2 = other.resolved_modifier.downcase, other.resolved_type.downcase + t1 = t2 if t1.empty? + t2 = t1 if t2.empty? + if (m1 == 'not') ^ (m2 == 'not') + return if t1 == t2 + type = m1 == 'not' ? t2 : t1 + mod = m1 == 'not' ? m2 : m1 + elsif m1 == 'not' && m2 == 'not' + # CSS has no way of representing "neither screen nor print" + return unless t1 == t2 + type = t1 + mod = 'not' + elsif t1 != t2 + return + else # t1 == t2, neither m1 nor m2 are "not" + type = t1 + mod = m1.empty? ? m2 : m1 + end + Query.new([mod], [type], other.expressions + expressions) + end + + # Returns the CSS for the media query. + # + # @return [String] + def to_css + css = '' + css << resolved_modifier + css << ' ' unless resolved_modifier.empty? + css << resolved_type + css << ' and ' unless resolved_type.empty? || expressions.empty? + css << expressions.map do |e| + # It's possible for there to be script nodes in Expressions even when + # we're converting to CSS in the case where we parsed the document as + # CSS originally (as in css_test.rb). + e.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.to_sass : c.to_s}.join + end.join(' and ') + css + end + + # Returns the Sass/SCSS code for the media query. + # + # @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}). + # @return [String] + def to_src(options) + src = '' + src << Sass::Media._interp_to_src(modifier, options) + src << ' ' unless modifier.empty? + src << Sass::Media._interp_to_src(type, options) + src << ' and ' unless type.empty? || expressions.empty? + src << expressions.map do |e| + Sass::Media._interp_to_src(e, options) + end.join(' and ') + src + end + + # @see \{MediaQuery#to\_a} + def to_a + res = [] + res += modifier + res << ' ' unless modifier.empty? + res += type + res << ' and ' unless type.empty? || expressions.empty? + res += Sass::Util.intersperse(expressions, ' and ').flatten + res + end + + # Returns a deep copy of this query and all its children. + # + # @return [Query] + def deep_copy + Query.new( + modifier.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}, + type.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}, + expressions.map {|e| e.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}}) + end + end + + # Converts an interpolation array to source. + # + # @param interp [Array] The interpolation array to convert. + # @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}). + # @return [String] + def self._interp_to_src(interp, options) + interp.map {|r| r.is_a?(String) ? r : r.to_sass(options)}.join + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/plugin.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/plugin.rb new file mode 100644 index 00000000..b558a977 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/plugin.rb @@ -0,0 +1,134 @@ +require 'fileutils' + +require 'sass' +require 'sass/plugin/compiler' + +module Sass + # This module provides a single interface to the compilation of Sass/SCSS files + # for an application. It provides global options and checks whether CSS files + # need to be updated. + # + # This module is used as the primary interface with Sass + # when it's used as a plugin for various frameworks. + # All Rack-enabled frameworks are supported out of the box. + # The plugin is + # {file:SASS_REFERENCE.md#Rack_Rails_Merb_Plugin automatically activated for Rails and Merb}. + # Other frameworks must enable it explicitly; see {Sass::Plugin::Rack}. + # + # This module has a large set of callbacks available + # to allow users to run code (such as logging) when certain things happen. + # All callback methods are of the form `on_#{name}`, + # and they all take a block that's called when the given action occurs. + # + # Note that this class proxies almost all methods to its {Sass::Plugin::Compiler} instance. + # See \{#compiler}. + # + # @example Using a callback + # Sass::Plugin.on_updating_stylesheet do |template, css| + # puts "Compiling #{template} to #{css}" + # end + # Sass::Plugin.update_stylesheets + # #=> Compiling app/sass/screen.scss to public/stylesheets/screen.css + # #=> Compiling app/sass/print.scss to public/stylesheets/print.css + # #=> Compiling app/sass/ie.scss to public/stylesheets/ie.css + # @see Sass::Plugin::Compiler + module Plugin + extend self + + @checked_for_updates = false + + # Whether or not Sass has **ever** checked if the stylesheets need to be updated + # (in this Ruby instance). + # + # @return [Boolean] + attr_accessor :checked_for_updates + + # Same as \{#update\_stylesheets}, but respects \{#checked\_for\_updates} + # and the {file:SASS_REFERENCE.md#always_update-option `:always_update`} + # and {file:SASS_REFERENCE.md#always_check-option `:always_check`} options. + # + # @see #update_stylesheets + def check_for_updates + return unless !Sass::Plugin.checked_for_updates || + Sass::Plugin.options[:always_update] || Sass::Plugin.options[:always_check] + update_stylesheets + end + + # Returns the singleton compiler instance. + # This compiler has been pre-configured according + # to the plugin configuration. + # + # @return [Sass::Plugin::Compiler] + def compiler + @compiler ||= Compiler.new + end + + # Updates out-of-date stylesheets. + # + # Checks each Sass/SCSS file in + # {file:SASS_REFERENCE.md#template_location-option `:template_location`} + # to see if it's been modified more recently than the corresponding CSS file + # in {file:SASS_REFERENCE.md#css_location-option `:css_location`}. + # If it has, it updates the CSS file. + # + # @param individual_files [Array<(String, String)>] + # A list of files to check for updates + # **in addition to those specified by the + # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.** + # The first string in each pair is the location of the Sass/SCSS file, + # the second is the location of the CSS file that it should be compiled to. + def update_stylesheets(individual_files = []) + return if options[:never_update] + compiler.update_stylesheets(individual_files) + end + + # Updates all stylesheets, even those that aren't out-of-date. + # Ignores the cache. + # + # @param individual_files [Array<(String, String)>] + # A list of files to check for updates + # **in addition to those specified by the + # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.** + # The first string in each pair is the location of the Sass/SCSS file, + # the second is the location of the CSS file that it should be compiled to. + # @see #update_stylesheets + def force_update_stylesheets(individual_files = []) + Compiler.new( + options.dup.merge( + :never_update => false, + :always_update => true, + :cache => false)).update_stylesheets(individual_files) + end + + # All other method invocations are proxied to the \{#compiler}. + # + # @see #compiler + # @see Sass::Plugin::Compiler + def method_missing(method, *args, &block) + if compiler.respond_to?(method) + compiler.send(method, *args, &block) + else + super + end + end + + # For parity with method_missing + def respond_to?(method) + super || compiler.respond_to?(method) + end + + # There's a small speedup by not using method missing for frequently delegated methods. + def options + compiler.options + end + end +end + +if defined?(ActionController) + # On Rails 3+ the rails plugin is loaded at the right time in railtie.rb + require 'sass/plugin/rails' unless Sass::Util.ap_geq_3? +elsif defined?(Merb::Plugins) + require 'sass/plugin/merb' +else + require 'sass/plugin/generic' +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/plugin/compiler.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/plugin/compiler.rb new file mode 100644 index 00000000..f3ae6e3d --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/plugin/compiler.rb @@ -0,0 +1,552 @@ +require 'fileutils' + +require 'sass' +# XXX CE: is this still necessary now that we have the compiler class? +require 'sass/callbacks' +require 'sass/plugin/configuration' +require 'sass/plugin/staleness_checker' + +module Sass::Plugin + # The Compiler class handles compilation of multiple files and/or directories, + # including checking which CSS files are out-of-date and need to be updated + # and calling Sass to perform the compilation on those files. + # + # {Sass::Plugin} uses this class to update stylesheets for a single application. + # Unlike {Sass::Plugin}, though, the Compiler class has no global state, + # and so multiple instances may be created and used independently. + # + # If you need to compile a Sass string into CSS, + # please see the {Sass::Engine} class. + # + # Unlike {Sass::Plugin}, this class doesn't keep track of + # whether or how many times a stylesheet should be updated. + # Therefore, the following `Sass::Plugin` options are ignored by the Compiler: + # + # * `:never_update` + # * `:always_check` + class Compiler + include Configuration + extend Sass::Callbacks + + # Creates a new compiler. + # + # @param opts [{Symbol => Object}] + # See {file:SASS_REFERENCE.md#Options the Sass options documentation}. + def initialize(opts = {}) + @watched_files = Set.new + options.merge!(opts) + end + + # Register a callback to be run before stylesheets are mass-updated. + # This is run whenever \{#update\_stylesheets} is called, + # unless the \{file:SASS_REFERENCE.md#never_update-option `:never_update` option} + # is enabled. + # + # @yield [files] + # @yieldparam files [<(String, String, String)>] + # Individual files to be updated. Files in directories specified are included in this list. + # The first element of each pair is the source file, + # the second is the target CSS file, + # the third is the target sourcemap file. + define_callback :updating_stylesheets + + # Register a callback to be run after stylesheets are mass-updated. + # This is run whenever \{#update\_stylesheets} is called, + # unless the \{file:SASS_REFERENCE.md#never_update-option `:never_update` option} + # is enabled. + # + # @yield [updated_files] + # @yieldparam updated_files [<(String, String)>] + # Individual files that were updated. + # The first element of each pair is the source file, the second is the target CSS file. + define_callback :updated_stylesheets + + # Register a callback to be run after a single stylesheet is updated. + # The callback is only run if the stylesheet is really updated; + # if the CSS file is fresh, this won't be run. + # + # Even if the \{file:SASS_REFERENCE.md#full_exception-option `:full_exception` option} + # is enabled, this callback won't be run + # when an exception CSS file is being written. + # To run an action for those files, use \{#on\_compilation\_error}. + # + # @yield [template, css, sourcemap] + # @yieldparam template [String] + # The location of the Sass/SCSS file being updated. + # @yieldparam css [String] + # The location of the CSS file being generated. + # @yieldparam sourcemap [String] + # The location of the sourcemap being generated, if any. + define_callback :updated_stylesheet + + # Register a callback to be run when compilation starts. + # + # In combination with on_updated_stylesheet, this could be used + # to collect compilation statistics like timing or to take a + # diff of the changes to the output file. + # + # @yield [template, css, sourcemap] + # @yieldparam template [String] + # The location of the Sass/SCSS file being updated. + # @yieldparam css [String] + # The location of the CSS file being generated. + # @yieldparam sourcemap [String] + # The location of the sourcemap being generated, if any. + define_callback :compilation_starting + + # Register a callback to be run when Sass decides not to update a stylesheet. + # In particular, the callback is run when Sass finds that + # the template file and none of its dependencies + # have been modified since the last compilation. + # + # Note that this is **not** run when the + # \{file:SASS_REFERENCE.md#never-update_option `:never_update` option} is set, + # nor when Sass decides not to compile a partial. + # + # @yield [template, css] + # @yieldparam template [String] + # The location of the Sass/SCSS file not being updated. + # @yieldparam css [String] + # The location of the CSS file not being generated. + define_callback :not_updating_stylesheet + + # Register a callback to be run when there's an error + # compiling a Sass file. + # This could include not only errors in the Sass document, + # but also errors accessing the file at all. + # + # @yield [error, template, css] + # @yieldparam error [Exception] The exception that was raised. + # @yieldparam template [String] + # The location of the Sass/SCSS file being updated. + # @yieldparam css [String] + # The location of the CSS file being generated. + define_callback :compilation_error + + # Register a callback to be run when Sass creates a directory + # into which to put CSS files. + # + # Note that even if multiple levels of directories need to be created, + # the callback may only be run once. + # For example, if "foo/" exists and "foo/bar/baz/" needs to be created, + # this may only be run for "foo/bar/baz/". + # This is not a guarantee, however; + # it may also be run for "foo/bar/". + # + # @yield [dirname] + # @yieldparam dirname [String] + # The location of the directory that was created. + define_callback :creating_directory + + # Register a callback to be run when Sass detects + # that a template has been modified. + # This is only run when using \{#watch}. + # + # @yield [template] + # @yieldparam template [String] + # The location of the template that was modified. + define_callback :template_modified + + # Register a callback to be run when Sass detects + # that a new template has been created. + # This is only run when using \{#watch}. + # + # @yield [template] + # @yieldparam template [String] + # The location of the template that was created. + define_callback :template_created + + # Register a callback to be run when Sass detects + # that a template has been deleted. + # This is only run when using \{#watch}. + # + # @yield [template] + # @yieldparam template [String] + # The location of the template that was deleted. + define_callback :template_deleted + + # Register a callback to be run when Sass deletes a CSS file. + # This happens when the corresponding Sass/SCSS file has been deleted + # and when the compiler cleans the output files. + # + # @yield [filename] + # @yieldparam filename [String] + # The location of the CSS file that was deleted. + define_callback :deleting_css + + # Register a callback to be run when Sass deletes a sourcemap file. + # This happens when the corresponding Sass/SCSS file has been deleted + # and when the compiler cleans the output files. + # + # @yield [filename] + # @yieldparam filename [String] + # The location of the sourcemap file that was deleted. + define_callback :deleting_sourcemap + + # Updates out-of-date stylesheets. + # + # Checks each Sass/SCSS file in + # {file:SASS_REFERENCE.md#template_location-option `:template_location`} + # to see if it's been modified more recently than the corresponding CSS file + # in {file:SASS_REFERENCE.md#css_location-option `:css_location`}. + # If it has, it updates the CSS file. + # + # @param individual_files [Array<(String, String[, String])>] + # A list of files to check for updates + # **in addition to those specified by the + # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.** + # The first string in each pair is the location of the Sass/SCSS file, + # the second is the location of the CSS file that it should be compiled to. + # The third string, if provided, is the location of the Sourcemap file. + def update_stylesheets(individual_files = []) + Sass::Plugin.checked_for_updates = true + staleness_checker = StalenessChecker.new(engine_options) + + files = file_list(individual_files) + run_updating_stylesheets(files) + + updated_stylesheets = [] + files.each do |file, css, sourcemap| + # TODO: Does staleness_checker need to check the sourcemap file as well? + if options[:always_update] || staleness_checker.stylesheet_needs_update?(css, file) + # XXX For consistency, this should return the sourcemap too, but it would + # XXX be an API change. + updated_stylesheets << [file, css] + update_stylesheet(file, css, sourcemap) + else + run_not_updating_stylesheet(file, css, sourcemap) + end + end + run_updated_stylesheets(updated_stylesheets) + end + + # Construct a list of files that might need to be compiled + # from the provided individual_files and the template_locations. + # + # Note: this method does not cache the results as they can change + # across invocations when sass files are added or removed. + # + # @param individual_files [Array<(String, String[, String])>] + # A list of files to check for updates + # **in addition to those specified by the + # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.** + # The first string in each pair is the location of the Sass/SCSS file, + # the second is the location of the CSS file that it should be compiled to. + # The third string, if provided, is the location of the Sourcemap file. + # @return [Array<(String, String, String)>] + # A list of [sass_file, css_file, sourcemap_file] tuples similar + # to what was passed in, but expanded to include the current state + # of the directories being updated. + def file_list(individual_files = []) + files = individual_files.map do |tuple| + if engine_options[:sourcemap] == :none + tuple[0..1] + elsif tuple.size < 3 + [tuple[0], tuple[1], Sass::Util.sourcemap_name(tuple[1])] + else + tuple.dup + end + end + + template_location_array.each do |template_location, css_location| + Sass::Util.glob(File.join(template_location, "**", "[^_]*.s[ca]ss")).sort.each do |file| + # Get the relative path to the file + name = Sass::Util.relative_path_from(file, template_location).to_s + css = css_filename(name, css_location) + sourcemap = Sass::Util.sourcemap_name(css) unless engine_options[:sourcemap] == :none + files << [file, css, sourcemap] + end + end + files + end + + # Watches the template directory (or directories) + # and updates the CSS files whenever the related Sass/SCSS files change. + # `watch` never returns. + # + # Whenever a change is detected to a Sass/SCSS file in + # {file:SASS_REFERENCE.md#template_location-option `:template_location`}, + # the corresponding CSS file in {file:SASS_REFERENCE.md#css_location-option `:css_location`} + # will be recompiled. + # The CSS files of any Sass/SCSS files that import the changed file will also be recompiled. + # + # Before the watching starts in earnest, `watch` calls \{#update\_stylesheets}. + # + # Note that `watch` uses the [Listen](http://github.com/guard/listen) library + # to monitor the filesystem for changes. + # Listen isn't loaded until `watch` is run. + # The version of Listen distributed with Sass is loaded by default, + # but if another version has already been loaded that will be used instead. + # + # @param individual_files [Array<(String, String[, String])>] + # A list of files to check for updates + # **in addition to those specified by the + # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.** + # The first string in each pair is the location of the Sass/SCSS file, + # the second is the location of the CSS file that it should be compiled to. + # The third string, if provided, is the location of the Sourcemap file. + # @param options [Hash] The options that control how watching works. + # @option options [Boolean] :skip_initial_update + # Don't do an initial update when starting the watcher when true + def watch(individual_files = [], options = {}) + @inferred_directories = [] + options, individual_files = individual_files, [] if individual_files.is_a?(Hash) + update_stylesheets(individual_files) unless options[:skip_initial_update] + + directories = watched_paths + individual_files.each do |(source, _, _)| + source = File.expand_path(source) + @watched_files << Sass::Util.realpath(source).to_s + @inferred_directories << File.dirname(source) + end + + directories += @inferred_directories + directories = remove_redundant_directories(directories) + + # TODO: Keep better track of what depends on what + # so we don't have to run a global update every time anything changes. + # XXX The :additional_watch_paths option exists for Compass to use until + # a deprecated feature is removed. It may be removed without warning. + directories += Array(options[:additional_watch_paths]) + + options = { + :relative_paths => false, + # The native windows listener is much slower than the polling option, according to + # https://github.com/nex3/sass/commit/a3031856b22bc834a5417dedecb038b7be9b9e3e + :force_polling => @options[:poll] || Sass::Util.windows? + } + + listener = create_listener(*directories, options) do |modified, added, removed| + on_file_changed(individual_files, modified, added, removed) + yield(modified, added, removed) if block_given? + end + + begin + listener.start + sleep + rescue Interrupt + # Squelch Interrupt for clean exit from Listen::Listener + end + end + + # Non-destructively modifies \{#options} so that default values are properly set, + # and returns the result. + # + # @param additional_options [{Symbol => Object}] An options hash with which to merge \{#options} + # @return [{Symbol => Object}] The modified options hash + def engine_options(additional_options = {}) + opts = options.merge(additional_options) + opts[:load_paths] = load_paths(opts) + options[:sourcemap] = :auto if options[:sourcemap] == true + options[:sourcemap] = :none if options[:sourcemap] == false + opts + end + + # Compass expects this to exist + def stylesheet_needs_update?(css_file, template_file) + StalenessChecker.stylesheet_needs_update?(css_file, template_file) + end + + # Remove all output files that would be created by calling update_stylesheets, if they exist. + # + # This method runs the deleting_css and deleting_sourcemap callbacks for + # the files that are deleted. + # + # @param individual_files [Array<(String, String[, String])>] + # A list of files to check for updates + # **in addition to those specified by the + # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.** + # The first string in each pair is the location of the Sass/SCSS file, + # the second is the location of the CSS file that it should be compiled to. + # The third string, if provided, is the location of the Sourcemap file. + def clean(individual_files = []) + file_list(individual_files).each do |(_, css_file, sourcemap_file)| + if File.exist?(css_file) + run_deleting_css css_file + File.delete(css_file) + end + if sourcemap_file && File.exist?(sourcemap_file) + run_deleting_sourcemap sourcemap_file + File.delete(sourcemap_file) + end + end + nil + end + + private + + # This is mocked out in compiler_test.rb. + def create_listener(*args, &block) + require 'sass-listen' + SassListen.to(*args, &block) + end + + def remove_redundant_directories(directories) + dedupped = [] + directories.each do |new_directory| + # no need to add a directory that is already watched. + next if dedupped.any? do |existing_directory| + child_of_directory?(existing_directory, new_directory) + end + # get rid of any sub directories of this new directory + dedupped.reject! do |existing_directory| + child_of_directory?(new_directory, existing_directory) + end + dedupped << new_directory + end + dedupped + end + + def on_file_changed(individual_files, modified, added, removed) + recompile_required = false + + modified.uniq.each do |f| + next unless watched_file?(f) + recompile_required = true + run_template_modified(relative_to_pwd(f)) + end + + added.uniq.each do |f| + next unless watched_file?(f) + recompile_required = true + run_template_created(relative_to_pwd(f)) + end + + removed.uniq.each do |f| + next unless watched_file?(f) + run_template_deleted(relative_to_pwd(f)) + if (files = individual_files.find {|(source, _, _)| File.expand_path(source) == f}) + recompile_required = true + # This was a file we were watching explicitly and compiling to a particular location. + # Delete the corresponding file. + try_delete_css files[1] + else + next unless watched_file?(f) + recompile_required = true + # Look for the sass directory that contained the sass file + # And try to remove the css file that corresponds to it + template_location_array.each do |(sass_dir, css_dir)| + sass_dir = File.expand_path(sass_dir) + next unless child_of_directory?(sass_dir, f) + remainder = f[(sass_dir.size + 1)..-1] + try_delete_css(css_filename(remainder, css_dir)) + break + end + end + end + + return unless recompile_required + + # In case a file we're watching is removed and then recreated we + # prune out the non-existant files here. + watched_files_remaining = individual_files.select {|(source, _, _)| File.exist?(source)} + update_stylesheets(watched_files_remaining) + end + + def update_stylesheet(filename, css, sourcemap) + dir = File.dirname(css) + unless File.exist?(dir) + run_creating_directory dir + FileUtils.mkdir_p dir + end + + begin + File.read(filename) unless File.readable?(filename) # triggers an error for handling + engine_opts = engine_options(:css_filename => css, + :filename => filename, + :sourcemap_filename => sourcemap) + mapping = nil + run_compilation_starting(filename, css, sourcemap) + engine = Sass::Engine.for_file(filename, engine_opts) + if sourcemap + rendered, mapping = engine.render_with_sourcemap(File.basename(sourcemap)) + else + rendered = engine.render + end + rescue StandardError => e + compilation_error_occurred = true + run_compilation_error e, filename, css, sourcemap + raise e unless options[:full_exception] + rendered = Sass::SyntaxError.exception_to_css(e, options[:line] || 1) + end + + write_file(css, rendered) + if mapping + write_file( + sourcemap, + mapping.to_json( + :css_path => css, :sourcemap_path => sourcemap, :type => options[:sourcemap])) + end + run_updated_stylesheet(filename, css, sourcemap) unless compilation_error_occurred + end + + def write_file(fileName, content) + flag = 'w' + flag = 'wb' if Sass::Util.windows? && options[:unix_newlines] + File.open(fileName, flag) do |file| + file.set_encoding(content.encoding) + file.print(content) + end + end + + def try_delete_css(css) + if File.exist?(css) + run_deleting_css css + File.delete css + end + map = Sass::Util.sourcemap_name(css) + + return unless File.exist?(map) + + run_deleting_sourcemap map + File.delete map + end + + def watched_file?(file) + @watched_files.include?(file) || + normalized_load_paths.any? {|lp| lp.watched_file?(file)} || + @inferred_directories.any? {|d| sass_file_in_directory?(d, file)} + end + + def sass_file_in_directory?(directory, filename) + filename =~ /\.s[ac]ss$/ && filename.start_with?(directory + File::SEPARATOR) + end + + def watched_paths + @watched_paths ||= normalized_load_paths.map {|lp| lp.directories_to_watch}.compact.flatten + end + + def normalized_load_paths + @normalized_load_paths ||= + Sass::Engine.normalize_options(:load_paths => load_paths)[:load_paths] + end + + def load_paths(opts = options) + (opts[:load_paths] || []) + template_locations + end + + def template_locations + template_location_array.to_a.map {|l| l.first} + end + + def css_locations + template_location_array.to_a.map {|l| l.last} + end + + def css_filename(name, path) + "#{path}#{File::SEPARATOR unless path.end_with?(File::SEPARATOR)}#{name}". + gsub(/\.s[ac]ss$/, '.css') + end + + def relative_to_pwd(f) + Sass::Util.relative_path_from(f, Dir.pwd).to_s + rescue ArgumentError # when a relative path cannot be computed + f + end + + def child_of_directory?(parent, child) + parent_dir = parent.end_with?(File::SEPARATOR) ? parent : (parent + File::SEPARATOR) + child.start_with?(parent_dir) || parent == child + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/plugin/configuration.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/plugin/configuration.rb new file mode 100644 index 00000000..93a8aa80 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/plugin/configuration.rb @@ -0,0 +1,134 @@ +module Sass + module Plugin + # We keep configuration in its own self-contained file so that we can load + # it independently in Rails 3, where the full plugin stuff is lazy-loaded. + # + # Note that this is not guaranteed to be thread-safe. For guaranteed thread + # safety, use a separate {Sass::Plugin} for each thread. + module Configuration + # Returns the default options for a {Sass::Plugin::Compiler}. + # + # @return [{Symbol => Object}] + def default_options + @default_options ||= { + :css_location => './public/stylesheets', + :always_update => false, + :always_check => true, + :full_exception => true, + :cache_location => ".sass-cache" + }.freeze + end + + # Resets the options and + # {Sass::Callbacks::InstanceMethods#clear_callbacks! clears all callbacks}. + def reset! + @options = nil + clear_callbacks! + end + + # An options hash. See {file:SASS_REFERENCE.md#Options the Sass options + # documentation}. + # + # @return [{Symbol => Object}] + def options + @options ||= default_options.dup + end + + # Adds a new template-location/css-location mapping. + # This means that Sass/SCSS files in `template_location` + # will be compiled to CSS files in `css_location`. + # + # This is preferred over manually manipulating the + # {file:SASS_REFERENCE.md#template_location-option `:template_location` option} + # since the option can be in multiple formats. + # + # Note that this method will change `options[:template_location]` + # to be in the Array format. + # This means that even if `options[:template_location]` + # had previously been a Hash or a String, + # it will now be an Array. + # + # @param template_location [String] The location where Sass/SCSS files will be. + # @param css_location [String] The location where compiled CSS files will go. + def add_template_location(template_location, css_location = options[:css_location]) + normalize_template_location! + template_location_array << [template_location, css_location] + end + + # Removes a template-location/css-location mapping. + # This means that Sass/SCSS files in `template_location` + # will no longer be compiled to CSS files in `css_location`. + # + # This is preferred over manually manipulating the + # {file:SASS_REFERENCE.md#template_location-option `:template_location` option} + # since the option can be in multiple formats. + # + # Note that this method will change `options[:template_location]` + # to be in the Array format. + # This means that even if `options[:template_location]` + # had previously been a Hash or a String, + # it will now be an Array. + # + # @param template_location [String] + # The location where Sass/SCSS files were, + # which is now going to be ignored. + # @param css_location [String] + # The location where compiled CSS files went, but will no longer go. + # @return [Boolean] + # Non-`nil` if the given mapping already existed and was removed, + # or `nil` if nothing was changed. + def remove_template_location(template_location, css_location = options[:css_location]) + normalize_template_location! + template_location_array.delete([template_location, css_location]) + end + + # Returns the template locations configured for Sass + # as an array of `[template_location, css_location]` pairs. + # See the {file:SASS_REFERENCE.md#template_location-option `:template_location` option} + # for details. + # + # Modifications to the returned array may not be persistent. Use {#add_template_location} + # and {#remove_template_location} instead. + # + # @return [Array<(String, String)>] + # An array of `[template_location, css_location]` pairs. + def template_location_array + convert_template_location(options[:template_location], options[:css_location]) + end + + private + + # Returns the given template location, as an array. If it's already an array, + # it is returned unmodified. Otherwise, a new array is created and returned. + # + # @param template_location [String, Array<(String, String)>] + # A single template location, or a pre-normalized array of template + # locations and CSS locations. + # @param css_location [String?] + # The location for compiled CSS files. + # @return [Array<(String, String)>] + # An array of `[template_location, css_location]` pairs. + def convert_template_location(template_location, css_location) + return template_location if template_location.is_a?(Array) + + case template_location + when nil + if css_location + [[File.join(css_location, 'sass'), css_location]] + else + [] + end + when String + [[template_location, css_location]] + else + template_location.to_a + end + end + + def normalize_template_location! + options[:template_location] = convert_template_location( + options[:template_location], options[:css_location]) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/plugin/generic.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/plugin/generic.rb new file mode 100644 index 00000000..3e82d2d0 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/plugin/generic.rb @@ -0,0 +1,15 @@ +# The reason some options are declared here rather than in sass/plugin/configuration.rb +# is that otherwise they'd clobber the Rails-specific options. +# Since Rails' options are lazy-loaded in Rails 3, +# they're reverse-merged with the default options +# so that user configuration is preserved. +# This means that defaults that differ from Rails' +# must be declared here. + +unless defined?(Sass::GENERIC_LOADED) + Sass::GENERIC_LOADED = true + + Sass::Plugin.options.merge!(:css_location => './public/stylesheets', + :always_update => false, + :always_check => true) +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/plugin/merb.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/plugin/merb.rb new file mode 100644 index 00000000..c8f66b1e --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/plugin/merb.rb @@ -0,0 +1,48 @@ +unless defined?(Sass::MERB_LOADED) + Sass::MERB_LOADED = true + + module Sass::Plugin::Configuration + # Different default options in a m environment. + def default_options + @default_options ||= begin + version = Merb::VERSION.split('.').map {|n| n.to_i} + if version[0] <= 0 && version[1] < 5 + root = MERB_ROOT + env = MERB_ENV + else + root = Merb.root.to_s + env = Merb.environment + end + + { + :always_update => false, + :template_location => root + '/public/stylesheets/sass', + :css_location => root + '/public/stylesheets', + :cache_location => root + '/tmp/sass-cache', + :always_check => env != "production", + :quiet => env != "production", + :full_exception => env != "production" + }.freeze + end + end + end + + config = Merb::Plugins.config[:sass] || Merb::Plugins.config["sass"] || {} + + if defined? config.symbolize_keys! + config.symbolize_keys! + end + + Sass::Plugin.options.merge!(config) + + require 'sass/plugin/rack' + class Sass::Plugin::MerbBootLoader < Merb::BootLoader + after Merb::BootLoader::RackUpApplication + + def self.run + # Apparently there's no better way than this to add Sass + # to Merb's Rack stack. + Merb::Config[:app] = Sass::Plugin::Rack.new(Merb::Config[:app]) + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/plugin/rack.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/plugin/rack.rb new file mode 100644 index 00000000..a147aea9 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/plugin/rack.rb @@ -0,0 +1,60 @@ +module Sass + module Plugin + # Rack middleware for compiling Sass code. + # + # ## Activate + # + # require 'sass/plugin/rack' + # use Sass::Plugin::Rack + # + # ## Customize + # + # Sass::Plugin.options.merge!( + # :cache_location => './tmp/sass-cache', + # :never_update => environment != :production, + # :full_exception => environment != :production) + # + # {file:SASS_REFERENCE.md#Options See the Reference for more options}. + # + # ## Use + # + # Put your Sass files in `public/stylesheets/sass`. + # Your CSS will be generated in `public/stylesheets`, + # and regenerated every request if necessary. + # The locations and frequency {file:SASS_REFERENCE.md#Options can be customized}. + # That's all there is to it! + class Rack + # The delay, in seconds, between update checks. + # Useful when many resources are requested for a single page. + # `nil` means no delay at all. + # + # @return [Float] + attr_accessor :dwell + + # Initialize the middleware. + # + # @param app [#call] The Rack application + # @param dwell [Float] See \{#dwell} + def initialize(app, dwell = 1.0) + @app = app + @dwell = dwell + @check_after = Time.now.to_f + end + + # Process a request, checking the Sass stylesheets for changes + # and updating them if necessary. + # + # @param env The Rack request environment + # @return [(#to_i, {String => String}, Object)] The Rack response + def call(env) + if @dwell.nil? || Time.now.to_f > @check_after + Sass::Plugin.check_for_updates + @check_after = Time.now.to_f + @dwell if @dwell + end + @app.call(env) + end + end + end +end + +require 'sass/plugin' diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/plugin/rails.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/plugin/rails.rb new file mode 100644 index 00000000..9e1677f7 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/plugin/rails.rb @@ -0,0 +1,47 @@ +unless defined?(Sass::RAILS_LOADED) + Sass::RAILS_LOADED = true + + module Sass::Plugin::Configuration + # Different default options in a rails environment. + def default_options + return @default_options if @default_options + opts = { + :quiet => Sass::Util.rails_env != "production", + :full_exception => Sass::Util.rails_env != "production", + :cache_location => Sass::Util.rails_root + '/tmp/sass-cache' + } + + opts.merge!( + :always_update => false, + :template_location => Sass::Util.rails_root + '/public/stylesheets/sass', + :css_location => Sass::Util.rails_root + '/public/stylesheets', + :always_check => Sass::Util.rails_env == "development") + + @default_options = opts.freeze + end + end + + Sass::Plugin.options.reverse_merge!(Sass::Plugin.default_options) + + # Rails 3.1 loads and handles Sass all on its own + if defined?(ActionController::Metal) + # 3.1 > Rails >= 3.0 + require 'sass/plugin/rack' + Rails.configuration.middleware.use(Sass::Plugin::Rack) + elsif defined?(ActionController::Dispatcher) && + defined?(ActionController::Dispatcher.middleware) + # Rails >= 2.3 + require 'sass/plugin/rack' + ActionController::Dispatcher.middleware.use(Sass::Plugin::Rack) + else + module ActionController + class Base + alias_method :sass_old_process, :process + def process(*args) + Sass::Plugin.check_for_updates + sass_old_process(*args) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/plugin/staleness_checker.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/plugin/staleness_checker.rb new file mode 100644 index 00000000..cecc0014 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/plugin/staleness_checker.rb @@ -0,0 +1,199 @@ +require 'thread' + +module Sass + module Plugin + # The class handles `.s[ca]ss` file staleness checks via their mtime timestamps. + # + # To speed things up two level of caches are employed: + # + # * A class-level dependency cache which stores @import paths for each file. + # This is a long-lived cache that is reused by every StalenessChecker instance. + # * Three short-lived instance-level caches, one for file mtimes, + # one for whether a file is stale during this particular run. + # and one for the parse tree for a file. + # These are only used by a single StalenessChecker instance. + # + # Usage: + # + # * For a one-off staleness check of a single `.s[ca]ss` file, + # the class-level {stylesheet_needs_update?} method + # should be used. + # * For a series of staleness checks (e.g. checking all files for staleness) + # a StalenessChecker instance should be created, + # and the instance-level \{#stylesheet\_needs\_update?} method should be used. + # the caches should make the whole process significantly faster. + # *WARNING*: It is important not to retain the instance for too long, + # as its instance-level caches are never explicitly expired. + class StalenessChecker + @dependencies_cache = {} + @dependency_cache_mutex = Mutex.new + + class << self + # TODO: attach this to a compiler instance. + # @private + attr_accessor :dependencies_cache + attr_reader :dependency_cache_mutex + end + + # Creates a new StalenessChecker + # for checking the staleness of several stylesheets at once. + # + # @param options [{Symbol => Object}] + # See {file:SASS_REFERENCE.md#Options the Sass options documentation}. + def initialize(options) + # URIs that are being actively checked for staleness. Protects against + # import loops. + @actively_checking = Set.new + + # Entries in the following instance-level caches are never explicitly expired. + # Instead they are supposed to automatically go out of scope when a series of staleness + # checks (this instance of StalenessChecker was created for) is finished. + @mtimes, @dependencies_stale, @parse_trees = {}, {}, {} + @options = Sass::Engine.normalize_options(options) + end + + # Returns whether or not a given CSS file is out of date + # and needs to be regenerated. + # + # @param css_file [String] The location of the CSS file to check. + # @param template_file [String] The location of the Sass or SCSS template + # that is compiled to `css_file`. + # @return [Boolean] Whether the stylesheet needs to be updated. + def stylesheet_needs_update?(css_file, template_file, importer = nil) + template_file = File.expand_path(template_file) + begin + css_mtime = File.mtime(css_file) + rescue Errno::ENOENT + return true + end + stylesheet_modified_since?(template_file, css_mtime, importer) + end + + # Returns whether a Sass or SCSS stylesheet has been modified since a given time. + # + # @param template_file [String] The location of the Sass or SCSS template. + # @param mtime [Time] The modification time to check against. + # @param importer [Sass::Importers::Base] The importer used to locate the stylesheet. + # Defaults to the filesystem importer. + # @return [Boolean] Whether the stylesheet has been modified. + def stylesheet_modified_since?(template_file, mtime, importer = nil) + importer ||= @options[:filesystem_importer].new(".") + dependency_updated?(mtime).call(template_file, importer) + end + + # Returns whether or not a given CSS file is out of date + # and needs to be regenerated. + # + # The distinction between this method and the instance-level \{#stylesheet\_needs\_update?} + # is that the instance method preserves mtime and stale-dependency caches, + # so it's better to use when checking multiple stylesheets at once. + # + # @param css_file [String] The location of the CSS file to check. + # @param template_file [String] The location of the Sass or SCSS template + # that is compiled to `css_file`. + # @return [Boolean] Whether the stylesheet needs to be updated. + def self.stylesheet_needs_update?(css_file, template_file, importer = nil) + new(Plugin.engine_options).stylesheet_needs_update?(css_file, template_file, importer) + end + + # Returns whether a Sass or SCSS stylesheet has been modified since a given time. + # + # The distinction between this method and the instance-level \{#stylesheet\_modified\_since?} + # is that the instance method preserves mtime and stale-dependency caches, + # so it's better to use when checking multiple stylesheets at once. + # + # @param template_file [String] The location of the Sass or SCSS template. + # @param mtime [Time] The modification time to check against. + # @param importer [Sass::Importers::Base] The importer used to locate the stylesheet. + # Defaults to the filesystem importer. + # @return [Boolean] Whether the stylesheet has been modified. + def self.stylesheet_modified_since?(template_file, mtime, importer = nil) + new(Plugin.engine_options).stylesheet_modified_since?(template_file, mtime, importer) + end + + private + + def dependencies_stale?(uri, importer, css_mtime) + timestamps = @dependencies_stale[[uri, importer]] ||= {} + timestamps.each_pair do |checked_css_mtime, is_stale| + if checked_css_mtime <= css_mtime && !is_stale + return false + elsif checked_css_mtime > css_mtime && is_stale + return true + end + end + timestamps[css_mtime] = dependencies(uri, importer).any?(&dependency_updated?(css_mtime)) + rescue Sass::SyntaxError + # If there's an error finding dependencies, default to recompiling. + true + end + + def mtime(uri, importer) + @mtimes[[uri, importer]] ||= + begin + mtime = importer.mtime(uri, @options) + if mtime.nil? + with_dependency_cache {|cache| cache.delete([uri, importer])} + nil + else + mtime + end + end + end + + def dependencies(uri, importer) + stored_mtime, dependencies = + with_dependency_cache {|cache| Sass::Util.destructure(cache[[uri, importer]])} + + if !stored_mtime || stored_mtime < mtime(uri, importer) + dependencies = compute_dependencies(uri, importer) + with_dependency_cache do |cache| + cache[[uri, importer]] = [mtime(uri, importer), dependencies] + end + end + + dependencies + end + + def dependency_updated?(css_mtime) + proc do |uri, importer| + next true if @actively_checking.include?(uri) + begin + @actively_checking << uri + sass_mtime = mtime(uri, importer) + !sass_mtime || + sass_mtime > css_mtime || + dependencies_stale?(uri, importer, css_mtime) + ensure + @actively_checking.delete uri + end + end + end + + def compute_dependencies(uri, importer) + tree(uri, importer).grep(Tree::ImportNode) do |n| + next if n.css_import? + file = n.imported_file + key = [file.options[:filename], file.options[:importer]] + @parse_trees[key] = file.to_tree + key + end.compact + end + + def tree(uri, importer) + @parse_trees[[uri, importer]] ||= importer.find(uri, @options).to_tree + end + + # Get access to the global dependency cache in a threadsafe manner. + # Inside the block, no other thread can access the dependency cache. + # + # @yieldparam cache [Hash] The hash that is the global dependency cache + # @return The value returned by the block to which this method yields + def with_dependency_cache + StalenessChecker.dependency_cache_mutex.synchronize do + yield StalenessChecker.dependencies_cache + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/railtie.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/railtie.rb new file mode 100644 index 00000000..ad1f03d0 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/railtie.rb @@ -0,0 +1,10 @@ +# Rails 3.0.0.beta.2+, < 3.1 +if defined?(ActiveSupport) && ActiveSupport.public_methods.include?(:on_load) && + !Sass::Util.ap_geq?('3.1.0.beta') + require 'sass/plugin/configuration' + ActiveSupport.on_load(:before_configuration) do + require 'sass' + require 'sass/plugin' + require 'sass/plugin/rails' + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/repl.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/repl.rb new file mode 100644 index 00000000..e9b9e6cc --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/repl.rb @@ -0,0 +1,57 @@ +require 'readline' + +module Sass + # Runs a SassScript read-eval-print loop. + # It presents a prompt on the terminal, + # reads in SassScript expressions, + # evaluates them, + # and prints the result. + class Repl + # @param options [{Symbol => Object}] An options hash. + def initialize(options = {}) + @options = options + end + + # Starts the read-eval-print loop. + def run + environment = Environment.new + @line = 0 + loop do + @line += 1 + unless (text = Readline.readline('>> ')) + puts + return + end + + Readline::HISTORY << text + parse_input(environment, text) + end + end + + private + + def parse_input(environment, text) + case text + when Script::MATCH + name = $1 + guarded = !!$3 + val = Script::Parser.parse($2, @line, text.size - ($3 || '').size - $2.size) + + unless guarded && environment.var(name) + environment.set_var(name, val.perform(environment)) + end + + p environment.var(name) + else + p Script::Parser.parse(text, @line, 0).perform(environment) + end + rescue Sass::SyntaxError => e + puts "SyntaxError: #{e.message}" + if @options[:trace] + e.backtrace.each do |line| + puts "\tfrom #{line}" + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/root.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/root.rb new file mode 100644 index 00000000..31e19c50 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/root.rb @@ -0,0 +1,7 @@ +module Sass + # The root directory of the Sass source tree. + # This may be overridden by the package manager + # if the lib directory is separated from the main source tree. + # @api public + ROOT_DIR = File.expand_path(File.join(__FILE__, "../../..")) +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script.rb new file mode 100644 index 00000000..c852f06d --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script.rb @@ -0,0 +1,66 @@ +require 'sass/scss/rx' + +module Sass + # SassScript is code that's embedded in Sass documents + # to allow for property values to be computed from variables. + # + # This module contains code that handles the parsing and evaluation of SassScript. + module Script + # The regular expression used to parse variables. + MATCH = /^\$(#{Sass::SCSS::RX::IDENT})\s*:\s*(.+?) + (!#{Sass::SCSS::RX::IDENT}(?:\s+!#{Sass::SCSS::RX::IDENT})*)?$/x + + # The regular expression used to validate variables without matching. + VALIDATE = /^\$#{Sass::SCSS::RX::IDENT}$/ + + # Parses a string of SassScript + # + # @param value [String] The SassScript + # @param line [Integer] The number of the line on which the SassScript appeared. + # Used for error reporting + # @param offset [Integer] The number of characters in on `line` that the SassScript started. + # Used for error reporting + # @param options [{Symbol => Object}] An options hash; + # see {file:SASS_REFERENCE.md#Options the Sass options documentation} + # @return [Script::Tree::Node] The root node of the parse tree + def self.parse(value, line, offset, options = {}) + Parser.parse(value, line, offset, options) + rescue Sass::SyntaxError => e + e.message << ": #{value.inspect}." if e.message == "SassScript error" + e.modify_backtrace(:line => line, :filename => options[:filename]) + raise e + end + + require 'sass/script/functions' + require 'sass/script/parser' + require 'sass/script/tree' + require 'sass/script/value' + + # @private + CONST_RENAMES = { + :Literal => Sass::Script::Value::Base, + :ArgList => Sass::Script::Value::ArgList, + :Bool => Sass::Script::Value::Bool, + :Color => Sass::Script::Value::Color, + :List => Sass::Script::Value::List, + :Null => Sass::Script::Value::Null, + :Number => Sass::Script::Value::Number, + :String => Sass::Script::Value::String, + :Node => Sass::Script::Tree::Node, + :Funcall => Sass::Script::Tree::Funcall, + :Interpolation => Sass::Script::Tree::Interpolation, + :Operation => Sass::Script::Tree::Operation, + :StringInterpolation => Sass::Script::Tree::StringInterpolation, + :UnaryOperation => Sass::Script::Tree::UnaryOperation, + :Variable => Sass::Script::Tree::Variable, + } + + # @private + def self.const_missing(name) + klass = CONST_RENAMES[name] + super unless klass + CONST_RENAMES.each {|n, k| const_set(n, k)} + klass + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/css_lexer.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/css_lexer.rb new file mode 100644 index 00000000..6362a9da --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/css_lexer.rb @@ -0,0 +1,33 @@ +module Sass + module Script + # This is a subclass of {Lexer} for use in parsing plain CSS properties. + # + # @see Sass::SCSS::CssParser + class CssLexer < Lexer + private + + def token + important || super + end + + def string(re, *args) + if re == :uri + uri = scan(URI) + return unless uri + return [:string, Script::Value::String.new(uri)] + end + + return unless scan(STRING) + string_value = Sass::Script::Value::String.value(@scanner[1] || @scanner[2]) + value = Script::Value::String.new(string_value, :string) + [:string, value] + end + + def important + s = scan(IMPORTANT) + return unless s + [:raw, s] + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/css_parser.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/css_parser.rb new file mode 100644 index 00000000..32f05afa --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/css_parser.rb @@ -0,0 +1,36 @@ +require 'sass/script' +require 'sass/script/css_lexer' + +module Sass + module Script + # This is a subclass of {Parser} for use in parsing plain CSS properties. + # + # @see Sass::SCSS::CssParser + class CssParser < Parser + private + + # @private + def lexer_class; CssLexer; end + + # We need a production that only does /, + # since * and % aren't allowed in plain CSS + production :div, :unary_plus, :div + + def string + tok = try_tok(:string) + return number unless tok + return if @lexer.peek && @lexer.peek.type == :begin_interpolation + literal_node(tok.value, tok.source_range) + end + + # Short-circuit all the SassScript-only productions + def interpolation(first: nil, inner: :space) + first || send(inner) + end + + alias_method :or_expr, :div + alias_method :unary_div, :ident + alias_method :paren, :string + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/functions.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/functions.rb new file mode 100644 index 00000000..5bd9593d --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/functions.rb @@ -0,0 +1,2920 @@ +require 'sass/script/value/helpers' + +module Sass::Script + # YARD can't handle some multiline tags, and we need really long tags for function declarations. + # Methods in this module are accessible from the SassScript context. + # For example, you can write + # + # $color: hsl(120deg, 100%, 50%) + # + # and it will call {Functions#hsl}. + # + # The following functions are provided: + # + # *Note: These functions are described in more detail below.* + # + # ## RGB Functions + # + # \{#rgb rgb($red, $green, $blue)} + # : Creates a {Sass::Script::Value::Color Color} from red, green, and blue + # values. + # + # \{#rgba rgba($red, $green, $blue, $alpha)} + # : Creates a {Sass::Script::Value::Color Color} from red, green, blue, and + # alpha values. + # + # \{#red red($color)} + # : Gets the red component of a color. + # + # \{#green green($color)} + # : Gets the green component of a color. + # + # \{#blue blue($color)} + # : Gets the blue component of a color. + # + # \{#mix mix($color1, $color2, \[$weight\])} + # : Mixes two colors together. + # + # ## HSL Functions + # + # \{#hsl hsl($hue, $saturation, $lightness)} + # : Creates a {Sass::Script::Value::Color Color} from hue, saturation, and + # lightness values. + # + # \{#hsla hsla($hue, $saturation, $lightness, $alpha)} + # : Creates a {Sass::Script::Value::Color Color} from hue, saturation, + # lightness, and alpha values. + # + # \{#hue hue($color)} + # : Gets the hue component of a color. + # + # \{#saturation saturation($color)} + # : Gets the saturation component of a color. + # + # \{#lightness lightness($color)} + # : Gets the lightness component of a color. + # + # \{#adjust_hue adjust-hue($color, $degrees)} + # : Changes the hue of a color. + # + # \{#lighten lighten($color, $amount)} + # : Makes a color lighter. + # + # \{#darken darken($color, $amount)} + # : Makes a color darker. + # + # \{#saturate saturate($color, $amount)} + # : Makes a color more saturated. + # + # \{#desaturate desaturate($color, $amount)} + # : Makes a color less saturated. + # + # \{#grayscale grayscale($color)} + # : Converts a color to grayscale. + # + # \{#complement complement($color)} + # : Returns the complement of a color. + # + # \{#invert invert($color, \[$weight\])} + # : Returns the inverse of a color. + # + # ## Opacity Functions + # + # \{#alpha alpha($color)} / \{#opacity opacity($color)} + # : Gets the alpha component (opacity) of a color. + # + # \{#rgba rgba($color, $alpha)} + # : Changes the alpha component for a color. + # + # \{#opacify opacify($color, $amount)} / \{#fade_in fade-in($color, $amount)} + # : Makes a color more opaque. + # + # \{#transparentize transparentize($color, $amount)} / \{#fade_out fade-out($color, $amount)} + # : Makes a color more transparent. + # + # ## Other Color Functions + # + # \{#adjust_color adjust-color($color, \[$red\], \[$green\], \[$blue\], \[$hue\], \[$saturation\], \[$lightness\], \[$alpha\])} + # : Increases or decreases one or more components of a color. + # + # \{#scale_color scale-color($color, \[$red\], \[$green\], \[$blue\], \[$saturation\], \[$lightness\], \[$alpha\])} + # : Fluidly scales one or more properties of a color. + # + # \{#change_color change-color($color, \[$red\], \[$green\], \[$blue\], \[$hue\], \[$saturation\], \[$lightness\], \[$alpha\])} + # : Changes one or more properties of a color. + # + # \{#ie_hex_str ie-hex-str($color)} + # : Converts a color into the format understood by IE filters. + # + # ## String Functions + # + # \{#unquote unquote($string)} + # : Removes quotes from a string. + # + # \{#quote quote($string)} + # : Adds quotes to a string. + # + # \{#str_length str-length($string)} + # : Returns the number of characters in a string. + # + # \{#str_insert str-insert($string, $insert, $index)} + # : Inserts `$insert` into `$string` at `$index`. + # + # \{#str_index str-index($string, $substring)} + # : Returns the index of the first occurrence of `$substring` in `$string`. + # + # \{#str_slice str-slice($string, $start-at, [$end-at])} + # : Extracts a substring from `$string`. + # + # \{#to_upper_case to-upper-case($string)} + # : Converts a string to upper case. + # + # \{#to_lower_case to-lower-case($string)} + # : Converts a string to lower case. + # + # ## Number Functions + # + # \{#percentage percentage($number)} + # : Converts a unitless number to a percentage. + # + # \{#round round($number)} + # : Rounds a number to the nearest whole number. + # + # \{#ceil ceil($number)} + # : Rounds a number up to the next whole number. + # + # \{#floor floor($number)} + # : Rounds a number down to the previous whole number. + # + # \{#abs abs($number)} + # : Returns the absolute value of a number. + # + # \{#min min($numbers...)\} + # : Finds the minimum of several numbers. + # + # \{#max max($numbers...)\} + # : Finds the maximum of several numbers. + # + # \{#random random([$limit])\} + # : Returns a random number. + # + # ## List Functions {#list-functions} + # + # Lists in Sass are immutable; all list functions return a new list rather + # than updating the existing list in-place. + # + # All list functions work for maps as well, treating them as lists of pairs. + # + # \{#length length($list)} + # : Returns the length of a list. + # + # \{#nth nth($list, $n)} + # : Returns a specific item in a list. + # + # \{#set-nth set-nth($list, $n, $value)} + # : Replaces the nth item in a list. + # + # \{#join join($list1, $list2, \[$separator, $bracketed\])} + # : Joins together two lists into one. + # + # \{#append append($list1, $val, \[$separator\])} + # : Appends a single value onto the end of a list. + # + # \{#zip zip($lists...)} + # : Combines several lists into a single multidimensional list. + # + # \{#index index($list, $value)} + # : Returns the position of a value within a list. + # + # \{#list_separator list-separator($list)} + # : Returns the separator of a list. + # + # \{#is_bracketed is-bracketed($list)} + # : Returns whether a list has square brackets. + # + # ## Map Functions {#map-functions} + # + # Maps in Sass are immutable; all map functions return a new map rather than + # updating the existing map in-place. + # + # \{#map_get map-get($map, $key)} + # : Returns the value in a map associated with a given key. + # + # \{#map_merge map-merge($map1, $map2)} + # : Merges two maps together into a new map. + # + # \{#map_remove map-remove($map, $keys...)} + # : Returns a new map with keys removed. + # + # \{#map_keys map-keys($map)} + # : Returns a list of all keys in a map. + # + # \{#map_values map-values($map)} + # : Returns a list of all values in a map. + # + # \{#map_has_key map-has-key($map, $key)} + # : Returns whether a map has a value associated with a given key. + # + # \{#keywords keywords($args)} + # : Returns the keywords passed to a function that takes variable arguments. + # + # ## Selector Functions + # + # Selector functions are very liberal in the formats they support + # for selector arguments. They can take a plain string, a list of + # lists as returned by `&` or anything in between: + # + # * A plain string, such as `".foo .bar, .baz .bang"`. + # * A space-separated list of strings such as `(".foo" ".bar")`. + # * A comma-separated list of strings such as `(".foo .bar", ".baz .bang")`. + # * A comma-separated list of space-separated lists of strings such + # as `((".foo" ".bar"), (".baz" ".bang"))`. + # + # In general, selector functions allow placeholder selectors + # (`%foo`) but disallow parent-reference selectors (`&`). + # + # \{#selector_nest selector-nest($selectors...)} + # : Nests selector beneath one another like they would be nested in the + # stylesheet. + # + # \{#selector_append selector-append($selectors...)} + # : Appends selectors to one another without spaces in between. + # + # \{#selector_extend selector-extend($selector, $extendee, $extender)} + # : Extends `$extendee` with `$extender` within `$selector`. + # + # \{#selector_replace selector-replace($selector, $original, $replacement)} + # : Replaces `$original` with `$replacement` within `$selector`. + # + # \{#selector_unify selector-unify($selector1, $selector2)} + # : Unifies two selectors to produce a selector that matches + # elements matched by both. + # + # \{#is_superselector is-superselector($super, $sub)} + # : Returns whether `$super` matches all the elements `$sub` does, and + # possibly more. + # + # \{#simple_selectors simple-selectors($selector)} + # : Returns the simple selectors that comprise a compound selector. + # + # \{#selector_parse selector-parse($selector)} + # : Parses a selector into the format returned by `&`. + # + # ## Introspection Functions + # + # \{#feature_exists feature-exists($feature)} + # : Returns whether a feature exists in the current Sass runtime. + # + # \{#variable_exists variable-exists($name)} + # : Returns whether a variable with the given name exists in the current scope. + # + # \{#global_variable_exists global-variable-exists($name)} + # : Returns whether a variable with the given name exists in the global scope. + # + # \{#function_exists function-exists($name)} + # : Returns whether a function with the given name exists. + # + # \{#mixin_exists mixin-exists($name)} + # : Returns whether a mixin with the given name exists. + # + # \{#content_exists content-exists()} + # : Returns whether the current mixin was passed a content block. + # + # \{#inspect inspect($value)} + # : Returns the string representation of a value as it would be represented in Sass. + # + # \{#type_of type-of($value)} + # : Returns the type of a value. + # + # \{#unit unit($number)} + # : Returns the unit(s) associated with a number. + # + # \{#unitless unitless($number)} + # : Returns whether a number has units. + # + # \{#comparable comparable($number1, $number2)} + # : Returns whether two numbers can be added, subtracted, or compared. + # + # \{#call call($function, $args...)} + # : Dynamically calls a Sass function reference returned by `get-function`. + # + # \{#get_function get-function($name, $css: false)} + # : Looks up a function with the given name in the current lexical scope + # and returns a reference to it. + # + # ## Miscellaneous Functions + # + # \{#if if($condition, $if-true, $if-false)} + # : Returns one of two values, depending on whether or not `$condition` is + # true. + # + # \{#unique_id unique-id()} + # : Returns a unique CSS identifier. + # + # ## Adding Custom Functions + # + # New Sass functions can be added by adding Ruby methods to this module. + # For example: + # + # module Sass::Script::Functions + # def reverse(string) + # assert_type string, :String + # Sass::Script::Value::String.new(string.value.reverse) + # end + # declare :reverse, [:string] + # end + # + # Calling {declare} tells Sass the argument names for your function. + # If omitted, the function will still work, but will not be able to accept keyword arguments. + # {declare} can also allow your function to take arbitrary keyword arguments. + # + # There are a few things to keep in mind when modifying this module. + # First of all, the arguments passed are {Value} objects. + # Value objects are also expected to be returned. + # This means that Ruby values must be unwrapped and wrapped. + # + # Most Value objects support the {Value::Base#value value} accessor for getting + # their Ruby values. Color objects, though, must be accessed using + # {Sass::Script::Value::Color#rgb rgb}, {Sass::Script::Value::Color#red red}, + # {Sass::Script::Value::Color#blue green}, or {Sass::Script::Value::Color#blue + # blue}. + # + # Second, making Ruby functions accessible from Sass introduces the temptation + # to do things like database access within stylesheets. + # This is generally a bad idea; + # since Sass files are by default only compiled once, + # dynamic code is not a great fit. + # + # If you really, really need to compile Sass on each request, + # first make sure you have adequate caching set up. + # Then you can use {Sass::Engine} to render the code, + # using the {file:SASS_REFERENCE.md#custom-option `options` parameter} + # to pass in data that {EvaluationContext#options can be accessed} + # from your Sass functions. + # + # Within one of the functions in this module, + # methods of {EvaluationContext} can be used. + # + # ### Caveats + # + # When creating new {Value} objects within functions, be aware that it's not + # safe to call {Value::Base#to_s #to_s} (or other methods that use the string + # representation) on those objects without first setting {Tree::Node#options= + # the #options attribute}. + # + module Functions + @signatures = {} + + # A class representing a Sass function signature. + # + # @attr args [Array] The names of the arguments to the function. + # @attr delayed_args [Array] The names of the arguments whose evaluation should be + # delayed. + # @attr var_args [Boolean] Whether the function takes a variable number of arguments. + # @attr var_kwargs [Boolean] Whether the function takes an arbitrary set of keyword arguments. + Signature = Struct.new(:args, :delayed_args, :var_args, :var_kwargs, :deprecated) + + # Declare a Sass signature for a Ruby-defined function. + # This includes the names of the arguments, + # whether the function takes a variable number of arguments, + # and whether the function takes an arbitrary set of keyword arguments. + # + # It's not necessary to declare a signature for a function. + # However, without a signature it won't support keyword arguments. + # + # A single function can have multiple signatures declared + # as long as each one takes a different number of arguments. + # It's also possible to declare multiple signatures + # that all take the same number of arguments, + # but none of them but the first will be used + # unless the user uses keyword arguments. + # + # @example + # declare :rgba, [:hex, :alpha] + # declare :rgba, [:red, :green, :blue, :alpha] + # declare :accepts_anything, [], :var_args => true, :var_kwargs => true + # declare :some_func, [:foo, :bar, :baz], :var_kwargs => true + # + # @param method_name [Symbol] The name of the method + # whose signature is being declared. + # @param args [Array] The names of the arguments for the function signature. + # @option options :var_args [Boolean] (false) + # Whether the function accepts a variable number of (unnamed) arguments + # in addition to the named arguments. + # @option options :var_kwargs [Boolean] (false) + # Whether the function accepts other keyword arguments + # in addition to those in `:args`. + # If this is true, the Ruby function will be passed a hash from strings + # to {Value}s as the last argument. + # In addition, if this is true and `:var_args` is not, + # Sass will ensure that the last argument passed is a hash. + def self.declare(method_name, args, options = {}) + delayed_args = [] + args = args.map do |a| + a = a.to_s + if a[0] == ?& + a = a[1..-1] + delayed_args << a + end + a + end + # We don't expose this functionality except to certain builtin methods. + if delayed_args.any? && method_name != :if + raise ArgumentError.new("Delayed arguments are not allowed for method #{method_name}") + end + @signatures[method_name] ||= [] + @signatures[method_name] << Signature.new( + args, + delayed_args, + options[:var_args], + options[:var_kwargs], + options[:deprecated] && options[:deprecated].map {|a| a.to_s}) + end + + # Determine the correct signature for the number of arguments + # passed in for a given function. + # If no signatures match, the first signature is returned for error messaging. + # + # @param method_name [Symbol] The name of the Ruby function to be called. + # @param arg_arity [Integer] The number of unnamed arguments the function was passed. + # @param kwarg_arity [Integer] The number of keyword arguments the function was passed. + # + # @return [{Symbol => Object}, nil] + # The signature options for the matching signature, + # or nil if no signatures are declared for this function. See {declare}. + def self.signature(method_name, arg_arity, kwarg_arity) + return unless @signatures[method_name] + @signatures[method_name].each do |signature| + sig_arity = signature.args.size + return signature if sig_arity == arg_arity + kwarg_arity + next unless sig_arity < arg_arity + kwarg_arity + + # We have enough args. + # Now we need to figure out which args are varargs + # and if the signature allows them. + t_arg_arity, t_kwarg_arity = arg_arity, kwarg_arity + if sig_arity > t_arg_arity + # we transfer some kwargs arity to args arity + # if it does not have enough args -- assuming the names will work out. + t_kwarg_arity -= (sig_arity - t_arg_arity) + t_arg_arity = sig_arity + end + + if (t_arg_arity == sig_arity || t_arg_arity > sig_arity && signature.var_args) && + (t_kwarg_arity == 0 || t_kwarg_arity > 0 && signature.var_kwargs) + return signature + end + end + @signatures[method_name].first + end + + # Sets the random seed used by Sass's internal random number generator. + # + # This can be used to ensure consistent random number sequences which + # allows for consistent results when testing, etc. + # + # @param seed [Integer] + # @return [Integer] The same seed. + def self.random_seed=(seed) + @random_number_generator = Random.new(seed) + end + + # Get Sass's internal random number generator. + # + # @return [Random] + def self.random_number_generator + @random_number_generator ||= Random.new + end + + # The context in which methods in {Script::Functions} are evaluated. + # That means that all instance methods of {EvaluationContext} + # are available to use in functions. + class EvaluationContext + include Functions + include Value::Helpers + + # The human-readable names for [Sass::Script::Value::Base]. The default is + # just the downcased name of the type. + TYPE_NAMES = {:ArgList => 'variable argument list'} + + # The environment for this function. This environment's + # {Environment#parent} is the global environment, and its + # {Environment#caller} is a read-only view of the local environment of the + # caller of this function. + # + # @return [Environment] + attr_reader :environment + + # The options hash for the {Sass::Engine} that is processing the function call + # + # @return [{Symbol => Object}] + attr_reader :options + + # @param environment [Environment] See \{#environment} + def initialize(environment) + @environment = environment + @options = environment.options + end + + # Asserts that the type of a given SassScript value + # is the expected type (designated by a symbol). + # + # Valid types are `:Bool`, `:Color`, `:Number`, and `:String`. + # Note that `:String` will match both double-quoted strings + # and unquoted identifiers. + # + # @example + # assert_type value, :String + # assert_type value, :Number + # @param value [Sass::Script::Value::Base] A SassScript value + # @param type [Symbol, Array] The name(s) of the type the value is expected to be + # @param name [String, Symbol, nil] The name of the argument. + # @raise [ArgumentError] if value is not of the correct type. + def assert_type(value, type, name = nil) + valid_types = Array(type) + found_type = valid_types.find do |t| + value.is_a?(Sass::Script::Value.const_get(t)) || + t == :Map && value.is_a?(Sass::Script::Value::List) && value.value.empty? + end + + if found_type + value.check_deprecated_interp if found_type == :String + return + end + + err = if valid_types.size == 1 + "#{value.inspect} is not a #{TYPE_NAMES[type] || type.to_s.downcase}" + else + type_names = valid_types.map {|t| TYPE_NAMES[t] || t.to_s.downcase} + "#{value.inspect} is not any of #{type_names.join(', ')}" + end + err = "$#{name.to_s.tr('_', '-')}: " + err if name + raise ArgumentError.new(err) + end + + # Asserts that the unit of the number is as expected. + # + # @example + # assert_unit number, "px" + # assert_unit number, nil + # @param number [Sass::Script::Value::Number] The number to be validated. + # @param unit [::String] + # The unit that the number must have. + # If nil, the number must be unitless. + # @param name [::String] The name of the parameter being validated. + # @raise [ArgumentError] if number is not of the correct unit or is not a number. + def assert_unit(number, unit, name = nil) + assert_type number, :Number, name + return if number.is_unit?(unit) + expectation = unit ? "have a unit of #{unit}" : "be unitless" + if name + raise ArgumentError.new("Expected $#{name} to #{expectation} but got #{number}") + else + raise ArgumentError.new("Expected #{number} to #{expectation}") + end + end + + # Asserts that the value is an integer. + # + # @example + # assert_integer 2px + # assert_integer 2.5px + # => SyntaxError: "Expected 2.5px to be an integer" + # assert_integer 2.5px, "width" + # => SyntaxError: "Expected width to be an integer but got 2.5px" + # @param number [Sass::Script::Value::Base] The value to be validated. + # @param name [::String] The name of the parameter being validated. + # @raise [ArgumentError] if number is not an integer or is not a number. + def assert_integer(number, name = nil) + assert_type number, :Number, name + return if number.int? + if name + raise ArgumentError.new("Expected $#{name} to be an integer but got #{number}") + else + raise ArgumentError.new("Expected #{number} to be an integer") + end + end + + # Performs a node that has been delayed for execution. + # + # @private + # @param node [Sass::Script::Tree::Node, + # Sass::Script::Value::Base] When this is a tree node, it's + # performed in the caller's environment. When it's a value + # (which can happen when the value had to be performed already + # -- like for a splat), it's returned as-is. + # @param env [Sass::Environment] The environment within which to perform the node. + # Defaults to the (read-only) environment of the caller. + def perform(node, env = environment.caller) + if node.is_a?(Sass::Script::Value::Base) + node + else + node.perform(env) + end + end + end + + class << self + # Returns whether user function with a given name exists. + # + # @param function_name [String] + # @return [Boolean] + alias_method :callable?, :public_method_defined? + + private + + def include(*args) + r = super + # We have to re-include ourselves into EvaluationContext to work around + # an icky Ruby restriction. + EvaluationContext.send :include, self + r + end + end + + # Creates a {Sass::Script::Value::Color Color} object from red, green, and + # blue values. + # + # @see #rgba + # @overload rgb($red, $green, $blue) + # @param $red [Sass::Script::Value::Number] The amount of red in the color. + # Must be between 0 and 255 inclusive, or between `0%` and `100%` + # inclusive + # @param $green [Sass::Script::Value::Number] The amount of green in the + # color. Must be between 0 and 255 inclusive, or between `0%` and `100%` + # inclusive + # @param $blue [Sass::Script::Value::Number] The amount of blue in the + # color. Must be between 0 and 255 inclusive, or between `0%` and `100%` + # inclusive + # @return [Sass::Script::Value::Color] + # @raise [ArgumentError] if any parameter is the wrong type or out of bounds + def rgb(red, green = nil, blue = nil) + if green.nil? + return unquoted_string("rgb(#{red})") if var?(red) + raise ArgumentError.new("wrong number of arguments (1 for 3)") + elsif blue.nil? + return unquoted_string("rgb(#{red}, #{green})") if var?(red) || var?(green) + raise ArgumentError.new("wrong number of arguments (2 for 3)") + end + + if special_number?(red) || special_number?(green) || special_number?(blue) + return unquoted_string("rgb(#{red}, #{green}, #{blue})") + end + assert_type red, :Number, :red + assert_type green, :Number, :green + assert_type blue, :Number, :blue + + color_attrs = [ + percentage_or_unitless(red, 255, "red"), + percentage_or_unitless(green, 255, "green"), + percentage_or_unitless(blue, 255, "blue") + ] + + # Don't store the string representation for function-created colors, both + # because it's not very useful and because some functions aren't supported + # on older browsers. + Sass::Script::Value::Color.new(color_attrs) + end + declare :rgb, [:red, :green, :blue] + declare :rgb, [:red, :green] + declare :rgb, [:red] + + # Creates a {Sass::Script::Value::Color Color} from red, green, blue, and + # alpha values. + # @see #rgb + # + # @overload rgba($red, $green, $blue, $alpha) + # @param $red [Sass::Script::Value::Number] The amount of red in the + # color. Must be between 0 and 255 inclusive or 0% and 100% inclusive + # @param $green [Sass::Script::Value::Number] The amount of green in the + # color. Must be between 0 and 255 inclusive or 0% and 100% inclusive + # @param $blue [Sass::Script::Value::Number] The amount of blue in the + # color. Must be between 0 and 255 inclusive or 0% and 100% inclusive + # @param $alpha [Sass::Script::Value::Number] The opacity of the color. + # Must be between 0 and 1 inclusive + # @return [Sass::Script::Value::Color] + # @raise [ArgumentError] if any parameter is the wrong type or out of + # bounds + # + # @overload rgba($color, $alpha) + # Sets the opacity of an existing color. + # + # @example + # rgba(#102030, 0.5) => rgba(16, 32, 48, 0.5) + # rgba(blue, 0.2) => rgba(0, 0, 255, 0.2) + # + # @param $color [Sass::Script::Value::Color] The color whose opacity will + # be changed. + # @param $alpha [Sass::Script::Value::Number] The new opacity of the + # color. Must be between 0 and 1 inclusive + # @return [Sass::Script::Value::Color] + # @raise [ArgumentError] if `$alpha` is out of bounds or either parameter + # is the wrong type + def rgba(*args) + case args.size + when 1 + return unquoted_string("rgba(#{args.first})") if var?(args.first) + raise ArgumentError.new("wrong number of arguments (1 for 4)") + when 2 + color, alpha = args + + if var?(color) + return unquoted_string("rgba(#{color}, #{alpha})") + elsif var?(alpha) + if color.is_a?(Sass::Script::Value::Color) + return unquoted_string("rgba(#{color.red}, #{color.green}, #{color.blue}, #{alpha})") + else + return unquoted_string("rgba(#{color}, #{alpha})") + end + end + + assert_type color, :Color, :color + if special_number?(alpha) + unquoted_string("rgba(#{color.red}, #{color.green}, #{color.blue}, #{alpha})") + else + assert_type alpha, :Number, :alpha + color.with(:alpha => percentage_or_unitless(alpha, 1, "alpha")) + end + when 3 + if var?(args[0]) || var?(args[1]) || var?(args[2]) + unquoted_string("rgba(#{args.join(', ')})") + else + raise ArgumentError.new("wrong number of arguments (3 for 4)") + end + when 4 + red, green, blue, alpha = args + if special_number?(red) || special_number?(green) || + special_number?(blue) || special_number?(alpha) + unquoted_string("rgba(#{red}, #{green}, #{blue}, #{alpha})") + else + rgba(rgb(red, green, blue), alpha) + end + else + raise ArgumentError.new("wrong number of arguments (#{args.size} for 4)") + end + end + declare :rgba, [:red, :green, :blue, :alpha] + declare :rgba, [:red, :green, :blue] + declare :rgba, [:color, :alpha] + declare :rgba, [:red] + + # Creates a {Sass::Script::Value::Color Color} from hue, saturation, and + # lightness values. Uses the algorithm from the [CSS3 spec][]. + # + # [CSS3 spec]: http://www.w3.org/TR/css3-color/#hsl-color + # + # @see #hsla + # @overload hsl($hue, $saturation, $lightness) + # @param $hue [Sass::Script::Value::Number] The hue of the color. Should be + # between 0 and 360 degrees, inclusive + # @param $saturation [Sass::Script::Value::Number] The saturation of the + # color. Must be between `0%` and `100%`, inclusive + # @param $lightness [Sass::Script::Value::Number] The lightness of the + # color. Must be between `0%` and `100%`, inclusive + # @return [Sass::Script::Value::Color] + # @raise [ArgumentError] if `$saturation` or `$lightness` are out of bounds + # or any parameter is the wrong type + def hsl(hue, saturation = nil, lightness = nil) + if saturation.nil? + return unquoted_string("hsl(#{hue})") if var?(hue) + raise ArgumentError.new("wrong number of arguments (1 for 3)") + elsif lightness.nil? + return unquoted_string("hsl(#{hue}, #{saturation})") if var?(hue) || var?(saturation) + raise ArgumentError.new("wrong number of arguments (2 for 3)") + end + + if special_number?(hue) || special_number?(saturation) || special_number?(lightness) + unquoted_string("hsl(#{hue}, #{saturation}, #{lightness})") + else + hsla(hue, saturation, lightness, number(1)) + end + end + declare :hsl, [:hue, :saturation, :lightness] + declare :hsl, [:hue, :saturation] + declare :hsl, [:hue] + + # Creates a {Sass::Script::Value::Color Color} from hue, + # saturation, lightness, and alpha values. Uses the algorithm from + # the [CSS3 spec][]. + # + # [CSS3 spec]: http://www.w3.org/TR/css3-color/#hsl-color + # + # @see #hsl + # @overload hsla($hue, $saturation, $lightness, $alpha) + # @param $hue [Sass::Script::Value::Number] The hue of the color. Should be + # between 0 and 360 degrees, inclusive + # @param $saturation [Sass::Script::Value::Number] The saturation of the + # color. Must be between `0%` and `100%`, inclusive + # @param $lightness [Sass::Script::Value::Number] The lightness of the + # color. Must be between `0%` and `100%`, inclusive + # @param $alpha [Sass::Script::Value::Number] The opacity of the color. Must + # be between 0 and 1, inclusive + # @return [Sass::Script::Value::Color] + # @raise [ArgumentError] if `$saturation`, `$lightness`, or `$alpha` are out + # of bounds or any parameter is the wrong type + def hsla(hue, saturation = nil, lightness = nil, alpha = nil) + if saturation.nil? + return unquoted_string("hsla(#{hue})") if var?(hue) + raise ArgumentError.new("wrong number of arguments (1 for 4)") + elsif lightness.nil? + return unquoted_string("hsla(#{hue}, #{saturation})") if var?(hue) || var?(saturation) + raise ArgumentError.new("wrong number of arguments (2 for 4)") + elsif alpha.nil? + if var?(hue) || var?(saturation) || var?(lightness) + return unquoted_string("hsla(#{hue}, #{saturation}, #{lightness})") + else + raise ArgumentError.new("wrong number of arguments (2 for 4)") + end + end + + if special_number?(hue) || special_number?(saturation) || + special_number?(lightness) || special_number?(alpha) + return unquoted_string("hsla(#{hue}, #{saturation}, #{lightness}, #{alpha})") + end + assert_type hue, :Number, :hue + assert_type saturation, :Number, :saturation + assert_type lightness, :Number, :lightness + assert_type alpha, :Number, :alpha + + h = hue.value + s = saturation.value + l = lightness.value + + # Don't store the string representation for function-created colors, both + # because it's not very useful and because some functions aren't supported + # on older browsers. + Sass::Script::Value::Color.new( + :hue => h, :saturation => s, :lightness => l, + :alpha => percentage_or_unitless(alpha, 1, "alpha")) + end + declare :hsla, [:hue, :saturation, :lightness, :alpha] + declare :hsla, [:hue, :saturation, :lightness] + declare :hsla, [:hue, :saturation] + declare :hsla, [:hue] + + # Gets the red component of a color. Calculated from HSL where necessary via + # [this algorithm][hsl-to-rgb]. + # + # [hsl-to-rgb]: http://www.w3.org/TR/css3-color/#hsl-color + # + # @overload red($color) + # @param $color [Sass::Script::Value::Color] + # @return [Sass::Script::Value::Number] The red component, between 0 and 255 + # inclusive + # @raise [ArgumentError] if `$color` isn't a color + def red(color) + assert_type color, :Color, :color + number(color.red) + end + declare :red, [:color] + + # Gets the green component of a color. Calculated from HSL where necessary + # via [this algorithm][hsl-to-rgb]. + # + # [hsl-to-rgb]: http://www.w3.org/TR/css3-color/#hsl-color + # + # @overload green($color) + # @param $color [Sass::Script::Value::Color] + # @return [Sass::Script::Value::Number] The green component, between 0 and + # 255 inclusive + # @raise [ArgumentError] if `$color` isn't a color + def green(color) + assert_type color, :Color, :color + number(color.green) + end + declare :green, [:color] + + # Gets the blue component of a color. Calculated from HSL where necessary + # via [this algorithm][hsl-to-rgb]. + # + # [hsl-to-rgb]: http://www.w3.org/TR/css3-color/#hsl-color + # + # @overload blue($color) + # @param $color [Sass::Script::Value::Color] + # @return [Sass::Script::Value::Number] The blue component, between 0 and + # 255 inclusive + # @raise [ArgumentError] if `$color` isn't a color + def blue(color) + assert_type color, :Color, :color + number(color.blue) + end + declare :blue, [:color] + + # Returns the hue component of a color. See [the CSS3 HSL + # specification][hsl]. Calculated from RGB where necessary via [this + # algorithm][rgb-to-hsl]. + # + # [hsl]: http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV + # [rgb-to-hsl]: http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV + # + # @overload hue($color) + # @param $color [Sass::Script::Value::Color] + # @return [Sass::Script::Value::Number] The hue component, between 0deg and + # 360deg + # @raise [ArgumentError] if `$color` isn't a color + def hue(color) + assert_type color, :Color, :color + number(color.hue, "deg") + end + declare :hue, [:color] + + # Returns the saturation component of a color. See [the CSS3 HSL + # specification][hsl]. Calculated from RGB where necessary via [this + # algorithm][rgb-to-hsl]. + # + # [hsl]: http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV + # [rgb-to-hsl]: http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV + # + # @overload saturation($color) + # @param $color [Sass::Script::Value::Color] + # @return [Sass::Script::Value::Number] The saturation component, between 0% + # and 100% + # @raise [ArgumentError] if `$color` isn't a color + def saturation(color) + assert_type color, :Color, :color + number(color.saturation, "%") + end + declare :saturation, [:color] + + # Returns the lightness component of a color. See [the CSS3 HSL + # specification][hsl]. Calculated from RGB where necessary via [this + # algorithm][rgb-to-hsl]. + # + # [hsl]: http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV + # [rgb-to-hsl]: http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV + # + # @overload lightness($color) + # @param $color [Sass::Script::Value::Color] + # @return [Sass::Script::Value::Number] The lightness component, between 0% + # and 100% + # @raise [ArgumentError] if `$color` isn't a color + def lightness(color) + assert_type color, :Color, :color + number(color.lightness, "%") + end + declare :lightness, [:color] + + # Returns the alpha component (opacity) of a color. This is 1 unless + # otherwise specified. + # + # This function also supports the proprietary Microsoft `alpha(opacity=20)` + # syntax as a special case. + # + # @overload alpha($color) + # @param $color [Sass::Script::Value::Color] + # @return [Sass::Script::Value::Number] The alpha component, between 0 and 1 + # @raise [ArgumentError] if `$color` isn't a color + def alpha(*args) + if args.all? do |a| + a.is_a?(Sass::Script::Value::String) && a.type == :identifier && + a.value =~ /^[a-zA-Z]+\s*=/ + end + # Support the proprietary MS alpha() function + return identifier("alpha(#{args.map {|a| a.to_s}.join(', ')})") + end + + raise ArgumentError.new("wrong number of arguments (#{args.size} for 1)") if args.size != 1 + + assert_type args.first, :Color, :color + number(args.first.alpha) + end + declare :alpha, [:color] + + # Returns the alpha component (opacity) of a color. This is 1 unless + # otherwise specified. + # + # @overload opacity($color) + # @param $color [Sass::Script::Value::Color] + # @return [Sass::Script::Value::Number] The alpha component, between 0 and 1 + # @raise [ArgumentError] if `$color` isn't a color + def opacity(color) + if color.is_a?(Sass::Script::Value::Number) + return identifier("opacity(#{color})") + end + assert_type color, :Color, :color + number(color.alpha) + end + declare :opacity, [:color] + + # Makes a color more opaque. Takes a color and a number between 0 and 1, and + # returns a color with the opacity increased by that amount. + # + # @see #transparentize + # @example + # opacify(rgba(0, 0, 0, 0.5), 0.1) => rgba(0, 0, 0, 0.6) + # opacify(rgba(0, 0, 17, 0.8), 0.2) => #001 + # @overload opacify($color, $amount) + # @param $color [Sass::Script::Value::Color] + # @param $amount [Sass::Script::Value::Number] The amount to increase the + # opacity by, between 0 and 1 + # @return [Sass::Script::Value::Color] + # @raise [ArgumentError] if `$amount` is out of bounds, or either parameter + # is the wrong type + def opacify(color, amount) + _adjust(color, amount, :alpha, 0..1, :+) + end + declare :opacify, [:color, :amount] + + alias_method :fade_in, :opacify + declare :fade_in, [:color, :amount] + + # Makes a color more transparent. Takes a color and a number between 0 and + # 1, and returns a color with the opacity decreased by that amount. + # + # @see #opacify + # @example + # transparentize(rgba(0, 0, 0, 0.5), 0.1) => rgba(0, 0, 0, 0.4) + # transparentize(rgba(0, 0, 0, 0.8), 0.2) => rgba(0, 0, 0, 0.6) + # @overload transparentize($color, $amount) + # @param $color [Sass::Script::Value::Color] + # @param $amount [Sass::Script::Value::Number] The amount to decrease the + # opacity by, between 0 and 1 + # @return [Sass::Script::Value::Color] + # @raise [ArgumentError] if `$amount` is out of bounds, or either parameter + # is the wrong type + def transparentize(color, amount) + _adjust(color, amount, :alpha, 0..1, :-) + end + declare :transparentize, [:color, :amount] + + alias_method :fade_out, :transparentize + declare :fade_out, [:color, :amount] + + # Makes a color lighter. Takes a color and a number between `0%` and `100%`, + # and returns a color with the lightness increased by that amount. + # + # @see #darken + # @example + # lighten(hsl(0, 0%, 0%), 30%) => hsl(0, 0, 30) + # lighten(#800, 20%) => #e00 + # @overload lighten($color, $amount) + # @param $color [Sass::Script::Value::Color] + # @param $amount [Sass::Script::Value::Number] The amount to increase the + # lightness by, between `0%` and `100%` + # @return [Sass::Script::Value::Color] + # @raise [ArgumentError] if `$amount` is out of bounds, or either parameter + # is the wrong type + def lighten(color, amount) + _adjust(color, amount, :lightness, 0..100, :+, "%") + end + declare :lighten, [:color, :amount] + + # Makes a color darker. Takes a color and a number between 0% and 100%, and + # returns a color with the lightness decreased by that amount. + # + # @see #lighten + # @example + # darken(hsl(25, 100%, 80%), 30%) => hsl(25, 100%, 50%) + # darken(#800, 20%) => #200 + # @overload darken($color, $amount) + # @param $color [Sass::Script::Value::Color] + # @param $amount [Sass::Script::Value::Number] The amount to decrease the + # lightness by, between `0%` and `100%` + # @return [Sass::Script::Value::Color] + # @raise [ArgumentError] if `$amount` is out of bounds, or either parameter + # is the wrong type + def darken(color, amount) + _adjust(color, amount, :lightness, 0..100, :-, "%") + end + declare :darken, [:color, :amount] + + # Makes a color more saturated. Takes a color and a number between 0% and + # 100%, and returns a color with the saturation increased by that amount. + # + # @see #desaturate + # @example + # saturate(hsl(120, 30%, 90%), 20%) => hsl(120, 50%, 90%) + # saturate(#855, 20%) => #9e3f3f + # @overload saturate($color, $amount) + # @param $color [Sass::Script::Value::Color] + # @param $amount [Sass::Script::Value::Number] The amount to increase the + # saturation by, between `0%` and `100%` + # @return [Sass::Script::Value::Color] + # @raise [ArgumentError] if `$amount` is out of bounds, or either parameter + # is the wrong type + def saturate(color, amount = nil) + # Support the filter effects definition of saturate. + # https://dvcs.w3.org/hg/FXTF/raw-file/tip/filters/index.html + return identifier("saturate(#{color})") if amount.nil? + _adjust(color, amount, :saturation, 0..100, :+, "%") + end + declare :saturate, [:color, :amount] + declare :saturate, [:amount] + + # Makes a color less saturated. Takes a color and a number between 0% and + # 100%, and returns a color with the saturation decreased by that value. + # + # @see #saturate + # @example + # desaturate(hsl(120, 30%, 90%), 20%) => hsl(120, 10%, 90%) + # desaturate(#855, 20%) => #726b6b + # @overload desaturate($color, $amount) + # @param $color [Sass::Script::Value::Color] + # @param $amount [Sass::Script::Value::Number] The amount to decrease the + # saturation by, between `0%` and `100%` + # @return [Sass::Script::Value::Color] + # @raise [ArgumentError] if `$amount` is out of bounds, or either parameter + # is the wrong type + def desaturate(color, amount) + _adjust(color, amount, :saturation, 0..100, :-, "%") + end + declare :desaturate, [:color, :amount] + + # Changes the hue of a color. Takes a color and a number of degrees (usually + # between `-360deg` and `360deg`), and returns a color with the hue rotated + # along the color wheel by that amount. + # + # @example + # adjust-hue(hsl(120, 30%, 90%), 60deg) => hsl(180, 30%, 90%) + # adjust-hue(hsl(120, 30%, 90%), -60deg) => hsl(60, 30%, 90%) + # adjust-hue(#811, 45deg) => #886a11 + # @overload adjust_hue($color, $degrees) + # @param $color [Sass::Script::Value::Color] + # @param $degrees [Sass::Script::Value::Number] The number of degrees to + # rotate the hue + # @return [Sass::Script::Value::Color] + # @raise [ArgumentError] if either parameter is the wrong type + def adjust_hue(color, degrees) + assert_type color, :Color, :color + assert_type degrees, :Number, :degrees + color.with(:hue => color.hue + degrees.value) + end + declare :adjust_hue, [:color, :degrees] + + # Converts a color into the format understood by IE filters. + # + # @example + # ie-hex-str(#abc) => #FFAABBCC + # ie-hex-str(#3322BB) => #FF3322BB + # ie-hex-str(rgba(0, 255, 0, 0.5)) => #8000FF00 + # @overload ie_hex_str($color) + # @param $color [Sass::Script::Value::Color] + # @return [Sass::Script::Value::String] The IE-formatted string + # representation of the color + # @raise [ArgumentError] if `$color` isn't a color + def ie_hex_str(color) + assert_type color, :Color, :color + alpha = Sass::Util.round(color.alpha * 255).to_s(16).rjust(2, '0') + identifier("##{alpha}#{color.send(:hex_str)[1..-1]}".upcase) + end + declare :ie_hex_str, [:color] + + # Increases or decreases one or more properties of a color. This can change + # the red, green, blue, hue, saturation, value, and alpha properties. The + # properties are specified as keyword arguments, and are added to or + # subtracted from the color's current value for that property. + # + # All properties are optional. You can't specify both RGB properties + # (`$red`, `$green`, `$blue`) and HSL properties (`$hue`, `$saturation`, + # `$value`) at the same time. + # + # @example + # adjust-color(#102030, $blue: 5) => #102035 + # adjust-color(#102030, $red: -5, $blue: 5) => #0b2035 + # adjust-color(hsl(25, 100%, 80%), $lightness: -30%, $alpha: -0.4) => hsla(25, 100%, 50%, 0.6) + # @overload adjust_color($color, [$red], [$green], [$blue], [$hue], [$saturation], [$lightness], [$alpha]) + # @param $color [Sass::Script::Value::Color] + # @param $red [Sass::Script::Value::Number] The adjustment to make on the + # red component, between -255 and 255 inclusive + # @param $green [Sass::Script::Value::Number] The adjustment to make on the + # green component, between -255 and 255 inclusive + # @param $blue [Sass::Script::Value::Number] The adjustment to make on the + # blue component, between -255 and 255 inclusive + # @param $hue [Sass::Script::Value::Number] The adjustment to make on the + # hue component, in degrees + # @param $saturation [Sass::Script::Value::Number] The adjustment to make on + # the saturation component, between `-100%` and `100%` inclusive + # @param $lightness [Sass::Script::Value::Number] The adjustment to make on + # the lightness component, between `-100%` and `100%` inclusive + # @param $alpha [Sass::Script::Value::Number] The adjustment to make on the + # alpha component, between -1 and 1 inclusive + # @return [Sass::Script::Value::Color] + # @raise [ArgumentError] if any parameter is the wrong type or out-of + # bounds, or if RGB properties and HSL properties are adjusted at the + # same time + def adjust_color(color, kwargs) + assert_type color, :Color, :color + with = Sass::Util.map_hash( + "red" => [-255..255, ""], + "green" => [-255..255, ""], + "blue" => [-255..255, ""], + "hue" => nil, + "saturation" => [-100..100, "%"], + "lightness" => [-100..100, "%"], + "alpha" => [-1..1, ""] + ) do |name, (range, units)| + val = kwargs.delete(name) + next unless val + assert_type val, :Number, name + Sass::Util.check_range("$#{name}: Amount", range, val, units) if range + adjusted = color.send(name) + val.value + adjusted = [0, Sass::Util.restrict(adjusted, range)].max if range + [name.to_sym, adjusted] + end + + unless kwargs.empty? + name, val = kwargs.to_a.first + raise ArgumentError.new("Unknown argument $#{name} (#{val})") + end + + color.with(with) + end + declare :adjust_color, [:color], :var_kwargs => true + + # Fluidly scales one or more properties of a color. Unlike + # \{#adjust_color adjust-color}, which changes a color's properties by fixed + # amounts, \{#scale_color scale-color} fluidly changes them based on how + # high or low they already are. That means that lightening an already-light + # color with \{#scale_color scale-color} won't change the lightness much, + # but lightening a dark color by the same amount will change it more + # dramatically. This has the benefit of making `scale-color($color, ...)` + # have a similar effect regardless of what `$color` is. + # + # For example, the lightness of a color can be anywhere between `0%` and + # `100%`. If `scale-color($color, $lightness: 40%)` is called, the resulting + # color's lightness will be 40% of the way between its original lightness + # and 100. If `scale-color($color, $lightness: -40%)` is called instead, the + # lightness will be 40% of the way between the original and 0. + # + # This can change the red, green, blue, saturation, value, and alpha + # properties. The properties are specified as keyword arguments. All + # arguments should be percentages between `0%` and `100%`. + # + # All properties are optional. You can't specify both RGB properties + # (`$red`, `$green`, `$blue`) and HSL properties (`$saturation`, `$value`) + # at the same time. + # + # @example + # scale-color(hsl(120, 70%, 80%), $lightness: 50%) => hsl(120, 70%, 90%) + # scale-color(rgb(200, 150%, 170%), $green: -40%, $blue: 70%) => rgb(200, 90, 229) + # scale-color(hsl(200, 70%, 80%), $saturation: -90%, $alpha: -30%) => hsla(200, 7%, 80%, 0.7) + # @overload scale_color($color, [$red], [$green], [$blue], [$saturation], [$lightness], [$alpha]) + # @param $color [Sass::Script::Value::Color] + # @param $red [Sass::Script::Value::Number] + # @param $green [Sass::Script::Value::Number] + # @param $blue [Sass::Script::Value::Number] + # @param $saturation [Sass::Script::Value::Number] + # @param $lightness [Sass::Script::Value::Number] + # @param $alpha [Sass::Script::Value::Number] + # @return [Sass::Script::Value::Color] + # @raise [ArgumentError] if any parameter is the wrong type or out-of + # bounds, or if RGB properties and HSL properties are adjusted at the + # same time + def scale_color(color, kwargs) + assert_type color, :Color, :color + with = Sass::Util.map_hash( + "red" => 255, + "green" => 255, + "blue" => 255, + "saturation" => 100, + "lightness" => 100, + "alpha" => 1 + ) do |name, max| + val = kwargs.delete(name) + next unless val + assert_type val, :Number, name + assert_unit val, '%', name + Sass::Util.check_range("$#{name}: Amount", -100..100, val, '%') + + current = color.send(name) + scale = val.value / 100.0 + diff = scale > 0 ? max - current : current + [name.to_sym, current + diff * scale] + end + + unless kwargs.empty? + name, val = kwargs.to_a.first + raise ArgumentError.new("Unknown argument $#{name} (#{val})") + end + + color.with(with) + end + declare :scale_color, [:color], :var_kwargs => true + + # Changes one or more properties of a color. This can change the red, green, + # blue, hue, saturation, value, and alpha properties. The properties are + # specified as keyword arguments, and replace the color's current value for + # that property. + # + # All properties are optional. You can't specify both RGB properties + # (`$red`, `$green`, `$blue`) and HSL properties (`$hue`, `$saturation`, + # `$value`) at the same time. + # + # @example + # change-color(#102030, $blue: 5) => #102005 + # change-color(#102030, $red: 120, $blue: 5) => #782005 + # change-color(hsl(25, 100%, 80%), $lightness: 40%, $alpha: 0.8) => hsla(25, 100%, 40%, 0.8) + # @overload change_color($color, [$red], [$green], [$blue], [$hue], [$saturation], [$lightness], [$alpha]) + # @param $color [Sass::Script::Value::Color] + # @param $red [Sass::Script::Value::Number] The new red component for the + # color, within 0 and 255 inclusive + # @param $green [Sass::Script::Value::Number] The new green component for + # the color, within 0 and 255 inclusive + # @param $blue [Sass::Script::Value::Number] The new blue component for the + # color, within 0 and 255 inclusive + # @param $hue [Sass::Script::Value::Number] The new hue component for the + # color, in degrees + # @param $saturation [Sass::Script::Value::Number] The new saturation + # component for the color, between `0%` and `100%` inclusive + # @param $lightness [Sass::Script::Value::Number] The new lightness + # component for the color, within `0%` and `100%` inclusive + # @param $alpha [Sass::Script::Value::Number] The new alpha component for + # the color, within 0 and 1 inclusive + # @return [Sass::Script::Value::Color] + # @raise [ArgumentError] if any parameter is the wrong type or out-of + # bounds, or if RGB properties and HSL properties are adjusted at the + # same time + def change_color(color, kwargs) + assert_type color, :Color, :color + with = Sass::Util.map_hash( + 'red' => ['Red value', 0..255], + 'green' => ['Green value', 0..255], + 'blue' => ['Blue value', 0..255], + 'hue' => [], + 'saturation' => ['Saturation', 0..100, '%'], + 'lightness' => ['Lightness', 0..100, '%'], + 'alpha' => ['Alpha channel', 0..1] + ) do |name, (desc, range, unit)| + val = kwargs.delete(name) + next unless val + assert_type val, :Number, name + + if range + val = Sass::Util.check_range(desc, range, val, unit) + else + val = val.value + end + + [name.to_sym, val] + end + + unless kwargs.empty? + name, val = kwargs.to_a.first + raise ArgumentError.new("Unknown argument $#{name} (#{val})") + end + + color.with(with) + end + declare :change_color, [:color], :var_kwargs => true + + # Mixes two colors together. Specifically, takes the average of each of the + # RGB components, optionally weighted by the given percentage. The opacity + # of the colors is also considered when weighting the components. + # + # The weight specifies the amount of the first color that should be included + # in the returned color. The default, `50%`, means that half the first color + # and half the second color should be used. `25%` means that a quarter of + # the first color and three quarters of the second color should be used. + # + # @example + # mix(#f00, #00f) => #7f007f + # mix(#f00, #00f, 25%) => #3f00bf + # mix(rgba(255, 0, 0, 0.5), #00f) => rgba(63, 0, 191, 0.75) + # @overload mix($color1, $color2, $weight: 50%) + # @param $color1 [Sass::Script::Value::Color] + # @param $color2 [Sass::Script::Value::Color] + # @param $weight [Sass::Script::Value::Number] The relative weight of each + # color. Closer to `100%` gives more weight to `$color1`, closer to `0%` + # gives more weight to `$color2` + # @return [Sass::Script::Value::Color] + # @raise [ArgumentError] if `$weight` is out of bounds or any parameter is + # the wrong type + def mix(color1, color2, weight = number(50)) + assert_type color1, :Color, :color1 + assert_type color2, :Color, :color2 + assert_type weight, :Number, :weight + + Sass::Util.check_range("Weight", 0..100, weight, '%') + + # This algorithm factors in both the user-provided weight (w) and the + # difference between the alpha values of the two colors (a) to decide how + # to perform the weighted average of the two RGB values. + # + # It works by first normalizing both parameters to be within [-1, 1], + # where 1 indicates "only use color1", -1 indicates "only use color2", and + # all values in between indicated a proportionately weighted average. + # + # Once we have the normalized variables w and a, we apply the formula + # (w + a)/(1 + w*a) to get the combined weight (in [-1, 1]) of color1. + # This formula has two especially nice properties: + # + # * When either w or a are -1 or 1, the combined weight is also that number + # (cases where w * a == -1 are undefined, and handled as a special case). + # + # * When a is 0, the combined weight is w, and vice versa. + # + # Finally, the weight of color1 is renormalized to be within [0, 1] + # and the weight of color2 is given by 1 minus the weight of color1. + p = (weight.value / 100.0).to_f + w = p * 2 - 1 + a = color1.alpha - color2.alpha + + w1 = ((w * a == -1 ? w : (w + a) / (1 + w * a)) + 1) / 2.0 + w2 = 1 - w1 + + rgba = color1.rgb.zip(color2.rgb).map {|v1, v2| v1 * w1 + v2 * w2} + rgba << color1.alpha * p + color2.alpha * (1 - p) + rgb_color(*rgba) + end + declare :mix, [:color1, :color2] + declare :mix, [:color1, :color2, :weight] + + # Converts a color to grayscale. This is identical to `desaturate(color, + # 100%)`. + # + # @see #desaturate + # @overload grayscale($color) + # @param $color [Sass::Script::Value::Color] + # @return [Sass::Script::Value::Color] + # @raise [ArgumentError] if `$color` isn't a color + def grayscale(color) + if color.is_a?(Sass::Script::Value::Number) + return identifier("grayscale(#{color})") + end + desaturate color, number(100) + end + declare :grayscale, [:color] + + # Returns the complement of a color. This is identical to `adjust-hue(color, + # 180deg)`. + # + # @see #adjust_hue #adjust-hue + # @overload complement($color) + # @param $color [Sass::Script::Value::Color] + # @return [Sass::Script::Value::Color] + # @raise [ArgumentError] if `$color` isn't a color + def complement(color) + adjust_hue color, number(180) + end + declare :complement, [:color] + + # Returns the inverse (negative) of a color. The red, green, and blue values + # are inverted, while the opacity is left alone. + # + # @overload invert($color) + # @param $color [Sass::Script::Value::Color] + # @overload invert($color, $weight: 100%) + # @param $color [Sass::Script::Value::Color] + # @param $weight [Sass::Script::Value::Number] The relative weight of the + # color color's inverse + # @return [Sass::Script::Value::Color] + # @raise [ArgumentError] if `$color` isn't a color or `$weight` + # isn't a percentage between 0% and 100% + def invert(color, weight = number(100)) + if color.is_a?(Sass::Script::Value::Number) + return identifier("invert(#{color})") + end + + assert_type color, :Color, :color + inv = color.with( + :red => (255 - color.red), + :green => (255 - color.green), + :blue => (255 - color.blue)) + + mix(inv, color, weight) + end + declare :invert, [:color] + declare :invert, [:color, :weight] + + # Removes quotes from a string. If the string is already unquoted, this will + # return it unmodified. + # + # @see #quote + # @example + # unquote("foo") => foo + # unquote(foo) => foo + # @overload unquote($string) + # @param $string [Sass::Script::Value::String] + # @return [Sass::Script::Value::String] + # @raise [ArgumentError] if `$string` isn't a string + def unquote(string) + unless string.is_a?(Sass::Script::Value::String) + # Don't warn multiple times for the same source line. + $_sass_warned_for_unquote ||= Set.new + frame = environment.stack.frames.last + key = [frame.filename, frame.line] if frame + return string if frame && $_sass_warned_for_unquote.include?(key) + $_sass_warned_for_unquote << key if frame + + Sass::Util.sass_warn(< "foo" + # quote(foo) => "foo" + # @overload quote($string) + # @param $string [Sass::Script::Value::String] + # @return [Sass::Script::Value::String] + # @raise [ArgumentError] if `$string` isn't a string + def quote(string) + assert_type string, :String, :string + if string.type != :string + quoted_string(string.value) + else + string + end + end + declare :quote, [:string] + + # Returns the number of characters in a string. + # + # @example + # str-length("foo") => 3 + # @overload str_length($string) + # @param $string [Sass::Script::Value::String] + # @return [Sass::Script::Value::Number] + # @raise [ArgumentError] if `$string` isn't a string + def str_length(string) + assert_type string, :String, :string + number(string.value.size) + end + declare :str_length, [:string] + + # Inserts `$insert` into `$string` at `$index`. + # + # Note that unlike some languages, the first character in a Sass string is + # number 1, the second number 2, and so forth. + # + # @example + # str-insert("abcd", "X", 1) => "Xabcd" + # str-insert("abcd", "X", 4) => "abcXd" + # str-insert("abcd", "X", 5) => "abcdX" + # + # @overload str_insert($string, $insert, $index) + # @param $string [Sass::Script::Value::String] + # @param $insert [Sass::Script::Value::String] + # @param $index [Sass::Script::Value::Number] The position at which + # `$insert` will be inserted. Negative indices count from the end of + # `$string`. An index that's outside the bounds of the string will insert + # `$insert` at the front or back of the string + # @return [Sass::Script::Value::String] The result string. This will be + # quoted if and only if `$string` was quoted + # @raise [ArgumentError] if any parameter is the wrong type + def str_insert(original, insert, index) + assert_type original, :String, :string + assert_type insert, :String, :insert + assert_integer index, :index + assert_unit index, nil, :index + insertion_point = if index.to_i > 0 + [index.to_i - 1, original.value.size].min + else + [index.to_i, -original.value.size - 1].max + end + result = original.value.dup.insert(insertion_point, insert.value) + Sass::Script::Value::String.new(result, original.type) + end + declare :str_insert, [:string, :insert, :index] + + # Returns the index of the first occurrence of `$substring` in `$string`. If + # there is no such occurrence, returns `null`. + # + # Note that unlike some languages, the first character in a Sass string is + # number 1, the second number 2, and so forth. + # + # @example + # str-index(abcd, a) => 1 + # str-index(abcd, ab) => 1 + # str-index(abcd, X) => null + # str-index(abcd, c) => 3 + # + # @overload str_index($string, $substring) + # @param $string [Sass::Script::Value::String] + # @param $substring [Sass::Script::Value::String] + # @return [Sass::Script::Value::Number, Sass::Script::Value::Null] + # @raise [ArgumentError] if any parameter is the wrong type + def str_index(string, substring) + assert_type string, :String, :string + assert_type substring, :String, :substring + index = string.value.index(substring.value) + index ? number(index + 1) : null + end + declare :str_index, [:string, :substring] + + # Extracts a substring from `$string`. The substring will begin at index + # `$start-at` and ends at index `$end-at`. + # + # Note that unlike some languages, the first character in a Sass string is + # number 1, the second number 2, and so forth. + # + # @example + # str-slice("abcd", 2, 3) => "bc" + # str-slice("abcd", 2) => "bcd" + # str-slice("abcd", -3, -2) => "bc" + # str-slice("abcd", 2, -2) => "bc" + # + # @overload str_slice($string, $start-at, $end-at: -1) + # @param $start-at [Sass::Script::Value::Number] The index of the first + # character of the substring. If this is negative, it counts from the end + # of `$string` + # @param $end-at [Sass::Script::Value::Number] The index of the last + # character of the substring. If this is negative, it counts from the end + # of `$string`. Defaults to -1 + # @return [Sass::Script::Value::String] The substring. This will be quoted + # if and only if `$string` was quoted + # @raise [ArgumentError] if any parameter is the wrong type + def str_slice(string, start_at, end_at = nil) + assert_type string, :String, :string + assert_unit start_at, nil, "start-at" + + end_at = number(-1) if end_at.nil? + assert_unit end_at, nil, "end-at" + + return Sass::Script::Value::String.new("", string.type) if end_at.value == 0 + s = start_at.value > 0 ? start_at.value - 1 : start_at.value + e = end_at.value > 0 ? end_at.value - 1 : end_at.value + s = string.value.length + s if s < 0 + s = 0 if s < 0 + e = string.value.length + e if e < 0 + return Sass::Script::Value::String.new("", string.type) if e < 0 + extracted = string.value.slice(s..e) + Sass::Script::Value::String.new(extracted || "", string.type) + end + declare :str_slice, [:string, :start_at] + declare :str_slice, [:string, :start_at, :end_at] + + # Converts a string to upper case. + # + # @example + # to-upper-case(abcd) => ABCD + # + # @overload to_upper_case($string) + # @param $string [Sass::Script::Value::String] + # @return [Sass::Script::Value::String] + # @raise [ArgumentError] if `$string` isn't a string + def to_upper_case(string) + assert_type string, :String, :string + Sass::Script::Value::String.new(Sass::Util.upcase(string.value), string.type) + end + declare :to_upper_case, [:string] + + # Convert a string to lower case, + # + # @example + # to-lower-case(ABCD) => abcd + # + # @overload to_lower_case($string) + # @param $string [Sass::Script::Value::String] + # @return [Sass::Script::Value::String] + # @raise [ArgumentError] if `$string` isn't a string + def to_lower_case(string) + assert_type string, :String, :string + Sass::Script::Value::String.new(Sass::Util.downcase(string.value), string.type) + end + declare :to_lower_case, [:string] + + # Returns the type of a value. + # + # @example + # type-of(100px) => number + # type-of(asdf) => string + # type-of("asdf") => string + # type-of(true) => bool + # type-of(#fff) => color + # type-of(blue) => color + # type-of(null) => null + # type-of(a b c) => list + # type-of((a: 1, b: 2)) => map + # type-of(get-function("foo")) => function + # + # @overload type_of($value) + # @param $value [Sass::Script::Value::Base] The value to inspect + # @return [Sass::Script::Value::String] The unquoted string name of the + # value's type + def type_of(value) + value.check_deprecated_interp if value.is_a?(Sass::Script::Value::String) + identifier(value.class.name.gsub(/Sass::Script::Value::/, '').downcase) + end + declare :type_of, [:value] + + # Returns whether a feature exists in the current Sass runtime. + # + # The following features are supported: + # + # * `global-variable-shadowing` indicates that a local variable will shadow + # a global variable unless `!global` is used. + # + # * `extend-selector-pseudoclass` indicates that `@extend` will reach into + # selector pseudoclasses like `:not`. + # + # * `units-level-3` indicates full support for unit arithmetic using units + # defined in the [Values and Units Level 3][] spec. + # + # [Values and Units Level 3]: http://www.w3.org/TR/css3-values/ + # + # * `at-error` indicates that the Sass `@error` directive is supported. + # + # * `custom-property` indicates that the [Custom Properties Level 1][] spec + # is supported. This means that custom properties are parsed statically, + # with only interpolation treated as SassScript. + # + # [Custom Properties Level 1]: https://www.w3.org/TR/css-variables-1/ + # + # @example + # feature-exists(some-feature-that-exists) => true + # feature-exists(what-is-this-i-dont-know) => false + # + # @overload feature_exists($feature) + # @param $feature [Sass::Script::Value::String] The name of the feature + # @return [Sass::Script::Value::Bool] Whether the feature is supported in this version of Sass + # @raise [ArgumentError] if `$feature` isn't a string + def feature_exists(feature) + assert_type feature, :String, :feature + bool(Sass.has_feature?(feature.value)) + end + declare :feature_exists, [:feature] + + # Returns a reference to a function for later invocation with the `call()` function. + # + # If `$css` is `false`, the function reference may refer to a function + # defined in your stylesheet or built-in to the host environment. If it's + # `true` it will refer to a plain-CSS function. + # + # @example + # get-function("rgb") + # + # @function myfunc { @return "something"; } + # get-function("myfunc") + # + # @overload get_function($name, $css: false) + # @param name [Sass::Script::Value::String] The name of the function being referenced. + # @param css [Sass::Script::Value::Bool] Whether to get a plain CSS function. + # + # @return [Sass::Script::Value::Function] A function reference. + def get_function(name, kwargs = {}) + assert_type name, :String, :name + + css = if kwargs.has_key?("css") + v = kwargs.delete("css") + assert_type v, :Bool, :css + v.value + else + false + end + + if kwargs.any? + raise ArgumentError.new("Illegal keyword argument '#{kwargs.keys.first}'") + end + + if css + return Sass::Script::Value::Function.new( + Sass::Callable.new(name.value, nil, nil, nil, nil, nil, "function", :css)) + end + + callable = environment.caller.function(name.value) || + (Sass::Script::Functions.callable?(name.value.tr("-", "_")) && + Sass::Callable.new(name.value, nil, nil, nil, nil, nil, "function", :builtin)) + + if callable + Sass::Script::Value::Function.new(callable) + else + raise Sass::SyntaxError.new("Function not found: #{name}") + end + end + declare :get_function, [:name], :var_kwargs => true + + # Returns the unit(s) associated with a number. Complex units are sorted in + # alphabetical order by numerator and denominator. + # + # @example + # unit(100) => "" + # unit(100px) => "px" + # unit(3em) => "em" + # unit(10px * 5em) => "em*px" + # unit(10px * 5em / 30cm / 1rem) => "em*px/cm*rem" + # @overload unit($number) + # @param $number [Sass::Script::Value::Number] + # @return [Sass::Script::Value::String] The unit(s) of the number, as a + # quoted string + # @raise [ArgumentError] if `$number` isn't a number + def unit(number) + assert_type number, :Number, :number + quoted_string(number.unit_str) + end + declare :unit, [:number] + + # Returns whether a number has units. + # + # @example + # unitless(100) => true + # unitless(100px) => false + # @overload unitless($number) + # @param $number [Sass::Script::Value::Number] + # @return [Sass::Script::Value::Bool] + # @raise [ArgumentError] if `$number` isn't a number + def unitless(number) + assert_type number, :Number, :number + bool(number.unitless?) + end + declare :unitless, [:number] + + # Returns whether two numbers can added, subtracted, or compared. + # + # @example + # comparable(2px, 1px) => true + # comparable(100px, 3em) => false + # comparable(10cm, 3mm) => true + # @overload comparable($number1, $number2) + # @param $number1 [Sass::Script::Value::Number] + # @param $number2 [Sass::Script::Value::Number] + # @return [Sass::Script::Value::Bool] + # @raise [ArgumentError] if either parameter is the wrong type + def comparable(number1, number2) + assert_type number1, :Number, :number1 + assert_type number2, :Number, :number2 + bool(number1.comparable_to?(number2)) + end + declare :comparable, [:number1, :number2] + + # Converts a unitless number to a percentage. + # + # @example + # percentage(0.2) => 20% + # percentage(100px / 50px) => 200% + # @overload percentage($number) + # @param $number [Sass::Script::Value::Number] + # @return [Sass::Script::Value::Number] + # @raise [ArgumentError] if `$number` isn't a unitless number + def percentage(number) + unless number.is_a?(Sass::Script::Value::Number) && number.unitless? + raise ArgumentError.new("$number: #{number.inspect} is not a unitless number") + end + number(number.value * 100, '%') + end + declare :percentage, [:number] + + # Rounds a number to the nearest whole number. + # + # @example + # round(10.4px) => 10px + # round(10.6px) => 11px + # @overload round($number) + # @param $number [Sass::Script::Value::Number] + # @return [Sass::Script::Value::Number] + # @raise [ArgumentError] if `$number` isn't a number + def round(number) + numeric_transformation(number) {|n| Sass::Util.round(n)} + end + declare :round, [:number] + + # Rounds a number up to the next whole number. + # + # @example + # ceil(10.4px) => 11px + # ceil(10.6px) => 11px + # @overload ceil($number) + # @param $number [Sass::Script::Value::Number] + # @return [Sass::Script::Value::Number] + # @raise [ArgumentError] if `$number` isn't a number + def ceil(number) + numeric_transformation(number) {|n| n.ceil} + end + declare :ceil, [:number] + + # Rounds a number down to the previous whole number. + # + # @example + # floor(10.4px) => 10px + # floor(10.6px) => 10px + # @overload floor($number) + # @param $number [Sass::Script::Value::Number] + # @return [Sass::Script::Value::Number] + # @raise [ArgumentError] if `$number` isn't a number + def floor(number) + numeric_transformation(number) {|n| n.floor} + end + declare :floor, [:number] + + # Returns the absolute value of a number. + # + # @example + # abs(10px) => 10px + # abs(-10px) => 10px + # @overload abs($number) + # @param $number [Sass::Script::Value::Number] + # @return [Sass::Script::Value::Number] + # @raise [ArgumentError] if `$number` isn't a number + def abs(number) + numeric_transformation(number) {|n| n.abs} + end + declare :abs, [:number] + + # Finds the minimum of several numbers. This function takes any number of + # arguments. + # + # @example + # min(1px, 4px) => 1px + # min(5em, 3em, 4em) => 3em + # @overload min($numbers...) + # @param $numbers [[Sass::Script::Value::Number]] + # @return [Sass::Script::Value::Number] + # @raise [ArgumentError] if any argument isn't a number, or if not all of + # the arguments have comparable units + def min(*numbers) + numbers.each {|n| assert_type n, :Number} + numbers.inject {|min, num| min.lt(num).to_bool ? min : num} + end + declare :min, [], :var_args => :true + + # Finds the maximum of several numbers. This function takes any number of + # arguments. + # + # @example + # max(1px, 4px) => 4px + # max(5em, 3em, 4em) => 5em + # @overload max($numbers...) + # @param $numbers [[Sass::Script::Value::Number]] + # @return [Sass::Script::Value::Number] + # @raise [ArgumentError] if any argument isn't a number, or if not all of + # the arguments have comparable units + def max(*values) + values.each {|v| assert_type v, :Number} + values.inject {|max, val| max.gt(val).to_bool ? max : val} + end + declare :max, [], :var_args => :true + + # Return the length of a list. + # + # This can return the number of pairs in a map as well. + # + # @example + # length(10px) => 1 + # length(10px 20px 30px) => 3 + # length((width: 10px, height: 20px)) => 2 + # @overload length($list) + # @param $list [Sass::Script::Value::Base] + # @return [Sass::Script::Value::Number] + def length(list) + number(list.to_a.size) + end + declare :length, [:list] + + # Return a new list, based on the list provided, but with the nth + # element changed to the value given. + # + # Note that unlike some languages, the first item in a Sass list is number + # 1, the second number 2, and so forth. + # + # Negative index values address elements in reverse order, starting with the last element + # in the list. + # + # @example + # set-nth($list: 10px 20px 30px, $n: 2, $value: -20px) => 10px -20px 30px + # @overload set-nth($list, $n, $value) + # @param $list [Sass::Script::Value::Base] The list that will be copied, having the element + # at index `$n` changed. + # @param $n [Sass::Script::Value::Number] The index of the item to set. + # Negative indices count from the end of the list. + # @param $value [Sass::Script::Value::Base] The new value at index `$n`. + # @return [Sass::Script::Value::List] + # @raise [ArgumentError] if `$n` isn't an integer between 1 and the length + # of `$list` + def set_nth(list, n, value) + assert_type n, :Number, :n + Sass::Script::Value::List.assert_valid_index(list, n) + index = n.to_i > 0 ? n.to_i - 1 : n.to_i + new_list = list.to_a.dup + new_list[index] = value + list.with_contents(new_list) + end + declare :set_nth, [:list, :n, :value] + + # Gets the nth item in a list. + # + # Note that unlike some languages, the first item in a Sass list is number + # 1, the second number 2, and so forth. + # + # This can return the nth pair in a map as well. + # + # Negative index values address elements in reverse order, starting with the last element in + # the list. + # + # @example + # nth(10px 20px 30px, 1) => 10px + # nth((Helvetica, Arial, sans-serif), 3) => sans-serif + # nth((width: 10px, length: 20px), 2) => length, 20px + # @overload nth($list, $n) + # @param $list [Sass::Script::Value::Base] + # @param $n [Sass::Script::Value::Number] The index of the item to get. + # Negative indices count from the end of the list. + # @return [Sass::Script::Value::Base] + # @raise [ArgumentError] if `$n` isn't an integer between 1 and the length + # of `$list` + def nth(list, n) + assert_type n, :Number, :n + Sass::Script::Value::List.assert_valid_index(list, n) + + index = n.to_i > 0 ? n.to_i - 1 : n.to_i + list.to_a[index] + end + declare :nth, [:list, :n] + + # Joins together two lists into one. + # + # Unless `$separator` is passed, if one list is comma-separated and one is + # space-separated, the first parameter's separator is used for the resulting + # list. If both lists have fewer than two items, spaces are used for the + # resulting list. + # + # Unless `$bracketed` is passed, the resulting list is bracketed if the + # first parameter is. + # + # Like all list functions, `join()` returns a new list rather than modifying + # its arguments in place. + # + # @example + # join(10px 20px, 30px 40px) => 10px 20px 30px 40px + # join((blue, red), (#abc, #def)) => blue, red, #abc, #def + # join(10px, 20px) => 10px 20px + # join(10px, 20px, comma) => 10px, 20px + # join((blue, red), (#abc, #def), space) => blue red #abc #def + # join([10px], 20px) => [10px 20px] + # @overload join($list1, $list2, $separator: auto, $bracketed: auto) + # @param $list1 [Sass::Script::Value::Base] + # @param $list2 [Sass::Script::Value::Base] + # @param $separator [Sass::Script::Value::String] The list separator to use. + # If this is `comma` or `space`, that separator will be used. If this is + # `auto` (the default), the separator is determined as explained above. + # @param $bracketed [Sass::Script::Value::Base] Whether the resulting list + # will be bracketed. If this is `auto` (the default), the separator is + # determined as explained above. + # @return [Sass::Script::Value::List] + def join(list1, list2, + separator = identifier("auto"), bracketed = identifier("auto"), + kwargs = nil, *rest) + if separator.is_a?(Hash) + kwargs = separator + separator = identifier("auto") + elsif bracketed.is_a?(Hash) + kwargs = bracketed + bracketed = identifier("auto") + elsif rest.last.is_a?(Hash) + rest.unshift kwargs + kwargs = rest.pop + end + + unless rest.empty? + # Add 4 to rest.length because we don't want to count the kwargs hash, + # which is always passed. + raise ArgumentError.new("wrong number of arguments (#{rest.length + 4} for 2..4)") + end + + if kwargs + separator = kwargs.delete("separator") || separator + bracketed = kwargs.delete("bracketed") || bracketed + + unless kwargs.empty? + name, val = kwargs.to_a.first + raise ArgumentError.new("Unknown argument $#{name} (#{val})") + end + end + + assert_type separator, :String, :separator + unless %w(auto space comma).include?(separator.value) + raise ArgumentError.new("Separator name must be space, comma, or auto") + end + + list(list1.to_a + list2.to_a, + separator: + if separator.value == 'auto' + list1.separator || list2.separator || :space + else + separator.value.to_sym + end, + bracketed: + if bracketed.is_a?(Sass::Script::Value::String) && bracketed.value == 'auto' + list1.bracketed + else + bracketed.to_bool + end) + end + # We don't actually take variable arguments or keyword arguments, but this + # is the best way to take either `$separator` or `$bracketed` as keywords + # without complaining about the other missing. + declare :join, [:list1, :list2], :var_args => true, :var_kwargs => true + + # Appends a single value onto the end of a list. + # + # Unless the `$separator` argument is passed, if the list had only one item, + # the resulting list will be space-separated. + # + # Like all list functions, `append()` returns a new list rather than + # modifying its argument in place. + # + # @example + # append(10px 20px, 30px) => 10px 20px 30px + # append((blue, red), green) => blue, red, green + # append(10px 20px, 30px 40px) => 10px 20px (30px 40px) + # append(10px, 20px, comma) => 10px, 20px + # append((blue, red), green, space) => blue red green + # @overload append($list, $val, $separator: auto) + # @param $list [Sass::Script::Value::Base] + # @param $val [Sass::Script::Value::Base] + # @param $separator [Sass::Script::Value::String] The list separator to use. + # If this is `comma` or `space`, that separator will be used. If this is + # `auto` (the default), the separator is determined as explained above. + # @return [Sass::Script::Value::List] + def append(list, val, separator = identifier("auto")) + assert_type separator, :String, :separator + unless %w(auto space comma).include?(separator.value) + raise ArgumentError.new("Separator name must be space, comma, or auto") + end + list.with_contents(list.to_a + [val], + separator: + if separator.value == 'auto' + list.separator || :space + else + separator.value.to_sym + end) + end + declare :append, [:list, :val] + declare :append, [:list, :val, :separator] + + # Combines several lists into a single multidimensional list. The nth value + # of the resulting list is a space separated list of the source lists' nth + # values. + # + # The length of the resulting list is the length of the + # shortest list. + # + # @example + # zip(1px 1px 3px, solid dashed solid, red green blue) + # => 1px solid red, 1px dashed green, 3px solid blue + # @overload zip($lists...) + # @param $lists [[Sass::Script::Value::Base]] + # @return [Sass::Script::Value::List] + def zip(*lists) + length = nil + values = [] + lists.each do |list| + array = list.to_a + values << array.dup + length = length.nil? ? array.length : [length, array.length].min + end + values.each do |value| + value.slice!(length) + end + new_list_value = values.first.zip(*values[1..-1]) + list(new_list_value.map {|list| list(list, :space)}, :comma) + end + declare :zip, [], :var_args => true + + # Returns the position of a value within a list. If the value isn't found, + # returns `null` instead. + # + # Note that unlike some languages, the first item in a Sass list is number + # 1, the second number 2, and so forth. + # + # This can return the position of a pair in a map as well. + # + # @example + # index(1px solid red, solid) => 2 + # index(1px solid red, dashed) => null + # index((width: 10px, height: 20px), (height 20px)) => 2 + # @overload index($list, $value) + # @param $list [Sass::Script::Value::Base] + # @param $value [Sass::Script::Value::Base] + # @return [Sass::Script::Value::Number, Sass::Script::Value::Null] The + # 1-based index of `$value` in `$list`, or `null` + def index(list, value) + index = list.to_a.index {|e| e.eq(value).to_bool} + index ? number(index + 1) : null + end + declare :index, [:list, :value] + + # Returns the separator of a list. If the list doesn't have a separator due + # to having fewer than two elements, returns `space`. + # + # @example + # list-separator(1px 2px 3px) => space + # list-separator(1px, 2px, 3px) => comma + # list-separator('foo') => space + # @overload list_separator($list) + # @param $list [Sass::Script::Value::Base] + # @return [Sass::Script::Value::String] `comma` or `space` + def list_separator(list) + identifier((list.separator || :space).to_s) + end + declare :list_separator, [:list] + + # Returns whether a list uses square brackets. + # + # @example + # is-bracketed(1px 2px 3px) => false + # is-bracketed([1px, 2px, 3px]) => true + # @overload is_bracketed($list) + # @param $list [Sass::Script::Value::Base] + # @return [Sass::Script::Value::Bool] + def is_bracketed(list) + bool(list.bracketed) + end + declare :is_bracketed, [:list] + + # Returns the value in a map associated with the given key. If the map + # doesn't have such a key, returns `null`. + # + # @example + # map-get(("foo": 1, "bar": 2), "foo") => 1 + # map-get(("foo": 1, "bar": 2), "bar") => 2 + # map-get(("foo": 1, "bar": 2), "baz") => null + # @overload map_get($map, $key) + # @param $map [Sass::Script::Value::Map] + # @param $key [Sass::Script::Value::Base] + # @return [Sass::Script::Value::Base] The value indexed by `$key`, or `null` + # if the map doesn't contain the given key + # @raise [ArgumentError] if `$map` is not a map + def map_get(map, key) + assert_type map, :Map, :map + map.to_h[key] || null + end + declare :map_get, [:map, :key] + + # Merges two maps together into a new map. Keys in `$map2` will take + # precedence over keys in `$map1`. + # + # This is the best way to add new values to a map. + # + # All keys in the returned map that also appear in `$map1` will have the + # same order as in `$map1`. New keys from `$map2` will be placed at the end + # of the map. + # + # Like all map functions, `map-merge()` returns a new map rather than + # modifying its arguments in place. + # + # @example + # map-merge(("foo": 1), ("bar": 2)) => ("foo": 1, "bar": 2) + # map-merge(("foo": 1, "bar": 2), ("bar": 3)) => ("foo": 1, "bar": 3) + # @overload map_merge($map1, $map2) + # @param $map1 [Sass::Script::Value::Map] + # @param $map2 [Sass::Script::Value::Map] + # @return [Sass::Script::Value::Map] + # @raise [ArgumentError] if either parameter is not a map + def map_merge(map1, map2) + assert_type map1, :Map, :map1 + assert_type map2, :Map, :map2 + map(map1.to_h.merge(map2.to_h)) + end + declare :map_merge, [:map1, :map2] + + # Returns a new map with keys removed. + # + # Like all map functions, `map-merge()` returns a new map rather than + # modifying its arguments in place. + # + # @example + # map-remove(("foo": 1, "bar": 2), "bar") => ("foo": 1) + # map-remove(("foo": 1, "bar": 2, "baz": 3), "bar", "baz") => ("foo": 1) + # map-remove(("foo": 1, "bar": 2), "baz") => ("foo": 1, "bar": 2) + # @overload map_remove($map, $keys...) + # @param $map [Sass::Script::Value::Map] + # @param $keys [[Sass::Script::Value::Base]] + # @return [Sass::Script::Value::Map] + # @raise [ArgumentError] if `$map` is not a map + def map_remove(map, *keys) + assert_type map, :Map, :map + hash = map.to_h.dup + hash.delete_if {|key, _| keys.include?(key)} + map(hash) + end + declare :map_remove, [:map, :key], :var_args => true + + # Returns a list of all keys in a map. + # + # @example + # map-keys(("foo": 1, "bar": 2)) => "foo", "bar" + # @overload map_keys($map) + # @param $map [Map] + # @return [List] the list of keys, comma-separated + # @raise [ArgumentError] if `$map` is not a map + def map_keys(map) + assert_type map, :Map, :map + list(map.to_h.keys, :comma) + end + declare :map_keys, [:map] + + # Returns a list of all values in a map. This list may include duplicate + # values, if multiple keys have the same value. + # + # @example + # map-values(("foo": 1, "bar": 2)) => 1, 2 + # map-values(("foo": 1, "bar": 2, "baz": 1)) => 1, 2, 1 + # @overload map_values($map) + # @param $map [Map] + # @return [List] the list of values, comma-separated + # @raise [ArgumentError] if `$map` is not a map + def map_values(map) + assert_type map, :Map, :map + list(map.to_h.values, :comma) + end + declare :map_values, [:map] + + # Returns whether a map has a value associated with a given key. + # + # @example + # map-has-key(("foo": 1, "bar": 2), "foo") => true + # map-has-key(("foo": 1, "bar": 2), "baz") => false + # @overload map_has_key($map, $key) + # @param $map [Sass::Script::Value::Map] + # @param $key [Sass::Script::Value::Base] + # @return [Sass::Script::Value::Bool] + # @raise [ArgumentError] if `$map` is not a map + def map_has_key(map, key) + assert_type map, :Map, :map + bool(map.to_h.has_key?(key)) + end + declare :map_has_key, [:map, :key] + + # Returns the map of named arguments passed to a function or mixin that + # takes a variable argument list. The argument names are strings, and they + # do not contain the leading `$`. + # + # @example + # @mixin foo($args...) { + # @debug keywords($args); //=> (arg1: val, arg2: val) + # } + # + # @include foo($arg1: val, $arg2: val); + # @overload keywords($args) + # @param $args [Sass::Script::Value::ArgList] + # @return [Sass::Script::Value::Map] + # @raise [ArgumentError] if `$args` isn't a variable argument list + def keywords(args) + assert_type args, :ArgList, :args + map(Sass::Util.map_keys(args.keywords.as_stored) {|k| Sass::Script::Value::String.new(k)}) + end + declare :keywords, [:args] + + # Returns one of two values, depending on whether or not `$condition` is + # true. Just like in `@if`, all values other than `false` and `null` are + # considered to be true. + # + # @example + # if(true, 1px, 2px) => 1px + # if(false, 1px, 2px) => 2px + # @overload if($condition, $if-true, $if-false) + # @param $condition [Sass::Script::Value::Base] Whether the `$if-true` or + # `$if-false` will be returned + # @param $if-true [Sass::Script::Tree::Node] + # @param $if-false [Sass::Script::Tree::Node] + # @return [Sass::Script::Value::Base] `$if-true` or `$if-false` + def if(condition, if_true, if_false) + if condition.to_bool + perform(if_true) + else + perform(if_false) + end + end + declare :if, [:condition, :"&if_true", :"&if_false"] + + # Returns a unique CSS identifier. The identifier is returned as an unquoted + # string. The identifier returned is only guaranteed to be unique within the + # scope of a single Sass run. + # + # @overload unique_id() + # @return [Sass::Script::Value::String] + def unique_id + generator = Sass::Script::Functions.random_number_generator + Thread.current[:sass_last_unique_id] ||= generator.rand(36**8) + # avoid the temptation of trying to guess the next unique value. + value = (Thread.current[:sass_last_unique_id] += (generator.rand(10) + 1)) + # the u makes this a legal identifier if it would otherwise start with a number. + identifier("u" + value.to_s(36).rjust(8, '0')) + end + declare :unique_id, [] + + # Dynamically calls a function. This can call user-defined + # functions, built-in functions, or plain CSS functions. It will + # pass along all arguments, including keyword arguments, to the + # called function. + # + # @example + # call(rgb, 10, 100, 255) => #0a64ff + # call(scale-color, #0a64ff, $lightness: -10%) => #0058ef + # + # $fn: nth; + # call($fn, (a b c), 2) => b + # + # @overload call($function, $args...) + # @param $function [Sass::Script::Value::Function] The function to call. + def call(name, *args) + unless name.is_a?(Sass::Script::Value::String) || + name.is_a?(Sass::Script::Value::Function) + assert_type name, :Function, :function + end + if name.is_a?(Sass::Script::Value::String) + name = if function_exists(name).to_bool + get_function(name) + else + get_function(name, "css" => bool(true)) + end + Sass::Util.sass_warn(< true, :var_kwargs => true + + # This function only exists as a workaround for IE7's [`content: + # counter` bug](http://jes.st/2013/ie7s-css-breaking-content-counter-bug/). + # It works identically to any other plain-CSS function, except it + # avoids adding spaces between the argument commas. + # + # @example + # counter(item, ".") => counter(item,".") + # @overload counter($args...) + # @return [Sass::Script::Value::String] + def counter(*args) + identifier("counter(#{args.map {|a| a.to_s(options)}.join(',')})") + end + declare :counter, [], :var_args => true + + # This function only exists as a workaround for IE7's [`content: + # counter` bug](http://jes.st/2013/ie7s-css-breaking-content-counter-bug/). + # It works identically to any other plain-CSS function, except it + # avoids adding spaces between the argument commas. + # + # @example + # counters(item, ".") => counters(item,".") + # @overload counters($args...) + # @return [Sass::Script::Value::String] + def counters(*args) + identifier("counters(#{args.map {|a| a.to_s(options)}.join(',')})") + end + declare :counters, [], :var_args => true + + # Check whether a variable with the given name exists in the current + # scope or in the global scope. + # + # @example + # $a-false-value: false; + # variable-exists(a-false-value) => true + # variable-exists(a-null-value) => true + # + # variable-exists(nonexistent) => false + # + # @overload variable_exists($name) + # @param $name [Sass::Script::Value::String] The name of the variable to + # check. The name should not include the `$`. + # @return [Sass::Script::Value::Bool] Whether the variable is defined in + # the current scope. + def variable_exists(name) + assert_type name, :String, :name + bool(environment.caller.var(name.value)) + end + declare :variable_exists, [:name] + + # Check whether a variable with the given name exists in the global + # scope (at the top level of the file). + # + # @example + # $a-false-value: false; + # global-variable-exists(a-false-value) => true + # global-variable-exists(a-null-value) => true + # + # .foo { + # $some-var: false; + # @if global-variable-exists(some-var) { /* false, doesn't run */ } + # } + # + # @overload global_variable_exists($name) + # @param $name [Sass::Script::Value::String] The name of the variable to + # check. The name should not include the `$`. + # @return [Sass::Script::Value::Bool] Whether the variable is defined in + # the global scope. + def global_variable_exists(name) + assert_type name, :String, :name + bool(environment.global_env.var(name.value)) + end + declare :global_variable_exists, [:name] + + # Check whether a function with the given name exists. + # + # @example + # function-exists(lighten) => true + # + # @function myfunc { @return "something"; } + # function-exists(myfunc) => true + # + # @overload function_exists($name) + # @param name [Sass::Script::Value::String] The name of the function to + # check or a function reference. + # @return [Sass::Script::Value::Bool] Whether the function is defined. + def function_exists(name) + assert_type name, :String, :name + exists = Sass::Script::Functions.callable?(name.value.tr("-", "_")) + exists ||= environment.caller.function(name.value) + bool(exists) + end + declare :function_exists, [:name] + + # Check whether a mixin with the given name exists. + # + # @example + # mixin-exists(nonexistent) => false + # + # @mixin red-text { color: red; } + # mixin-exists(red-text) => true + # + # @overload mixin_exists($name) + # @param name [Sass::Script::Value::String] The name of the mixin to + # check. + # @return [Sass::Script::Value::Bool] Whether the mixin is defined. + def mixin_exists(name) + assert_type name, :String, :name + bool(environment.mixin(name.value)) + end + declare :mixin_exists, [:name] + + # Check whether a mixin was passed a content block. + # + # Unless `content-exists()` is called directly from a mixin, an error will be raised. + # + # @example + # @mixin needs-content { + # @if not content-exists() { + # @error "You must pass a content block!" + # } + # @content; + # } + # + # @overload content_exists() + # @return [Sass::Script::Value::Bool] Whether a content block was passed to the mixin. + def content_exists + # frames.last is the stack frame for this function, + # so we use frames[-2] to get the frame before that. + mixin_frame = environment.stack.frames[-2] + unless mixin_frame && mixin_frame.type == :mixin + raise Sass::SyntaxError.new("Cannot call content-exists() except within a mixin.") + end + bool(!environment.caller.content.nil?) + end + declare :content_exists, [] + + # Return a string containing the value as its Sass representation. + # + # @overload inspect($value) + # @param $value [Sass::Script::Value::Base] The value to inspect. + # @return [Sass::Script::Value::String] A representation of the value as + # it would be written in Sass. + def inspect(value) + value.check_deprecated_interp if value.is_a?(Sass::Script::Value::String) + unquoted_string(value.to_sass) + end + declare :inspect, [:value] + + # @overload random() + # Return a decimal between 0 and 1, inclusive of 0 but not 1. + # @return [Sass::Script::Value::Number] A decimal value. + # @overload random($limit) + # Return an integer between 1 and `$limit`, inclusive of both 1 and `$limit`. + # @param $limit [Sass::Script::Value::Number] The maximum of the random integer to be + # returned, a positive integer. + # @return [Sass::Script::Value::Number] An integer. + # @raise [ArgumentError] if the `$limit` is not 1 or greater + def random(limit = nil) + generator = Sass::Script::Functions.random_number_generator + if limit + assert_integer limit, "limit" + if limit.to_i < 1 + raise ArgumentError.new("$limit #{limit} must be greater than or equal to 1") + end + number(1 + generator.rand(limit.to_i)) + else + number(generator.rand) + end + end + declare :random, [] + declare :random, [:limit] + + # Parses a user-provided selector into a list of lists of strings + # as returned by `&`. + # + # @example + # selector-parse(".foo .bar, .baz .bang") => ('.foo' '.bar', '.baz' '.bang') + # + # @overload selector_parse($selector) + # @param $selector [Sass::Script::Value::String, Sass::Script::Value::List] + # The selector to parse. This can be either a string, a list of + # strings, or a list of lists of strings as returned by `&`. + # @return [Sass::Script::Value::List] + # A list of lists of strings representing `$selector`. This is + # in the same format as a selector returned by `&`. + def selector_parse(selector) + parse_selector(selector, :selector).to_sass_script + end + declare :selector_parse, [:selector] + + # Return a new selector with all selectors in `$selectors` nested beneath + # one another as though they had been nested in the stylesheet as + # `$selector1 { $selector2 { ... } }`. + # + # Unlike most selector functions, `selector-nest` allows the + # parent selector `&` to be used in any selector but the first. + # + # @example + # selector-nest(".foo", ".bar", ".baz") => .foo .bar .baz + # selector-nest(".a .foo", ".b .bar") => .a .foo .b .bar + # selector-nest(".foo", "&.bar") => .foo.bar + # + # @overload selector_nest($selectors...) + # @param $selectors [[Sass::Script::Value::String, Sass::Script::Value::List]] + # The selectors to nest. At least one selector must be passed. Each of + # these can be either a string, a list of strings, or a list of lists of + # strings as returned by `&`. + # @return [Sass::Script::Value::List] + # A list of lists of strings representing the result of nesting + # `$selectors`. This is in the same format as a selector returned by + # `&`. + def selector_nest(*selectors) + if selectors.empty? + raise ArgumentError.new("$selectors: At least one selector must be passed") + end + + parsed = [parse_selector(selectors.first, :selectors)] + parsed += selectors[1..-1].map {|sel| parse_selector(sel, :selectors, true)} + parsed.inject {|result, child| child.resolve_parent_refs(result)}.to_sass_script + end + declare :selector_nest, [], :var_args => true + + # Return a new selector with all selectors in `$selectors` appended one + # another as though they had been nested in the stylesheet as `$selector1 { + # &$selector2 { ... } }`. + # + # @example + # selector-append(".foo", ".bar", ".baz") => .foo.bar.baz + # selector-append(".a .foo", ".b .bar") => "a .foo.b .bar" + # selector-append(".foo", "-suffix") => ".foo-suffix" + # + # @overload selector_append($selectors...) + # @param $selectors [[Sass::Script::Value::String, Sass::Script::Value::List]] + # The selectors to append. At least one selector must be passed. Each of + # these can be either a string, a list of strings, or a list of lists of + # strings as returned by `&`. + # @return [Sass::Script::Value::List] + # A list of lists of strings representing the result of appending + # `$selectors`. This is in the same format as a selector returned by + # `&`. + # @raise [ArgumentError] if a selector could not be appended. + def selector_append(*selectors) + if selectors.empty? + raise ArgumentError.new("$selectors: At least one selector must be passed") + end + + selectors.map {|sel| parse_selector(sel, :selectors)}.inject do |parent, child| + child.members.each do |seq| + sseq = seq.members.first + unless sseq.is_a?(Sass::Selector::SimpleSequence) + raise ArgumentError.new("Can't append \"#{seq}\" to \"#{parent}\"") + end + + base = sseq.base + case base + when Sass::Selector::Universal + raise ArgumentError.new("Can't append \"#{seq}\" to \"#{parent}\"") + when Sass::Selector::Element + unless base.namespace.nil? + raise ArgumentError.new("Can't append \"#{seq}\" to \"#{parent}\"") + end + sseq.members[0] = Sass::Selector::Parent.new(base.name) + else + sseq.members.unshift Sass::Selector::Parent.new + end + end + child.resolve_parent_refs(parent) + end.to_sass_script + end + declare :selector_append, [], :var_args => true + + # Returns a new version of `$selector` with `$extendee` extended + # with `$extender`. This works just like the result of + # + # $selector { ... } + # $extender { @extend $extendee } + # + # @example + # selector-extend(".a .b", ".b", ".foo .bar") => .a .b, .a .foo .bar, .foo .a .bar + # + # @overload selector_extend($selector, $extendee, $extender) + # @param $selector [Sass::Script::Value::String, Sass::Script::Value::List] + # The selector within which `$extendee` is extended with + # `$extender`. This can be either a string, a list of strings, + # or a list of lists of strings as returned by `&`. + # @param $extendee [Sass::Script::Value::String, Sass::Script::Value::List] + # The selector being extended. This can be either a string, a + # list of strings, or a list of lists of strings as returned + # by `&`. + # @param $extender [Sass::Script::Value::String, Sass::Script::Value::List] + # The selector being injected into `$selector`. This can be + # either a string, a list of strings, or a list of lists of + # strings as returned by `&`. + # @return [Sass::Script::Value::List] + # A list of lists of strings representing the result of the + # extension. This is in the same format as a selector returned + # by `&`. + # @raise [ArgumentError] if the extension fails + def selector_extend(selector, extendee, extender) + selector = parse_selector(selector, :selector) + extendee = parse_selector(extendee, :extendee) + extender = parse_selector(extender, :extender) + + extends = Sass::Util::SubsetMap.new + begin + extender.populate_extends(extends, extendee, nil, [], true) + selector.do_extend(extends).to_sass_script + rescue Sass::SyntaxError => e + raise ArgumentError.new(e.to_s) + end + end + declare :selector_extend, [:selector, :extendee, :extender] + + # Replaces all instances of `$original` with `$replacement` in `$selector` + # + # This works by using `@extend` and throwing away the original + # selector. This means that it can be used to do very advanced + # replacements; see the examples below. + # + # @example + # selector-replace(".foo .bar", ".bar", ".baz") => ".foo .baz" + # selector-replace(".foo.bar.baz", ".foo.baz", ".qux") => ".bar.qux" + # + # @overload selector_replace($selector, $original, $replacement) + # @param $selector [Sass::Script::Value::String, Sass::Script::Value::List] + # The selector within which `$original` is replaced with + # `$replacement`. This can be either a string, a list of + # strings, or a list of lists of strings as returned by `&`. + # @param $original [Sass::Script::Value::String, Sass::Script::Value::List] + # The selector being replaced. This can be either a string, a + # list of strings, or a list of lists of strings as returned + # by `&`. + # @param $replacement [Sass::Script::Value::String, Sass::Script::Value::List] + # The selector that `$original` is being replaced with. This + # can be either a string, a list of strings, or a list of + # lists of strings as returned by `&`. + # @return [Sass::Script::Value::List] + # A list of lists of strings representing the result of the + # extension. This is in the same format as a selector returned + # by `&`. + # @raise [ArgumentError] if the replacement fails + def selector_replace(selector, original, replacement) + selector = parse_selector(selector, :selector) + original = parse_selector(original, :original) + replacement = parse_selector(replacement, :replacement) + + extends = Sass::Util::SubsetMap.new + begin + replacement.populate_extends(extends, original, nil, [], true) + selector.do_extend(extends, [], true).to_sass_script + rescue Sass::SyntaxError => e + raise ArgumentError.new(e.to_s) + end + end + declare :selector_replace, [:selector, :original, :replacement] + + # Unifies two selectors into a single selector that matches only + # elements matched by both input selectors. Returns `null` if + # there is no such selector. + # + # Like the selector unification done for `@extend`, this doesn't + # guarantee that the output selector will match *all* elements + # matched by both input selectors. For example, if `.a .b` is + # unified with `.x .y`, `.a .x .b.y, .x .a .b.y` will be returned, + # but `.a.x .b.y` will not. This avoids exponential output size + # while matching all elements that are likely to exist in + # practice. + # + # @example + # selector-unify(".a", ".b") => .a.b + # selector-unify(".a .b", ".x .y") => .a .x .b.y, .x .a .b.y + # selector-unify(".a.b", ".b.c") => .a.b.c + # selector-unify("#a", "#b") => null + # + # @overload selector_unify($selector1, $selector2) + # @param $selector1 [Sass::Script::Value::String, Sass::Script::Value::List] + # The first selector to be unified. This can be either a + # string, a list of strings, or a list of lists of strings as + # returned by `&`. + # @param $selector2 [Sass::Script::Value::String, Sass::Script::Value::List] + # The second selector to be unified. This can be either a + # string, a list of strings, or a list of lists of strings as + # returned by `&`. + # @return [Sass::Script::Value::List, Sass::Script::Value::Null] + # A list of lists of strings representing the result of the + # unification, or null if no unification exists. This is in + # the same format as a selector returned by `&`. + def selector_unify(selector1, selector2) + selector1 = parse_selector(selector1, :selector1) + selector2 = parse_selector(selector2, :selector2) + return null unless (unified = selector1.unify(selector2)) + unified.to_sass_script + end + declare :selector_unify, [:selector1, :selector2] + + # Returns the [simple + # selectors](http://dev.w3.org/csswg/selectors4/#simple) that + # comprise the compound selector `$selector`. + # + # Note that `$selector` **must be** a [compound + # selector](http://dev.w3.org/csswg/selectors4/#compound). That + # means it cannot contain commas or spaces. It also means that + # unlike other selector functions, this takes only strings, not + # lists. + # + # @example + # simple-selectors(".foo.bar") => ".foo", ".bar" + # simple-selectors(".foo.bar.baz") => ".foo", ".bar", ".baz" + # + # @overload simple_selectors($selector) + # @param $selector [Sass::Script::Value::String] + # The compound selector whose simple selectors will be extracted. + # @return [Sass::Script::Value::List] + # A list of simple selectors in the compound selector. + def simple_selectors(selector) + selector = parse_compound_selector(selector, :selector) + list(selector.members.map {|simple| unquoted_string(simple.to_s)}, :comma) + end + declare :simple_selectors, [:selector] + + # Returns whether `$super` is a superselector of `$sub`. This means that + # `$super` matches all the elements that `$sub` matches, as well as possibly + # additional elements. In general, simpler selectors tend to be + # superselectors of more complex oned. + # + # @example + # is-superselector(".foo", ".foo.bar") => true + # is-superselector(".foo.bar", ".foo") => false + # is-superselector(".bar", ".foo .bar") => true + # is-superselector(".foo .bar", ".bar") => false + # + # @overload is_superselector($super, $sub) + # @param $super [Sass::Script::Value::String, Sass::Script::Value::List] + # The potential superselector. This can be either a string, a list of + # strings, or a list of lists of strings as returned by `&`. + # @param $sub [Sass::Script::Value::String, Sass::Script::Value::List] + # The potential subselector. This can be either a string, a list of + # strings, or a list of lists of strings as returned by `&`. + # @return [Sass::Script::Value::Bool] + # Whether `$selector1` is a superselector of `$selector2`. + def is_superselector(sup, sub) + sup = parse_selector(sup, :super) + sub = parse_selector(sub, :sub) + bool(sup.superselector?(sub)) + end + declare :is_superselector, [:super, :sub] + + private + + # This method implements the pattern of transforming a numeric value into + # another numeric value with the same units. + # It yields a number to a block to perform the operation and return a number + def numeric_transformation(value) + assert_type value, :Number, :value + Sass::Script::Value::Number.new( + yield(value.value), value.numerator_units, value.denominator_units) + end + + def _adjust(color, amount, attr, range, op, units = "") + assert_type color, :Color, :color + assert_type amount, :Number, :amount + Sass::Util.check_range('Amount', range, amount, units) + + color.with(attr => color.send(attr).send(op, amount.value)) + end + + def percentage_or_unitless(number, max, name) + if number.unitless? + number.value + elsif number.is_unit?("%") + max * number.value / 100.0; + else + raise ArgumentError.new( + "$#{name}: Expected #{number} to have no units or \"%\""); + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/lexer.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/lexer.rb new file mode 100644 index 00000000..5f02ad98 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/lexer.rb @@ -0,0 +1,518 @@ +require 'sass/scss/rx' + +module Sass + module Script + # The lexical analyzer for SassScript. + # It takes a raw string and converts it to individual tokens + # that are easier to parse. + class Lexer + include Sass::SCSS::RX + + # A struct containing information about an individual token. + # + # `type`: \[`Symbol`\] + # : The type of token. + # + # `value`: \[`Object`\] + # : The Ruby object corresponding to the value of the token. + # + # `source_range`: \[`Sass::Source::Range`\] + # : The range in the source file in which the token appeared. + # + # `pos`: \[`Integer`\] + # : The scanner position at which the SassScript token appeared. + Token = Struct.new(:type, :value, :source_range, :pos) + + # The line number of the lexer's current position. + # + # @return [Integer] + def line + return @line unless @tok + @tok.source_range.start_pos.line + end + + # The number of bytes into the current line + # of the lexer's current position (1-based). + # + # @return [Integer] + def offset + return @offset unless @tok + @tok.source_range.start_pos.offset + end + + # A hash from operator strings to the corresponding token types. + OPERATORS = { + '+' => :plus, + '-' => :minus, + '*' => :times, + '/' => :div, + '%' => :mod, + '=' => :single_eq, + ':' => :colon, + '(' => :lparen, + ')' => :rparen, + '[' => :lsquare, + ']' => :rsquare, + ',' => :comma, + 'and' => :and, + 'or' => :or, + 'not' => :not, + '==' => :eq, + '!=' => :neq, + '>=' => :gte, + '<=' => :lte, + '>' => :gt, + '<' => :lt, + '#{' => :begin_interpolation, + '}' => :end_interpolation, + ';' => :semicolon, + '{' => :lcurly, + '...' => :splat, + } + + OPERATORS_REVERSE = Sass::Util.map_hash(OPERATORS) {|k, v| [v, k]} + + TOKEN_NAMES = Sass::Util.map_hash(OPERATORS_REVERSE) {|k, v| [k, v.inspect]}.merge( + :const => "variable (e.g. $foo)", + :ident => "identifier (e.g. middle)") + + # A list of operator strings ordered with longer names first + # so that `>` and `<` don't clobber `>=` and `<=`. + OP_NAMES = OPERATORS.keys.sort_by {|o| -o.size} + + # A sub-list of {OP_NAMES} that only includes operators + # with identifier names. + IDENT_OP_NAMES = OP_NAMES.select {|k, _v| k =~ /^\w+/} + + PARSEABLE_NUMBER = /(?:(\d*\.\d+)|(\d+))(?:[eE]([+-]?\d+))?(#{UNIT})?/ + + # A hash of regular expressions that are used for tokenizing. + REGULAR_EXPRESSIONS = { + :whitespace => /\s+/, + :comment => COMMENT, + :single_line_comment => SINGLE_LINE_COMMENT, + :variable => /(\$)(#{IDENT})/, + :ident => /(#{IDENT})(\()?/, + :number => PARSEABLE_NUMBER, + :unary_minus_number => /-#{PARSEABLE_NUMBER}/, + :color => HEXCOLOR, + :id => /##{IDENT}/, + :selector => /&/, + :ident_op => /(#{Regexp.union(*IDENT_OP_NAMES.map do |s| + Regexp.new(Regexp.escape(s) + "(?!#{NMCHAR}|\Z)") + end)})/, + :op => /(#{Regexp.union(*OP_NAMES)})/, + } + + class << self + private + + def string_re(open, close) + /#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/m + end + end + + # A hash of regular expressions that are used for tokenizing strings. + # + # The key is a `[Symbol, Boolean]` pair. + # The symbol represents which style of quotation to use, + # while the boolean represents whether or not the string + # is following an interpolated segment. + STRING_REGULAR_EXPRESSIONS = { + :double => { + false => string_re('"', '"'), + true => string_re('', '"') + }, + :single => { + false => string_re("'", "'"), + true => string_re('', "'") + }, + :uri => { + false => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, + true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ + }, + # Defined in https://developer.mozilla.org/en/CSS/@-moz-document as a + # non-standard version of http://www.w3.org/TR/css3-conditional/ + :url_prefix => { + false => /url-prefix\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, + true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ + }, + :domain => { + false => /domain\(#{W}(#{URLCHAR}*?)(#{W}\)|#\{)/, + true => /(#{URLCHAR}*?)(#{W}\)|#\{)/ + } + } + + # @param str [String, StringScanner] The source text to lex + # @param line [Integer] The 1-based line on which the SassScript appears. + # Used for error reporting and sourcemap building + # @param offset [Integer] The 1-based character (not byte) offset in the line in the source. + # Used for error reporting and sourcemap building + # @param options [{Symbol => Object}] An options hash; + # see {file:SASS_REFERENCE.md#Options the Sass options documentation} + def initialize(str, line, offset, options) + @scanner = str.is_a?(StringScanner) ? str : Sass::Util::MultibyteStringScanner.new(str) + @line = line + @offset = offset + @options = options + @interpolation_stack = [] + @prev = nil + @tok = nil + @next_tok = nil + end + + # Moves the lexer forward one token. + # + # @return [Token] The token that was moved past + def next + @tok ||= read_token + @tok, tok = nil, @tok + @prev = tok + tok + end + + # Returns whether or not there's whitespace before the next token. + # + # @return [Boolean] + def whitespace?(tok = @tok) + if tok + @scanner.string[0...tok.pos] =~ /\s\Z/ + else + @scanner.string[@scanner.pos, 1] =~ /^\s/ || + @scanner.string[@scanner.pos - 1, 1] =~ /\s\Z/ + end + end + + # Returns the given character. + # + # @return [String] + def char(pos = @scanner.pos) + @scanner.string[pos, 1] + end + + # Consumes and returns single raw character from the input stream. + # + # @return [String] + def next_char + unpeek! + scan(/./) + end + + # Returns the next token without moving the lexer forward. + # + # @return [Token] The next token + def peek + @tok ||= read_token + end + + # Rewinds the underlying StringScanner + # to before the token returned by \{#peek}. + def unpeek! + raise "[BUG] Can't unpeek before a queued token!" if @next_tok + return unless @tok + @scanner.pos = @tok.pos + @line = @tok.source_range.start_pos.line + @offset = @tok.source_range.start_pos.offset + end + + # @return [Boolean] Whether or not there's more source text to lex. + def done? + return if @next_tok + whitespace unless after_interpolation? && !@interpolation_stack.empty? + @scanner.eos? && @tok.nil? + end + + # @return [Boolean] Whether or not the last token lexed was `:end_interpolation`. + def after_interpolation? + @prev && @prev.type == :end_interpolation + end + + # Raise an error to the effect that `name` was expected in the input stream + # and wasn't found. + # + # This calls \{#unpeek!} to rewind the scanner to immediately after + # the last returned token. + # + # @param name [String] The name of the entity that was expected but not found + # @raise [Sass::SyntaxError] + def expected!(name) + unpeek! + Sass::SCSS::Parser.expected(@scanner, name, @line) + end + + # Records all non-comment text the lexer consumes within the block + # and returns it as a string. + # + # @yield A block in which text is recorded + # @return [String] + def str + old_pos = @tok ? @tok.pos : @scanner.pos + yield + new_pos = @tok ? @tok.pos : @scanner.pos + @scanner.string[old_pos...new_pos] + end + + # Runs a block, and rewinds the state of the lexer to the beginning of the + # block if it returns `nil` or `false`. + def try + old_pos = @scanner.pos + old_line = @line + old_offset = @offset + old_interpolation_stack = @interpolation_stack.dup + old_prev = @prev + old_tok = @tok + old_next_tok = @next_tok + + result = yield + return result if result + + @scanner.pos = old_pos + @line = old_line + @offset = old_offset + @interpolation_stack = old_interpolation_stack + @prev = old_prev + @tok = old_tok + @next_tok = old_next_tok + nil + end + + private + + def read_token + if (tok = @next_tok) + @next_tok = nil + return tok + end + + return if done? + start_pos = source_position + value = token + return unless value + type, val = value + Token.new(type, val, range(start_pos), @scanner.pos - @scanner.matched_size) + end + + def whitespace + nil while scan(REGULAR_EXPRESSIONS[:whitespace]) || + scan(REGULAR_EXPRESSIONS[:comment]) || + scan(REGULAR_EXPRESSIONS[:single_line_comment]) + end + + def token + if after_interpolation? + interp_type, interp_value = @interpolation_stack.pop + if interp_type == :special_fun + return special_fun_body(interp_value) + elsif interp_type.nil? + if @scanner.string[@scanner.pos - 1] == '}' && scan(REGULAR_EXPRESSIONS[:ident]) + return [@scanner[2] ? :funcall : :ident, Sass::Util.normalize_ident_escapes(@scanner[1], start: false)] + end + else + raise "[BUG]: Unknown interp_type #{interp_type}" unless interp_type == :string + return string(interp_value, true) + end + end + + variable || string(:double, false) || string(:single, false) || number || id || color || + selector || string(:uri, false) || raw(UNICODERANGE) || special_fun || special_val || + ident_op || ident || op + end + + def variable + _variable(REGULAR_EXPRESSIONS[:variable]) + end + + def _variable(rx) + return unless scan(rx) + [:const, Sass::Util.normalize_ident_escapes(@scanner[2])] + end + + def ident + return unless scan(REGULAR_EXPRESSIONS[:ident]) + [@scanner[2] ? :funcall : :ident, Sass::Util.normalize_ident_escapes(@scanner[1])] + end + + def string(re, open) + line, offset = @line, @offset + return unless scan(STRING_REGULAR_EXPRESSIONS[re][open]) + if @scanner[0] =~ /([^\\]|^)\n/ + filename = @options[:filename] + Sass::Util.sass_warn < Object}] An options hash; see + # {file:SASS_REFERENCE.md#Options the Sass options documentation}. + # This supports an additional `:allow_extra_text` option that controls + # whether the parser throws an error when extra text is encountered + # after the parsed construct. + def initialize(str, line, offset, options = {}) + @options = options + @allow_extra_text = options.delete(:allow_extra_text) + @lexer = lexer_class.new(str, line, offset, options) + @stop_at = nil + end + + # Parses a SassScript expression within an interpolated segment (`#{}`). + # This means that it stops when it comes across an unmatched `}`, + # which signals the end of an interpolated segment, + # it returns rather than throwing an error. + # + # @param warn_for_color [Boolean] Whether raw color values passed to + # interoplation should cause a warning. + # @return [Script::Tree::Node] The root node of the parse tree + # @raise [Sass::SyntaxError] if the expression isn't valid SassScript + def parse_interpolated(warn_for_color = false) + # Start two characters back to compensate for #{ + start_pos = Sass::Source::Position.new(line, offset - 2) + expr = assert_expr :expr + assert_tok :end_interpolation + expr = Sass::Script::Tree::Interpolation.new( + nil, expr, nil, false, false, :warn_for_color => warn_for_color) + check_for_interpolation expr + expr.options = @options + node(expr, start_pos) + rescue Sass::SyntaxError => e + e.modify_backtrace :line => @lexer.line, :filename => @options[:filename] + raise e + end + + # Parses a SassScript expression. + # + # @return [Script::Tree::Node] The root node of the parse tree + # @raise [Sass::SyntaxError] if the expression isn't valid SassScript + def parse + expr = assert_expr :expr + assert_done + expr.options = @options + check_for_interpolation expr + expr + rescue Sass::SyntaxError => e + e.modify_backtrace :line => @lexer.line, :filename => @options[:filename] + raise e + end + + # Parses a SassScript expression, + # ending it when it encounters one of the given identifier tokens. + # + # @param tokens [#include?(String | Symbol)] A set of strings or symbols that delimit the expression. + # @return [Script::Tree::Node] The root node of the parse tree + # @raise [Sass::SyntaxError] if the expression isn't valid SassScript + def parse_until(tokens) + @stop_at = tokens + expr = assert_expr :expr + assert_done + expr.options = @options + check_for_interpolation expr + expr + rescue Sass::SyntaxError => e + e.modify_backtrace :line => @lexer.line, :filename => @options[:filename] + raise e + end + + # Parses the argument list for a mixin include. + # + # @return [(Array, + # {String => Script::Tree::Node}, + # Script::Tree::Node, + # Script::Tree::Node)] + # The root nodes of the positional arguments, keyword arguments, and + # splat argument(s). Keyword arguments are in a hash from names to values. + # @raise [Sass::SyntaxError] if the argument list isn't valid SassScript + def parse_mixin_include_arglist + args, keywords = [], {} + if try_tok(:lparen) + args, keywords, splat, kwarg_splat = mixin_arglist + assert_tok(:rparen) + end + assert_done + + args.each do |a| + check_for_interpolation a + a.options = @options + end + + keywords.each do |_, v| + check_for_interpolation v + v.options = @options + end + + if splat + check_for_interpolation splat + splat.options = @options + end + + if kwarg_splat + check_for_interpolation kwarg_splat + kwarg_splat.options = @options + end + + return args, keywords, splat, kwarg_splat + rescue Sass::SyntaxError => e + e.modify_backtrace :line => @lexer.line, :filename => @options[:filename] + raise e + end + + # Parses the argument list for a mixin definition. + # + # @return [(Array, Script::Tree::Node)] + # The root nodes of the arguments, and the splat argument. + # @raise [Sass::SyntaxError] if the argument list isn't valid SassScript + def parse_mixin_definition_arglist + args, splat = defn_arglist!(false) + assert_done + + args.each do |k, v| + check_for_interpolation k + k.options = @options + + if v + check_for_interpolation v + v.options = @options + end + end + + if splat + check_for_interpolation splat + splat.options = @options + end + + return args, splat + rescue Sass::SyntaxError => e + e.modify_backtrace :line => @lexer.line, :filename => @options[:filename] + raise e + end + + # Parses the argument list for a function definition. + # + # @return [(Array, Script::Tree::Node)] + # The root nodes of the arguments, and the splat argument. + # @raise [Sass::SyntaxError] if the argument list isn't valid SassScript + def parse_function_definition_arglist + args, splat = defn_arglist!(true) + assert_done + + args.each do |k, v| + check_for_interpolation k + k.options = @options + + if v + check_for_interpolation v + v.options = @options + end + end + + if splat + check_for_interpolation splat + splat.options = @options + end + + return args, splat + rescue Sass::SyntaxError => e + e.modify_backtrace :line => @lexer.line, :filename => @options[:filename] + raise e + end + + # Parse a single string value, possibly containing interpolation. + # Doesn't assert that the scanner is finished after parsing. + # + # @return [Script::Tree::Node] The root node of the parse tree. + # @raise [Sass::SyntaxError] if the string isn't valid SassScript + def parse_string + unless (peek = @lexer.peek) && + (peek.type == :string || + (peek.type == :funcall && peek.value.downcase == 'url')) + lexer.expected!("string") + end + + expr = assert_expr :funcall + check_for_interpolation expr + expr.options = @options + @lexer.unpeek! + expr + rescue Sass::SyntaxError => e + e.modify_backtrace :line => @lexer.line, :filename => @options[:filename] + raise e + end + + # Parses a SassScript expression. + # + # @overload parse(str, line, offset, filename = nil) + # @return [Script::Tree::Node] The root node of the parse tree + # @see Parser#initialize + # @see Parser#parse + def self.parse(*args) + new(*args).parse + end + + PRECEDENCE = [ + :comma, :single_eq, :space, :or, :and, + [:eq, :neq], + [:gt, :gte, :lt, :lte], + [:plus, :minus], + [:times, :div, :mod], + ] + + ASSOCIATIVE = [:plus, :times] + + class << self + # Returns an integer representing the precedence + # of the given operator. + # A lower integer indicates a looser binding. + # + # @private + def precedence_of(op) + PRECEDENCE.each_with_index do |e, i| + return i if Array(e).include?(op) + end + raise "[BUG] Unknown operator #{op.inspect}" + end + + # Returns whether or not the given operation is associative. + # + # @private + def associative?(op) + ASSOCIATIVE.include?(op) + end + + private + + # Defines a simple left-associative production. + # name is the name of the production, + # sub is the name of the production beneath it, + # and ops is a list of operators for this precedence level + def production(name, sub, *ops) + class_eval < true, :deprecation => deprecation), + (prev || str).source_range.start_pos) + interpolation(first: interp) + end + + def try_ops_after_interp(ops, name, prev = nil) + return unless @lexer.after_interpolation? + op = peek_toks(*ops) + return unless op + return if @stop_at && @stop_at.include?(op.type) + @lexer.next + + interp = try_op_before_interp(op, prev, :after_interp) + return interp if interp + + wa = @lexer.whitespace? + str = literal_node(Script::Value::String.new(Lexer::OPERATORS_REVERSE[op.type]), + op.source_range) + str.line = @lexer.line + + deprecation = + case op.type + when :comma; :potential + when :div, :single_eq; :none + when :minus; @lexer.whitespace?(op) ? :immediate : :none + else; :immediate + end + interp = node( + Script::Tree::Interpolation.new( + prev, str, assert_expr(name), false, wa, + :originally_text => true, :deprecation => deprecation), + (prev || str).source_range.start_pos) + interp + end + + def interpolation(first: nil, inner: :space) + e = first || send(inner) + while (interp = try_tok(:begin_interpolation)) + wb = @lexer.whitespace?(interp) + char_before = @lexer.char(interp.pos - 1) + mid = without_stop_at {assert_expr :expr} + assert_tok :end_interpolation + wa = @lexer.whitespace? + char_after = @lexer.char + + after = send(inner) + before_deprecation = e.is_a?(Script::Tree::Interpolation) ? e.deprecation : :none + after_deprecation = after.is_a?(Script::Tree::Interpolation) ? after.deprecation : :none + + deprecation = + if before_deprecation == :immediate || after_deprecation == :immediate || + # Warn for #{foo}$var and #{foo}(1) but not #{$foo}1. + (after && !wa && char_after =~ /[$(]/) || + # Warn for $var#{foo} and (a)#{foo} but not a#{foo}. + (e && !wb && is_unsafe_before?(e, char_before)) + :immediate + else + :potential + end + + e = node( + Script::Tree::Interpolation.new(e, mid, after, wb, wa, :deprecation => deprecation), + (e || interp).source_range.start_pos) + end + e + end + + # Returns whether `expr` is unsafe to include before an interpolation. + # + # @param expr [Node] The expression to check. + # @param char_before [String] The character immediately before the + # interpolation being checked (and presumably the last character of + # `expr`). + # @return [Boolean] + def is_unsafe_before?(expr, char_before) + return char_before == ')' if is_safe_value?(expr) + + # Otherwise, it's only safe if it was another interpolation. + !expr.is_a?(Script::Tree::Interpolation) + end + + # Returns whether `expr` is safe as the value immediately before an + # interpolation. + # + # It's safe as long as the previous expression is an identifier or number, + # or a list whose last element is also safe. + def is_safe_value?(expr) + return is_safe_value?(expr.elements.last) if expr.is_a?(Script::Tree::ListLiteral) + return false unless expr.is_a?(Script::Tree::Literal) + expr.value.is_a?(Script::Value::Number) || + (expr.value.is_a?(Script::Value::String) && expr.value.type == :identifier) + end + + def space + start_pos = source_position + e = or_expr + return unless e + arr = [e] + while (e = or_expr) + arr << e + end + if arr.size == 1 + arr.first + else + node(Sass::Script::Tree::ListLiteral.new(arr, separator: :space), start_pos) + end + end + + production :or_expr, :and_expr, :or + production :and_expr, :eq_or_neq, :and + production :eq_or_neq, :relational, :eq, :neq + production :relational, :plus_or_minus, :gt, :gte, :lt, :lte + production :plus_or_minus, :times_div_or_mod, :plus, :minus + production :times_div_or_mod, :unary_plus, :times, :div, :mod + + unary :plus, :unary_minus + unary :minus, :unary_div + unary :div, :unary_not # For strings, so /foo/bar works + unary :not, :ident + + def ident + return css_min_max unless @lexer.peek && @lexer.peek.type == :ident + return if @stop_at && @stop_at.include?(@lexer.peek.value) + + name = @lexer.next + if (color = Sass::Script::Value::Color::COLOR_NAMES[name.value.downcase]) + literal_node(Sass::Script::Value::Color.new(color, name.value), name.source_range) + elsif name.value == "true" + literal_node(Sass::Script::Value::Bool.new(true), name.source_range) + elsif name.value == "false" + literal_node(Sass::Script::Value::Bool.new(false), name.source_range) + elsif name.value == "null" + literal_node(Sass::Script::Value::Null.new, name.source_range) + else + literal_node(Sass::Script::Value::String.new(name.value, :identifier), name.source_range) + end + end + + def css_min_max + @lexer.try do + next unless tok = try_tok(:funcall) + next unless %w[min max].include?(tok.value.downcase) + next unless contents = min_max_contents + node(array_to_interpolation(["#{tok.value}(", *contents]), + tok.source_range.start_pos, source_position) + end || funcall + end + + def min_max_contents(allow_comma: true) + result = [] + loop do + if tok = try_tok(:number) + result << tok.value.to_s + elsif value = min_max_interpolation + result << value + elsif value = min_max_calc + result << value.value + elsif value = min_max_function || + min_max_parens || + nested_min_max + result.concat value + else + return + end + + if try_tok(:rparen) + result << ")" + return result + elsif tok = try_tok(:plus) || try_tok(:minus) || try_tok(:times) || try_tok(:div) + result << " #{Lexer::OPERATORS_REVERSE[tok.type]} " + elsif allow_comma && try_tok(:comma) + result << ", " + else + return + end + end + end + + def min_max_interpolation + without_stop_at do + tok = try_tok(:begin_interpolation) + return unless tok + expr = without_stop_at {assert_expr :expr} + assert_tok :end_interpolation + expr + end + end + + def min_max_function + return unless tok = peek_tok(:funcall) + return unless %w[calc env var].include?(tok.value.downcase) + @lexer.next + result = [tok.value, '(', *declaration_value, ')'] + assert_tok :rparen + result + end + + def min_max_calc + return unless tok = peek_tok(:special_fun) + return unless tok.value.value.downcase.start_with?("calc(") + @lexer.next.value + end + + def min_max_parens + return unless try_tok :lparen + return unless contents = min_max_contents(allow_comma: false) + ['(', *contents] + end + + def nested_min_max + return unless tok = peek_tok(:funcall) + return unless %w[min max].include?(tok.value.downcase) + @lexer.next + return unless contents = min_max_contents + [tok.value, '(', *contents] + end + + def declaration_value + result = [] + brackets = [] + loop do + result << @lexer.str do + until @lexer.done? || + peek_toks(:begin_interpolation, + :end_interpolation, + :lcurly, + :lparen, + :lsquare, + :rparen, + :rsquare) + @lexer.next || @lexer.next_char + end + end + + if try_tok(:begin_interpolation) + result << assert_expr(:expr) + assert_tok :end_interpolation + elsif tok = try_toks(:lcurly, :lparen, :lsquare) + brackets << case tok.type + when :lcurly; :end_interpolation + when :lparen; :rparen + when :lsquare; :rsquare + end + result << Lexer::OPERATORS_REVERSE[tok.type] + elsif brackets.empty? + return result + else + bracket = brackets.pop + assert_tok bracket + result << Lexer::OPERATORS_REVERSE[bracket] + end + end + end + + def funcall + tok = try_tok(:funcall) + return raw unless tok + args, keywords, splat, kwarg_splat = fn_arglist + assert_tok(:rparen) + node(Script::Tree::Funcall.new(tok.value, args, keywords, splat, kwarg_splat), + tok.source_range.start_pos, source_position) + end + + def defn_arglist!(must_have_parens) + if must_have_parens + assert_tok(:lparen) + else + return [], nil unless try_tok(:lparen) + end + + without_stop_at do + res = [] + splat = nil + must_have_default = false + loop do + break if peek_tok(:rparen) + c = assert_tok(:const) + var = node(Script::Tree::Variable.new(c.value), c.source_range) + if try_tok(:colon) + val = assert_expr(:space) + must_have_default = true + elsif try_tok(:splat) + splat = var + break + elsif must_have_default + raise SyntaxError.new( + "Required argument #{var.inspect} must come before any optional arguments.") + end + res << [var, val] + break unless try_tok(:comma) + end + assert_tok(:rparen) + return res, splat + end + end + + def fn_arglist + arglist(:equals, "function argument") + end + + def mixin_arglist + arglist(:interpolation, "mixin argument") + end + + def arglist(subexpr, description) + without_stop_at do + args = [] + keywords = Sass::Util::NormalizedMap.new + splat = nil + while (e = send(subexpr)) + if @lexer.peek && @lexer.peek.type == :colon + name = e + @lexer.expected!("comma") unless name.is_a?(Tree::Variable) + assert_tok(:colon) + value = assert_expr(subexpr, description) + + if keywords[name.name] + raise SyntaxError.new("Keyword argument \"#{name.to_sass}\" passed more than once") + end + + keywords[name.name] = value + else + if try_tok(:splat) + return args, keywords, splat, e if splat + splat, e = e, nil + elsif splat + raise SyntaxError.new("Only keyword arguments may follow variable arguments (...).") + elsif !keywords.empty? + raise SyntaxError.new("Positional arguments must come before keyword arguments.") + end + args << e if e + end + + return args, keywords, splat unless try_tok(:comma) + end + return args, keywords + end + end + + def raw + tok = try_tok(:raw) + return special_fun unless tok + literal_node(Script::Value::String.new(tok.value), tok.source_range) + end + + def special_fun + first = try_tok(:special_fun) + return square_list unless first + str = literal_node(first.value, first.source_range) + return str unless try_tok(:string_interpolation) + mid = without_stop_at {assert_expr :expr} + assert_tok :end_interpolation + last = assert_expr(:special_fun) + node( + Tree::Interpolation.new(str, mid, last, false, false), + first.source_range.start_pos) + end + + def square_list + start_pos = source_position + return paren unless try_tok(:lsquare) + + without_stop_at do + space_start_pos = source_position + e = interpolation(inner: :or_expr) + separator = nil + if e + elements = [e] + while (e = interpolation(inner: :or_expr)) + elements << e + end + + # If there's a comma after a space-separated list, it's actually a + # space-separated list nested in a comma-separated list. + if try_tok(:comma) + e = if elements.length == 1 + elements.first + else + node( + Sass::Script::Tree::ListLiteral.new(elements, separator: :space), + space_start_pos) + end + elements = [e] + + while (e = space) + elements << e + break unless try_tok(:comma) + end + separator = :comma + else + separator = :space if elements.length > 1 + end + else + elements = [] + end + + assert_tok(:rsquare) + end_pos = source_position + + node(Sass::Script::Tree::ListLiteral.new(elements, separator: separator, bracketed: true), + start_pos, end_pos) + end + end + + def paren + return variable unless try_tok(:lparen) + without_stop_at do + start_pos = source_position + e = map + e.force_division! if e + end_pos = source_position + assert_tok(:rparen) + e || node(Sass::Script::Tree::ListLiteral.new([]), start_pos, end_pos) + end + end + + def variable + start_pos = source_position + c = try_tok(:const) + return string unless c + node(Tree::Variable.new(*c.value), start_pos) + end + + def string + first = try_tok(:string) + return number unless first + str = literal_node(first.value, first.source_range) + return str unless try_tok(:string_interpolation) + mid = assert_expr :expr + assert_tok :end_interpolation + last = without_stop_at {assert_expr(:string)} + node(Tree::StringInterpolation.new(str, mid, last), first.source_range.start_pos) + end + + def number + tok = try_tok(:number) + return selector unless tok + num = tok.value + num.options = @options + num.original = num.to_s + literal_node(num, tok.source_range.start_pos) + end + + def selector + tok = try_tok(:selector) + return literal unless tok + node(tok.value, tok.source_range.start_pos) + end + + def literal + t = try_tok(:color) + return literal_node(t.value, t.source_range) if t + end + + # It would be possible to have unified #assert and #try methods, + # but detecting the method/token difference turns out to be quite expensive. + + EXPR_NAMES = { + :string => "string", + :default => "expression (e.g. 1px, bold)", + :mixin_arglist => "mixin argument", + :fn_arglist => "function argument", + :splat => "...", + :special_fun => '")"', + } + + def assert_expr(name, expected = nil) + e = send(name) + return e if e + @lexer.expected!(expected || EXPR_NAMES[name] || EXPR_NAMES[:default]) + end + + def assert_tok(name) + # Avoids an array allocation caused by argument globbing in assert_toks. + t = try_tok(name) + return t if t + @lexer.expected!(Lexer::TOKEN_NAMES[name] || name.to_s) + end + + def assert_toks(*names) + t = try_toks(*names) + return t if t + @lexer.expected!(names.map {|tok| Lexer::TOKEN_NAMES[tok] || tok}.join(" or ")) + end + + def peek_tok(name) + # Avoids an array allocation caused by argument globbing in the try_toks method. + peeked = @lexer.peek + peeked && name == peeked.type && peeked + end + + def peek_toks(*names) + peeked = @lexer.peek + peeked && names.include?(peeked.type) && peeked + end + + def try_tok(name) + peek_tok(name) && @lexer.next + end + + def try_toks(*names) + peek_toks(*names) && @lexer.next + end + + def assert_done + if @allow_extra_text + # If extra text is allowed, just rewind the lexer so that the + # StringScanner is pointing to the end of the parsed text. + @lexer.unpeek! + else + return if @lexer.done? + @lexer.expected!(EXPR_NAMES[:default]) + end + end + + def without_stop_at + old_stop_at = @stop_at + @stop_at = nil + yield + ensure + @stop_at = old_stop_at + end + + # @overload node(value, source_range) + # @param value [Sass::Script::Value::Base] + # @param source_range [Sass::Source::Range] + # @overload node(value, start_pos, end_pos = source_position) + # @param value [Sass::Script::Value::Base] + # @param start_pos [Sass::Source::Position] + # @param end_pos [Sass::Source::Position] + def literal_node(value, source_range_or_start_pos, end_pos = source_position) + node(Sass::Script::Tree::Literal.new(value), source_range_or_start_pos, end_pos) + end + + # @overload node(node, source_range) + # @param node [Sass::Script::Tree::Node] + # @param source_range [Sass::Source::Range] + # @overload node(node, start_pos, end_pos = source_position) + # @param node [Sass::Script::Tree::Node] + # @param start_pos [Sass::Source::Position] + # @param end_pos [Sass::Source::Position] + def node(node, source_range_or_start_pos, end_pos = source_position) + source_range = + if source_range_or_start_pos.is_a?(Sass::Source::Range) + source_range_or_start_pos + else + range(source_range_or_start_pos, end_pos) + end + + node.line = source_range.start_pos.line + node.source_range = source_range + node.filename = @options[:filename] + node + end + + # Converts an array of strings and expressions to a string interoplation + # object. + # + # @param array [Array] + # @return [Script::Tree::StringInterpolation] + def array_to_interpolation(array) + Sass::Util.merge_adjacent_strings(array).reverse.inject(nil) do |after, value| + if value.is_a?(::String) + literal = Sass::Script::Tree::Literal.new( + Sass::Script::Value::String.new(value)) + next literal unless after + Sass::Script::Tree::StringInterpolation.new(literal, after.mid, after.after) + else + Sass::Script::Tree::StringInterpolation.new( + Sass::Script::Tree::Literal.new( + Sass::Script::Value::String.new('')), + value, + after || Sass::Script::Tree::Literal.new( + Sass::Script::Value::String.new(''))) + end + end + end + + # Checks a script node for any immediately-deprecated interpolations, and + # emits warnings for them. + # + # @param node [Sass::Script::Tree::Node] + def check_for_interpolation(node) + nodes = [node] + until nodes.empty? + node = nodes.pop + unless node.is_a?(Sass::Script::Tree::Interpolation) && + node.deprecation == :immediate + nodes.concat node.children + next + end + + interpolation_deprecation(node) + end + end + + # Emits a deprecation warning for an interpolation node. + # + # @param node [Sass::Script::Tree::Node] + def interpolation_deprecation(interpolation) + return if @options[:_convert] + location = "on line #{interpolation.line}" + location << " of #{interpolation.filename}" if interpolation.filename + Sass::Util.sass_warn <] + attr_reader :args + + # The keyword arguments to the function. + # + # @return [Sass::Util::NormalizedMap] + attr_reader :keywords + + # The first splat argument for this function, if one exists. + # + # This could be a list of positional arguments, a map of keyword + # arguments, or an arglist containing both. + # + # @return [Node?] + attr_accessor :splat + + # The second splat argument for this function, if one exists. + # + # If this exists, it's always a map of keyword arguments, and + # \{#splat} is always either a list or an arglist. + # + # @return [Node?] + attr_accessor :kwarg_splat + + # @param name_or_callable [String, Sass::Callable] See \{#name} + # @param args [Array] See \{#args} + # @param keywords [Sass::Util::NormalizedMap] See \{#keywords} + # @param splat [Node] See \{#splat} + # @param kwarg_splat [Node] See \{#kwarg_splat} + def initialize(name_or_callable, args, keywords, splat, kwarg_splat) + if name_or_callable.is_a?(Sass::Callable) + @callable = name_or_callable + @name = name_or_callable.name + else + @callable = nil + @name = name_or_callable + end + @args = args + @keywords = keywords + @splat = splat + @kwarg_splat = kwarg_splat + super() + end + + # @return [String] A string representation of the function call + def inspect + args = @args.map {|a| a.inspect}.join(', ') + keywords = @keywords.as_stored.to_a.map {|k, v| "$#{k}: #{v.inspect}"}.join(', ') + if self.splat + splat = args.empty? && keywords.empty? ? "" : ", " + splat = "#{splat}#{self.splat.inspect}..." + splat = "#{splat}, #{kwarg_splat.inspect}..." if kwarg_splat + end + "#{name}(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords}#{splat})" + end + + # @see Node#to_sass + def to_sass(opts = {}) + arg_to_sass = lambda do |arg| + sass = arg.to_sass(opts) + sass = "(#{sass})" if arg.is_a?(Sass::Script::Tree::ListLiteral) && arg.separator == :comma + sass + end + + args = @args.map(&arg_to_sass) + keywords = @keywords.as_stored.to_a.map {|k, v| "$#{dasherize(k, opts)}: #{arg_to_sass[v]}"} + + if self.splat + splat = "#{arg_to_sass[self.splat]}..." + kwarg_splat = "#{arg_to_sass[self.kwarg_splat]}..." if self.kwarg_splat + end + + arglist = [args, splat, keywords, kwarg_splat].flatten.compact.join(', ') + "#{dasherize(name, opts)}(#{arglist})" + end + + # Returns the arguments to the function. + # + # @return [Array] + # @see Node#children + def children + res = @args + @keywords.values + res << @splat if @splat + res << @kwarg_splat if @kwarg_splat + res + end + + # @see Node#deep_copy + def deep_copy + node = dup + node.instance_variable_set('@args', args.map {|a| a.deep_copy}) + copied_keywords = Sass::Util::NormalizedMap.new + @keywords.as_stored.each {|k, v| copied_keywords[k] = v.deep_copy} + node.instance_variable_set('@keywords', copied_keywords) + node + end + + protected + + # Evaluates the function call. + # + # @param environment [Sass::Environment] The environment in which to evaluate the SassScript + # @return [Sass::Script::Value] The SassScript object that is the value of the function call + # @raise [Sass::SyntaxError] if the function call raises an ArgumentError + def _perform(environment) + args = @args.each_with_index. + map {|a, i| perform_arg(a, environment, signature && signature.args[i])} + keywords = Sass::Util.map_hash(@keywords) do |k, v| + [k, perform_arg(v, environment, k.tr('-', '_'))] + end + splat = Sass::Tree::Visitors::Perform.perform_splat( + @splat, keywords, @kwarg_splat, environment) + + fn = @callable || environment.function(@name) + + if fn && fn.origin == :stylesheet + environment.stack.with_function(filename, line, name) do + return without_original(perform_sass_fn(fn, args, splat, environment)) + end + end + + args = construct_ruby_args(ruby_name, args, splat, environment) + + if Sass::Script::Functions.callable?(ruby_name) && (!fn || fn.origin == :builtin) + local_environment = Sass::Environment.new(environment.global_env, environment.options) + local_environment.caller = Sass::ReadOnlyEnvironment.new(environment, environment.options) + result = local_environment.stack.with_function(filename, line, name) do + opts(Sass::Script::Functions::EvaluationContext.new( + local_environment).send(ruby_name, *args)) + end + without_original(result) + else + opts(to_literal(args)) + end + rescue ArgumentError => e + reformat_argument_error(e) + end + + # Compass historically overrode this before it changed name to {Funcall#to_value}. + # We should get rid of it in the future. + def to_literal(args) + to_value(args) + end + + # This method is factored out from `_perform` so that compass can override + # it with a cross-browser implementation for functions that require vendor prefixes + # in the generated css. + def to_value(args) + Sass::Script::Value::String.new("#{name}(#{args.join(', ')})") + end + + private + + def ruby_name + @ruby_name ||= @name.tr('-', '_') + end + + def perform_arg(argument, environment, name) + return argument if signature && signature.delayed_args.include?(name) + argument.perform(environment) + end + + def signature + @signature ||= Sass::Script::Functions.signature(name.to_sym, @args.size, @keywords.size) + end + + def without_original(value) + return value unless value.is_a?(Sass::Script::Value::Number) + value = value.dup + value.original = nil + value + end + + def construct_ruby_args(name, args, splat, environment) + args += splat.to_a if splat + + # All keywords are contained in splat.keywords for consistency, + # even if there were no splats passed in. + old_keywords_accessed = splat.keywords_accessed + keywords = splat.keywords + splat.keywords_accessed = old_keywords_accessed + + unless (signature = Sass::Script::Functions.signature(name.to_sym, args.size, keywords.size)) + return args if keywords.empty? + raise Sass::SyntaxError.new("Function #{name} doesn't support keyword arguments") + end + + # If the user passes more non-keyword args than the function expects, + # but it does expect keyword args, Ruby's arg handling won't raise an error. + # Since we don't want to make functions think about this, + # we'll handle it for them here. + if signature.var_kwargs && !signature.var_args && args.size > signature.args.size + raise Sass::SyntaxError.new( + "#{args[signature.args.size].inspect} is not a keyword argument for `#{name}'") + elsif keywords.empty? + args << {} if signature.var_kwargs + return args + end + + argnames = signature.args[args.size..-1] || [] + deprecated_argnames = (signature.deprecated && signature.deprecated[args.size..-1]) || [] + args += argnames.zip(deprecated_argnames).map do |(argname, deprecated_argname)| + if keywords.has_key?(argname) + keywords.delete(argname) + elsif deprecated_argname && keywords.has_key?(deprecated_argname) + deprecated_argname = keywords.denormalize(deprecated_argname) + Sass::Util.sass_warn("DEPRECATION WARNING: The `$#{deprecated_argname}' argument for " + + "`#{@name}()' has been renamed to `$#{argname}'.") + keywords.delete(deprecated_argname) + else + raise Sass::SyntaxError.new("Function #{name} requires an argument named $#{argname}") + end + end + + if keywords.size > 0 + if signature.var_kwargs + # Don't pass a NormalizedMap to a Ruby function. + args << keywords.to_hash + else + argname = keywords.keys.sort.first + if signature.args.include?(argname) + raise Sass::SyntaxError.new( + "Function #{name} was passed argument $#{argname} both by position and by name") + else + raise Sass::SyntaxError.new( + "Function #{name} doesn't have an argument named $#{argname}") + end + end + end + + args + end + + def perform_sass_fn(function, args, splat, environment) + Sass::Tree::Visitors::Perform.perform_arguments(function, args, splat, environment) do |env| + env.caller = Sass::Environment.new(environment) + + val = catch :_sass_return do + function.tree.each {|c| Sass::Tree::Visitors::Perform.visit(c, env)} + raise Sass::SyntaxError.new("Function #{@name} finished without @return") + end + val + end + end + + def reformat_argument_error(e) + message = e.message + + # If this is a legitimate Ruby-raised argument error, re-raise it. + # Otherwise, it's an error in the user's stylesheet, so wrap it. + if Sass::Util.rbx? + # Rubinius has a different error report string than vanilla Ruby. It + # also doesn't put the actual method for which the argument error was + # thrown in the backtrace, nor does it include `send`, so we look for + # `_perform`. + if e.message =~ /^method '([^']+)': given (\d+), expected (\d+)/ + error_name, given, expected = $1, $2, $3 + raise e if error_name != ruby_name || e.backtrace[0] !~ /:in `_perform'$/ + message = "wrong number of arguments (#{given} for #{expected})" + end + elsif Sass::Util.jruby? + should_maybe_raise = + e.message =~ /^wrong number of arguments calling `[^`]+` \((\d+) for (\d+)\)/ + given, expected = $1, $2 + + if should_maybe_raise + # JRuby 1.7 includes __send__ before send and _perform. + trace = e.backtrace.dup + raise e if trace.shift !~ /:in `__send__'$/ + + # JRuby (as of 1.7.2) doesn't put the actual method + # for which the argument error was thrown in the backtrace, so we + # detect whether our send threw an argument error. + if !(trace[0] =~ /:in `send'$/ && trace[1] =~ /:in `_perform'$/) + raise e + else + # JRuby 1.7 doesn't use standard formatting for its ArgumentErrors. + message = "wrong number of arguments (#{given} for #{expected})" + end + end + elsif (md = /^wrong number of arguments \(given (\d+), expected (\d+)\)/.match(e.message)) && + e.backtrace[0] =~ /:in `#{ruby_name}'$/ + # Handle ruby 2.3 error formatting + message = "wrong number of arguments (#{md[1]} for #{md[2]})" + elsif e.message =~ /^wrong number of arguments/ && + e.backtrace[0] !~ /:in `(block in )?#{ruby_name}'$/ + raise e + end + raise Sass::SyntaxError.new("#{message} for `#{name}'") + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/tree/interpolation.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/tree/interpolation.rb new file mode 100644 index 00000000..c62ac4ec --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/tree/interpolation.rb @@ -0,0 +1,220 @@ +module Sass::Script::Tree + # A SassScript object representing `#{}` interpolation outside a string. + # + # @see StringInterpolation + class Interpolation < Node + # @return [Node] The SassScript before the interpolation + attr_reader :before + + # @return [Node] The SassScript within the interpolation + attr_reader :mid + + # @return [Node] The SassScript after the interpolation + attr_reader :after + + # @return [Boolean] Whether there was whitespace between `before` and `#{` + attr_reader :whitespace_before + + # @return [Boolean] Whether there was whitespace between `}` and `after` + attr_reader :whitespace_after + + # @return [Boolean] Whether the original format of the interpolation was + # plain text, not an interpolation. This is used when converting back to + # SassScript. + attr_reader :originally_text + + # @return [Boolean] Whether a color value passed to the interpolation should + # generate a warning. + attr_reader :warn_for_color + + # The type of interpolation deprecation for this node. + # + # This can be `:none`, indicating that the node doesn't use deprecated + # interpolation; `:immediate`, indicating that a deprecation warning should + # be emitted as soon as possible; or `:potential`, indicating that a + # deprecation warning should be emitted if the resulting string is used in a + # way that would distinguish it from a list. + # + # @return [Symbol] + attr_reader :deprecation + + # Interpolation in a property is of the form `before #{mid} after`. + # + # @param before [Node] See {Interpolation#before} + # @param mid [Node] See {Interpolation#mid} + # @param after [Node] See {Interpolation#after} + # @param wb [Boolean] See {Interpolation#whitespace_before} + # @param wa [Boolean] See {Interpolation#whitespace_after} + # @param originally_text [Boolean] See {Interpolation#originally_text} + # @param warn_for_color [Boolean] See {Interpolation#warn_for_color} + def initialize(before, mid, after, wb, wa, opts = {}) + @before = before + @mid = mid + @after = after + @whitespace_before = wb + @whitespace_after = wa + @originally_text = opts[:originally_text] || false + @warn_for_color = opts[:warn_for_color] || false + @deprecation = opts[:deprecation] || :none + end + + # @return [String] A human-readable s-expression representation of the interpolation + def inspect + "(interpolation #{@before.inspect} #{@mid.inspect} #{@after.inspect})" + end + + # @see Node#to_sass + def to_sass(opts = {}) + return to_quoted_equivalent.to_sass if deprecation == :immediate + + res = "" + res << @before.to_sass(opts) if @before + res << ' ' if @before && @whitespace_before + res << '#{' unless @originally_text + res << @mid.to_sass(opts) + res << '}' unless @originally_text + res << ' ' if @after && @whitespace_after + res << @after.to_sass(opts) if @after + res + end + + # Returns an `unquote()` expression that will evaluate to the same value as + # this interpolation. + # + # @return [Sass::Script::Tree::Node] + def to_quoted_equivalent + Funcall.new( + "unquote", + [to_string_interpolation(self)], + Sass::Util::NormalizedMap.new, + nil, + nil) + end + + # Returns the three components of the interpolation, `before`, `mid`, and `after`. + # + # @return [Array] + # @see #initialize + # @see Node#children + def children + [@before, @mid, @after].compact + end + + # @see Node#deep_copy + def deep_copy + node = dup + node.instance_variable_set('@before', @before.deep_copy) if @before + node.instance_variable_set('@mid', @mid.deep_copy) + node.instance_variable_set('@after', @after.deep_copy) if @after + node + end + + protected + + # Converts a script node into a corresponding string interpolation + # expression. + # + # @param node_or_interp [Sass::Script::Tree::Node] + # @return [Sass::Script::Tree::StringInterpolation] + def to_string_interpolation(node_or_interp) + unless node_or_interp.is_a?(Interpolation) + node = node_or_interp + return string_literal(node.value.to_s) if node.is_a?(Literal) + if node.is_a?(StringInterpolation) + return concat(string_literal(node.quote), concat(node, string_literal(node.quote))) + end + return StringInterpolation.new(string_literal(""), node, string_literal("")) + end + + interp = node_or_interp + after_string_or_interp = + if interp.after + to_string_interpolation(interp.after) + else + string_literal("") + end + if interp.after && interp.whitespace_after + after_string_or_interp = concat(string_literal(' '), after_string_or_interp) + end + + mid_string_or_interp = to_string_interpolation(interp.mid) + + before_string_or_interp = + if interp.before + to_string_interpolation(interp.before) + else + string_literal("") + end + if interp.before && interp.whitespace_before + before_string_or_interp = concat(before_string_or_interp, string_literal(' ')) + end + + concat(before_string_or_interp, concat(mid_string_or_interp, after_string_or_interp)) + end + + private + + # Evaluates the interpolation. + # + # @param environment [Sass::Environment] The environment in which to evaluate the SassScript + # @return [Sass::Script::Value::String] + # The SassScript string that is the value of the interpolation + def _perform(environment) + res = "" + res << @before.perform(environment).to_s if @before + res << " " if @before && @whitespace_before + + val = @mid.perform(environment) + if @warn_for_color && val.is_a?(Sass::Script::Value::Color) && val.name + alternative = Operation.new(Sass::Script::Value::String.new("", :string), @mid, :plus) + Sass::Util.sass_warn < :none) + res << " " if @after && @whitespace_after + res << @after.perform(environment).to_s if @after + str = Sass::Script::Value::String.new( + res, :identifier, + (to_quoted_equivalent.to_sass if deprecation == :potential)) + str.source_range = source_range + opts(str) + end + + # Concatenates two string literals or string interpolation expressions. + # + # @param string_or_interp1 [Sass::Script::Tree::Literal|Sass::Script::Tree::StringInterpolation] + # @param string_or_interp2 [Sass::Script::Tree::Literal|Sass::Script::Tree::StringInterpolation] + # @return [Sass::Script::Tree::StringInterpolation] + def concat(string_or_interp1, string_or_interp2) + if string_or_interp1.is_a?(Literal) && string_or_interp2.is_a?(Literal) + return string_literal(string_or_interp1.value.value + string_or_interp2.value.value) + end + + if string_or_interp1.is_a?(Literal) + string = string_or_interp1 + interp = string_or_interp2 + before = string_literal(string.value.value + interp.before.value.value) + return StringInterpolation.new(before, interp.mid, interp.after) + end + + StringInterpolation.new( + string_or_interp1.before, + string_or_interp1.mid, + concat(string_or_interp1.after, string_or_interp2)) + end + + # Returns a string literal with the given contents. + # + # @param string [String] + # @return string [Sass::Script::Tree::Literal] + def string_literal(string) + Literal.new(Sass::Script::Value::String.new(string, :string)) + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/tree/list_literal.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/tree/list_literal.rb new file mode 100644 index 00000000..9fbaad41 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/tree/list_literal.rb @@ -0,0 +1,119 @@ +module Sass::Script::Tree + # A parse tree node representing a list literal. When resolved, this returns a + # {Sass::Tree::Value::List}. + class ListLiteral < Node + # The parse nodes for members of this list. + # + # @return [Array] + attr_reader :elements + + # The operator separating the values of the list. Either `:comma` or + # `:space`. + # + # @return [Symbol] + attr_reader :separator + + # Whether the list is surrounded by square brackets. + # + # @return [Boolean] + attr_reader :bracketed + + # Creates a new list literal. + # + # @param elements [Array] See \{#elements} + # @param separator [Symbol] See \{#separator} + # @param bracketed [Boolean] See \{#bracketed} + def initialize(elements, separator: nil, bracketed: false) + @elements = elements + @separator = separator + @bracketed = bracketed + end + + # @see Node#children + def children; elements; end + + # @see Value#to_sass + def to_sass(opts = {}) + return bracketed ? "[]" : "()" if elements.empty? + members = elements.map do |v| + if element_needs_parens?(v) + "(#{v.to_sass(opts)})" + else + v.to_sass(opts) + end + end + + if separator == :comma && members.length == 1 + return "#{bracketed ? '[' : '('}#{members.first},#{bracketed ? ']' : ')'}" + end + + contents = members.join(sep_str(nil)) + bracketed ? "[#{contents}]" : contents + end + + # @see Node#deep_copy + def deep_copy + node = dup + node.instance_variable_set('@elements', elements.map {|e| e.deep_copy}) + node + end + + def inspect + (bracketed ? '[' : '(') + + elements.map {|e| e.inspect}.join(separator == :space ? ' ' : ', ') + + (bracketed ? ']' : ')') + end + + def force_division! + # Do nothing. Lists prevent division propagation. + end + + protected + + def _perform(environment) + list = Sass::Script::Value::List.new( + elements.map {|e| e.perform(environment)}, + separator: separator, + bracketed: bracketed) + list.source_range = source_range + list.options = options + list + end + + private + + # Returns whether an element in the list should be wrapped in parentheses + # when serialized to Sass. + def element_needs_parens?(element) + if element.is_a?(ListLiteral) + return false if element.elements.length < 2 + return false if element.bracketed + return Sass::Script::Parser.precedence_of(element.separator || :space) <= + Sass::Script::Parser.precedence_of(separator || :space) + end + + return false unless separator == :space + + if element.is_a?(UnaryOperation) + return element.operator == :minus || element.operator == :plus + end + + return false unless element.is_a?(Operation) + return true unless element.operator == :div + !(is_literal_number?(element.operand1) && is_literal_number?(element.operand2)) + end + + # Returns whether a value is a number literal that shouldn't be divided. + def is_literal_number?(value) + value.is_a?(Literal) && + value.value.is_a?((Sass::Script::Value::Number)) && + !value.value.original.nil? + end + + def sep_str(opts = options) + return ' ' if separator == :space + return ',' if opts && opts[:style] == :compressed + ', ' + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/tree/literal.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/tree/literal.rb new file mode 100644 index 00000000..d0dd6e3e --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/tree/literal.rb @@ -0,0 +1,49 @@ +module Sass::Script::Tree + # The parse tree node for a literal scalar value. This wraps an instance of + # {Sass::Script::Value::Base}. + # + # List literals should use {ListLiteral} instead. + class Literal < Node + # The wrapped value. + # + # @return [Sass::Script::Value::Base] + attr_reader :value + + # Creates a new literal value. + # + # @param value [Sass::Script::Value::Base] + # @see #value + def initialize(value) + @value = value + end + + # @see Node#children + def children; []; end + + # @see Node#to_sass + def to_sass(opts = {}); value.to_sass(opts); end + + # @see Node#deep_copy + def deep_copy; dup; end + + # @see Node#options= + def options=(options) + value.options = options + end + + def inspect + value.inspect + end + + def force_division! + value.original = nil if value.is_a?(Sass::Script::Value::Number) + end + + protected + + def _perform(environment) + value.source_range = source_range + value + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/tree/map_literal.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/tree/map_literal.rb new file mode 100644 index 00000000..5c2e88f3 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/tree/map_literal.rb @@ -0,0 +1,64 @@ +module Sass::Script::Tree + # A class representing a map literal. When resolved, this returns a + # {Sass::Script::Node::Map}. + class MapLiteral < Node + # The key/value pairs that make up this map node. This isn't a Hash so that + # we can detect key collisions once all the keys have been performed. + # + # @return [Array<(Node, Node)>] + attr_reader :pairs + + # Creates a new map literal. + # + # @param pairs [Array<(Node, Node)>] See \{#pairs} + def initialize(pairs) + @pairs = pairs + end + + # @see Node#children + def children + @pairs.flatten + end + + # @see Node#to_sass + def to_sass(opts = {}) + return "()" if pairs.empty? + + to_sass = lambda do |value| + if value.is_a?(ListLiteral) && value.separator == :comma + "(#{value.to_sass(opts)})" + else + value.to_sass(opts) + end + end + + "(" + pairs.map {|(k, v)| "#{to_sass[k]}: #{to_sass[v]}"}.join(', ') + ")" + end + alias_method :inspect, :to_sass + + # @see Node#deep_copy + def deep_copy + node = dup + node.instance_variable_set('@pairs', + pairs.map {|(k, v)| [k.deep_copy, v.deep_copy]}) + node + end + + protected + + # @see Node#_perform + def _perform(environment) + keys = Set.new + map = Sass::Script::Value::Map.new(Hash[pairs.map do |(k, v)| + k, v = k.perform(environment), v.perform(environment) + if keys.include?(k) + raise Sass::SyntaxError.new("Duplicate key #{k.inspect} in map #{to_sass}.") + end + keys << k + [k, v] + end]) + map.options = options + map + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/tree/node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/tree/node.rb new file mode 100644 index 00000000..f68dd769 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/tree/node.rb @@ -0,0 +1,119 @@ +module Sass::Script::Tree + # The abstract superclass for SassScript parse tree nodes. + # + # Use \{#perform} to evaluate a parse tree. + class Node + # The options hash for this node. + # + # @return [{Symbol => Object}] + attr_reader :options + + # The line of the document on which this node appeared. + # + # @return [Integer] + attr_accessor :line + + # The source range in the document on which this node appeared. + # + # @return [Sass::Source::Range] + attr_accessor :source_range + + # The file name of the document on which this node appeared. + # + # @return [String] + attr_accessor :filename + + # Sets the options hash for this node, + # as well as for all child nodes. + # See {file:SASS_REFERENCE.md#Options the Sass options documentation}. + # + # @param options [{Symbol => Object}] The options + def options=(options) + @options = options + children.each do |c| + if c.is_a? Hash + c.values.each {|v| v.options = options} + else + c.options = options + end + end + end + + # Evaluates the node. + # + # \{#perform} shouldn't be overridden directly; + # instead, override \{#\_perform}. + # + # @param environment [Sass::Environment] The environment in which to evaluate the SassScript + # @return [Sass::Script::Value] The SassScript object that is the value of the SassScript + def perform(environment) + _perform(environment) + rescue Sass::SyntaxError => e + e.modify_backtrace(:line => line) + raise e + end + + # Returns all child nodes of this node. + # + # @return [Array] + def children + Sass::Util.abstract(self) + end + + # Returns the text of this SassScript expression. + # + # @options opts :quote [String] + # The preferred quote style for quoted strings. If `:none`, strings are + # always emitted unquoted. + # + # @return [String] + def to_sass(opts = {}) + Sass::Util.abstract(self) + end + + # Returns a deep clone of this node. + # The child nodes are cloned, but options are not. + # + # @return [Node] + def deep_copy + Sass::Util.abstract(self) + end + + # Forces any division operations with number literals in this expression to + # do real division, rather than returning strings. + def force_division! + children.each {|c| c.force_division!} + end + + protected + + # Converts underscores to dashes if the :dasherize option is set. + def dasherize(s, opts) + if opts[:dasherize] + s.tr('_', '-') + else + s + end + end + + # Evaluates this node. + # Note that all {Sass::Script::Value} objects created within this method + # should have their \{#options} attribute set, probably via \{#opts}. + # + # @param environment [Sass::Environment] The environment in which to evaluate the SassScript + # @return [Sass::Script::Value] The SassScript object that is the value of the SassScript + # @see #perform + def _perform(environment) + Sass::Util.abstract(self) + end + + # Sets the \{#options} field on the given value and returns it. + # + # @param value [Sass::Script::Value] + # @return [Sass::Script::Value] + def opts(value) + value.options = options + value + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/tree/operation.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/tree/operation.rb new file mode 100644 index 00000000..d8c4ea4a --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/tree/operation.rb @@ -0,0 +1,149 @@ +module Sass::Script::Tree + # A SassScript parse node representing a binary operation, + # such as `$a + $b` or `"foo" + 1`. + class Operation < Node + @@color_arithmetic_deprecation = Sass::Deprecation.new + @@unitless_equals_deprecation = Sass::Deprecation.new + + attr_reader :operand1 + attr_reader :operand2 + attr_reader :operator + + # @param operand1 [Sass::Script::Tree::Node] The parse-tree node + # for the right-hand side of the operator + # @param operand2 [Sass::Script::Tree::Node] The parse-tree node + # for the left-hand side of the operator + # @param operator [Symbol] The operator to perform. + # This should be one of the binary operator names in {Sass::Script::Lexer::OPERATORS} + def initialize(operand1, operand2, operator) + @operand1 = operand1 + @operand2 = operand2 + @operator = operator + super() + end + + # @return [String] A human-readable s-expression representation of the operation + def inspect + "(#{@operator.inspect} #{@operand1.inspect} #{@operand2.inspect})" + end + + # @see Node#to_sass + def to_sass(opts = {}) + o1 = operand_to_sass @operand1, :left, opts + o2 = operand_to_sass @operand2, :right, opts + sep = + case @operator + when :comma; ", " + when :space; " " + else; " #{Sass::Script::Lexer::OPERATORS_REVERSE[@operator]} " + end + "#{o1}#{sep}#{o2}" + end + + # Returns the operands for this operation. + # + # @return [Array] + # @see Node#children + def children + [@operand1, @operand2] + end + + # @see Node#deep_copy + def deep_copy + node = dup + node.instance_variable_set('@operand1', @operand1.deep_copy) + node.instance_variable_set('@operand2', @operand2.deep_copy) + node + end + + protected + + # Evaluates the operation. + # + # @param environment [Sass::Environment] The environment in which to evaluate the SassScript + # @return [Sass::Script::Value] The SassScript object that is the value of the operation + # @raise [Sass::SyntaxError] if the operation is undefined for the operands + def _perform(environment) + value1 = @operand1.perform(environment) + + # Special-case :and and :or to support short-circuiting. + if @operator == :and + return value1.to_bool ? @operand2.perform(environment) : value1 + elsif @operator == :or + return value1.to_bool ? value1 : @operand2.perform(environment) + end + + value2 = @operand2.perform(environment) + + if (value1.is_a?(Sass::Script::Value::Null) || value2.is_a?(Sass::Script::Value::Null)) && + @operator != :eq && @operator != :neq + raise Sass::SyntaxError.new( + "Invalid null operation: \"#{value1.inspect} #{@operator} #{value2.inspect}\".") + end + + begin + result = opts(value1.send(@operator, value2)) + rescue NoMethodError => e + raise e unless e.name.to_s == @operator.to_s + raise Sass::SyntaxError.new("Undefined operation: \"#{value1} #{@operator} #{value2}\".") + end + + warn_for_color_arithmetic(value1, value2) + warn_for_unitless_equals(value1, value2, result) + + result + end + + private + + def warn_for_color_arithmetic(value1, value2) + return unless @operator == :plus || @operator == :times || @operator == :minus || + @operator == :div || @operator == :mod + + if value1.is_a?(Sass::Script::Value::Number) + return unless value2.is_a?(Sass::Script::Value::Color) + elsif value1.is_a?(Sass::Script::Value::Color) + return unless value2.is_a?(Sass::Script::Value::Color) || value2.is_a?(Sass::Script::Value::Number) + else + return + end + + @@color_arithmetic_deprecation.warn(filename, line, < quote) + + res = "" + res << quote if quote != :none + res << _to_sass(before, opts) + res << '#{' << @mid.to_sass(opts.merge(:quote => nil)) << '}' + res << _to_sass(after, opts) + res << quote if quote != :none + res + end + + # Returns the three components of the interpolation, `before`, `mid`, and `after`. + # + # @return [Array] + # @see #initialize + # @see Node#children + def children + [@before, @mid, @after].compact + end + + # @see Node#deep_copy + def deep_copy + node = dup + node.instance_variable_set('@before', @before.deep_copy) if @before + node.instance_variable_set('@mid', @mid.deep_copy) + node.instance_variable_set('@after', @after.deep_copy) if @after + node + end + + protected + + # Evaluates the interpolation. + # + # @param environment [Sass::Environment] The environment in which to evaluate the SassScript + # @return [Sass::Script::Value::String] + # The SassScript string that is the value of the interpolation + def _perform(environment) + res = "" + before = @before.perform(environment) + res << before.value + mid = @mid.perform(environment) + res << (mid.is_a?(Sass::Script::Value::String) ? mid.value : mid.to_s(:quote => :none)) + res << @after.perform(environment).value + opts(Sass::Script::Value::String.new(res, before.type)) + end + + private + + def _to_sass(string_or_interp, opts) + result = string_or_interp.to_sass(opts) + opts[:quote] == :none ? result : result.slice(1...-1) + end + + def quote_for(string_or_interp) + if string_or_interp.is_a?(Sass::Script::Tree::Literal) + return nil if string_or_interp.value.value.empty? + return '"' if string_or_interp.value.value.include?("'") + return "'" if string_or_interp.value.value.include?('"') + return nil + end + + # Double-quotes take precedence over single quotes. + before_quote = quote_for(string_or_interp.before) + return '"' if before_quote == '"' + after_quote = quote_for(string_or_interp.after) + return '"' if after_quote == '"' + + # Returns "'" if either or both insist on single quotes, and nil + # otherwise. + before_quote || after_quote + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/tree/unary_operation.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/tree/unary_operation.rb new file mode 100644 index 00000000..b32da087 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/tree/unary_operation.rb @@ -0,0 +1,69 @@ +module Sass::Script::Tree + # A SassScript parse node representing a unary operation, + # such as `-$b` or `not true`. + # + # Currently only `-`, `/`, and `not` are unary operators. + class UnaryOperation < Node + # @return [Symbol] The operation to perform + attr_reader :operator + + # @return [Script::Node] The parse-tree node for the object of the operator + attr_reader :operand + + # @param operand [Script::Node] See \{#operand} + # @param operator [Symbol] See \{#operator} + def initialize(operand, operator) + @operand = operand + @operator = operator + super() + end + + # @return [String] A human-readable s-expression representation of the operation + def inspect + "(#{@operator.inspect} #{@operand.inspect})" + end + + # @see Node#to_sass + def to_sass(opts = {}) + operand = @operand.to_sass(opts) + if @operand.is_a?(Operation) || + (@operator == :minus && + (operand =~ Sass::SCSS::RX::IDENT) == 0) + operand = "(#{@operand.to_sass(opts)})" + end + op = Sass::Script::Lexer::OPERATORS_REVERSE[@operator] + op + (op =~ /[a-z]/ ? " " : "") + operand + end + + # Returns the operand of the operation. + # + # @return [Array] + # @see Node#children + def children + [@operand] + end + + # @see Node#deep_copy + def deep_copy + node = dup + node.instance_variable_set('@operand', @operand.deep_copy) + node + end + + protected + + # Evaluates the operation. + # + # @param environment [Sass::Environment] The environment in which to evaluate the SassScript + # @return [Sass::Script::Value] The SassScript object that is the value of the operation + # @raise [Sass::SyntaxError] if the operation is undefined for the operand + def _perform(environment) + operator = "unary_#{@operator}" + value = @operand.perform(environment) + value.send(operator) + rescue NoMethodError => e + raise e unless e.name.to_s == operator.to_s + raise Sass::SyntaxError.new("Undefined unary operation: \"#{@operator} #{value}\".") + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/tree/variable.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/tree/variable.rb new file mode 100644 index 00000000..7e197381 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/tree/variable.rb @@ -0,0 +1,57 @@ +module Sass::Script::Tree + # A SassScript parse node representing a variable. + class Variable < Node + # The name of the variable. + # + # @return [String] + attr_reader :name + + # The underscored name of the variable. + # + # @return [String] + attr_reader :underscored_name + + # @param name [String] See \{#name} + def initialize(name) + @name = name + @underscored_name = name.tr("-", "_") + super() + end + + # @return [String] A string representation of the variable + def inspect(opts = {}) + "$#{dasherize(name, opts)}" + end + alias_method :to_sass, :inspect + + # Returns an empty array. + # + # @return [Array] empty + # @see Node#children + def children + [] + end + + # @see Node#deep_copy + def deep_copy + dup + end + + protected + + # Evaluates the variable. + # + # @param environment [Sass::Environment] The environment in which to evaluate the SassScript + # @return [Sass::Script::Value] The SassScript object that is the value of the variable + # @raise [Sass::SyntaxError] if the variable is undefined + def _perform(environment) + val = environment.var(name) + raise Sass::SyntaxError.new("Undefined variable: \"$#{name}\".") unless val + if val.is_a?(Sass::Script::Value::Number) && val.original + val = val.dup + val.original = nil + end + val + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value.rb new file mode 100644 index 00000000..4f35d634 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value.rb @@ -0,0 +1,13 @@ +module Sass::Script::Value; end + +require 'sass/script/value/base' +require 'sass/script/value/string' +require 'sass/script/value/number' +require 'sass/script/value/color' +require 'sass/script/value/bool' +require 'sass/script/value/null' +require 'sass/script/value/list' +require 'sass/script/value/arg_list' +require 'sass/script/value/map' +require 'sass/script/value/callable' +require 'sass/script/value/function' diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/arg_list.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/arg_list.rb new file mode 100644 index 00000000..a68d7963 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/arg_list.rb @@ -0,0 +1,36 @@ +module Sass::Script::Value + # A SassScript object representing a variable argument list. This works just + # like a normal list, but can also contain keyword arguments. + # + # The keyword arguments attached to this list are unused except when this is + # passed as a glob argument to a function or mixin. + class ArgList < List + # Whether \{#keywords} has been accessed. If so, we assume that all keywords + # were valid for the function that created this ArgList. + # + # @return [Boolean] + attr_accessor :keywords_accessed + + # Creates a new argument list. + # + # @param value [Array] See \{List#value}. + # @param keywords [Hash, NormalizedMap] See \{#keywords} + # @param separator [String] See \{List#separator}. + def initialize(value, keywords, separator) + super(value, separator: separator) + if keywords.is_a?(Sass::Util::NormalizedMap) + @keywords = keywords + else + @keywords = Sass::Util::NormalizedMap.new(keywords) + end + end + + # The keyword arguments attached to this list. + # + # @return [NormalizedMap] + def keywords + @keywords_accessed = true + @keywords + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/base.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/base.rb new file mode 100644 index 00000000..65963570 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/base.rb @@ -0,0 +1,258 @@ +module Sass::Script::Value + # The abstract superclass for SassScript objects. + # + # Many of these methods, especially the ones that correspond to SassScript operations, + # are designed to be overridden by subclasses which may change the semantics somewhat. + # The operations listed here are just the defaults. + class Base + # Returns the Ruby value of the value. + # The type of this value varies based on the subclass. + # + # @return [Object] + attr_reader :value + + # The source range in the document on which this node appeared. + # + # @return [Sass::Source::Range] + attr_accessor :source_range + + # Creates a new value. + # + # @param value [Object] The object for \{#value} + def initialize(value = nil) + value.freeze unless value.nil? || value == true || value == false + @value = value + @options = nil + end + + # Sets the options hash for this node, + # as well as for all child nodes. + # See {file:SASS_REFERENCE.md#Options the Sass options documentation}. + # + # @param options [{Symbol => Object}] The options + attr_writer :options + + # Returns the options hash for this node. + # + # @return [{Symbol => Object}] + # @raise [Sass::SyntaxError] if the options hash hasn't been set. + # This should only happen when the value was created + # outside of the parser and \{#to\_s} was called on it + def options + return @options if @options + raise Sass::SyntaxError.new(< :none) + other.to_s(:quote => :none), type) + end + + # The SassScript `-` operation. + # + # @param other [Value] The right-hand side of the operator + # @return [Script::Value::String] A string containing both values + # separated by `"-"` + def minus(other) + Sass::Script::Value::String.new("#{self}-#{other}") + end + + # The SassScript `/` operation. + # + # @param other [Value] The right-hand side of the operator + # @return [Script::Value::String] A string containing both values + # separated by `"/"` + def div(other) + Sass::Script::Value::String.new("#{self}/#{other}") + end + + # The SassScript unary `+` operation (e.g. `+$a`). + # + # @param other [Value] The right-hand side of the operator + # @return [Script::Value::String] A string containing the value + # preceded by `"+"` + def unary_plus + Sass::Script::Value::String.new("+#{self}") + end + + # The SassScript unary `-` operation (e.g. `-$a`). + # + # @param other [Value] The right-hand side of the operator + # @return [Script::Value::String] A string containing the value + # preceded by `"-"` + def unary_minus + Sass::Script::Value::String.new("-#{self}") + end + + # The SassScript unary `/` operation (e.g. `/$a`). + # + # @param other [Value] The right-hand side of the operator + # @return [Script::Value::String] A string containing the value + # preceded by `"/"` + def unary_div + Sass::Script::Value::String.new("/#{self}") + end + + # Returns the hash code of this value. Two objects' hash codes should be + # equal if the objects are equal. + # + # @return [Integer for Ruby 2.4.0+, Fixnum for earlier Ruby versions] The hash code. + def hash + value.hash + end + + def eql?(other) + self == other + end + + # @return [String] A readable representation of the value + def inspect + value.inspect + end + + # @return [Boolean] `true` (the Ruby boolean value) + def to_bool + true + end + + # Compares this object with another. + # + # @param other [Object] The object to compare with + # @return [Boolean] Whether or not this value is equivalent to `other` + def ==(other) + eq(other).to_bool + end + + # @return [Integer] The integer value of this value + # @raise [Sass::SyntaxError] if this value isn't an integer + def to_i + raise Sass::SyntaxError.new("#{inspect} is not an integer.") + end + + # @raise [Sass::SyntaxError] if this value isn't an integer + def assert_int!; to_i; end + + # Returns the separator for this value. For non-list-like values or the + # empty list, this will be `nil`. For lists or maps, it will be `:space` or + # `:comma`. + # + # @return [Symbol] + def separator; nil; end + + # Whether the value is surrounded by square brackets. For non-list values, + # this will be `false`. + # + # @return [Boolean] + def bracketed; false; end + + # Returns the value of this value as a list. + # Single values are considered the same as single-element lists. + # + # @return [Array] This value as a list + def to_a + [self] + end + + # Returns the value of this value as a hash. Most values don't have hash + # representations, but [Map]s and empty [List]s do. + # + # @return [Hash] This value as a hash + # @raise [Sass::SyntaxError] if this value doesn't have a hash representation + def to_h + raise Sass::SyntaxError.new("#{inspect} is not a map.") + end + + # Returns the string representation of this value + # as it would be output to the CSS document. + # + # @options opts :quote [String] + # The preferred quote style for quoted strings. If `:none`, strings are + # always emitted unquoted. + # @return [String] + def to_s(opts = {}) + Sass::Util.abstract(self) + end + alias_method :to_sass, :to_s + + # Returns whether or not this object is null. + # + # @return [Boolean] `false` + def null? + false + end + + # Creates a new list containing `contents` but with the same brackets and + # separators as this object, when interpreted as a list. + # + # @param contents [Array] The contents of the new list. + # @param separator [Symbol] The separator of the new list. Defaults to \{#separator}. + # @param bracketed [Boolean] Whether the new list is bracketed. Defaults to \{#bracketed}. + # @return [Sass::Script::Value::List] + def with_contents(contents, separator: self.separator, bracketed: self.bracketed) + Sass::Script::Value::List.new(contents, separator: separator, bracketed: bracketed) + end + + protected + + # Evaluates the value. + # + # @param environment [Sass::Environment] The environment in which to evaluate the SassScript + # @return [Value] This value + def _perform(environment) + self + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/bool.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/bool.rb new file mode 100644 index 00000000..fd1789ba --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/bool.rb @@ -0,0 +1,35 @@ +module Sass::Script::Value + # A SassScript object representing a boolean (true or false) value. + class Bool < Base + # The true value in SassScript. + # + # This is assigned before new is overridden below so that we use the default implementation. + TRUE = new(true) + + # The false value in SassScript. + # + # This is assigned before new is overridden below so that we use the default implementation. + FALSE = new(false) + + # We override object creation so that users of the core API + # will not need to know that booleans are specific constants. + # + # @param value A ruby value that will be tested for truthiness. + # @return [Bool] TRUE if value is truthy, FALSE if value is falsey + def self.new(value) + value ? TRUE : FALSE + end + + # The Ruby value of the boolean. + # + # @return [Boolean] + attr_reader :value + alias_method :to_bool, :value + + # @return [String] "true" or "false" + def to_s(opts = {}) + @value.to_s + end + alias_method :to_sass, :to_s + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/callable.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/callable.rb new file mode 100644 index 00000000..4e10d0dc --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/callable.rb @@ -0,0 +1,25 @@ +module Sass::Script::Value + # A SassScript object representing a null value. + class Callable < Base + # Constructs a Callable value for use in SassScript. + # + # @param callable [Sass::Callable] The callable to be used when the + # callable is called. + def initialize(callable) + super(callable) + end + + def to_s(opts = {}) + raise Sass::SyntaxError.new("#{to_sass} isn't a valid CSS value.") + end + + def inspect + to_sass + end + + # @abstract + def to_sass + Sass::Util.abstract(self) + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/color.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/color.rb new file mode 100644 index 00000000..ccd148dc --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/color.rb @@ -0,0 +1,704 @@ +module Sass::Script::Value + # A SassScript object representing a CSS color. + # + # A color may be represented internally as RGBA, HSLA, or both. + # It's originally represented as whatever its input is; + # if it's created with RGB values, it's represented as RGBA, + # and if it's created with HSL values, it's represented as HSLA. + # Once a property is accessed that requires the other representation -- + # for example, \{#red} for an HSL color -- + # that component is calculated and cached. + # + # The alpha channel of a color is independent of its RGB or HSL representation. + # It's always stored, as 1 if nothing else is specified. + # If only the alpha channel is modified using \{#with}, + # the cached RGB and HSL values are retained. + class Color < Base + # @private + # + # Convert a ruby integer to a rgba components + # @param color [Integer] + # @return [Array] Array of 4 numbers representing r,g,b and alpha + def self.int_to_rgba(color) + rgba = (0..3).map {|n| color >> (n << 3) & 0xff}.reverse + rgba[-1] = rgba[-1] / 255.0 + rgba + end + + ALTERNATE_COLOR_NAMES = Sass::Util.map_vals( + { + 'aqua' => 0x00FFFFFF, + 'darkgrey' => 0xA9A9A9FF, + 'darkslategrey' => 0x2F4F4FFF, + 'dimgrey' => 0x696969FF, + 'fuchsia' => 0xFF00FFFF, + 'grey' => 0x808080FF, + 'lightgrey' => 0xD3D3D3FF, + 'lightslategrey' => 0x778899FF, + 'slategrey' => 0x708090FF, + }, &method(:int_to_rgba)) + + # A hash from color names to `[red, green, blue]` value arrays. + COLOR_NAMES = Sass::Util.map_vals( + { + 'aliceblue' => 0xF0F8FFFF, + 'antiquewhite' => 0xFAEBD7FF, + 'aquamarine' => 0x7FFFD4FF, + 'azure' => 0xF0FFFFFF, + 'beige' => 0xF5F5DCFF, + 'bisque' => 0xFFE4C4FF, + 'black' => 0x000000FF, + 'blanchedalmond' => 0xFFEBCDFF, + 'blue' => 0x0000FFFF, + 'blueviolet' => 0x8A2BE2FF, + 'brown' => 0xA52A2AFF, + 'burlywood' => 0xDEB887FF, + 'cadetblue' => 0x5F9EA0FF, + 'chartreuse' => 0x7FFF00FF, + 'chocolate' => 0xD2691EFF, + 'coral' => 0xFF7F50FF, + 'cornflowerblue' => 0x6495EDFF, + 'cornsilk' => 0xFFF8DCFF, + 'crimson' => 0xDC143CFF, + 'cyan' => 0x00FFFFFF, + 'darkblue' => 0x00008BFF, + 'darkcyan' => 0x008B8BFF, + 'darkgoldenrod' => 0xB8860BFF, + 'darkgray' => 0xA9A9A9FF, + 'darkgreen' => 0x006400FF, + 'darkkhaki' => 0xBDB76BFF, + 'darkmagenta' => 0x8B008BFF, + 'darkolivegreen' => 0x556B2FFF, + 'darkorange' => 0xFF8C00FF, + 'darkorchid' => 0x9932CCFF, + 'darkred' => 0x8B0000FF, + 'darksalmon' => 0xE9967AFF, + 'darkseagreen' => 0x8FBC8FFF, + 'darkslateblue' => 0x483D8BFF, + 'darkslategray' => 0x2F4F4FFF, + 'darkturquoise' => 0x00CED1FF, + 'darkviolet' => 0x9400D3FF, + 'deeppink' => 0xFF1493FF, + 'deepskyblue' => 0x00BFFFFF, + 'dimgray' => 0x696969FF, + 'dodgerblue' => 0x1E90FFFF, + 'firebrick' => 0xB22222FF, + 'floralwhite' => 0xFFFAF0FF, + 'forestgreen' => 0x228B22FF, + 'gainsboro' => 0xDCDCDCFF, + 'ghostwhite' => 0xF8F8FFFF, + 'gold' => 0xFFD700FF, + 'goldenrod' => 0xDAA520FF, + 'gray' => 0x808080FF, + 'green' => 0x008000FF, + 'greenyellow' => 0xADFF2FFF, + 'honeydew' => 0xF0FFF0FF, + 'hotpink' => 0xFF69B4FF, + 'indianred' => 0xCD5C5CFF, + 'indigo' => 0x4B0082FF, + 'ivory' => 0xFFFFF0FF, + 'khaki' => 0xF0E68CFF, + 'lavender' => 0xE6E6FAFF, + 'lavenderblush' => 0xFFF0F5FF, + 'lawngreen' => 0x7CFC00FF, + 'lemonchiffon' => 0xFFFACDFF, + 'lightblue' => 0xADD8E6FF, + 'lightcoral' => 0xF08080FF, + 'lightcyan' => 0xE0FFFFFF, + 'lightgoldenrodyellow' => 0xFAFAD2FF, + 'lightgreen' => 0x90EE90FF, + 'lightgray' => 0xD3D3D3FF, + 'lightpink' => 0xFFB6C1FF, + 'lightsalmon' => 0xFFA07AFF, + 'lightseagreen' => 0x20B2AAFF, + 'lightskyblue' => 0x87CEFAFF, + 'lightslategray' => 0x778899FF, + 'lightsteelblue' => 0xB0C4DEFF, + 'lightyellow' => 0xFFFFE0FF, + 'lime' => 0x00FF00FF, + 'limegreen' => 0x32CD32FF, + 'linen' => 0xFAF0E6FF, + 'magenta' => 0xFF00FFFF, + 'maroon' => 0x800000FF, + 'mediumaquamarine' => 0x66CDAAFF, + 'mediumblue' => 0x0000CDFF, + 'mediumorchid' => 0xBA55D3FF, + 'mediumpurple' => 0x9370DBFF, + 'mediumseagreen' => 0x3CB371FF, + 'mediumslateblue' => 0x7B68EEFF, + 'mediumspringgreen' => 0x00FA9AFF, + 'mediumturquoise' => 0x48D1CCFF, + 'mediumvioletred' => 0xC71585FF, + 'midnightblue' => 0x191970FF, + 'mintcream' => 0xF5FFFAFF, + 'mistyrose' => 0xFFE4E1FF, + 'moccasin' => 0xFFE4B5FF, + 'navajowhite' => 0xFFDEADFF, + 'navy' => 0x000080FF, + 'oldlace' => 0xFDF5E6FF, + 'olive' => 0x808000FF, + 'olivedrab' => 0x6B8E23FF, + 'orange' => 0xFFA500FF, + 'orangered' => 0xFF4500FF, + 'orchid' => 0xDA70D6FF, + 'palegoldenrod' => 0xEEE8AAFF, + 'palegreen' => 0x98FB98FF, + 'paleturquoise' => 0xAFEEEEFF, + 'palevioletred' => 0xDB7093FF, + 'papayawhip' => 0xFFEFD5FF, + 'peachpuff' => 0xFFDAB9FF, + 'peru' => 0xCD853FFF, + 'pink' => 0xFFC0CBFF, + 'plum' => 0xDDA0DDFF, + 'powderblue' => 0xB0E0E6FF, + 'purple' => 0x800080FF, + 'red' => 0xFF0000FF, + 'rebeccapurple' => 0x663399FF, + 'rosybrown' => 0xBC8F8FFF, + 'royalblue' => 0x4169E1FF, + 'saddlebrown' => 0x8B4513FF, + 'salmon' => 0xFA8072FF, + 'sandybrown' => 0xF4A460FF, + 'seagreen' => 0x2E8B57FF, + 'seashell' => 0xFFF5EEFF, + 'sienna' => 0xA0522DFF, + 'silver' => 0xC0C0C0FF, + 'skyblue' => 0x87CEEBFF, + 'slateblue' => 0x6A5ACDFF, + 'slategray' => 0x708090FF, + 'snow' => 0xFFFAFAFF, + 'springgreen' => 0x00FF7FFF, + 'steelblue' => 0x4682B4FF, + 'tan' => 0xD2B48CFF, + 'teal' => 0x008080FF, + 'thistle' => 0xD8BFD8FF, + 'tomato' => 0xFF6347FF, + 'transparent' => 0x00000000, + 'turquoise' => 0x40E0D0FF, + 'violet' => 0xEE82EEFF, + 'wheat' => 0xF5DEB3FF, + 'white' => 0xFFFFFFFF, + 'whitesmoke' => 0xF5F5F5FF, + 'yellow' => 0xFFFF00FF, + 'yellowgreen' => 0x9ACD32FF + }, &method(:int_to_rgba)) + + # A hash from `[red, green, blue, alpha]` value arrays to color names. + COLOR_NAMES_REVERSE = COLOR_NAMES.invert.freeze + + # We add the alternate color names after inverting because + # different ruby implementations and versions vary on the ordering of the result of invert. + COLOR_NAMES.update(ALTERNATE_COLOR_NAMES).freeze + + # The user's original representation of the color. + # + # @return [String] + attr_reader :representation + + # Constructs an RGB or HSL color object, + # optionally with an alpha channel. + # + # RGB values are clipped within 0 and 255. + # Saturation and lightness values are clipped within 0 and 100. + # The alpha value is clipped within 0 and 1. + # + # @raise [Sass::SyntaxError] if any color value isn't in the specified range + # + # @overload initialize(attrs) + # The attributes are specified as a hash. This hash must contain either + # `:hue`, `:saturation`, and `:lightness` keys, or `:red`, `:green`, and + # `:blue` keys. It cannot contain both HSL and RGB keys. It may also + # optionally contain an `:alpha` key, and a `:representation` key + # indicating the original representation of the color that the user wrote + # in their stylesheet. + # + # @param attrs [{Symbol => Numeric}] A hash of color attributes to values + # @raise [ArgumentError] if not enough attributes are specified, + # or both RGB and HSL attributes are specified + # + # @overload initialize(rgba, [representation]) + # The attributes are specified as an array. + # This overload only supports RGB or RGBA colors. + # + # @param rgba [Array] A three- or four-element array + # of the red, green, blue, and optionally alpha values (respectively) + # of the color + # @param representation [String] The original representation of the color + # that the user wrote in their stylesheet. + # @raise [ArgumentError] if not enough attributes are specified + def initialize(attrs, representation = nil, allow_both_rgb_and_hsl = false) + super(nil) + + if attrs.is_a?(Array) + unless (3..4).include?(attrs.size) + raise ArgumentError.new("Color.new(array) expects a three- or four-element array") + end + + red, green, blue = attrs[0...3].map {|c| Sass::Util.round(c)} + @attrs = {:red => red, :green => green, :blue => blue} + @attrs[:alpha] = attrs[3] ? attrs[3].to_f : 1 + @representation = representation + else + attrs = attrs.reject {|_k, v| v.nil?} + hsl = [:hue, :saturation, :lightness] & attrs.keys + rgb = [:red, :green, :blue] & attrs.keys + if !allow_both_rgb_and_hsl && !hsl.empty? && !rgb.empty? + raise ArgumentError.new("Color.new(hash) may not have both HSL and RGB keys specified") + elsif hsl.empty? && rgb.empty? + raise ArgumentError.new("Color.new(hash) must have either HSL or RGB keys specified") + elsif !hsl.empty? && hsl.size != 3 + raise ArgumentError.new("Color.new(hash) must have all three HSL values specified") + elsif !rgb.empty? && rgb.size != 3 + raise ArgumentError.new("Color.new(hash) must have all three RGB values specified") + end + + @attrs = attrs + @attrs[:hue] %= 360 if @attrs[:hue] + @attrs[:alpha] ||= 1 + @representation = @attrs.delete(:representation) + end + + [:red, :green, :blue].each do |k| + next if @attrs[k].nil? + @attrs[k] = Sass::Util.restrict(Sass::Util.round(@attrs[k]), 0..255) + end + + [:saturation, :lightness].each do |k| + next if @attrs[k].nil? + @attrs[k] = Sass::Util.restrict(@attrs[k], 0..100) + end + + @attrs[:alpha] = Sass::Util.restrict(@attrs[:alpha], 0..1) + end + + # Create a new color from a valid CSS hex string. + # + # The leading hash is optional. + # + # @return [Color] + def self.from_hex(hex_string, alpha = nil) + unless hex_string =~ /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?$/i || + hex_string =~ /^#?([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])?$/i + raise ArgumentError.new("#{hex_string.inspect} is not a valid hex color.") + end + red = $1.ljust(2, $1).to_i(16) + green = $2.ljust(2, $2).to_i(16) + blue = $3.ljust(2, $3).to_i(16) + alpha = $4.ljust(2, $4).to_i(16).to_f / 0xff if $4 + + hex_string = "##{hex_string}" unless hex_string[0] == ?# + attrs = {:red => red, :green => green, :blue => blue, :representation => hex_string} + attrs[:alpha] = alpha if alpha + new(attrs) + end + + # The red component of the color. + # + # @return [Integer] + def red + hsl_to_rgb! + @attrs[:red] + end + + # The green component of the color. + # + # @return [Integer] + def green + hsl_to_rgb! + @attrs[:green] + end + + # The blue component of the color. + # + # @return [Integer] + def blue + hsl_to_rgb! + @attrs[:blue] + end + + # The hue component of the color. + # + # @return [Numeric] + def hue + rgb_to_hsl! + @attrs[:hue] + end + + # The saturation component of the color. + # + # @return [Numeric] + def saturation + rgb_to_hsl! + @attrs[:saturation] + end + + # The lightness component of the color. + # + # @return [Numeric] + def lightness + rgb_to_hsl! + @attrs[:lightness] + end + + # The alpha channel (opacity) of the color. + # This is 1 unless otherwise defined. + # + # @return [Integer] + def alpha + @attrs[:alpha].to_f + end + + # Returns whether this color object is translucent; + # that is, whether the alpha channel is non-1. + # + # @return [Boolean] + def alpha? + alpha < 1 + end + + # Returns the red, green, and blue components of the color. + # + # @return [Array] A frozen three-element array of the red, green, and blue + # values (respectively) of the color + def rgb + [red, green, blue].freeze + end + + # Returns the red, green, blue, and alpha components of the color. + # + # @return [Array] A frozen four-element array of the red, green, + # blue, and alpha values (respectively) of the color + def rgba + [red, green, blue, alpha].freeze + end + + # Returns the hue, saturation, and lightness components of the color. + # + # @return [Array] A frozen three-element array of the + # hue, saturation, and lightness values (respectively) of the color + def hsl + [hue, saturation, lightness].freeze + end + + # Returns the hue, saturation, lightness, and alpha components of the color. + # + # @return [Array] A frozen four-element array of the hue, + # saturation, lightness, and alpha values (respectively) of the color + def hsla + [hue, saturation, lightness, alpha].freeze + end + + # The SassScript `==` operation. + # **Note that this returns a {Sass::Script::Value::Bool} object, + # not a Ruby boolean**. + # + # @param other [Value] The right-hand side of the operator + # @return [Bool] True if this value is the same as the other, + # false otherwise + def eq(other) + Sass::Script::Value::Bool.new( + other.is_a?(Color) && rgb == other.rgb && alpha == other.alpha) + end + + def hash + [rgb, alpha].hash + end + + # Returns a copy of this color with one or more channels changed. + # RGB or HSL colors may be changed, but not both at once. + # + # For example: + # + # Color.new([10, 20, 30]).with(:blue => 40) + # #=> rgb(10, 40, 30) + # Color.new([126, 126, 126]).with(:red => 0, :green => 255) + # #=> rgb(0, 255, 126) + # Color.new([255, 0, 127]).with(:saturation => 60) + # #=> rgb(204, 51, 127) + # Color.new([1, 2, 3]).with(:alpha => 0.4) + # #=> rgba(1, 2, 3, 0.4) + # + # @param attrs [{Symbol => Numeric}] + # A map of channel names (`:red`, `:green`, `:blue`, + # `:hue`, `:saturation`, `:lightness`, or `:alpha`) to values + # @return [Color] The new Color object + # @raise [ArgumentError] if both RGB and HSL keys are specified + def with(attrs) + attrs = attrs.reject {|_k, v| v.nil?} + hsl = !([:hue, :saturation, :lightness] & attrs.keys).empty? + rgb = !([:red, :green, :blue] & attrs.keys).empty? + if hsl && rgb + raise ArgumentError.new("Cannot specify HSL and RGB values for a color at the same time") + end + + if hsl + [:hue, :saturation, :lightness].each {|k| attrs[k] ||= send(k)} + elsif rgb + [:red, :green, :blue].each {|k| attrs[k] ||= send(k)} + else + # If we're just changing the alpha channel, + # keep all the HSL/RGB stuff we've calculated + attrs = @attrs.merge(attrs) + end + attrs[:alpha] ||= alpha + + Color.new(attrs, nil, :allow_both_rgb_and_hsl) + end + + # The SassScript `+` operation. + # Its functionality depends on the type of its argument: + # + # {Number} + # : Adds the number to each of the RGB color channels. + # + # {Color} + # : Adds each of the RGB color channels together. + # + # {Value} + # : See {Value::Base#plus}. + # + # @param other [Value] The right-hand side of the operator + # @return [Color] The resulting color + # @raise [Sass::SyntaxError] if `other` is a number with units + def plus(other) + if other.is_a?(Sass::Script::Value::Number) || other.is_a?(Sass::Script::Value::Color) + piecewise(other, :+) + else + super + end + end + + # The SassScript `-` operation. + # Its functionality depends on the type of its argument: + # + # {Number} + # : Subtracts the number from each of the RGB color channels. + # + # {Color} + # : Subtracts each of the other color's RGB color channels from this color's. + # + # {Value} + # : See {Value::Base#minus}. + # + # @param other [Value] The right-hand side of the operator + # @return [Color] The resulting color + # @raise [Sass::SyntaxError] if `other` is a number with units + def minus(other) + if other.is_a?(Sass::Script::Value::Number) || other.is_a?(Sass::Script::Value::Color) + piecewise(other, :-) + else + super + end + end + + # The SassScript `*` operation. + # Its functionality depends on the type of its argument: + # + # {Number} + # : Multiplies the number by each of the RGB color channels. + # + # {Color} + # : Multiplies each of the RGB color channels together. + # + # @param other [Number, Color] The right-hand side of the operator + # @return [Color] The resulting color + # @raise [Sass::SyntaxError] if `other` is a number with units + def times(other) + if other.is_a?(Sass::Script::Value::Number) || other.is_a?(Sass::Script::Value::Color) + piecewise(other, :*) + else + raise NoMethodError.new(nil, :times) + end + end + + # The SassScript `/` operation. + # Its functionality depends on the type of its argument: + # + # {Number} + # : Divides each of the RGB color channels by the number. + # + # {Color} + # : Divides each of this color's RGB color channels by the other color's. + # + # {Value} + # : See {Value::Base#div}. + # + # @param other [Value] The right-hand side of the operator + # @return [Color] The resulting color + # @raise [Sass::SyntaxError] if `other` is a number with units + def div(other) + if other.is_a?(Sass::Script::Value::Number) || + other.is_a?(Sass::Script::Value::Color) + piecewise(other, :/) + else + super + end + end + + # The SassScript `%` operation. + # Its functionality depends on the type of its argument: + # + # {Number} + # : Takes each of the RGB color channels module the number. + # + # {Color} + # : Takes each of this color's RGB color channels modulo the other color's. + # + # @param other [Number, Color] The right-hand side of the operator + # @return [Color] The resulting color + # @raise [Sass::SyntaxError] if `other` is a number with units + def mod(other) + if other.is_a?(Sass::Script::Value::Number) || + other.is_a?(Sass::Script::Value::Color) + piecewise(other, :%) + else + raise NoMethodError.new(nil, :mod) + end + end + + # Returns a string representation of the color. + # This is usually the color's hex value, + # but if the color has a name that's used instead. + # + # @return [String] The string representation + def to_s(opts = {}) + return smallest if options[:style] == :compressed + return representation if representation + + # IE10 doesn't properly support the color name "transparent", so we emit + # generated transparent colors as rgba(0, 0, 0, 0) in favor of that. See + # #1782. + return rgba_str if Number.basically_equal?(alpha, 0) + return name if name + alpha? ? rgba_str : hex_str + end + alias_method :to_sass, :to_s + + # Returns a string representation of the color. + # + # @return [String] The hex value + def inspect + alpha? ? rgba_str : hex_str + end + + # Returns the color's name, if it has one. + # + # @return [String, nil] + def name + COLOR_NAMES_REVERSE[rgba] + end + + private + + def smallest + small_explicit_str = alpha? ? rgba_str : hex_str.gsub(/^#(.)\1(.)\2(.)\3$/, '#\1\2\3') + [representation, COLOR_NAMES_REVERSE[rgba], small_explicit_str]. + compact.min_by {|str| str.size} + end + + def rgba_str + split = options[:style] == :compressed ? ',' : ', ' + "rgba(#{rgb.join(split)}#{split}#{Number.round(alpha)})" + end + + def hex_str + red, green, blue = rgb.map {|num| num.to_s(16).rjust(2, '0')} + "##{red}#{green}#{blue}" + end + + def operation_name(operation) + case operation + when :+ + "add" + when :- + "subtract" + when :* + "multiply" + when :/ + "divide" + when :% + "modulo" + end + end + + def piecewise(other, operation) + other_num = other.is_a? Number + if other_num && !other.unitless? + raise Sass::SyntaxError.new( + "Cannot #{operation_name(operation)} a number with units (#{other}) to a color (#{self})." + ) + end + + result = [] + (0...3).each do |i| + res = rgb[i].to_f.send(operation, other_num ? other.value : other.rgb[i]) + result[i] = [[res, 255].min, 0].max + end + + if !other_num && other.alpha != alpha + raise Sass::SyntaxError.new("Alpha channels must be equal: #{self} #{operation} #{other}") + end + + with(:red => result[0], :green => result[1], :blue => result[2]) + end + + def hsl_to_rgb! + return if @attrs[:red] && @attrs[:blue] && @attrs[:green] + + h = @attrs[:hue] / 360.0 + s = @attrs[:saturation] / 100.0 + l = @attrs[:lightness] / 100.0 + + # Algorithm from the CSS3 spec: http://www.w3.org/TR/css3-color/#hsl-color. + m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s + m1 = l * 2 - m2 + @attrs[:red], @attrs[:green], @attrs[:blue] = [ + hue_to_rgb(m1, m2, h + 1.0 / 3), + hue_to_rgb(m1, m2, h), + hue_to_rgb(m1, m2, h - 1.0 / 3) + ].map {|c| Sass::Util.round(c * 0xff)} + end + + def hue_to_rgb(m1, m2, h) + h += 1 if h < 0 + h -= 1 if h > 1 + return m1 + (m2 - m1) * h * 6 if h * 6 < 1 + return m2 if h * 2 < 1 + return m1 + (m2 - m1) * (2.0 / 3 - h) * 6 if h * 3 < 2 + m1 + end + + def rgb_to_hsl! + return if @attrs[:hue] && @attrs[:saturation] && @attrs[:lightness] + r, g, b = [:red, :green, :blue].map {|k| @attrs[k] / 255.0} + + # Algorithm from http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV + max = [r, g, b].max + min = [r, g, b].min + d = max - min + + h = + case max + when min; 0 + when r; 60 * (g - b) / d + when g; 60 * (b - r) / d + 120 + when b; 60 * (r - g) / d + 240 + end + + l = (max + min) / 2.0 + + s = + if max == min + 0 + elsif l < 0.5 + d / (2 * l) + else + d / (2 - 2 * l) + end + + @attrs[:hue] = h % 360 + @attrs[:saturation] = s * 100 + @attrs[:lightness] = l * 100 + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/function.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/function.rb new file mode 100644 index 00000000..f0f59a25 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/function.rb @@ -0,0 +1,19 @@ +module Sass::Script::Value + # A SassScript object representing a function. + class Function < Callable + # Constructs a Function value for use in SassScript. + # + # @param function [Sass::Callable] The callable to be used when the + # function is invoked. + def initialize(function) + unless function.type == "function" + raise ArgumentError.new("A callable of type function was expected.") + end + super + end + + def to_sass + %{get-function("#{value.name}")} + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/helpers.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/helpers.rb new file mode 100644 index 00000000..dcde4875 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/helpers.rb @@ -0,0 +1,298 @@ +module Sass::Script::Value + # Provides helper functions for creating sass values from within ruby methods. + # @since `3.3.0` + module Helpers + # Construct a Sass Boolean. + # + # @param value [Object] A ruby object that will be tested for truthiness. + # @return [Sass::Script::Value::Bool] whether the ruby value is truthy. + def bool(value) + Bool.new(value) + end + + # Construct a Sass Color from a hex color string. + # + # @param value [::String] A string representing a hex color. + # The leading hash ("#") is optional. + # @param alpha [::Number] The alpha channel. A number between 0 and 1. + # @return [Sass::Script::Value::Color] the color object + def hex_color(value, alpha = nil) + Color.from_hex(value, alpha) + end + + # Construct a Sass Color from hsl values. + # + # @param hue [::Number] The hue of the color in degrees. + # A non-negative number, usually less than 360. + # @param saturation [::Number] The saturation of the color. + # Must be between 0 and 100 inclusive. + # @param lightness [::Number] The lightness of the color. + # Must be between 0 and 100 inclusive. + # @param alpha [::Number] The alpha channel. A number between 0 and 1. + # + # @return [Sass::Script::Value::Color] the color object + def hsl_color(hue, saturation, lightness, alpha = nil) + attrs = {:hue => hue, :saturation => saturation, :lightness => lightness} + attrs[:alpha] = alpha if alpha + Color.new(attrs) + end + + # Construct a Sass Color from rgb values. + # + # @param red [::Number] The red component. Must be between 0 and 255 inclusive. + # @param green [::Number] The green component. Must be between 0 and 255 inclusive. + # @param blue [::Number] The blue component. Must be between 0 and 255 inclusive. + # @param alpha [::Number] The alpha channel. A number between 0 and 1. + # + # @return [Sass::Script::Value::Color] the color object + def rgb_color(red, green, blue, alpha = nil) + attrs = {:red => red, :green => green, :blue => blue} + attrs[:alpha] = alpha if alpha + Color.new(attrs) + end + + # Construct a Sass Number from a ruby number. + # + # @param number [::Number] A numeric value. + # @param unit_string [::String] A unit string of the form + # `numeral_unit1 * numeral_unit2 ... / denominator_unit1 * denominator_unit2 ...` + # this is the same format that is returned by + # {Sass::Script::Value::Number#unit_str the `unit_str` method} + # + # @see Sass::Script::Value::Number#unit_str + # + # @return [Sass::Script::Value::Number] The sass number representing the given ruby number. + def number(number, unit_string = nil) + Number.new(number, *parse_unit_string(unit_string)) + end + + # @overload list(*elements, separator:, bracketed: false) + # Create a space-separated list from the arguments given. + # @param elements [Array] Each argument will be a list element. + # @param separator [Symbol] Either :space or :comma. + # @param bracketed [Boolean] Whether the list uses square brackets. + # @return [Sass::Script::Value::List] The space separated list. + # + # @overload list(array, separator:, bracketed: false) + # Create a space-separated list from the array given. + # @param array [Array] A ruby array of Sass values + # to make into a list. + # @param separator [Symbol] Either :space or :comma. + # @param bracketed [Boolean] Whether the list uses square brackets. + # @return [Sass::Script::Value::List] The space separated list. + def list(*elements, separator: nil, bracketed: false) + # Support passing separator as the last value in elements for + # backwards-compatibility. + if separator.nil? + if elements.last.is_a?(Symbol) + separator = elements.pop + else + raise ArgumentError.new("A separator of :space or :comma must be specified.") + end + end + + if elements.size == 1 && elements.first.is_a?(Array) + elements = elements.first + end + Sass::Script::Value::List.new(elements, separator: separator, bracketed: bracketed) + end + + # Construct a Sass map. + # + # @param hash [Hash] A Ruby map to convert to a Sass map. + # @return [Sass::Script::Value::Map] The map. + def map(hash) + Map.new(hash) + end + + # Create a sass null value. + # + # @return [Sass::Script::Value::Null] + def null + Sass::Script::Value::Null.new + end + + # Create a quoted string. + # + # @param str [::String] A ruby string. + # @return [Sass::Script::Value::String] A quoted string. + def quoted_string(str) + Sass::Script::String.new(str, :string) + end + + # Create an unquoted string. + # + # @param str [::String] A ruby string. + # @return [Sass::Script::Value::String] An unquoted string. + def unquoted_string(str) + Sass::Script::String.new(str, :identifier) + end + alias_method :identifier, :unquoted_string + + # Parses a user-provided selector. + # + # @param value [Sass::Script::Value::String, Sass::Script::Value::List] + # The selector to parse. This can be either a string, a list of + # strings, or a list of lists of strings as returned by `&`. + # @param name [Symbol, nil] + # If provided, the name of the selector argument. This is used + # for error reporting. + # @param allow_parent_ref [Boolean] + # Whether the parsed selector should allow parent references. + # @return [Sass::Selector::CommaSequence] The parsed selector. + # @throw [ArgumentError] if the parse failed for any reason. + def parse_selector(value, name = nil, allow_parent_ref = false) + str = normalize_selector(value, name) + begin + Sass::SCSS::StaticParser.new(str, nil, nil, 1, 1, allow_parent_ref).parse_selector + rescue Sass::SyntaxError => e + err = "#{value.inspect} is not a valid selector: #{e}" + err = "$#{name.to_s.tr('_', '-')}: #{err}" if name + raise ArgumentError.new(err) + end + end + + # Parses a user-provided complex selector. + # + # A complex selector can contain combinators but cannot contain commas. + # + # @param value [Sass::Script::Value::String, Sass::Script::Value::List] + # The selector to parse. This can be either a string or a list of + # strings. + # @param name [Symbol, nil] + # If provided, the name of the selector argument. This is used + # for error reporting. + # @param allow_parent_ref [Boolean] + # Whether the parsed selector should allow parent references. + # @return [Sass::Selector::Sequence] The parsed selector. + # @throw [ArgumentError] if the parse failed for any reason. + def parse_complex_selector(value, name = nil, allow_parent_ref = false) + selector = parse_selector(value, name, allow_parent_ref) + return seq if selector.members.length == 1 + + err = "#{value.inspect} is not a complex selector" + err = "$#{name.to_s.tr('_', '-')}: #{err}" if name + raise ArgumentError.new(err) + end + + # Parses a user-provided compound selector. + # + # A compound selector cannot contain combinators or commas. + # + # @param value [Sass::Script::Value::String] The selector to parse. + # @param name [Symbol, nil] + # If provided, the name of the selector argument. This is used + # for error reporting. + # @param allow_parent_ref [Boolean] + # Whether the parsed selector should allow parent references. + # @return [Sass::Selector::SimpleSequence] The parsed selector. + # @throw [ArgumentError] if the parse failed for any reason. + def parse_compound_selector(value, name = nil, allow_parent_ref = false) + assert_type value, :String, name + selector = parse_selector(value, name, allow_parent_ref) + seq = selector.members.first + sseq = seq.members.first + if selector.members.length == 1 && seq.members.length == 1 && + sseq.is_a?(Sass::Selector::SimpleSequence) + return sseq + end + + err = "#{value.inspect} is not a compound selector" + err = "$#{name.to_s.tr('_', '-')}: #{err}" if name + raise ArgumentError.new(err) + end + + # Returns true when the literal is a string containing a calc(). + # + # Use \{#special_number?} in preference to this. + # + # @param literal [Sass::Script::Value::Base] The value to check + # @return Boolean + def calc?(literal) + literal.is_a?(Sass::Script::Value::String) && literal.value =~ /calc\(/ + end + + # Returns true when the literal is a string containing a var(). + # + # @param literal [Sass::Script::Value::Base] The value to check + # @return Boolean + def var?(literal) + literal.is_a?(Sass::Script::Value::String) && literal.value =~ /var\(/ + end + + # Returns whether the literal is a special CSS value that may evaluate to a + # number, such as `calc()` or `var()`. + # + # @param literal [Sass::Script::Value::Base] The value to check + # @return Boolean + def special_number?(literal) + literal.is_a?(Sass::Script::Value::String) && literal.value =~ /(calc|var)\(/ + end + + private + + # Converts a user-provided selector into string form or throws an + # ArgumentError if it's in an invalid format. + def normalize_selector(value, name) + if (str = selector_to_str(value)) + return str + end + + err = "#{value.inspect} is not a valid selector: it must be a string,\n" + + "a list of strings, or a list of lists of strings" + err = "$#{name.to_s.tr('_', '-')}: #{err}" if name + raise ArgumentError.new(err) + end + + # Converts a user-provided selector into string form or returns + # `nil` if it's in an invalid format. + def selector_to_str(value) + return value.value if value.is_a?(Sass::Script::String) + return unless value.is_a?(Sass::Script::List) + + if value.separator == :comma + return value.to_a.map do |complex| + next complex.value if complex.is_a?(Sass::Script::String) + return unless complex.is_a?(Sass::Script::List) && complex.separator == :space + return unless (str = selector_to_str(complex)) + str + end.join(', ') + end + + value.to_a.map do |compound| + return unless compound.is_a?(Sass::Script::String) + compound.value + end.join(' ') + end + + # @private + VALID_UNIT = /#{Sass::SCSS::RX::NMSTART}#{Sass::SCSS::RX::NMCHAR}|%*/ + + # @example + # parse_unit_string("em*px/in*%") # => [["em", "px], ["in", "%"]] + # + # @param unit_string [String] A string adhering to the output of a number with complex + # units. E.g. "em*px/in*%" + # @return [Array>] A list of numerator units and a list of denominator units. + def parse_unit_string(unit_string) + denominator_units = numerator_units = Sass::Script::Value::Number::NO_UNITS + return numerator_units, denominator_units unless unit_string && unit_string.length > 0 + num_over_denominator = unit_string.split(%r{ */ *}) + unless (1..2).include?(num_over_denominator.size) + raise ArgumentError.new("Malformed unit string: #{unit_string}") + end + numerator_units = num_over_denominator[0].split(/ *\* */) + denominator_units = (num_over_denominator[1] || "").split(/ *\* */) + [[numerator_units, "numerator"], [denominator_units, "denominator"]].each do |units, name| + if unit_string =~ %r{/} && units.size == 0 + raise ArgumentError.new("Malformed unit string: #{unit_string}") + end + if units.any? {|unit| unit !~ VALID_UNIT} + raise ArgumentError.new("Malformed #{name} in unit string: #{unit_string}") + end + end + [numerator_units, denominator_units] + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/list.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/list.rb new file mode 100644 index 00000000..689fdd0e --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/list.rb @@ -0,0 +1,134 @@ +module Sass::Script::Value + # A SassScript object representing a CSS list. + # This includes both comma-separated lists and space-separated lists. + class List < Base + # The Ruby array containing the contents of the list. + # + # @return [Array] + attr_reader :value + alias_method :to_a, :value + + # The operator separating the values of the list. + # Either `:comma` or `:space`. + # + # @return [Symbol] + attr_reader :separator + + # Whether the list is surrounded by square brackets. + # + # @return [Boolean] + attr_reader :bracketed + + # Creates a new list. + # + # @param value [Array] See \{#value} + # @param separator [Symbol] See \{#separator} + # @param bracketed [Boolean] See \{#bracketed} + def initialize(value, separator: nil, bracketed: false) + super(value) + @separator = separator + @bracketed = bracketed + end + + # @see Value#options= + def options=(options) + super + value.each {|v| v.options = options} + end + + # @see Value#eq + def eq(other) + Sass::Script::Value::Bool.new( + other.is_a?(List) && value == other.value && + separator == other.separator && bracketed == other.bracketed) + end + + def hash + @hash ||= [value, separator, bracketed].hash + end + + # @see Value#to_s + def to_s(opts = {}) + if !bracketed && value.empty? + raise Sass::SyntaxError.new("#{inspect} isn't a valid CSS value.") + end + + members = value. + reject {|e| e.is_a?(Null) || e.is_a?(List) && e.value.empty?}. + map {|e| e.to_s(opts)} + + contents = members.join(sep_str) + bracketed ? "[#{contents}]" : contents + end + + # @see Value#to_sass + def to_sass(opts = {}) + return bracketed ? "[]" : "()" if value.empty? + members = value.map do |v| + if element_needs_parens?(v) + "(#{v.to_sass(opts)})" + else + v.to_sass(opts) + end + end + + if separator == :comma && members.length == 1 + return "#{bracketed ? '[' : '('}#{members.first},#{bracketed ? ']' : ')'}" + end + + contents = members.join(sep_str(nil)) + bracketed ? "[#{contents}]" : contents + end + + # @see Value#to_h + def to_h + return {} if value.empty? + super + end + + # @see Value#inspect + def inspect + (bracketed ? '[' : '(') + + value.map {|e| e.inspect}.join(sep_str(nil)) + + (bracketed ? ']' : ')') + end + + # Asserts an index is within the list. + # + # @private + # + # @param list [Sass::Script::Value::List] The list for which the index should be checked. + # @param n [Sass::Script::Value::Number] The index being checked. + def self.assert_valid_index(list, n) + if !n.int? || n.to_i == 0 + raise ArgumentError.new("List index #{n} must be a non-zero integer") + elsif list.to_a.size == 0 + raise ArgumentError.new("List index is #{n} but list has no items") + elsif n.to_i.abs > (size = list.to_a.size) + raise ArgumentError.new( + "List index is #{n} but list is only #{size} item#{'s' if size != 1} long") + end + end + + private + + def element_needs_parens?(element) + if element.is_a?(List) + return false if element.value.length < 2 + return false if element.bracketed + precedence = Sass::Script::Parser.precedence_of(separator || :space) + return Sass::Script::Parser.precedence_of(element.separator || :space) <= precedence + end + + return false unless separator == :space + return false unless element.is_a?(Sass::Script::Tree::UnaryOperation) + element.operator == :minus || element.operator == :plus + end + + def sep_str(opts = options) + return ' ' if separator == :space + return ',' if opts && opts[:style] == :compressed + ', ' + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/map.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/map.rb new file mode 100644 index 00000000..2d1c6a36 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/map.rb @@ -0,0 +1,70 @@ +module Sass::Script::Value + # A SassScript object representing a map from keys to values. Both keys and + # values can be any SassScript object. + class Map < Base + # The Ruby hash containing the contents of this map. + # + # @return [Hash] + attr_reader :value + alias_method :to_h, :value + + # Creates a new map. + # + # @param hash [Hash] + def initialize(hash) + super(hash) + end + + # @see Value#options= + def options=(options) + super + value.each do |k, v| + k.options = options + v.options = options + end + end + + # @see Value#separator + def separator + :comma unless value.empty? + end + + # @see Value#to_a + def to_a + value.map do |k, v| + list = List.new([k, v], separator: :space) + list.options = options + list + end + end + + # @see Value#eq + def eq(other) + Bool.new(other.is_a?(Map) && value == other.value) + end + + def hash + @hash ||= value.hash + end + + # @see Value#to_s + def to_s(opts = {}) + raise Sass::SyntaxError.new("#{inspect} isn't a valid CSS value.") + end + + def to_sass(opts = {}) + return "()" if value.empty? + + to_sass = lambda do |value| + if value.is_a?(List) && value.separator == :comma + "(#{value.to_sass(opts)})" + else + value.to_sass(opts) + end + end + + "(#{value.map {|(k, v)| "#{to_sass[k]}: #{to_sass[v]}"}.join(', ')})" + end + alias_method :inspect, :to_sass + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/null.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/null.rb new file mode 100644 index 00000000..f6d573b7 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/null.rb @@ -0,0 +1,44 @@ +module Sass::Script::Value + # A SassScript object representing a null value. + class Null < Base + # The null value in SassScript. + # + # This is assigned before new is overridden below so that we use the default implementation. + NULL = new(nil) + + # We override object creation so that users of the core API + # will not need to know that null is a specific constant. + # + # @private + # @return [Null] the {NULL} constant. + def self.new + NULL + end + + # @return [Boolean] `false` (the Ruby boolean value) + def to_bool + false + end + + # @return [Boolean] `true` + def null? + true + end + + # @return [String] '' (An empty string) + def to_s(opts = {}) + '' + end + + def to_sass(opts = {}) + 'null' + end + + # Returns a string representing a null value. + # + # @return [String] + def inspect + 'null' + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/number.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/number.rb new file mode 100644 index 00000000..9c213936 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/number.rb @@ -0,0 +1,564 @@ +module Sass::Script::Value + # A SassScript object representing a number. + # SassScript numbers can have decimal values, + # and can also have units. + # For example, `12`, `1px`, and `10.45em` + # are all valid values. + # + # Numbers can also have more complex units, such as `1px*em/in`. + # These cannot be inputted directly in Sass code at the moment. + class Number < Base + # The Ruby value of the number. + # + # @return [Numeric] + attr_reader :value + + # A list of units in the numerator of the number. + # For example, `1px*em/in*cm` would return `["px", "em"]` + # @return [Array] + attr_reader :numerator_units + + # A list of units in the denominator of the number. + # For example, `1px*em/in*cm` would return `["in", "cm"]` + # @return [Array] + attr_reader :denominator_units + + # The original representation of this number. + # For example, although the result of `1px/2px` is `0.5`, + # the value of `#original` is `"1px/2px"`. + # + # This is only non-nil when the original value should be used as the CSS value, + # as in `font: 1px/2px`. + # + # @return [Boolean, nil] + attr_accessor :original + + def self.precision + Thread.current[:sass_numeric_precision] || Thread.main[:sass_numeric_precision] || 10 + end + + # Sets the number of digits of precision + # For example, if this is `3`, + # `3.1415926` will be printed as `3.142`. + # The numeric precision is stored as a thread local for thread safety reasons. + # To set for all threads, be sure to set the precision on the main thread. + def self.precision=(digits) + Thread.current[:sass_numeric_precision] = digits.round + Thread.current[:sass_numeric_precision_factor] = nil + Thread.current[:sass_numeric_epsilon] = nil + end + + # the precision factor used in numeric output + # it is derived from the `precision` method. + def self.precision_factor + Thread.current[:sass_numeric_precision_factor] ||= 10.0**precision + end + + # Used in checking equality of floating point numbers. Any + # numbers within an `epsilon` of each other are considered functionally equal. + # The value for epsilon is one tenth of the current numeric precision. + def self.epsilon + Thread.current[:sass_numeric_epsilon] ||= 1 / (precision_factor * 10) + end + + # Used so we don't allocate two new arrays for each new number. + NO_UNITS = [] + + # @param value [Numeric] The value of the number + # @param numerator_units [::String, Array<::String>] See \{#numerator\_units} + # @param denominator_units [::String, Array<::String>] See \{#denominator\_units} + def initialize(value, numerator_units = NO_UNITS, denominator_units = NO_UNITS) + numerator_units = [numerator_units] if numerator_units.is_a?(::String) + denominator_units = [denominator_units] if denominator_units.is_a?(::String) + super(value) + @numerator_units = numerator_units + @denominator_units = denominator_units + @options = nil + normalize! + end + + # The SassScript `+` operation. + # Its functionality depends on the type of its argument: + # + # {Number} + # : Adds the two numbers together, converting units if possible. + # + # {Color} + # : Adds this number to each of the RGB color channels. + # + # {Value} + # : See {Value::Base#plus}. + # + # @param other [Value] The right-hand side of the operator + # @return [Value] The result of the operation + # @raise [Sass::UnitConversionError] if `other` is a number with incompatible units + def plus(other) + if other.is_a? Number + operate(other, :+) + elsif other.is_a?(Color) + other.plus(self) + else + super + end + end + + # The SassScript binary `-` operation (e.g. `$a - $b`). + # Its functionality depends on the type of its argument: + # + # {Number} + # : Subtracts this number from the other, converting units if possible. + # + # {Value} + # : See {Value::Base#minus}. + # + # @param other [Value] The right-hand side of the operator + # @return [Value] The result of the operation + # @raise [Sass::UnitConversionError] if `other` is a number with incompatible units + def minus(other) + if other.is_a? Number + operate(other, :-) + else + super + end + end + + # The SassScript unary `+` operation (e.g. `+$a`). + # + # @return [Number] The value of this number + def unary_plus + self + end + + # The SassScript unary `-` operation (e.g. `-$a`). + # + # @return [Number] The negative value of this number + def unary_minus + Number.new(-value, @numerator_units, @denominator_units) + end + + # The SassScript `*` operation. + # Its functionality depends on the type of its argument: + # + # {Number} + # : Multiplies the two numbers together, converting units appropriately. + # + # {Color} + # : Multiplies each of the RGB color channels by this number. + # + # @param other [Number, Color] The right-hand side of the operator + # @return [Number, Color] The result of the operation + # @raise [NoMethodError] if `other` is an invalid type + def times(other) + if other.is_a? Number + operate(other, :*) + elsif other.is_a? Color + other.times(self) + else + raise NoMethodError.new(nil, :times) + end + end + + # The SassScript `/` operation. + # Its functionality depends on the type of its argument: + # + # {Number} + # : Divides this number by the other, converting units appropriately. + # + # {Value} + # : See {Value::Base#div}. + # + # @param other [Value] The right-hand side of the operator + # @return [Value] The result of the operation + def div(other) + if other.is_a? Number + res = operate(other, :/) + if original && other.original + res.original = "#{original}/#{other.original}" + end + res + else + super + end + end + + # The SassScript `%` operation. + # + # @param other [Number] The right-hand side of the operator + # @return [Number] This number modulo the other + # @raise [NoMethodError] if `other` is an invalid type + # @raise [Sass::UnitConversionError] if `other` has incompatible units + def mod(other) + if other.is_a?(Number) + return Number.new(Float::NAN) if other.value == 0 + operate(other, :%) + else + raise NoMethodError.new(nil, :mod) + end + end + + # The SassScript `==` operation. + # + # @param other [Value] The right-hand side of the operator + # @return [Boolean] Whether this number is equal to the other object + def eq(other) + return Bool::FALSE unless other.is_a?(Sass::Script::Value::Number) + this = self + begin + if unitless? + this = this.coerce(other.numerator_units, other.denominator_units) + else + other = other.coerce(@numerator_units, @denominator_units) + end + rescue Sass::UnitConversionError + return Bool::FALSE + end + Bool.new(basically_equal?(this.value, other.value)) + end + + def hash + [value, numerator_units, denominator_units].hash + end + + # Hash-equality works differently than `==` equality for numbers. + # Hash-equality must be transitive, so it just compares the exact value, + # numerator units, and denominator units. + def eql?(other) + basically_equal?(value, other.value) && numerator_units == other.numerator_units && + denominator_units == other.denominator_units + end + + # The SassScript `>` operation. + # + # @param other [Number] The right-hand side of the operator + # @return [Boolean] Whether this number is greater than the other + # @raise [NoMethodError] if `other` is an invalid type + def gt(other) + raise NoMethodError.new(nil, :gt) unless other.is_a?(Number) + operate(other, :>) + end + + # The SassScript `>=` operation. + # + # @param other [Number] The right-hand side of the operator + # @return [Boolean] Whether this number is greater than or equal to the other + # @raise [NoMethodError] if `other` is an invalid type + def gte(other) + raise NoMethodError.new(nil, :gte) unless other.is_a?(Number) + operate(other, :>=) + end + + # The SassScript `<` operation. + # + # @param other [Number] The right-hand side of the operator + # @return [Boolean] Whether this number is less than the other + # @raise [NoMethodError] if `other` is an invalid type + def lt(other) + raise NoMethodError.new(nil, :lt) unless other.is_a?(Number) + operate(other, :<) + end + + # The SassScript `<=` operation. + # + # @param other [Number] The right-hand side of the operator + # @return [Boolean] Whether this number is less than or equal to the other + # @raise [NoMethodError] if `other` is an invalid type + def lte(other) + raise NoMethodError.new(nil, :lte) unless other.is_a?(Number) + operate(other, :<=) + end + + # @return [String] The CSS representation of this number + # @raise [Sass::SyntaxError] if this number has units that can't be used in CSS + # (e.g. `px*in`) + def to_s(opts = {}) + return original if original + raise Sass::SyntaxError.new("#{inspect} isn't a valid CSS value.") unless legal_units? + inspect + end + + # Returns a readable representation of this number. + # + # This representation is valid CSS (and valid SassScript) + # as long as there is only one unit. + # + # @return [String] The representation + def inspect(opts = {}) + return original if original + + value = self.class.round(self.value) + str = value.to_s + + # Ruby will occasionally print in scientific notation if the number is + # small enough. That's technically valid CSS, but it's not well-supported + # and confusing. + str = ("%0.#{self.class.precision}f" % value).gsub(/0*$/, '') if str.include?('e') + + # Sometimes numeric formatting will result in a decimal number with a trailing zero (x.0) + if str =~ /(.*)\.0$/ + str = $1 + end + + # We omit a leading zero before the decimal point in compressed mode. + if @options && options[:style] == :compressed + str.sub!(/^(-)?0\./, '\1.') + end + + unitless? ? str : "#{str}#{unit_str}" + end + alias_method :to_sass, :inspect + + # @return [Integer] The integer value of the number + # @raise [Sass::SyntaxError] if the number isn't an integer + def to_i + super unless int? + value.to_i + end + + # @return [Boolean] Whether or not this number is an integer. + def int? + basically_equal?(value % 1, 0.0) + end + + # @return [Boolean] Whether or not this number has no units. + def unitless? + @numerator_units.empty? && @denominator_units.empty? + end + + # Checks whether the number has the numerator unit specified. + # + # @example + # number = Sass::Script::Value::Number.new(10, "px") + # number.is_unit?("px") => true + # number.is_unit?(nil) => false + # + # @param unit [::String, nil] The unit the number should have or nil if the number + # should be unitless. + # @see Number#unitless? The unitless? method may be more readable. + def is_unit?(unit) + if unit + denominator_units.size == 0 && numerator_units.size == 1 && numerator_units.first == unit + else + unitless? + end + end + + # @return [Boolean] Whether or not this number has units that can be represented in CSS + # (that is, zero or one \{#numerator\_units}). + def legal_units? + (@numerator_units.empty? || @numerator_units.size == 1) && @denominator_units.empty? + end + + # Returns this number converted to other units. + # The conversion takes into account the relationship between e.g. mm and cm, + # as well as between e.g. in and cm. + # + # If this number has no units, it will simply return itself + # with the given units. + # + # An incompatible coercion, e.g. between px and cm, will raise an error. + # + # @param num_units [Array] The numerator units to coerce this number into. + # See {\#numerator\_units} + # @param den_units [Array] The denominator units to coerce this number into. + # See {\#denominator\_units} + # @return [Number] The number with the new units + # @raise [Sass::UnitConversionError] if the given units are incompatible with the number's + # current units + def coerce(num_units, den_units) + Number.new(if unitless? + value + else + value * coercion_factor(@numerator_units, num_units) / + coercion_factor(@denominator_units, den_units) + end, num_units, den_units) + end + + # @param other [Number] A number to decide if it can be compared with this number. + # @return [Boolean] Whether or not this number can be compared with the other. + def comparable_to?(other) + operate(other, :+) + true + rescue Sass::UnitConversionError + false + end + + # Returns a human readable representation of the units in this number. + # For complex units this takes the form of: + # numerator_unit1 * numerator_unit2 / denominator_unit1 * denominator_unit2 + # @return [String] a string that represents the units in this number + def unit_str + rv = @numerator_units.sort.join("*") + if @denominator_units.any? + rv << "/" + rv << @denominator_units.sort.join("*") + end + rv + end + + private + + # @private + # @see Sass::Script::Number.basically_equal? + def basically_equal?(num1, num2) + self.class.basically_equal?(num1, num2) + end + + # Checks whether two numbers are within an epsilon of each other. + # @return [Boolean] + def self.basically_equal?(num1, num2) + (num1 - num2).abs < epsilon + end + + # @private + def self.round(num) + if num.is_a?(Float) && (num.infinite? || num.nan?) + num + elsif basically_equal?(num % 1, 0.0) + num.round + else + ((num * precision_factor).round / precision_factor).to_f + end + end + + OPERATIONS = [:+, :-, :<=, :<, :>, :>=, :%] + + def operate(other, operation) + this = self + if OPERATIONS.include?(operation) + if unitless? + this = this.coerce(other.numerator_units, other.denominator_units) + else + other = other.coerce(@numerator_units, @denominator_units) + end + end + # avoid integer division + value = :/ == operation ? this.value.to_f : this.value + result = value.send(operation, other.value) + + if result.is_a?(Numeric) + Number.new(result, *compute_units(this, other, operation)) + else # Boolean op + Bool.new(result) + end + end + + def coercion_factor(from_units, to_units) + # get a list of unmatched units + from_units, to_units = sans_common_units(from_units, to_units) + + if from_units.size != to_units.size || !convertable?(from_units | to_units) + raise Sass::UnitConversionError.new( + "Incompatible units: '#{from_units.join('*')}' and '#{to_units.join('*')}'.") + end + + from_units.zip(to_units).inject(1) {|m, p| m * conversion_factor(p[0], p[1])} + end + + def compute_units(this, other, operation) + case operation + when :* + [this.numerator_units + other.numerator_units, + this.denominator_units + other.denominator_units] + when :/ + [this.numerator_units + other.denominator_units, + this.denominator_units + other.numerator_units] + else + [this.numerator_units, this.denominator_units] + end + end + + def normalize! + return if unitless? + @numerator_units, @denominator_units = + sans_common_units(@numerator_units, @denominator_units) + + @denominator_units.each_with_index do |d, i| + next unless convertable?(d) && (u = @numerator_units.find {|n| convertable?([n, d])}) + @value /= conversion_factor(d, u) + @denominator_units.delete_at(i) + @numerator_units.delete_at(@numerator_units.index(u)) + end + end + + # This is the source data for all the unit logic. It's pre-processed to make + # it efficient to figure out whether a set of units is mutually compatible + # and what the conversion ratio is between two units. + # + # These come from http://www.w3.org/TR/2012/WD-css3-values-20120308/. + relative_sizes = [ + { + 'in' => Rational(1), + 'cm' => Rational(1, 2.54), + 'pc' => Rational(1, 6), + 'mm' => Rational(1, 25.4), + 'q' => Rational(1, 101.6), + 'pt' => Rational(1, 72), + 'px' => Rational(1, 96) + }, + { + 'deg' => Rational(1, 360), + 'grad' => Rational(1, 400), + 'rad' => Rational(1, 2 * Math::PI), + 'turn' => Rational(1) + }, + { + 's' => Rational(1), + 'ms' => Rational(1, 1000) + }, + { + 'Hz' => Rational(1), + 'kHz' => Rational(1000) + }, + { + 'dpi' => Rational(1), + 'dpcm' => Rational(254, 100), + 'dppx' => Rational(96) + } + ] + + # A hash from each known unit to the set of units that it's mutually + # convertible with. + MUTUALLY_CONVERTIBLE = {} + relative_sizes.map do |values| + set = values.keys.to_set + values.keys.each {|name| MUTUALLY_CONVERTIBLE[name] = set} + end + + # A two-dimensional hash from two units to the conversion ratio between + # them. Multiply `X` by `CONVERSION_TABLE[X][Y]` to convert it to `Y`. + CONVERSION_TABLE = {} + relative_sizes.each do |values| + values.each do |(name1, value1)| + CONVERSION_TABLE[name1] ||= {} + values.each do |(name2, value2)| + value = value1 / value2 + CONVERSION_TABLE[name1][name2] = value.denominator == 1 ? value.to_i : value.to_f + end + end + end + + def conversion_factor(from_unit, to_unit) + CONVERSION_TABLE[from_unit][to_unit] + end + + def convertable?(units) + units = Array(units).to_set + return true if units.empty? + return false unless (mutually_convertible = MUTUALLY_CONVERTIBLE[units.first]) + units.subset?(mutually_convertible) + end + + def sans_common_units(units1, units2) + units2 = units2.dup + # Can't just use -, because we want px*px to coerce properly to px*mm + units1 = units1.map do |u| + j = units2.index(u) + next u unless j + units2.delete_at(j) + nil + end + units1.compact! + return units1, units2 + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/string.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/string.rb new file mode 100644 index 00000000..9c9b088f --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/script/value/string.rb @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- +module Sass::Script::Value + # A SassScript object representing a CSS string *or* a CSS identifier. + class String < Base + @@interpolation_deprecation = Sass::Deprecation.new + + # The Ruby value of the string. + # + # @return [String] + attr_reader :value + + # Whether this is a CSS string or a CSS identifier. + # The difference is that strings are written with double-quotes, + # while identifiers aren't. + # + # @return [Symbol] `:string` or `:identifier` + attr_reader :type + + def self.value(contents) + contents.gsub("\\\n", "").gsub(/\\(?:([0-9a-fA-F]{1,6})\s?|(.))/) do + next $2 if $2 + # Handle unicode escapes as per CSS Syntax Level 3 section 4.3.8. + code_point = $1.to_i(16) + if code_point == 0 || code_point > 0x10FFFF || + (code_point >= 0xD800 && code_point <= 0xDFFF) + '�' + else + [code_point].pack("U") + end + end + end + + # Returns the quoted string representation of `contents`. + # + # @options opts :quote [String] + # The preferred quote style for quoted strings. If `:none`, strings are + # always emitted unquoted. If `nil`, quoting is determined automatically. + # @options opts :sass [String] + # Whether to quote strings for Sass source, as opposed to CSS. Defaults to `false`. + def self.quote(contents, opts = {}) + quote = opts[:quote] + + # Short-circuit if there are no characters that need quoting. + unless contents =~ /[\n\\"']|\#\{/ + quote ||= '"' + return "#{quote}#{contents}#{quote}" + end + + if quote.nil? + if contents.include?('"') + if contents.include?("'") + quote = '"' + else + quote = "'" + end + else + quote = '"' + end + end + + # Replace single backslashes with multiples. + contents = contents.gsub("\\", "\\\\\\\\") + + # Escape interpolation. + contents = contents.gsub('#{', "\\\#{") if opts[:sass] + + if quote == '"' + contents = contents.gsub('"', "\\\"") + else + contents = contents.gsub("'", "\\'") + end + + contents = contents.gsub(/\n(?![a-fA-F0-9\s])/, "\\a").gsub("\n", "\\a ") + "#{quote}#{contents}#{quote}" + end + + # Creates a new string. + # + # @param value [String] See \{#value} + # @param type [Symbol] See \{#type} + # @param deprecated_interp_equivalent [String?] + # If this was created via a potentially-deprecated string interpolation, + # this is the replacement expression that should be suggested to the user. + def initialize(value, type = :identifier, deprecated_interp_equivalent = nil) + super(value) + @type = type + @deprecated_interp_equivalent = deprecated_interp_equivalent + end + + # @see Value#plus + def plus(other) + other_value = if other.is_a?(Sass::Script::Value::String) + other.value + else + other.to_s(:quote => :none) + end + Sass::Script::Value::String.new(value + other_value, type) + end + + # @see Value#to_s + def to_s(opts = {}) + return @value.gsub(/\n\s*/, ' ') if opts[:quote] == :none || @type == :identifier + String.quote(value, opts) + end + + # @see Value#to_sass + def to_sass(opts = {}) + to_s(opts.merge(:sass => true)) + end + + def separator + check_deprecated_interp + super + end + + def to_a + check_deprecated_interp + super + end + + # Prints a warning if this string was created using potentially-deprecated + # interpolation. + def check_deprecated_interp + return unless @deprecated_interp_equivalent + + @@interpolation_deprecation.warn(source_range.file, source_range.start_pos.line, <, nil] + # The interpolated identifier, or nil if none could be parsed + def parse_interp_ident + init_scanner! + interp_ident + end + + # Parses a supports clause for an @import directive + def parse_supports_clause + init_scanner! + ss + clause = supports_clause + ss + clause + end + + # Parses a media query list. + # + # @return [Sass::Media::QueryList] The parsed query list + # @raise [Sass::SyntaxError] if there's a syntax error in the query list, + # or if it doesn't take up the entire input string. + def parse_media_query_list + init_scanner! + ql = media_query_list + expected("media query list") unless ql && @scanner.eos? + ql + end + + # Parses an at-root query. + # + # @return [Array] The interpolated query. + # @raise [Sass::SyntaxError] if there's a syntax error in the query, + # or if it doesn't take up the entire input string. + def parse_at_root_query + init_scanner! + query = at_root_query + expected("@at-root query list") unless query && @scanner.eos? + query + end + + # Parses a supports query condition. + # + # @return [Sass::Supports::Condition] The parsed condition + # @raise [Sass::SyntaxError] if there's a syntax error in the condition, + # or if it doesn't take up the entire input string. + def parse_supports_condition + init_scanner! + condition = supports_condition + expected("supports condition") unless condition && @scanner.eos? + condition + end + + # Parses a custom property value. + # + # @return [Array] The interpolated value. + # @raise [Sass::SyntaxError] if there's a syntax error in the value, + # or if it doesn't take up the entire input string. + def parse_declaration_value + init_scanner! + value = declaration_value + expected('"}"') unless value && @scanner.eos? + value + end + + private + + include Sass::SCSS::RX + + def source_position + Sass::Source::Position.new(@line, @offset) + end + + def range(start_pos, end_pos = source_position) + Sass::Source::Range.new(start_pos, end_pos, @filename, @importer) + end + + def init_scanner! + @scanner = + if @template.is_a?(StringScanner) + @template + else + Sass::Util::MultibyteStringScanner.new(@template.tr("\r", "")) + end + end + + def stylesheet + node = node(Sass::Tree::RootNode.new(@scanner.string), source_position) + block_contents(node, :stylesheet) {s(node)} + end + + def s(node) + while tok(S) || tok(CDC) || tok(CDO) || (c = tok(SINGLE_LINE_COMMENT)) || (c = tok(COMMENT)) + next unless c + process_comment c, node + c = nil + end + true + end + + def ss + nil while tok(S) || tok(SINGLE_LINE_COMMENT) || tok(COMMENT) + true + end + + def ss_comments(node) + while tok(S) || (c = tok(SINGLE_LINE_COMMENT)) || (c = tok(COMMENT)) + next unless c + process_comment c, node + c = nil + end + + true + end + + def whitespace + return unless tok(S) || tok(SINGLE_LINE_COMMENT) || tok(COMMENT) + ss + end + + def process_comment(text, node) + silent = text =~ %r{\A//} + loud = !silent && text =~ %r{\A/[/*]!} + line = @line - text.count("\n") + comment_start = @scanner.pos - text.length + index_before_line = @scanner.string.rindex("\n", comment_start) || -1 + offset = comment_start - index_before_line + + if silent + value = [text.sub(%r{\A\s*//}, '/*').gsub(%r{^\s*//}, ' *') + ' */'] + else + value = Sass::Engine.parse_interp(text, line, offset, :filename => @filename) + line_before_comment = @scanner.string[index_before_line + 1...comment_start] + value.unshift(line_before_comment.gsub(/[^\s]/, ' ')) + end + + type = if silent + :silent + elsif loud + :loud + else + :normal + end + start_pos = Sass::Source::Position.new(line, offset) + comment = node(Sass::Tree::CommentNode.new(value, type), start_pos) + node << comment + end + + DIRECTIVES = Set[:mixin, :include, :function, :return, :debug, :warn, :for, + :each, :while, :if, :else, :extend, :import, :media, :charset, :content, + :_moz_document, :at_root, :error] + + PREFIXED_DIRECTIVES = Set[:supports] + + def directive + start_pos = source_position + return unless tok(/@/) + name = ident! + ss + + if (dir = special_directive(name, start_pos)) + return dir + elsif (dir = prefixed_directive(name, start_pos)) + return dir + end + + val = almost_any_value + val = val ? ["@#{name} "] + Sass::Util.strip_string_array(val) : ["@#{name}"] + directive_body(val, start_pos) + end + + def directive_body(value, start_pos) + node = Sass::Tree::DirectiveNode.new(value) + + if tok(/\{/) + node.has_children = true + block_contents(node, :directive) + tok!(/\}/) + end + + node(node, start_pos) + end + + def special_directive(name, start_pos) + sym = name.tr('-', '_').to_sym + DIRECTIVES.include?(sym) && send("#{sym}_directive", start_pos) + end + + def prefixed_directive(name, start_pos) + sym = deprefix(name).tr('-', '_').to_sym + PREFIXED_DIRECTIVES.include?(sym) && send("#{sym}_directive", name, start_pos) + end + + def mixin_directive(start_pos) + name = ident! + args, splat = sass_script(:parse_mixin_definition_arglist) + ss + block(node(Sass::Tree::MixinDefNode.new(name, args, splat), start_pos), :directive) + end + + def include_directive(start_pos) + name = ident! + args, keywords, splat, kwarg_splat = sass_script(:parse_mixin_include_arglist) + ss + include_node = node( + Sass::Tree::MixinNode.new(name, args, keywords, splat, kwarg_splat), start_pos) + if tok?(/\{/) + include_node.has_children = true + block(include_node, :directive) + else + include_node + end + end + + def content_directive(start_pos) + ss + node(Sass::Tree::ContentNode.new, start_pos) + end + + def function_directive(start_pos) + name = ident! + args, splat = sass_script(:parse_function_definition_arglist) + ss + block(node(Sass::Tree::FunctionNode.new(name, args, splat), start_pos), :function) + end + + def return_directive(start_pos) + node(Sass::Tree::ReturnNode.new(sass_script(:parse)), start_pos) + end + + def debug_directive(start_pos) + node(Sass::Tree::DebugNode.new(sass_script(:parse)), start_pos) + end + + def warn_directive(start_pos) + node(Sass::Tree::WarnNode.new(sass_script(:parse)), start_pos) + end + + def for_directive(start_pos) + tok!(/\$/) + var = ident! + ss + + tok!(/from/) + from = sass_script(:parse_until, Set["to", "through"]) + ss + + @expected = '"to" or "through"' + exclusive = (tok(/to/) || tok!(/through/)) == 'to' + to = sass_script(:parse) + ss + + block(node(Sass::Tree::ForNode.new(var, from, to, exclusive), start_pos), :directive) + end + + def each_directive(start_pos) + tok!(/\$/) + vars = [ident!] + ss + while tok(/,/) + ss + tok!(/\$/) + vars << ident! + ss + end + + tok!(/in/) + list = sass_script(:parse) + ss + + block(node(Sass::Tree::EachNode.new(vars, list), start_pos), :directive) + end + + def while_directive(start_pos) + expr = sass_script(:parse) + ss + block(node(Sass::Tree::WhileNode.new(expr), start_pos), :directive) + end + + def if_directive(start_pos) + expr = sass_script(:parse) + ss + node = block(node(Sass::Tree::IfNode.new(expr), start_pos), :directive) + pos = @scanner.pos + line = @line + ss + + else_block(node) || + begin + # Backtrack in case there are any comments we want to parse + @scanner.pos = pos + @line = line + node + end + end + + def else_block(node) + start_pos = source_position + return unless tok(/@else/) + ss + else_node = block( + node(Sass::Tree::IfNode.new((sass_script(:parse) if tok(/if/))), start_pos), + :directive) + node.add_else(else_node) + pos = @scanner.pos + line = @line + ss + + else_block(node) || + begin + # Backtrack in case there are any comments we want to parse + @scanner.pos = pos + @line = line + node + end + end + + def else_directive(start_pos) + err("Invalid CSS: @else must come after @if") + end + + def extend_directive(start_pos) + selector_start_pos = source_position + @expected = "selector" + selector = Sass::Util.strip_string_array(expr!(:almost_any_value)) + optional = tok(OPTIONAL) + ss + node(Sass::Tree::ExtendNode.new(selector, !!optional, range(selector_start_pos)), start_pos) + end + + def import_directive(start_pos) + values = [] + + loop do + values << expr!(:import_arg) + break if use_css_import? + break unless tok(/,/) + ss + end + + values + end + + def import_arg + start_pos = source_position + return unless (str = string) || (uri = tok?(/url\(/i)) + if uri + str = sass_script(:parse_string) + ss + supports = supports_clause + ss + media = media_query_list + ss + return node(Tree::CssImportNode.new(str, media.to_a, supports), start_pos) + end + ss + + supports = supports_clause + ss + media = media_query_list + if str =~ %r{^(https?:)?//} || media || supports || use_css_import? + return node( + Sass::Tree::CssImportNode.new( + Sass::Script::Value::String.quote(str), media.to_a, supports), start_pos) + end + + node(Sass::Tree::ImportNode.new(str.strip), start_pos) + end + + def use_css_import?; false; end + + def media_directive(start_pos) + block(node(Sass::Tree::MediaNode.new(expr!(:media_query_list).to_a), start_pos), :directive) + end + + # http://www.w3.org/TR/css3-mediaqueries/#syntax + def media_query_list + query = media_query + return unless query + queries = [query] + + ss + while tok(/,/) + ss; queries << expr!(:media_query) + end + ss + + Sass::Media::QueryList.new(queries) + end + + def media_query + if (ident1 = interp_ident) + ss + ident2 = interp_ident + ss + if ident2 && ident2.length == 1 && ident2[0].is_a?(String) && ident2[0].downcase == 'and' + query = Sass::Media::Query.new([], ident1, []) + else + if ident2 + query = Sass::Media::Query.new(ident1, ident2, []) + else + query = Sass::Media::Query.new([], ident1, []) + end + return query unless tok(/and/i) + ss + end + end + + if query + expr = expr!(:media_expr) + else + expr = media_expr + return unless expr + end + query ||= Sass::Media::Query.new([], [], []) + query.expressions << expr + + ss + while tok(/and/i) + ss; query.expressions << expr!(:media_expr) + end + + query + end + + def query_expr + interp = interpolation + return interp if interp + return unless tok(/\(/) + res = ['('] + ss + stop_at = Set[:single_eq, :lt, :lte, :gt, :gte] + res << sass_script(:parse_until, stop_at) + + if tok(/:/) + res << ': ' + ss + res << sass_script(:parse) + elsif comparison1 = tok(/=|[<>]=?/) + res << ' ' << comparison1 << ' ' + ss + res << sass_script(:parse_until, stop_at) + if ((comparison1 == ">" || comparison1 == ">=") && comparison2 = tok(/>=?/)) || + ((comparison1 == "<" || comparison1 == "<=") && comparison2 = tok(/<=?/)) + res << ' ' << comparison2 << ' ' + ss + res << sass_script(:parse_until, stop_at) + end + end + res << tok!(/\)/) + ss + res + end + + # Aliases allow us to use different descriptions if the same + # expression fails in different contexts. + alias_method :media_expr, :query_expr + alias_method :at_root_query, :query_expr + + def charset_directive(start_pos) + name = expr!(:string) + ss + node(Sass::Tree::CharsetNode.new(name), start_pos) + end + + # The document directive is specified in + # http://www.w3.org/TR/css3-conditional/, but Gecko allows the + # `url-prefix` and `domain` functions to omit quotation marks, contrary to + # the standard. + # + # We could parse all document directives according to Mozilla's syntax, + # but if someone's using e.g. @-webkit-document we don't want them to + # think WebKit works sans quotes. + def _moz_document_directive(start_pos) + res = ["@-moz-document "] + loop do + res << str {ss} << expr!(:moz_document_function) + if (c = tok(/,/)) + res << c + else + break + end + end + directive_body(res.flatten, start_pos) + end + + def moz_document_function + val = interp_uri || _interp_string(:url_prefix) || + _interp_string(:domain) || function(false) || interpolation + return unless val + ss + val + end + + def at_root_directive(start_pos) + if tok?(/\(/) && (expr = at_root_query) + return block(node(Sass::Tree::AtRootNode.new(expr), start_pos), :directive) + end + + at_root_node = node(Sass::Tree::AtRootNode.new, start_pos) + rule_node = ruleset + return block(at_root_node, :stylesheet) unless rule_node + at_root_node << rule_node + at_root_node + end + + def at_root_directive_list + return unless (first = ident) + arr = [first] + ss + while (e = ident) + arr << e + ss + end + arr + end + + def error_directive(start_pos) + node(Sass::Tree::ErrorNode.new(sass_script(:parse)), start_pos) + end + + # http://www.w3.org/TR/css3-conditional/ + def supports_directive(name, start_pos) + condition = expr!(:supports_condition) + node = Sass::Tree::SupportsNode.new(name, condition) + + tok!(/\{/) + node.has_children = true + block_contents(node, :directive) + tok!(/\}/) + + node(node, start_pos) + end + + def supports_clause + return unless tok(/supports\(/i) + ss + supports = import_supports_condition + ss + tok!(/\)/) + supports + end + + def supports_condition + supports_negation || supports_operator || supports_interpolation + end + + def import_supports_condition + supports_condition || supports_declaration + end + + def supports_negation + return unless tok(/not/i) + ss + Sass::Supports::Negation.new(expr!(:supports_condition_in_parens)) + end + + def supports_operator + cond = supports_condition_in_parens + return unless cond + re = /and|or/i + while (op = tok(re)) + re = /#{op}/i + ss + cond = Sass::Supports::Operator.new( + cond, expr!(:supports_condition_in_parens), op) + end + cond + end + + def supports_declaration + name = sass_script(:parse) + tok!(/:/); ss + value = sass_script(:parse) + Sass::Supports::Declaration.new(name, value) + end + + def supports_condition_in_parens + interp = supports_interpolation + return interp if interp + return unless tok(/\(/); ss + if (cond = supports_condition) + tok!(/\)/); ss + cond + else + decl = supports_declaration + tok!(/\)/); ss + decl + end + end + + def supports_interpolation + interp = interpolation + return unless interp + ss + Sass::Supports::Interpolation.new(interp) + end + + def variable + return unless tok(/\$/) + start_pos = source_position + name = ident! + ss; tok!(/:/); ss + + expr = sass_script(:parse) + while tok(/!/) + flag_name = ident! + if flag_name == 'default' + guarded ||= true + elsif flag_name == 'global' + global ||= true + else + raise Sass::SyntaxError.new("Invalid flag \"!#{flag_name}\".", :line => @line) + end + ss + end + + result = Sass::Tree::VariableNode.new(name, expr, guarded, global) + node(result, start_pos) + end + + def operator + # Many of these operators (all except / and ,) + # are disallowed by the CSS spec, + # but they're included here for compatibility + # with some proprietary MS properties + str {ss if tok(%r{[/,:.=]})} + end + + def ruleset + start_pos = source_position + return unless (rules = almost_any_value) + block( + node( + Sass::Tree::RuleNode.new(rules, range(start_pos)), start_pos), :ruleset) + end + + def block(node, context) + node.has_children = true + tok!(/\{/) + block_contents(node, context) + tok!(/\}/) + node + end + + # A block may contain declarations and/or rulesets + def block_contents(node, context) + block_given? ? yield : ss_comments(node) + node << (child = block_child(context)) + while tok(/;/) || has_children?(child) + block_given? ? yield : ss_comments(node) + node << (child = block_child(context)) + end + node + end + + def block_child(context) + return variable || directive if context == :function + return variable || directive || ruleset if context == :stylesheet + variable || directive || declaration_or_ruleset + end + + def has_children?(child_or_array) + return false unless child_or_array + return child_or_array.last.has_children if child_or_array.is_a?(Array) + child_or_array.has_children + end + + # When parsing the contents of a ruleset, it can be difficult to tell + # declarations apart from nested rulesets. Since we don't thoroughly parse + # selectors until after resolving interpolation, we can share a bunch of + # the parsing of the two, but we need to disambiguate them first. We use + # the following criteria: + # + # * If the entity doesn't start with an identifier followed by a colon, + # it's a selector. There are some additional mostly-unimportant cases + # here to support various declaration hacks. + # + # * If the colon is followed by another colon, it's a selector. + # + # * Otherwise, if the colon is followed by anything other than + # interpolation or a character that's valid as the beginning of an + # identifier, it's a declaration. + # + # * If the colon is followed by interpolation or a valid identifier, try + # parsing it as a declaration value. If this fails, backtrack and parse + # it as a selector. + # + # * If the declaration value value valid but is followed by "{", backtrack + # and parse it as a selector anyway. This ensures that ".foo:bar {" is + # always parsed as a selector and never as a property with nested + # properties beneath it. + def declaration_or_ruleset + start_pos = source_position + declaration = try_declaration + + if declaration.nil? + return unless (selector = almost_any_value) + elsif declaration.is_a?(Array) + selector = declaration + else + # Declaration should be a PropNode. + return declaration + end + + if (additional_selector = almost_any_value) + selector << additional_selector + end + + block( + node( + Sass::Tree::RuleNode.new(merge(selector), range(start_pos)), start_pos), :ruleset) + end + + # Tries to parse a declaration, and returns the value parsed so far if it + # fails. + # + # This has three possible return types. It can return `nil`, indicating + # that parsing failed completely and the scanner hasn't moved forward at + # all. It can return an Array, indicating that parsing failed after + # consuming some text (possibly containing interpolation), which is + # returned. Or it can return a PropNode, indicating that parsing + # succeeded. + def try_declaration + # This allows the "*prop: val", ":prop: val", "#prop: val", and ".prop: + # val" hacks. + name_start_pos = source_position + if (s = tok(/[:\*\.]|\#(?!\{)/)) + name = [s, str {ss}] + return name unless (ident = interp_ident) + name << ident + else + return unless (name = interp_ident) + name = Array(name) + end + + if (comment = tok(COMMENT)) + name << comment + end + name_end_pos = source_position + + mid = [str {ss}] + return name + mid unless tok(/:/) + mid << ':' + + # If this is a CSS variable, parse it as a property no matter what. + if name.first.is_a?(String) && name.first.start_with?("--") + return css_variable_declaration(name, name_start_pos, name_end_pos) + end + + return name + mid + [':'] if tok(/:/) + mid << str {ss} + post_colon_whitespace = !mid.last.empty? + could_be_selector = !post_colon_whitespace && (tok?(IDENT_START) || tok?(INTERP_START)) + + value_start_pos = source_position + value = nil + error = catch_error do + value = value! + if tok?(/\{/) + # Properties that are ambiguous with selectors can't have additional + # properties nested beneath them. + tok!(/;/) if could_be_selector + elsif !tok?(/[;{}]/) + # We want an exception if there's no valid end-of-property character + # exists, but we don't want to consume it if it does. + tok!(/[;{}]/) + end + end + + if error + rethrow error unless could_be_selector + + # If the value would be followed by a semicolon, it's definitely + # supposed to be a property, not a selector. + additional_selector = almost_any_value + rethrow error if tok?(/;/) + + return name + mid + (additional_selector || []) + end + + value_end_pos = source_position + ss + require_block = tok?(/\{/) + + node = node(Sass::Tree::PropNode.new(name.flatten.compact, [value], :new), + name_start_pos, value_end_pos) + node.name_source_range = range(name_start_pos, name_end_pos) + node.value_source_range = range(value_start_pos, value_end_pos) + + return node unless require_block + nested_properties! node + end + + def css_variable_declaration(name, name_start_pos, name_end_pos) + value_start_pos = source_position + value = declaration_value + value_end_pos = source_position + + node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new), + name_start_pos, value_end_pos) + node.name_source_range = range(name_start_pos, name_end_pos) + node.value_source_range = range(value_start_pos, value_end_pos) + node + end + + # This production consumes values that could be a selector, an expression, + # or a combination of both. It respects strings and comments and supports + # interpolation. It will consume up to "{", "}", ";", or "!". + # + # Values consumed by this production will usually be parsed more + # thoroughly once interpolation has been resolved. + def almost_any_value + return unless (tok = almost_any_value_token) + sel = [tok] + while (tok = almost_any_value_token) + sel << tok + end + merge(sel) + end + + def almost_any_value_token + tok(%r{ + ( + \\. + | + (?!url\() + [^"'/\#!;\{\}] # " + | + # interp_uri will handle most url() calls, but not ones that take strings + url\(#{W}(?=") + | + /(?![/*]) + | + \#(?!\{) + | + !(?![a-z]) # TODO: never consume "!" when issue 1126 is fixed. + )+ + }xi) || tok(COMMENT) || tok(SINGLE_LINE_COMMENT) || interp_string || interp_uri || + interpolation(:warn_for_color) + end + + def declaration_value(top_level: true) + return unless (tok = declaration_value_token(top_level)) + value = [tok] + while (tok = declaration_value_token(top_level)) + value << tok + end + merge(value) + end + + def declaration_value_token(top_level) + # This comes, more or less, from the [token consumption algorithm][]. + # However, since we don't have to worry about the token semantics, we + # just consume everything until we come across a token with special + # semantics. + # + # [token consumption algorithm]: https://drafts.csswg.org/css-syntax-3/#consume-token. + result = tok(%r{ + ( + (?! + url\( + ) + [^()\[\]{}"'#/ \t\r\n\f#{top_level ? ";" : ""}] + | + \#(?!\{) + | + /(?!\*) + )+ + }xi) || interp_string || interp_uri || interpolation || tok(COMMENT) + return result if result + + # Fold together multiple characters of whitespace that don't include + # newlines. The value only cares about the tokenization, so this is safe + # as long as we don't delete whitespace entirely. It's important that we + # fold here rather than post-processing, since we aren't allowed to fold + # whitespace within strings and we lose that context later on. + if (ws = tok(S)) + return ws.include?("\n") ? ws.gsub(/\A[^\n]*/, '') : ' ' + end + + if tok(/\(/) + value = declaration_value(top_level: false) + tok!(/\)/) + ['(', *value, ')'] + elsif tok(/\[/) + value = declaration_value(top_level: false) + tok!(/\]/) + ['[', *value, ']'] + elsif tok(/\{/) + value = declaration_value(top_level: false) + tok!(/\}/) + ['{', *value, '}'] + end + end + + def declaration + # This allows the "*prop: val", ":prop: val", "#prop: val", and ".prop: + # val" hacks. + name_start_pos = source_position + if (s = tok(/[:\*\.]|\#(?!\{)/)) + name = [s, str {ss}, *expr!(:interp_ident)] + else + return unless (name = interp_ident) + name = Array(name) + end + + if (comment = tok(COMMENT)) + name << comment + end + name_end_pos = source_position + ss + + tok!(/:/) + ss + value_start_pos = source_position + value = value! + value_end_pos = source_position + ss + require_block = tok?(/\{/) + + node = node(Sass::Tree::PropNode.new(name.flatten.compact, [value], :new), + name_start_pos, value_end_pos) + node.name_source_range = range(name_start_pos, name_end_pos) + node.value_source_range = range(value_start_pos, value_end_pos) + + return node unless require_block + nested_properties! node + end + + def value! + if tok?(/\{/) + str = Sass::Script::Tree::Literal.new(Sass::Script::Value::String.new("")) + str.line = source_position.line + str.source_range = range(source_position) + return str + end + + start_pos = source_position + # This is a bit of a dirty trick: + # if the value is completely static, + # we don't parse it at all, and instead return a plain old string + # containing the value. + # This results in a dramatic speed increase. + if (val = tok(STATIC_VALUE)) + # If val ends with escaped whitespace, leave it be. + str = Sass::Script::Tree::Literal.new( + Sass::Script::Value::String.new( + Sass::Util.strip_except_escapes(val))) + str.line = start_pos.line + str.source_range = range(start_pos) + return str + end + sass_script(:parse) + end + + def nested_properties!(node) + @expected = 'expression (e.g. 1px, bold) or "{"' + block(node, :property) + end + + def expr(allow_var = true) + t = term(allow_var) + return unless t + res = [t, str {ss}] + + while (o = operator) && (t = term(allow_var)) + res << o << t << str {ss} + end + + res.flatten + end + + def term(allow_var) + e = tok(NUMBER) || + interp_uri || + function(allow_var) || + interp_string || + tok(UNICODERANGE) || + interp_ident || + tok(HEXCOLOR) || + (allow_var && var_expr) + return e if e + + op = tok(/[+-]/) + return unless op + @expected = "number or function" + [op, + tok(NUMBER) || function(allow_var) || (allow_var && var_expr) || expr!(:interpolation)] + end + + def function(allow_var) + name = tok(FUNCTION) + return unless name + if name == "expression(" || name == "calc(" + str, _ = Sass::Shared.balance(@scanner, ?(, ?), 1) + [name, str] + else + [name, str {ss}, expr(allow_var), tok!(/\)/)] + end + end + + def var_expr + return unless tok(/\$/) + line = @line + var = Sass::Script::Tree::Variable.new(ident!) + var.line = line + var + end + + def interpolation(warn_for_color = false) + return unless tok(INTERP_START) + sass_script(:parse_interpolated, warn_for_color) + end + + def string + return unless tok(STRING) + Sass::Script::Value::String.value(@scanner[1] || @scanner[2]) + end + + def interp_string + _interp_string(:double) || _interp_string(:single) + end + + def interp_uri + _interp_string(:uri) + end + + def _interp_string(type) + start = tok(Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[type][false]) + return unless start + res = [start] + + mid_re = Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[type][true] + # @scanner[2].empty? means we've started an interpolated section + while @scanner[2] == '#{' + @scanner.pos -= 2 # Don't consume the #{ + res.last.slice!(-2..-1) + res << expr!(:interpolation) << tok(mid_re) + end + res + end + + def ident + (ident = tok(IDENT)) && Sass::Util.normalize_ident_escapes(ident) + end + + def ident! + Sass::Util.normalize_ident_escapes(tok!(IDENT)) + end + + def name + (name = tok(NAME)) && Sass::Util.normalize_ident_escapes(name) + end + + def name! + Sass::Util.normalize_ident_escapes(tok!(NAME)) + end + + def interp_ident + val = ident || interpolation(:warn_for_color) || tok(IDENT_HYPHEN_INTERP) + return unless val + res = [val] + while (val = name || interpolation(:warn_for_color)) + res << val + end + res + end + + def interp_ident_or_var + id = interp_ident + return id if id + var = var_expr + return [var] if var + end + + def str + @strs.push String.new("") + yield + @strs.last + ensure + @strs.pop + end + + def str? + pos = @scanner.pos + line = @line + offset = @offset + @strs.push "" + throw_error {yield} && @strs.last + rescue Sass::SyntaxError + @scanner.pos = pos + @line = line + @offset = offset + nil + ensure + @strs.pop + end + + def node(node, start_pos, end_pos = source_position) + node.line = start_pos.line + node.source_range = range(start_pos, end_pos) + node + end + + @sass_script_parser = Sass::Script::Parser + + class << self + # @private + attr_accessor :sass_script_parser + end + + def sass_script(*args) + parser = self.class.sass_script_parser.new(@scanner, @line, @offset, + :filename => @filename, :importer => @importer, :allow_extra_text => true) + result = parser.send(*args) + unless @strs.empty? + # Convert to CSS manually so that comments are ignored. + src = result.to_sass + @strs.each {|s| s << src} + end + @line = parser.line + @offset = parser.offset + result + rescue Sass::SyntaxError => e + throw(:_sass_parser_error, true) if @throw_error + raise e + end + + def merge(arr) + arr && Sass::Util.merge_adjacent_strings([arr].flatten) + end + + EXPR_NAMES = { + :media_query => "media query (e.g. print, screen, print and screen)", + :media_query_list => "media query (e.g. print, screen, print and screen)", + :media_expr => "media expression (e.g. (min-device-width: 800px))", + :at_root_query => "@at-root query (e.g. (without: media))", + :at_root_directive_list => '* or identifier', + :declaration_value => "expression (e.g. fr, 2n+1)", + :interp_ident => "identifier", + :qualified_name => "identifier", + :expr => "expression (e.g. 1px, bold)", + :selector_comma_sequence => "selector", + :string => "string", + :import_arg => "file to import (string or url())", + :moz_document_function => "matching function (e.g. url-prefix(), domain())", + :supports_condition => "@supports condition (e.g. (display: flexbox))", + :supports_condition_in_parens => "@supports condition (e.g. (display: flexbox))", + :a_n_plus_b => "An+B expression", + :keyframes_selector_component => "from, to, or a percentage", + :keyframes_selector => "keyframes selector (e.g. 10%)" + } + + TOK_NAMES = Hash[Sass::SCSS::RX.constants.map do |c| + [Sass::SCSS::RX.const_get(c), c.downcase] + end].merge( + IDENT => "identifier", + /[;{}]/ => '";"', + /\b(without|with)\b/ => '"with" or "without"' + ) + + def tok?(rx) + @scanner.match?(rx) + end + + def expr!(name) + e = send(name) + return e if e + expected(EXPR_NAMES[name] || name.to_s) + end + + def tok!(rx) + t = tok(rx) + return t if t + name = TOK_NAMES[rx] + + unless name + # Display basic regexps as plain old strings + source = rx.source.gsub(%r{\\/}, '/') + string = rx.source.gsub(/\\(.)/, '\1') + name = source == Regexp.escape(string) ? string.inspect : rx.inspect + end + + expected(name) + end + + def expected(name) + throw(:_sass_parser_error, true) if @throw_error + self.class.expected(@scanner, @expected || name, @line) + end + + def err(msg) + throw(:_sass_parser_error, true) if @throw_error + raise Sass::SyntaxError.new(msg, :line => @line) + end + + def throw_error + old_throw_error, @throw_error = @throw_error, false + yield + ensure + @throw_error = old_throw_error + end + + def catch_error(&block) + old_throw_error, @throw_error = @throw_error, true + pos = @scanner.pos + line = @line + offset = @offset + expected = @expected + + logger = Sass::Logger::Delayed.install! + if catch(:_sass_parser_error) {yield; false} + @scanner.pos = pos + @line = line + @offset = offset + @expected = expected + {:pos => pos, :line => line, :expected => @expected, :block => block} + else + logger.flush + nil + end + ensure + logger.uninstall! if logger + @throw_error = old_throw_error + end + + def rethrow(err) + if @throw_error + throw :_sass_parser_error, err + else + @scanner = Sass::Util::MultibyteStringScanner.new(@scanner.string) + @scanner.pos = err[:pos] + @line = err[:line] + @expected = err[:expected] + err[:block].call + end + end + + # @private + def self.expected(scanner, expected, line) + pos = scanner.pos + + after = scanner.string[0...pos] + # Get rid of whitespace between pos and the last token, + # but only if there's a newline in there + after.gsub!(/\s*\n\s*$/, '') + # Also get rid of stuff before the last newline + after.gsub!(/.*\n/, '') + after = "..." + after[-15..-1] if after.size > 18 + + was = scanner.rest.dup + # Get rid of whitespace between pos and the next token, + # but only if there's a newline in there + was.gsub!(/^\s*\n\s*/, '') + # Also get rid of stuff after the next newline + was.gsub!(/\n.*/, '') + was = was[0...15] + "..." if was.size > 18 + + raise Sass::SyntaxError.new( + "Invalid CSS after \"#{after}\": expected #{expected}, was \"#{was}\"", + :line => line) + end + + # Avoid allocating lots of new strings for `#tok`. + # This is important because `#tok` is called all the time. + NEWLINE = "\n" + + def tok(rx) + res = @scanner.scan(rx) + + return unless res + + newline_count = res.count(NEWLINE) + if newline_count > 0 + @line += newline_count + @offset = res[res.rindex(NEWLINE)..-1].size + else + @offset += res.size + end + + @expected = nil + if !@strs.empty? && rx != COMMENT && rx != SINGLE_LINE_COMMENT + @strs.each {|s| s << res} + end + res + end + + # Remove a vendor prefix from `str`. + def deprefix(str) + str.gsub(/^-[a-zA-Z0-9]+-/, '') + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/scss/rx.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/scss/rx.rb new file mode 100644 index 00000000..bfb3e4a8 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/scss/rx.rb @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- +module Sass + module SCSS + # A module containing regular expressions used + # for lexing tokens in an SCSS document. + # Most of these are taken from [the CSS3 spec](http://www.w3.org/TR/css3-syntax/#lexical), + # although some have been modified for various reasons. + module RX + # Takes a string and returns a CSS identifier + # that will have the value of the given string. + # + # @param str [String] The string to escape + # @return [String] The escaped string + def self.escape_ident(str) + return "" if str.empty? + return "\\#{str}" if str == '-' || str == '_' + out = "" + value = str.dup + out << value.slice!(0...1) if value =~ /^[-_]/ + if value[0...1] =~ NMSTART + out << value.slice!(0...1) + else + out << escape_char(value.slice!(0...1)) + end + out << value.gsub(/[^a-zA-Z0-9_-]/) {|c| escape_char c} + out + end + + # Escapes a single character for a CSS identifier. + # + # @param c [String] The character to escape. Should have length 1 + # @return [String] The escaped character + # @private + def self.escape_char(c) + return "\\%06x" % c.ord unless c =~ %r{[ -/:-~]} + "\\#{c}" + end + + # Creates a Regexp from a plain text string, + # escaping all significant characters. + # + # @param str [String] The text of the regexp + # @param flags [Integer] Flags for the created regular expression + # @return [Regexp] + # @private + def self.quote(str, flags = 0) + Regexp.new(Regexp.quote(str), flags) + end + + H = /[0-9a-fA-F]/ + NL = /\n|\r\n|\r|\f/ + UNICODE = /\\#{H}{1,6}[ \t\r\n\f]?/ + s = '\u{80}-\u{D7FF}\u{E000}-\u{FFFD}\u{10000}-\u{10FFFF}' + NONASCII = /[#{s}]/ + ESCAPE = /#{UNICODE}|\\[^0-9a-fA-F\r\n\f]/ + NMSTART = /[_a-zA-Z]|#{NONASCII}|#{ESCAPE}/ + NMCHAR = /[a-zA-Z0-9_-]|#{NONASCII}|#{ESCAPE}/ + STRING1 = /\"((?:[^\n\r\f\\"]|\\#{NL}|#{ESCAPE})*)\"/ + STRING2 = /\'((?:[^\n\r\f\\']|\\#{NL}|#{ESCAPE})*)\'/ + + IDENT = /-*#{NMSTART}#{NMCHAR}*/ + NAME = /#{NMCHAR}+/ + STRING = /#{STRING1}|#{STRING2}/ + URLCHAR = /[#%&*-~]|#{NONASCII}|#{ESCAPE}/ + URL = /(#{URLCHAR}*)/ + W = /[ \t\r\n\f]*/ + VARIABLE = /(\$)(#{Sass::SCSS::RX::IDENT})/ + + # This is more liberal than the spec's definition, + # but that definition didn't work well with the greediness rules + RANGE = /(?:#{H}|\?){1,6}/ + + ## + + S = /[ \t\r\n\f]+/ + + COMMENT = %r{/\*([^*]|\*+[^/*])*\**\*/} + SINGLE_LINE_COMMENT = %r{//.*(\n[ \t]*//.*)*} + + CDO = quote("") + INCLUDES = quote("~=") + DASHMATCH = quote("|=") + PREFIXMATCH = quote("^=") + SUFFIXMATCH = quote("$=") + SUBSTRINGMATCH = quote("*=") + + HASH = /##{NAME}/ + + IMPORTANT = /!#{W}important/i + + # A unit is like an IDENT, but disallows a hyphen followed by a digit. + # This allows "1px-2px" to be interpreted as subtraction rather than "1" + # with the unit "px-2px". It also allows "%". + UNIT = /-?#{NMSTART}(?:[a-zA-Z0-9_]|#{NONASCII}|#{ESCAPE}|-(?!\.?\d))*|%/ + + UNITLESS_NUMBER = /(?:[0-9]+|[0-9]*\.[0-9]+)(?:[eE][+-]?\d+)?/ + NUMBER = /#{UNITLESS_NUMBER}(?:#{UNIT})?/ + PERCENTAGE = /#{UNITLESS_NUMBER}%/ + + URI = /url\(#{W}(?:#{STRING}|#{URL})#{W}\)/i + FUNCTION = /#{IDENT}\(/ + + UNICODERANGE = /u\+(?:#{H}{1,6}-#{H}{1,6}|#{RANGE})/i + + # Defined in http://www.w3.org/TR/css3-selectors/#lex + PLUS = /#{W}\+/ + GREATER = /#{W}>/ + TILDE = /#{W}~/ + NOT = quote(":not(", Regexp::IGNORECASE) + + # Defined in https://developer.mozilla.org/en/CSS/@-moz-document as a + # non-standard version of http://www.w3.org/TR/css3-conditional/ + URL_PREFIX = /url-prefix\(#{W}(?:#{STRING}|#{URL})#{W}\)/i + DOMAIN = /domain\(#{W}(?:#{STRING}|#{URL})#{W}\)/i + + # Custom + HEXCOLOR = /\#[0-9a-fA-F]+/ + INTERP_START = /#\{/ + ANY = /:(-[-\w]+-)?any\(/i + OPTIONAL = /!#{W}optional/i + IDENT_START = /-|#{NMSTART}/ + + IDENT_HYPHEN_INTERP = /-+(?=#\{)/ + STRING1_NOINTERP = /\"((?:[^\n\r\f\\"#]|#(?!\{)|#{ESCAPE})*)\"/ + STRING2_NOINTERP = /\'((?:[^\n\r\f\\'#]|#(?!\{)|#{ESCAPE})*)\'/ + STRING_NOINTERP = /#{STRING1_NOINTERP}|#{STRING2_NOINTERP}/ + + STATIC_COMPONENT = /#{IDENT}|#{STRING_NOINTERP}|#{HEXCOLOR}|[+-]?#{NUMBER}|\!important/i + STATIC_VALUE = %r(#{STATIC_COMPONENT}(\s*[\s,\/]\s*#{STATIC_COMPONENT})*(?=[;}]))i + STATIC_SELECTOR = /(#{NMCHAR}|[ \t]|[,>+*]|[:#.]#{NMSTART}){1,50}([{])/i + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/scss/static_parser.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/scss/static_parser.rb new file mode 100644 index 00000000..fff9d0e9 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/scss/static_parser.rb @@ -0,0 +1,351 @@ +require 'sass/script/css_parser' + +module Sass + module SCSS + # A parser for a static SCSS tree. + # Parses with SCSS extensions, like nested rules and parent selectors, + # but without dynamic SassScript. + # This is useful for e.g. \{#parse\_selector parsing selectors} + # after resolving the interpolation. + class StaticParser < Parser + # Parses the text as a selector. + # + # @param filename [String, nil] The file in which the selector appears, + # or nil if there is no such file. + # Used for error reporting. + # @return [Selector::CommaSequence] The parsed selector + # @raise [Sass::SyntaxError] if there's a syntax error in the selector + def parse_selector + init_scanner! + seq = expr!(:selector_comma_sequence) + expected("selector") unless @scanner.eos? + seq.line = @line + seq.filename = @filename + seq + end + + # Parses a static at-root query. + # + # @return [(Symbol, Array)] The type of the query + # (`:with` or `:without`) and the values that are being filtered. + # @raise [Sass::SyntaxError] if there's a syntax error in the query, + # or if it doesn't take up the entire input string. + def parse_static_at_root_query + init_scanner! + tok!(/\(/); ss + type = tok!(/\b(without|with)\b/).to_sym; ss + tok!(/:/); ss + directives = expr!(:at_root_directive_list); ss + tok!(/\)/) + expected("@at-root query list") unless @scanner.eos? + return type, directives + end + + def parse_keyframes_selector + init_scanner! + sel = expr!(:keyframes_selector) + expected("keyframes selector") unless @scanner.eos? + sel + end + + # @see Parser#initialize + # @param allow_parent_ref [Boolean] Whether to allow the + # parent-reference selector, `&`, when parsing the document. + def initialize(str, filename, importer, line = 1, offset = 1, allow_parent_ref = true) + super(str, filename, importer, line, offset) + @allow_parent_ref = allow_parent_ref + end + + private + + def moz_document_function + val = tok(URI) || tok(URL_PREFIX) || tok(DOMAIN) || function(false) + return unless val + ss + [val] + end + + def variable; nil; end + def script_value; nil; end + def interpolation(warn_for_color = false); nil; end + def var_expr; nil; end + def interp_string; (s = tok(STRING)) && [s]; end + def interp_uri; (s = tok(URI)) && [s]; end + def interp_ident; (s = ident) && [s]; end + def use_css_import?; true; end + + def special_directive(name, start_pos) + return unless %w(media import charset -moz-document).include?(name) + super + end + + def selector_comma_sequence + sel = selector + return unless sel + selectors = [sel] + ws = '' + while tok(/,/) + ws << str {ss} + next unless (sel = selector) + selectors << sel + if ws.include?("\n") + selectors[-1] = Selector::Sequence.new(["\n"] + selectors.last.members) + end + ws = '' + end + Selector::CommaSequence.new(selectors) + end + + def selector_string + sel = selector + return unless sel + sel.to_s + end + + def selector + start_pos = source_position + # The combinator here allows the "> E" hack + val = combinator || simple_selector_sequence + return unless val + nl = str {ss}.include?("\n") + res = [] + res << val + res << "\n" if nl + + while (val = combinator || simple_selector_sequence) + res << val + res << "\n" if str {ss}.include?("\n") + end + seq = Selector::Sequence.new(res.compact) + + if seq.members.any? {|sseq| sseq.is_a?(Selector::SimpleSequence) && sseq.subject?} + location = " of #{@filename}" if @filename + Sass::Util.sass_warn < e + e.message << "\n\n\"#{sel}\" may only be used at the beginning of a compound selector." + raise e + end + end + + Selector::SimpleSequence.new(res, tok(/!/), range(start_pos)) + end + + def parent_selector + return unless @allow_parent_ref && tok(/&/) + Selector::Parent.new(name) + end + + def class_selector + return unless tok(/\./) + @expected = "class name" + Selector::Class.new(ident!) + end + + def id_selector + return unless tok(/#(?!\{)/) + @expected = "id name" + Selector::Id.new(name!) + end + + def placeholder_selector + return unless tok(/%/) + @expected = "placeholder name" + Selector::Placeholder.new(ident!) + end + + def element_name + ns, name = Sass::Util.destructure(qualified_name(:allow_star_name)) + return unless ns || name + + if name == '*' + Selector::Universal.new(ns) + else + Selector::Element.new(name, ns) + end + end + + def qualified_name(allow_star_name = false) + name = ident || tok(/\*/) || (tok?(/\|/) && "") + return unless name + return nil, name unless tok(/\|/) + + return name, ident! unless allow_star_name + @expected = "identifier or *" + return name, ident || tok!(/\*/) + end + + def attrib + return unless tok(/\[/) + ss + ns, name = attrib_name! + ss + + op = tok(/=/) || + tok(INCLUDES) || + tok(DASHMATCH) || + tok(PREFIXMATCH) || + tok(SUFFIXMATCH) || + tok(SUBSTRINGMATCH) + if op + @expected = "identifier or string" + ss + val = ident || tok!(STRING) + ss + end + flags = ident || tok(STRING) + tok!(/\]/) + + Selector::Attribute.new(name, ns, op, val, flags) + end + + def attrib_name! + if (name_or_ns = ident) + # E, E|E + if tok(/\|(?!=)/) + ns = name_or_ns + name = ident + else + name = name_or_ns + end + else + # *|E or |E + ns = tok(/\*/) || "" + tok!(/\|/) + name = ident! + end + return ns, name + end + + SELECTOR_PSEUDO_CLASSES = %w(not matches current any has host host-context).to_set + + PREFIXED_SELECTOR_PSEUDO_CLASSES = %w(nth-child nth-last-child).to_set + + SELECTOR_PSEUDO_ELEMENTS = %w(slotted).to_set + + def pseudo + s = tok(/::?/) + return unless s + @expected = "pseudoclass or pseudoelement" + name = ident! + if tok(/\(/) + ss + deprefixed = deprefix(name) + if s == ':' && SELECTOR_PSEUDO_CLASSES.include?(deprefixed) + sel = selector_comma_sequence + elsif s == ':' && PREFIXED_SELECTOR_PSEUDO_CLASSES.include?(deprefixed) + arg, sel = prefixed_selector_pseudo + elsif s == '::' && SELECTOR_PSEUDO_ELEMENTS.include?(deprefixed) + sel = selector_comma_sequence + else + arg = expr!(:declaration_value).join + end + + tok!(/\)/) + end + Selector::Pseudo.new(s == ':' ? :class : :element, name, arg, sel) + end + + def prefixed_selector_pseudo + prefix = str do + expr = str {expr!(:a_n_plus_b)} + ss + return expr, nil unless tok(/of/) + ss + end + return prefix, expr!(:selector_comma_sequence) + end + + def a_n_plus_b + if (parity = tok(/even|odd/i)) + return parity + end + + if tok(/[+-]?[0-9]+/) + ss + return true unless tok(/n/) + else + return unless tok(/[+-]?n/i) + end + ss + + return true unless tok(/[+-]/) + ss + @expected = "number" + tok!(/[0-9]+/) + true + end + + def keyframes_selector + ss + str do + return unless keyframes_selector_component + ss + while tok(/,/) + ss + expr!(:keyframes_selector_component) + ss + end + end + end + + def keyframes_selector_component + ident || tok(PERCENTAGE) + end + + @sass_script_parser = Class.new(Sass::Script::CssParser) + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/selector.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/selector.rb new file mode 100644 index 00000000..cac8de4e --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/selector.rb @@ -0,0 +1,327 @@ +require 'sass/selector/simple' +require 'sass/selector/abstract_sequence' +require 'sass/selector/comma_sequence' +require 'sass/selector/pseudo' +require 'sass/selector/sequence' +require 'sass/selector/simple_sequence' + +module Sass + # A namespace for nodes in the parse tree for selectors. + # + # {CommaSequence} is the toplevel selector, + # representing a comma-separated sequence of {Sequence}s, + # such as `foo bar, baz bang`. + # {Sequence} is the next level, + # representing {SimpleSequence}s separated by combinators (e.g. descendant or child), + # such as `foo bar` or `foo > bar baz`. + # {SimpleSequence} is a sequence of selectors that all apply to a single element, + # such as `foo.bar[attr=val]`. + # Finally, {Simple} is the superclass of the simplest selectors, + # such as `.foo` or `#bar`. + module Selector + # The base used for calculating selector specificity. The spec says this + # should be "sufficiently high"; it's extremely unlikely that any single + # selector sequence will contain 1,000 simple selectors. + SPECIFICITY_BASE = 1_000 + + # A parent-referencing selector (`&` in Sass). + # The function of this is to be replaced by the parent selector + # in the nested hierarchy. + class Parent < Simple + # The identifier following the `&`. `nil` indicates no suffix. + # + # @return [String, nil] + attr_reader :suffix + + # @param name [String, nil] See \{#suffix} + def initialize(suffix = nil) + @suffix = suffix + end + + # @see Selector#to_s + def to_s(opts = {}) + "&" + (@suffix || '') + end + + # Always raises an exception. + # + # @raise [Sass::SyntaxError] Parent selectors should be resolved before unification + # @see Selector#unify + def unify(sels) + raise Sass::SyntaxError.new("[BUG] Cannot unify parent selectors.") + end + end + + # A class selector (e.g. `.foo`). + class Class < Simple + # The class name. + # + # @return [String] + attr_reader :name + + # @param name [String] The class name + def initialize(name) + @name = name + end + + # @see Selector#to_s + def to_s(opts = {}) + "." + @name + end + + # @see AbstractSequence#specificity + def specificity + SPECIFICITY_BASE + end + end + + # An id selector (e.g. `#foo`). + class Id < Simple + # The id name. + # + # @return [String] + attr_reader :name + + # @param name [String] The id name + def initialize(name) + @name = name + end + + def unique? + true + end + + # @see Selector#to_s + def to_s(opts = {}) + "#" + @name + end + + # Returns `nil` if `sels` contains an {Id} selector + # with a different name than this one. + # + # @see Selector#unify + def unify(sels) + return if sels.any? {|sel2| sel2.is_a?(Id) && name != sel2.name} + super + end + + # @see AbstractSequence#specificity + def specificity + SPECIFICITY_BASE**2 + end + end + + # A placeholder selector (e.g. `%foo`). + # This exists to be replaced via `@extend`. + # Rulesets using this selector will not be printed, but can be extended. + # Otherwise, this acts just like a class selector. + class Placeholder < Simple + # The placeholder name. + # + # @return [String] + attr_reader :name + + # @param name [String] The placeholder name + def initialize(name) + @name = name + end + + # @see Selector#to_s + def to_s(opts = {}) + "%" + @name + end + + # @see AbstractSequence#specificity + def specificity + SPECIFICITY_BASE + end + end + + # A universal selector (`*` in CSS). + class Universal < Simple + # The selector namespace. `nil` means the default namespace, `""` means no + # namespace, `"*"` means any namespace. + # + # @return [String, nil] + attr_reader :namespace + + # @param namespace [String, nil] See \{#namespace} + def initialize(namespace) + @namespace = namespace + end + + # @see Selector#to_s + def to_s(opts = {}) + @namespace ? "#{@namespace}|*" : "*" + end + + # Unification of a universal selector is somewhat complicated, + # especially when a namespace is specified. + # If there is no namespace specified + # or any namespace is specified (namespace `"*"`), + # then `sel` is returned without change + # (unless it's empty, in which case `"*"` is required). + # + # If a namespace is specified + # but `sel` does not specify a namespace, + # then the given namespace is applied to `sel`, + # either by adding this {Universal} selector + # or applying this namespace to an existing {Element} selector. + # + # If both this selector *and* `sel` specify namespaces, + # those namespaces are unified via {Simple#unify_namespaces} + # and the unified namespace is used, if possible. + # + # @todo There are lots of cases that this documentation specifies; + # make sure we thoroughly test **all of them**. + # @todo Keep track of whether a default namespace has been declared + # and handle namespace-unspecified selectors accordingly. + # @todo If any branch of a CommaSequence ends up being just `"*"`, + # then all other branches should be eliminated + # + # @see Selector#unify + def unify(sels) + name = + case sels.first + when Universal; :universal + when Element; sels.first.name + else + return [self] + sels unless namespace.nil? || namespace == '*' + return sels unless sels.empty? + return [self] + end + + ns, accept = unify_namespaces(namespace, sels.first.namespace) + return unless accept + [name == :universal ? Universal.new(ns) : Element.new(name, ns)] + sels[1..-1] + end + + # @see AbstractSequence#specificity + def specificity + 0 + end + end + + # An element selector (e.g. `h1`). + class Element < Simple + # The element name. + # + # @return [String] + attr_reader :name + + # The selector namespace. `nil` means the default namespace, `""` means no + # namespace, `"*"` means any namespace. + # + # @return [String, nil] + attr_reader :namespace + + # @param name [String] The element name + # @param namespace [String, nil] See \{#namespace} + def initialize(name, namespace) + @name = name + @namespace = namespace + end + + # @see Selector#to_s + def to_s(opts = {}) + @namespace ? "#{@namespace}|#{@name}" : @name + end + + # Unification of an element selector is somewhat complicated, + # especially when a namespace is specified. + # First, if `sel` contains another {Element} with a different \{#name}, + # then the selectors can't be unified and `nil` is returned. + # + # Otherwise, if `sel` doesn't specify a namespace, + # or it specifies any namespace (via `"*"`), + # then it's returned with this element selector + # (e.g. `.foo` becomes `a.foo` or `svg|a.foo`). + # Similarly, if this selector doesn't specify a namespace, + # the namespace from `sel` is used. + # + # If both this selector *and* `sel` specify namespaces, + # those namespaces are unified via {Simple#unify_namespaces} + # and the unified namespace is used, if possible. + # + # @todo There are lots of cases that this documentation specifies; + # make sure we thoroughly test **all of them**. + # @todo Keep track of whether a default namespace has been declared + # and handle namespace-unspecified selectors accordingly. + # + # @see Selector#unify + def unify(sels) + case sels.first + when Universal; + when Element; return unless name == sels.first.name + else return [self] + sels + end + + ns, accept = unify_namespaces(namespace, sels.first.namespace) + return unless accept + [Element.new(name, ns)] + sels[1..-1] + end + + # @see AbstractSequence#specificity + def specificity + 1 + end + end + + # An attribute selector (e.g. `[href^="http://"]`). + class Attribute < Simple + # The attribute name. + # + # @return [Array] + attr_reader :name + + # The attribute namespace. `nil` means the default namespace, `""` means + # no namespace, `"*"` means any namespace. + # + # @return [String, nil] + attr_reader :namespace + + # The matching operator, e.g. `"="` or `"^="`. + # + # @return [String] + attr_reader :operator + + # The right-hand side of the operator. + # + # @return [String] + attr_reader :value + + # Flags for the attribute selector (e.g. `i`). + # + # @return [String] + attr_reader :flags + + # @param name [String] The attribute name + # @param namespace [String, nil] See \{#namespace} + # @param operator [String] The matching operator, e.g. `"="` or `"^="` + # @param value [String] See \{#value} + # @param flags [String] See \{#flags} + def initialize(name, namespace, operator, value, flags) + @name = name + @namespace = namespace + @operator = operator + @value = value + @flags = flags + end + + # @see Selector#to_s + def to_s(opts = {}) + res = "[" + res << @namespace << "|" if @namespace + res << @name + res << @operator << @value if @value + res << " " << @flags if @flags + res << "]" + end + + # @see AbstractSequence#specificity + def specificity + SPECIFICITY_BASE + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/selector/abstract_sequence.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/selector/abstract_sequence.rb new file mode 100644 index 00000000..ad167a01 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/selector/abstract_sequence.rb @@ -0,0 +1,112 @@ +module Sass + module Selector + # The abstract parent class of the various selector sequence classes. + # + # All subclasses should implement a `members` method that returns an array + # of object that respond to `#line=` and `#filename=`, as well as a `to_s` + # method that returns the string representation of the selector. + class AbstractSequence + # The line of the Sass template on which this selector was declared. + # + # @return [Integer] + attr_reader :line + + # The name of the file in which this selector was declared. + # + # @return [String, nil] + attr_reader :filename + + # Sets the line of the Sass template on which this selector was declared. + # This also sets the line for all child selectors. + # + # @param line [Integer] + # @return [Integer] + def line=(line) + members.each {|m| m.line = line} + @line = line + end + + # Sets the name of the file in which this selector was declared, + # or `nil` if it was not declared in a file (e.g. on stdin). + # This also sets the filename for all child selectors. + # + # @param filename [String, nil] + # @return [String, nil] + def filename=(filename) + members.each {|m| m.filename = filename} + @filename = filename + end + + # Returns a hash code for this sequence. + # + # Subclasses should define `#_hash` rather than overriding this method, + # which automatically handles memoizing the result. + # + # @return [Integer] + def hash + @_hash ||= _hash + end + + # Checks equality between this and another object. + # + # Subclasses should define `#_eql?` rather than overriding this method, + # which handles checking class equality and hash equality. + # + # @param other [Object] The object to test equality against + # @return [Boolean] Whether or not this is equal to `other` + def eql?(other) + other.class == self.class && other.hash == hash && _eql?(other) + end + alias_method :==, :eql? + + # Whether or not this selector should be hidden due to containing a + # placeholder. + def invisible? + @invisible ||= members.any? do |m| + next m.invisible? if m.is_a?(AbstractSequence) || m.is_a?(Pseudo) + m.is_a?(Placeholder) + end + end + + # Returns the selector string. + # + # @param opts [Hash] rendering options. + # @option opts [Symbol] :style The css rendering style. + # @option placeholders [Boolean] :placeholders + # Whether to include placeholder selectors. Defaults to `true`. + # @return [String] + def to_s(opts = {}) + Sass::Util.abstract(self) + end + + # Returns the specificity of the selector. + # + # The base is given by {Sass::Selector::SPECIFICITY_BASE}. This can be a + # number or a range representing possible specificities. + # + # @return [Integer, Range] + def specificity + _specificity(members) + end + + protected + + def _specificity(arr) + min = 0 + max = 0 + arr.each do |m| + next if m.is_a?(String) + spec = m.specificity + if spec.is_a?(Range) + min += spec.begin + max += spec.end + else + min += spec + max += spec + end + end + min == max ? min : (min..max) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/selector/comma_sequence.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/selector/comma_sequence.rb new file mode 100644 index 00000000..c62ea04b --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/selector/comma_sequence.rb @@ -0,0 +1,195 @@ +module Sass + module Selector + # A comma-separated sequence of selectors. + class CommaSequence < AbstractSequence + @@compound_extend_deprecation = Sass::Deprecation.new + + # The comma-separated selector sequences + # represented by this class. + # + # @return [Array] + attr_reader :members + + # @param seqs [Array] See \{#members} + def initialize(seqs) + @members = seqs + end + + # Resolves the {Parent} selectors within this selector + # by replacing them with the given parent selector, + # handling commas appropriately. + # + # @param super_cseq [CommaSequence] The parent selector + # @param implicit_parent [Boolean] Whether the the parent + # selector should automatically be prepended to the resolved + # selector if it contains no parent refs. + # @return [CommaSequence] This selector, with parent references resolved + # @raise [Sass::SyntaxError] If a parent selector is invalid + def resolve_parent_refs(super_cseq, implicit_parent = true) + if super_cseq.nil? + if contains_parent_ref? + raise Sass::SyntaxError.new( + "Base-level rules cannot contain the parent-selector-referencing character '&'.") + end + return self + end + + CommaSequence.new(Sass::Util.flatten_vertically(@members.map do |seq| + seq.resolve_parent_refs(super_cseq, implicit_parent).members + end)) + end + + # Returns whether there's a {Parent} selector anywhere in this sequence. + # + # @return [Boolean] + def contains_parent_ref? + @members.any? {|sel| sel.contains_parent_ref?} + end + + # Non-destrucively extends this selector with the extensions specified in a hash + # (which should come from {Sass::Tree::Visitors::Cssize}). + # + # @todo Link this to the reference documentation on `@extend` + # when such a thing exists. + # + # @param extends [Sass::Util::SubsetMap{Selector::Simple => + # Sass::Tree::Visitors::Cssize::Extend}] + # The extensions to perform on this selector + # @param parent_directives [Array] + # The directives containing this selector. + # @param replace [Boolean] + # Whether to replace the original selector entirely or include + # it in the result. + # @param seen [Set>] + # The set of simple sequences that are currently being replaced. + # @param original [Boolean] + # Whether this is the original selector being extended, as opposed to + # the result of a previous extension that's being re-extended. + # @return [CommaSequence] A copy of this selector, + # with extensions made according to `extends` + def do_extend(extends, parent_directives = [], replace = false, seen = Set.new, + original = true) + CommaSequence.new(members.map do |seq| + seq.do_extend(extends, parent_directives, replace, seen, original) + end.flatten) + end + + # Returns whether or not this selector matches all elements + # that the given selector matches (as well as possibly more). + # + # @example + # (.foo).superselector?(.foo.bar) #=> true + # (.foo).superselector?(.bar) #=> false + # @param cseq [CommaSequence] + # @return [Boolean] + def superselector?(cseq) + cseq.members.all? {|seq1| members.any? {|seq2| seq2.superselector?(seq1)}} + end + + # Populates a subset map that can then be used to extend + # selectors. This registers an extension with this selector as + # the extender and `extendee` as the extendee. + # + # @param extends [Sass::Util::SubsetMap{Selector::Simple => + # Sass::Tree::Visitors::Cssize::Extend}] + # The subset map representing the extensions to perform. + # @param extendee [CommaSequence] The selector being extended. + # @param extend_node [Sass::Tree::ExtendNode] + # The node that caused this extension. + # @param parent_directives [Array] + # The parent directives containing `extend_node`. + # @param allow_compound_target [Boolean] + # Whether `extendee` is allowed to contain compound selectors. + # @raise [Sass::SyntaxError] if this extension is invalid. + def populate_extends(extends, extendee, extend_node = nil, parent_directives = [], + allow_compound_target = false) + extendee.members.each do |seq| + if seq.members.size > 1 + raise Sass::SyntaxError.new("Can't extend #{seq}: can't extend nested selectors") + end + + sseq = seq.members.first + if !sseq.is_a?(Sass::Selector::SimpleSequence) + raise Sass::SyntaxError.new("Can't extend #{seq}: invalid selector") + elsif sseq.members.any? {|ss| ss.is_a?(Sass::Selector::Parent)} + raise Sass::SyntaxError.new("Can't extend #{seq}: can't extend parent selectors") + end + + sel = sseq.members + if !allow_compound_target && sel.length > 1 + @@compound_extend_deprecation.warn(sseq.filename, sseq.line, <] + ACTUALLY_ELEMENTS = %w(after before first-line first-letter).to_set + + # Like \{#type}, but returns the type of selector this looks like, rather + # than the type it is semantically. This only differs from type for + # selectors in \{ACTUALLY\_ELEMENTS}. + # + # @return [Symbol] + attr_reader :syntactic_type + + # The name of the selector. + # + # @return [String] + attr_reader :name + + # The argument to the selector, + # or `nil` if no argument was given. + # + # @return [String, nil] + attr_reader :arg + + # The selector argument, or `nil` if no selector exists. + # + # If this and \{#arg\} are both set, \{#arg\} is considered a non-selector + # prefix. + # + # @return [CommaSequence] + attr_reader :selector + + # @param syntactic_type [Symbol] See \{#syntactic_type} + # @param name [String] See \{#name} + # @param arg [nil, String] See \{#arg} + # @param selector [nil, CommaSequence] See \{#selector} + def initialize(syntactic_type, name, arg, selector) + @syntactic_type = syntactic_type + @name = name + @arg = arg + @selector = selector + end + + def unique? + type == :class && normalized_name == 'root' + end + + # Whether or not this selector should be hidden due to containing a + # placeholder. + def invisible? + # :not() is a special case—if you eliminate all the placeholders from + # it, it should match anything. + name != 'not' && @selector && @selector.members.all? {|s| s.invisible?} + end + + # Returns a copy of this with \{#selector} set to \{#new\_selector}. + # + # @param new_selector [CommaSequence] + # @return [Array] + def with_selector(new_selector) + result = Pseudo.new(syntactic_type, name, arg, + CommaSequence.new(new_selector.members.map do |seq| + next seq unless seq.members.length == 1 + sseq = seq.members.first + next seq unless sseq.is_a?(SimpleSequence) && sseq.members.length == 1 + sel = sseq.members.first + next seq unless sel.is_a?(Pseudo) && sel.selector + + case normalized_name + when 'not' + # In theory, if there's a nested :not its contents should be + # unified with the return value. For example, if :not(.foo) + # extends .bar, :not(.bar) should become .foo:not(.bar). However, + # this is a narrow edge case and supporting it properly would make + # this code and the code calling it a lot more complicated, so + # it's not supported for now. + next [] unless sel.normalized_name == 'matches' + sel.selector.members + when 'matches', 'any', 'current', 'nth-child', 'nth-last-child' + # As above, we could theoretically support :not within :matches, but + # doing so would require this method and its callers to handle much + # more complex cases that likely aren't worth the pain. + next [] unless sel.name == name && sel.arg == arg + sel.selector.members + when 'has', 'host', 'host-context', 'slotted' + # We can't expand nested selectors here, because each layer adds an + # additional layer of semantics. For example, `:has(:has(img))` + # doesn't match `
    ` but `:has(img)` does. + sel + else + [] + end + end.flatten)) + + # Older browsers support :not but only with a single complex selector. + # In order to support those browsers, we break up the contents of a :not + # unless it originally contained a selector list. + return [result] unless normalized_name == 'not' + return [result] if selector.members.length > 1 + result.selector.members.map do |seq| + Pseudo.new(syntactic_type, name, arg, CommaSequence.new([seq])) + end + end + + # The type of the selector. `:class` if this is a pseudoclass selector, + # `:element` if it's a pseudoelement. + # + # @return [Symbol] + def type + ACTUALLY_ELEMENTS.include?(normalized_name) ? :element : syntactic_type + end + + # Like \{#name\}, but without any vendor prefix. + # + # @return [String] + def normalized_name + @normalized_name ||= name.gsub(/^-[a-zA-Z0-9]+-/, '') + end + + # @see Selector#to_s + def to_s(opts = {}) + # :not() is a special case, because :not() should match + # everything. + return '' if name == 'not' && @selector && @selector.members.all? {|m| m.invisible?} + + res = (syntactic_type == :class ? ":" : "::") + @name + if @arg || @selector + res << "(" + res << Sass::Util.strip_except_escapes(@arg) if @arg + res << " " if @arg && @selector + res << @selector.to_s(opts) if @selector + res << ")" + end + res + end + + # Returns `nil` if this is a pseudoelement selector + # and `sels` contains a pseudoelement selector different than this one. + # + # @see SimpleSequence#unify + def unify(sels) + return if type == :element && sels.any? do |sel| + sel.is_a?(Pseudo) && sel.type == :element && + (sel.name != name || sel.arg != arg || sel.selector != selector) + end + super + end + + # Returns whether or not this selector matches all elements + # that the given selector matches (as well as possibly more). + # + # @example + # (.foo).superselector?(.foo.bar) #=> true + # (.foo).superselector?(.bar) #=> false + # @param their_sseq [SimpleSequence] + # @param parents [Array] The parent selectors of `their_sseq`, if any. + # @return [Boolean] + def superselector?(their_sseq, parents = []) + case normalized_name + when 'matches', 'any' + # :matches can be a superselector of another selector in one of two + # ways. Either its constituent selectors can be a superset of those of + # another :matches in the other selector, or any of its constituent + # selectors can individually be a superselector of the other selector. + (their_sseq.selector_pseudo_classes[normalized_name] || []).any? do |their_sel| + next false unless their_sel.is_a?(Pseudo) + next false unless their_sel.name == name + selector.superselector?(their_sel.selector) + end || selector.members.any? do |our_seq| + their_seq = Sequence.new(parents + [their_sseq]) + our_seq.superselector?(their_seq) + end + when 'has', 'host', 'host-context', 'slotted' + # Like :matches, :has (et al) can be a superselector of another + # selector if its constituent selectors are a superset of those of + # another :has in the other selector. However, the :matches other case + # doesn't work, because :has refers to nested elements. + (their_sseq.selector_pseudo_classes[normalized_name] || []).any? do |their_sel| + next false unless their_sel.is_a?(Pseudo) + next false unless their_sel.name == name + selector.superselector?(their_sel.selector) + end + when 'not' + selector.members.all? do |our_seq| + their_sseq.members.any? do |their_sel| + if their_sel.is_a?(Element) || their_sel.is_a?(Id) + # `:not(a)` is a superselector of `h1` and `:not(#foo)` is a + # superselector of `#bar`. + our_sseq = our_seq.members.last + next false unless our_sseq.is_a?(SimpleSequence) + our_sseq.members.any? do |our_sel| + our_sel.class == their_sel.class && our_sel != their_sel + end + else + next false unless their_sel.is_a?(Pseudo) + next false unless their_sel.name == name + # :not(X) is a superselector of :not(Y) exactly when Y is a + # superselector of X. + their_sel.selector.superselector?(CommaSequence.new([our_seq])) + end + end + end + when 'current' + (their_sseq.selector_pseudo_classes['current'] || []).any? do |their_current| + next false if their_current.name != name + # Explicitly don't check for nested superselector relationships + # here. :current(.foo) isn't always a superselector of + # :current(.foo.bar), since it matches the *innermost* ancestor of + # the current element that matches the selector. For example: + # + #
    + #

    + # current element + #

    + #
    + # + # Here :current(.foo) would match the p element and *not* the div + # element, whereas :current(.foo.bar) would match the div and not + # the p. + selector == their_current.selector + end + when 'nth-child', 'nth-last-child' + their_sseq.members.any? do |their_sel| + # This misses a few edge cases. For example, `:nth-child(n of X)` + # is a superselector of `X`, and `:nth-child(2n of X)` is a + # superselector of `:nth-child(4n of X)`. These seem rare enough + # not to be worth worrying about, though. + next false unless their_sel.is_a?(Pseudo) + next false unless their_sel.name == name + next false unless their_sel.arg == arg + selector.superselector?(their_sel.selector) + end + else + throw "[BUG] Unknown selector pseudo class #{name}" + end + end + + # @see AbstractSequence#specificity + def specificity + return 1 if type == :element + return SPECIFICITY_BASE unless selector + @specificity ||= + if normalized_name == 'not' + min = 0 + max = 0 + selector.members.each do |seq| + spec = seq.specificity + if spec.is_a?(Range) + min = Sass::Util.max(spec.begin, min) + max = Sass::Util.max(spec.end, max) + else + min = Sass::Util.max(spec, min) + max = Sass::Util.max(spec, max) + end + end + min == max ? max : (min..max) + else + min = 0 + max = 0 + selector.members.each do |seq| + spec = seq.specificity + if spec.is_a?(Range) + min = Sass::Util.min(spec.begin, min) + max = Sass::Util.max(spec.end, max) + else + min = Sass::Util.min(spec, min) + max = Sass::Util.max(spec, max) + end + end + min == max ? max : (min..max) + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/selector/sequence.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/selector/sequence.rb new file mode 100644 index 00000000..173412a1 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/selector/sequence.rb @@ -0,0 +1,661 @@ +module Sass + module Selector + # An operator-separated sequence of + # {SimpleSequence simple selector sequences}. + class Sequence < AbstractSequence + # Sets the line of the Sass template on which this selector was declared. + # This also sets the line for all child selectors. + # + # @param line [Integer] + # @return [Integer] + def line=(line) + members.each {|m| m.line = line if m.is_a?(SimpleSequence)} + @line = line + end + + # Sets the name of the file in which this selector was declared, + # or `nil` if it was not declared in a file (e.g. on stdin). + # This also sets the filename for all child selectors. + # + # @param filename [String, nil] + # @return [String, nil] + def filename=(filename) + members.each {|m| m.filename = filename if m.is_a?(SimpleSequence)} + filename + end + + # The array of {SimpleSequence simple selector sequences}, operators, and + # newlines. The operators are strings such as `"+"` and `">"` representing + # the corresponding CSS operators, or interpolated SassScript. Newlines + # are also newline strings; these aren't semantically relevant, but they + # do affect formatting. + # + # @return [Array>] + attr_reader :members + + # @param seqs_and_ops [Array>] + # See \{#members} + def initialize(seqs_and_ops) + @members = seqs_and_ops + end + + # Resolves the {Parent} selectors within this selector + # by replacing them with the given parent selector, + # handling commas appropriately. + # + # @param super_cseq [CommaSequence] The parent selector + # @param implicit_parent [Boolean] Whether the the parent + # selector should automatically be prepended to the resolved + # selector if it contains no parent refs. + # @return [CommaSequence] This selector, with parent references resolved + # @raise [Sass::SyntaxError] If a parent selector is invalid + def resolve_parent_refs(super_cseq, implicit_parent) + members = @members.dup + nl = (members.first == "\n" && members.shift) + contains_parent_ref = contains_parent_ref? + return CommaSequence.new([self]) if !implicit_parent && !contains_parent_ref + + unless contains_parent_ref + old_members, members = members, [] + members << nl if nl + members << SimpleSequence.new([Parent.new], false) + members += old_members + end + + CommaSequence.new(Sass::Util.paths(members.map do |sseq_or_op| + next [sseq_or_op] unless sseq_or_op.is_a?(SimpleSequence) + sseq_or_op.resolve_parent_refs(super_cseq).members + end).map do |path| + path_members = path.map do |seq_or_op| + next seq_or_op unless seq_or_op.is_a?(Sequence) + seq_or_op.members + end + if path_members.length == 2 && path_members[1][0] == "\n" + path_members[0].unshift path_members[1].shift + end + Sequence.new(path_members.flatten) + end) + end + + # Returns whether there's a {Parent} selector anywhere in this sequence. + # + # @return [Boolean] + def contains_parent_ref? + members.any? do |sseq_or_op| + next false unless sseq_or_op.is_a?(SimpleSequence) + next true if sseq_or_op.members.first.is_a?(Parent) + sseq_or_op.members.any? do |sel| + sel.is_a?(Pseudo) && sel.selector && sel.selector.contains_parent_ref? + end + end + end + + # Non-destructively extends this selector with the extensions specified in a hash + # (which should come from {Sass::Tree::Visitors::Cssize}). + # + # @param extends [Sass::Util::SubsetMap{Selector::Simple => + # Sass::Tree::Visitors::Cssize::Extend}] + # The extensions to perform on this selector + # @param parent_directives [Array] + # The directives containing this selector. + # @param replace [Boolean] + # Whether to replace the original selector entirely or include + # it in the result. + # @param seen [Set>] + # The set of simple sequences that are currently being replaced. + # @param original [Boolean] + # Whether this is the original selector being extended, as opposed to + # the result of a previous extension that's being re-extended. + # @return [Array] A list of selectors generated + # by extending this selector with `extends`. + # These correspond to a {CommaSequence}'s {CommaSequence#members members array}. + # @see CommaSequence#do_extend + def do_extend(extends, parent_directives, replace, seen, original) + extended_not_expanded = members.map do |sseq_or_op| + next [[sseq_or_op]] unless sseq_or_op.is_a?(SimpleSequence) + extended = sseq_or_op.do_extend(extends, parent_directives, replace, seen) + + # The First Law of Extend says that the generated selector should have + # specificity greater than or equal to that of the original selector. + # In order to ensure that, we record the original selector's + # (`extended.first`) original specificity. + extended.first.add_sources!([self]) if original && !invisible? + + extended.map {|seq| seq.members} + end + weaves = Sass::Util.paths(extended_not_expanded).map {|path| weave(path)} + trim(weaves).map {|p| Sequence.new(p)} + end + + # Unifies this with another selector sequence to produce a selector + # that matches (a subset of) the intersection of the two inputs. + # + # @param other [Sequence] + # @return [CommaSequence, nil] The unified selector, or nil if unification failed. + # @raise [Sass::SyntaxError] If this selector cannot be unified. + # This will only ever occur when a dynamic selector, + # such as {Parent} or {Interpolation}, is used in unification. + # Since these selectors should be resolved + # by the time extension and unification happen, + # this exception will only ever be raised as a result of programmer error + def unify(other) + base = members.last + other_base = other.members.last + return unless base.is_a?(SimpleSequence) && other_base.is_a?(SimpleSequence) + return unless (unified = other_base.unify(base)) + + woven = weave([members[0...-1], other.members[0...-1] + [unified]]) + CommaSequence.new(woven.map {|w| Sequence.new(w)}) + end + + # Returns whether or not this selector matches all elements + # that the given selector matches (as well as possibly more). + # + # @example + # (.foo).superselector?(.foo.bar) #=> true + # (.foo).superselector?(.bar) #=> false + # @param cseq [Sequence] + # @return [Boolean] + def superselector?(seq) + _superselector?(members, seq.members) + end + + # @see AbstractSequence#to_s + def to_s(opts = {}) + @members.map {|m| m.is_a?(String) ? m : m.to_s(opts)}.join(" ").gsub(/ ?\n ?/, "\n") + end + + # Returns a string representation of the sequence. + # This is basically the selector string. + # + # @return [String] + def inspect + members.map {|m| m.inspect}.join(" ") + end + + # Add to the {SimpleSequence#sources} sets of the child simple sequences. + # This destructively modifies this sequence's members array, but not the + # child simple sequences. + # + # @param sources [Set] + def add_sources!(sources) + members.map! {|m| m.is_a?(SimpleSequence) ? m.with_more_sources(sources) : m} + end + + # Converts the subject operator "!", if it exists, into a ":has()" + # selector. + # + # @retur [Sequence] + def subjectless + pre_subject = [] + has = [] + subject = nil + members.each do |sseq_or_op| + if subject + has << sseq_or_op + elsif sseq_or_op.is_a?(String) || !sseq_or_op.subject? + pre_subject << sseq_or_op + else + subject = sseq_or_op.dup + subject.members = sseq_or_op.members.dup + subject.subject = false + has = [] + end + end + + return self unless subject + + unless has.empty? + subject.members << Pseudo.new(:class, 'has', nil, CommaSequence.new([Sequence.new(has)])) + end + Sequence.new(pre_subject + [subject]) + end + + private + + # Conceptually, this expands "parenthesized selectors". That is, if we + # have `.A .B {@extend .C}` and `.D .C {...}`, this conceptually expands + # into `.D .C, .D (.A .B)`, and this function translates `.D (.A .B)` into + # `.D .A .B, .A .D .B`. For thoroughness, `.A.D .B` would also be + # required, but including merged selectors results in exponential output + # for very little gain. + # + # @param path [Array>] + # A list of parenthesized selector groups. + # @return [Array>] A list of fully-expanded selectors. + def weave(path) + # This function works by moving through the selector path left-to-right, + # building all possible prefixes simultaneously. + prefixes = [[]] + + path.each do |current| + next if current.empty? + current = current.dup + last_current = [current.pop] + prefixes = prefixes.map do |prefix| + sub = subweave(prefix, current) + next [] unless sub + sub.map {|seqs| seqs + last_current} + end.flatten(1) + end + prefixes + end + + # This interweaves two lists of selectors, + # returning all possible orderings of them (including using unification) + # that maintain the relative ordering of the input arrays. + # + # For example, given `.foo .bar` and `.baz .bang`, + # this would return `.foo .bar .baz .bang`, `.foo .bar.baz .bang`, + # `.foo .baz .bar .bang`, `.foo .baz .bar.bang`, `.foo .baz .bang .bar`, + # and so on until `.baz .bang .foo .bar`. + # + # Semantically, for selectors A and B, this returns all selectors `AB_i` + # such that the union over all i of elements matched by `AB_i X` is + # identical to the intersection of all elements matched by `A X` and all + # elements matched by `B X`. Some `AB_i` are elided to reduce the size of + # the output. + # + # @param seq1 [Array] + # @param seq2 [Array] + # @return [Array>] + def subweave(seq1, seq2) + return [seq2] if seq1.empty? + return [seq1] if seq2.empty? + + seq1, seq2 = seq1.dup, seq2.dup + return unless (init = merge_initial_ops(seq1, seq2)) + return unless (fin = merge_final_ops(seq1, seq2)) + + # Make sure there's only one root selector in the output. + root1 = has_root?(seq1.first) && seq1.shift + root2 = has_root?(seq2.first) && seq2.shift + if root1 && root2 + return unless (root = root1.unify(root2)) + seq1.unshift root + seq2.unshift root + elsif root1 + seq2.unshift root1 + elsif root2 + seq1.unshift root2 + end + + seq1 = group_selectors(seq1) + seq2 = group_selectors(seq2) + lcs = Sass::Util.lcs(seq2, seq1) do |s1, s2| + next s1 if s1 == s2 + next unless s1.first.is_a?(SimpleSequence) && s2.first.is_a?(SimpleSequence) + next s2 if parent_superselector?(s1, s2) + next s1 if parent_superselector?(s2, s1) + next unless must_unify?(s1, s2) + next unless (unified = Sequence.new(s1).unify(Sequence.new(s2))) + unified.members.first.members if unified.members.length == 1 + end + + diff = [[init]] + + until lcs.empty? + diff << chunks(seq1, seq2) {|s| parent_superselector?(s.first, lcs.first)} << [lcs.shift] + seq1.shift + seq2.shift + end + diff << chunks(seq1, seq2) {|s| s.empty?} + diff += fin.map {|sel| sel.is_a?(Array) ? sel : [sel]} + diff.reject! {|c| c.empty?} + + Sass::Util.paths(diff).map {|p| p.flatten}.reject {|p| path_has_two_subjects?(p)} + end + + # Extracts initial selector combinators (`"+"`, `">"`, `"~"`, and `"\n"`) + # from two sequences and merges them together into a single array of + # selector combinators. + # + # @param seq1 [Array] + # @param seq2 [Array] + # @return [Array, nil] If there are no operators in the merged + # sequence, this will be the empty array. If the operators cannot be + # merged, this will be nil. + def merge_initial_ops(seq1, seq2) + ops1, ops2 = [], [] + ops1 << seq1.shift while seq1.first.is_a?(String) + ops2 << seq2.shift while seq2.first.is_a?(String) + + newline = false + newline ||= !!ops1.shift if ops1.first == "\n" + newline ||= !!ops2.shift if ops2.first == "\n" + + # If neither sequence is a subsequence of the other, they cannot be + # merged successfully + lcs = Sass::Util.lcs(ops1, ops2) + return unless lcs == ops1 || lcs == ops2 + (newline ? ["\n"] : []) + (ops1.size > ops2.size ? ops1 : ops2) + end + + # Extracts final selector combinators (`"+"`, `">"`, `"~"`) and the + # selectors to which they apply from two sequences and merges them + # together into a single array. + # + # @param seq1 [Array] + # @param seq2 [Array] + # @return [Array>] + # If there are no trailing combinators to be merged, this will be the + # empty array. If the trailing combinators cannot be merged, this will + # be nil. Otherwise, this will contained the merged selector. Array + # elements are [Sass::Util#paths]-style options; conceptually, an "or" + # of multiple selectors. + def merge_final_ops(seq1, seq2, res = []) + ops1, ops2 = [], [] + ops1 << seq1.pop while seq1.last.is_a?(String) + ops2 << seq2.pop while seq2.last.is_a?(String) + + # Not worth the headache of trying to preserve newlines here. The most + # important use of newlines is at the beginning of the selector to wrap + # across lines anyway. + ops1.reject! {|o| o == "\n"} + ops2.reject! {|o| o == "\n"} + + return res if ops1.empty? && ops2.empty? + if ops1.size > 1 || ops2.size > 1 + # If there are multiple operators, something hacky's going on. If one + # is a supersequence of the other, use that, otherwise give up. + lcs = Sass::Util.lcs(ops1, ops2) + return unless lcs == ops1 || lcs == ops2 + res.unshift(*(ops1.size > ops2.size ? ops1 : ops2).reverse) + return res + end + + # This code looks complicated, but it's actually just a bunch of special + # cases for interactions between different combinators. + op1, op2 = ops1.first, ops2.first + if op1 && op2 + sel1 = seq1.pop + sel2 = seq2.pop + if op1 == '~' && op2 == '~' + if sel1.superselector?(sel2) + res.unshift sel2, '~' + elsif sel2.superselector?(sel1) + res.unshift sel1, '~' + else + merged = sel1.unify(sel2) + res.unshift [ + [sel1, '~', sel2, '~'], + [sel2, '~', sel1, '~'], + ([merged, '~'] if merged) + ].compact + end + elsif (op1 == '~' && op2 == '+') || (op1 == '+' && op2 == '~') + if op1 == '~' + tilde_sel, plus_sel = sel1, sel2 + else + tilde_sel, plus_sel = sel2, sel1 + end + + if tilde_sel.superselector?(plus_sel) + res.unshift plus_sel, '+' + else + merged = plus_sel.unify(tilde_sel) + res.unshift [ + [tilde_sel, '~', plus_sel, '+'], + ([merged, '+'] if merged) + ].compact + end + elsif op1 == '>' && %w(~ +).include?(op2) + res.unshift sel2, op2 + seq1.push sel1, op1 + elsif op2 == '>' && %w(~ +).include?(op1) + res.unshift sel1, op1 + seq2.push sel2, op2 + elsif op1 == op2 + merged = sel1.unify(sel2) + return unless merged + res.unshift merged, op1 + else + # Unknown selector combinators can't be unified + return + end + return merge_final_ops(seq1, seq2, res) + elsif op1 + seq2.pop if op1 == '>' && seq2.last && seq2.last.superselector?(seq1.last) + res.unshift seq1.pop, op1 + return merge_final_ops(seq1, seq2, res) + else # op2 + seq1.pop if op2 == '>' && seq1.last && seq1.last.superselector?(seq2.last) + res.unshift seq2.pop, op2 + return merge_final_ops(seq1, seq2, res) + end + end + + # Takes initial subsequences of `seq1` and `seq2` and returns all + # orderings of those subsequences. The initial subsequences are determined + # by a block. + # + # Destructively removes the initial subsequences of `seq1` and `seq2`. + # + # For example, given `(A B C | D E)` and `(1 2 | 3 4 5)` (with `|` + # denoting the boundary of the initial subsequence), this would return + # `[(A B C 1 2), (1 2 A B C)]`. The sequences would then be `(D E)` and + # `(3 4 5)`. + # + # @param seq1 [Array] + # @param seq2 [Array] + # @yield [a] Used to determine when to cut off the initial subsequences. + # Called repeatedly for each sequence until it returns true. + # @yieldparam a [Array] A final subsequence of one input sequence after + # cutting off some initial subsequence. + # @yieldreturn [Boolean] Whether or not to cut off the initial subsequence + # here. + # @return [Array] All possible orderings of the initial subsequences. + def chunks(seq1, seq2) + chunk1 = [] + chunk1 << seq1.shift until yield seq1 + chunk2 = [] + chunk2 << seq2.shift until yield seq2 + return [] if chunk1.empty? && chunk2.empty? + return [chunk2] if chunk1.empty? + return [chunk1] if chunk2.empty? + [chunk1 + chunk2, chunk2 + chunk1] + end + + # Groups a sequence into subsequences. The subsequences are determined by + # strings; adjacent non-string elements will be put into separate groups, + # but any element adjacent to a string will be grouped with that string. + # + # For example, `(A B "C" D E "F" G "H" "I" J)` will become `[(A) (B "C" D) + # (E "F" G "H" "I" J)]`. + # + # @param seq [Array] + # @return [Array] + def group_selectors(seq) + newseq = [] + tail = seq.dup + until tail.empty? + head = [] + begin + head << tail.shift + end while !tail.empty? && head.last.is_a?(String) || tail.first.is_a?(String) + newseq << head + end + newseq + end + + # Given two selector sequences, returns whether `seq1` is a + # superselector of `seq2`; that is, whether `seq1` matches every + # element `seq2` matches. + # + # @param seq1 [Array] + # @param seq2 [Array] + # @return [Boolean] + def _superselector?(seq1, seq2) + seq1 = seq1.reject {|e| e == "\n"} + seq2 = seq2.reject {|e| e == "\n"} + # Selectors with leading or trailing operators are neither + # superselectors nor subselectors. + return if seq1.last.is_a?(String) || seq2.last.is_a?(String) || + seq1.first.is_a?(String) || seq2.first.is_a?(String) + # More complex selectors are never superselectors of less complex ones + return if seq1.size > seq2.size + return seq1.first.superselector?(seq2.last, seq2[0...-1]) if seq1.size == 1 + + _, si = seq2.each_with_index.find do |e, i| + return if i == seq2.size - 1 + next if e.is_a?(String) + seq1.first.superselector?(e, seq2[0...i]) + end + return unless si + + if seq1[1].is_a?(String) + return unless seq2[si + 1].is_a?(String) + + # .foo ~ .bar is a superselector of .foo + .bar + return unless seq1[1] == "~" ? seq2[si + 1] != ">" : seq1[1] == seq2[si + 1] + + # .foo > .baz is not a superselector of .foo > .bar > .baz or .foo > + # .bar .baz, despite the fact that .baz is a superselector of .bar > + # .baz and .bar .baz. Same goes for + and ~. + return if seq1.length == 3 && seq2.length > 3 + + return _superselector?(seq1[2..-1], seq2[si + 2..-1]) + elsif seq2[si + 1].is_a?(String) + return unless seq2[si + 1] == ">" + return _superselector?(seq1[1..-1], seq2[si + 2..-1]) + else + return _superselector?(seq1[1..-1], seq2[si + 1..-1]) + end + end + + # Like \{#_superselector?}, but compares the selectors in the + # context of parent selectors, as though they shared an implicit + # base simple selector. For example, `B` is not normally a + # superselector of `B A`, since it doesn't match `A` elements. + # However, it is a parent superselector, since `B X` is a + # superselector of `B A X`. + # + # @param seq1 [Array] + # @param seq2 [Array] + # @return [Boolean] + def parent_superselector?(seq1, seq2) + base = Sass::Selector::SimpleSequence.new([Sass::Selector::Placeholder.new('')], + false) + _superselector?(seq1 + [base], seq2 + [base]) + end + + # Returns whether two selectors must be unified to produce a valid + # combined selector. This is true when both selectors contain the same + # unique simple selector such as an id. + # + # @param seq1 [Array] + # @param seq2 [Array] + # @return [Boolean] + def must_unify?(seq1, seq2) + unique_selectors = seq1.map do |sseq| + next [] if sseq.is_a?(String) + sseq.members.select {|sel| sel.unique?} + end.flatten.to_set + + return false if unique_selectors.empty? + + seq2.any? do |sseq| + next false if sseq.is_a?(String) + sseq.members.any? do |sel| + next unless sel.unique? + unique_selectors.include?(sel) + end + end + end + + # Removes redundant selectors from between multiple lists of + # selectors. This takes a list of lists of selector sequences; + # each individual list is assumed to have no redundancy within + # itself. A selector is only removed if it's redundant with a + # selector in another list. + # + # "Redundant" here means that one selector is a superselector of + # the other. The more specific selector is removed. + # + # @param seqses [Array>>] + # @return [Array>] + def trim(seqses) + # Avoid truly horrific quadratic behavior. TODO: I think there + # may be a way to get perfect trimming without going quadratic. + return seqses.flatten(1) if seqses.size > 100 + + # Keep the results in a separate array so we can be sure we aren't + # comparing against an already-trimmed selector. This ensures that two + # identical selectors don't mutually trim one another. + result = seqses.dup + + # This is n^2 on the sequences, but only comparing between + # separate sequences should limit the quadratic behavior. + seqses.each_with_index do |seqs1, i| + result[i] = seqs1.reject do |seq1| + # The maximum specificity of the sources that caused [seq1] to be + # generated. In order for [seq1] to be removed, there must be + # another selector that's a superselector of it *and* that has + # specificity greater or equal to this. + max_spec = _sources(seq1).map do |seq| + spec = seq.specificity + spec.is_a?(Range) ? spec.max : spec + end.max || 0 + + result.any? do |seqs2| + next if seqs1.equal?(seqs2) + # Second Law of Extend: the specificity of a generated selector + # should never be less than the specificity of the extending + # selector. + # + # See https://github.com/nex3/sass/issues/324. + seqs2.any? do |seq2| + spec2 = _specificity(seq2) + spec2 = spec2.begin if spec2.is_a?(Range) + spec2 >= max_spec && _superselector?(seq2, seq1) + end + end + end + end + result.flatten(1) + end + + def _hash + members.reject {|m| m == "\n"}.hash + end + + def _eql?(other) + other.members.reject {|m| m == "\n"}.eql?(members.reject {|m| m == "\n"}) + end + + def path_has_two_subjects?(path) + subject = false + path.each do |sseq_or_op| + next unless sseq_or_op.is_a?(SimpleSequence) + next unless sseq_or_op.subject? + return true if subject + subject = true + end + false + end + + def _sources(seq) + s = Set.new + seq.map {|sseq_or_op| s.merge sseq_or_op.sources if sseq_or_op.is_a?(SimpleSequence)} + s + end + + def extended_not_expanded_to_s(extended_not_expanded) + extended_not_expanded.map do |choices| + choices = choices.map do |sel| + next sel.first.to_s if sel.size == 1 + "#{sel.join ' '}" + end + next choices.first if choices.size == 1 && !choices.include?(' ') + "(#{choices.join ', '})" + end.join ' ' + end + + def has_root?(sseq) + sseq.is_a?(SimpleSequence) && + sseq.members.any? {|sel| sel.is_a?(Pseudo) && sel.normalized_name == "root"} + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/selector/simple.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/selector/simple.rb new file mode 100644 index 00000000..dff4b8a3 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/selector/simple.rb @@ -0,0 +1,124 @@ +module Sass + module Selector + # The abstract superclass for simple selectors + # (that is, those that don't compose multiple selectors). + class Simple + # The line of the Sass template on which this selector was declared. + # + # @return [Integer] + attr_accessor :line + + # The name of the file in which this selector was declared, + # or `nil` if it was not declared in a file (e.g. on stdin). + # + # @return [String, nil] + attr_accessor :filename + + # Whether only one instance of this simple selector is allowed in a given + # complex selector. + # + # @return [Boolean] + def unique? + false + end + + # @see #to_s + # + # @return [String] + def inspect + to_s + end + + # Returns the selector string. + # + # @param opts [Hash] rendering options. + # @option opts [Symbol] :style The css rendering style. + # @return [String] + def to_s(opts = {}) + Sass::Util.abstract(self) + end + + # Returns a hash code for this selector object. + # + # By default, this is based on the value of \{#to\_a}, + # so if that contains information irrelevant to the identity of the selector, + # this should be overridden. + # + # @return [Integer] + def hash + @_hash ||= equality_key.hash + end + + # Checks equality between this and another object. + # + # By default, this is based on the value of \{#to\_a}, + # so if that contains information irrelevant to the identity of the selector, + # this should be overridden. + # + # @param other [Object] The object to test equality against + # @return [Boolean] Whether or not this is equal to `other` + def eql?(other) + other.class == self.class && other.hash == hash && other.equality_key == equality_key + end + alias_method :==, :eql? + + # Unifies this selector with a {SimpleSequence}'s {SimpleSequence#members members array}, + # returning another `SimpleSequence` members array + # that matches both this selector and the input selector. + # + # By default, this just appends this selector to the end of the array + # (or returns the original array if this selector already exists in it). + # + # @param sels [Array] A {SimpleSequence}'s {SimpleSequence#members members array} + # @return [Array, nil] A {SimpleSequence} {SimpleSequence#members members array} + # matching both `sels` and this selector, + # or `nil` if this is impossible (e.g. unifying `#foo` and `#bar`) + # @raise [Sass::SyntaxError] If this selector cannot be unified. + # This will only ever occur when a dynamic selector, + # such as {Parent} or {Interpolation}, is used in unification. + # Since these selectors should be resolved + # by the time extension and unification happen, + # this exception will only ever be raised as a result of programmer error + def unify(sels) + return sels.first.unify([self]) if sels.length == 1 && sels.first.is_a?(Universal) + return sels if sels.any? {|sel2| eql?(sel2)} + if !is_a?(Pseudo) || (sels.last.is_a?(Pseudo) && sels.last.type == :element) + _, i = sels.each_with_index.find {|sel, _| sel.is_a?(Pseudo)} + end + return sels + [self] unless i + sels[0...i] + [self] + sels[i..-1] + end + + protected + + # Returns the key used for testing whether selectors are equal. + # + # This is a cached version of \{#to\_s}. + # + # @return [String] + def equality_key + @equality_key ||= to_s + end + + # Unifies two namespaces, + # returning a namespace that works for both of them if possible. + # + # @param ns1 [String, nil] The first namespace. + # `nil` means none specified, e.g. `foo`. + # The empty string means no namespace specified, e.g. `|foo`. + # `"*"` means any namespace is allowed, e.g. `*|foo`. + # @param ns2 [String, nil] The second namespace. See `ns1`. + # @return [Array(String or nil, Boolean)] + # The first value is the unified namespace, or `nil` for no namespace. + # The second value is whether or not a namespace that works for both inputs + # could be found at all. + # If the second value is `false`, the first should be ignored. + def unify_namespaces(ns1, ns2) + return ns2, true if ns1 == '*' + return ns1, true if ns2 == '*' + return nil, false unless ns1 == ns2 + [ns1, true] + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/selector/simple_sequence.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/selector/simple_sequence.rb new file mode 100644 index 00000000..13a65d88 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/selector/simple_sequence.rb @@ -0,0 +1,348 @@ +module Sass + module Selector + # A unseparated sequence of selectors + # that all apply to a single element. + # For example, `.foo#bar[attr=baz]` is a simple sequence + # of the selectors `.foo`, `#bar`, and `[attr=baz]`. + class SimpleSequence < AbstractSequence + # The array of individual selectors. + # + # @return [Array] + attr_accessor :members + + # The extending selectors that caused this selector sequence to be + # generated. For example: + # + # a.foo { ... } + # b.bar {@extend a} + # c.baz {@extend b} + # + # The generated selector `b.foo.bar` has `{b.bar}` as its `sources` set, + # and the generated selector `c.foo.bar.baz` has `{b.bar, c.baz}` as its + # `sources` set. + # + # This is populated during the {Sequence#do_extend} process. + # + # @return {Set} + attr_accessor :sources + + # This sequence source range. + # + # @return [Sass::Source::Range] + attr_accessor :source_range + + # @see \{#subject?} + attr_writer :subject + + # Returns the element or universal selector in this sequence, + # if it exists. + # + # @return [Element, Universal, nil] + def base + @base ||= (members.first if members.first.is_a?(Element) || members.first.is_a?(Universal)) + end + + def pseudo_elements + @pseudo_elements ||= members.select {|sel| sel.is_a?(Pseudo) && sel.type == :element} + end + + def selector_pseudo_classes + @selector_pseudo_classes ||= members. + select {|sel| sel.is_a?(Pseudo) && sel.type == :class && sel.selector}. + group_by {|sel| sel.normalized_name} + end + + # Returns the non-base, non-pseudo-element selectors in this sequence. + # + # @return [Set] + def rest + @rest ||= Set.new(members - [base] - pseudo_elements) + end + + # Whether or not this compound selector is the subject of the parent + # selector; that is, whether it is prepended with `$` and represents the + # actual element that will be selected. + # + # @return [Boolean] + def subject? + @subject + end + + # @param selectors [Array] See \{#members} + # @param subject [Boolean] See \{#subject?} + # @param source_range [Sass::Source::Range] + def initialize(selectors, subject, source_range = nil) + @members = selectors + @subject = subject + @sources = Set.new + @source_range = source_range + end + + # Resolves the {Parent} selectors within this selector + # by replacing them with the given parent selector, + # handling commas appropriately. + # + # @param super_cseq [CommaSequence] The parent selector + # @return [CommaSequence] This selector, with parent references resolved + # @raise [Sass::SyntaxError] If a parent selector is invalid + def resolve_parent_refs(super_cseq) + resolved_members = @members.map do |sel| + next sel unless sel.is_a?(Pseudo) && sel.selector + sel.with_selector(sel.selector.resolve_parent_refs(super_cseq, false)) + end.flatten + + # Parent selector only appears as the first selector in the sequence + unless (parent = resolved_members.first).is_a?(Parent) + return CommaSequence.new([Sequence.new([SimpleSequence.new(resolved_members, subject?)])]) + end + + return super_cseq if @members.size == 1 && parent.suffix.nil? + + CommaSequence.new(super_cseq.members.map do |super_seq| + members = super_seq.members.dup + newline = members.pop if members.last == "\n" + unless members.last.is_a?(SimpleSequence) + raise Sass::SyntaxError.new("Invalid parent selector for \"#{self}\": \"" + + super_seq.to_s + '"') + end + + parent_sub = members.last.members + unless parent.suffix.nil? + parent_sub = parent_sub.dup + parent_sub[-1] = parent_sub.last.dup + case parent_sub.last + when Sass::Selector::Class, Sass::Selector::Id, Sass::Selector::Placeholder + parent_sub[-1] = parent_sub.last.class.new(parent_sub.last.name + parent.suffix) + when Sass::Selector::Element + parent_sub[-1] = parent_sub.last.class.new( + parent_sub.last.name + parent.suffix, + parent_sub.last.namespace) + when Sass::Selector::Pseudo + if parent_sub.last.arg || parent_sub.last.selector + raise Sass::SyntaxError.new("Invalid parent selector for \"#{self}\": \"" + + super_seq.to_s + '"') + end + parent_sub[-1] = Sass::Selector::Pseudo.new( + parent_sub.last.type, + parent_sub.last.name + parent.suffix, + nil, nil) + else + raise Sass::SyntaxError.new("Invalid parent selector for \"#{self}\": \"" + + super_seq.to_s + '"') + end + end + + Sequence.new(members[0...-1] + + [SimpleSequence.new(parent_sub + resolved_members[1..-1], subject?)] + + [newline].compact) + end) + end + + # Non-destructively extends this selector with the extensions specified in a hash + # (which should come from {Sass::Tree::Visitors::Cssize}). + # + # @param extends [{Selector::Simple => + # Sass::Tree::Visitors::Cssize::Extend}] + # The extensions to perform on this selector + # @param parent_directives [Array] + # The directives containing this selector. + # @param seen [Set>] + # The set of simple sequences that are currently being replaced. + # @param original [Boolean] + # Whether this is the original selector being extended, as opposed to + # the result of a previous extension that's being re-extended. + # @return [Array] A list of selectors generated + # by extending this selector with `extends`. + # @see CommaSequence#do_extend + def do_extend(extends, parent_directives, replace, seen) + seen_with_pseudo_selectors = seen.dup + + modified_original = false + members = self.members.map do |sel| + next sel unless sel.is_a?(Pseudo) && sel.selector + next sel if seen.include?([sel]) + extended = sel.selector.do_extend(extends, parent_directives, replace, seen, false) + next sel if extended == sel.selector + extended.members.reject! {|seq| seq.invisible?} + + # For `:not()`, we usually want to get rid of any complex + # selectors because that will cause the selector to fail to + # parse on all browsers at time of writing. We can keep them + # if either the original selector had a complex selector, or + # the result of extending has only complex selectors, + # because either way we aren't breaking anything that isn't + # already broken. + if sel.normalized_name == 'not' && + (sel.selector.members.none? {|seq| seq.members.length > 1} && + extended.members.any? {|seq| seq.members.length == 1}) + extended.members.reject! {|seq| seq.members.length > 1} + end + + modified_original = true + result = sel.with_selector(extended) + result.each {|new_sel| seen_with_pseudo_selectors << [new_sel]} + result + end.flatten + + groups = extends[members.to_set].group_by {|ex| ex.extender}.to_a + groups.map! do |seq, group| + sels = group.map {|e| e.target}.flatten + # If A {@extend B} and C {...}, + # seq is A, sels is B, and self is C + + self_without_sel = Sass::Util.array_minus(members, sels) + group.each {|e| e.success = true} + unified = seq.members.last.unify(SimpleSequence.new(self_without_sel, subject?)) + next unless unified + group.each {|e| check_directives_match!(e, parent_directives)} + new_seq = Sequence.new(seq.members[0...-1] + [unified]) + new_seq.add_sources!(sources + [seq]) + [sels, new_seq] + end + groups.compact! + groups.map! do |sels, seq| + next [] if seen.include?(sels) + seq.do_extend( + extends, parent_directives, false, seen_with_pseudo_selectors + [sels], false) + end + groups.flatten! + + if modified_original || !replace || groups.empty? + # First Law of Extend: the result of extending a selector should + # (almost) always contain the base selector. + # + # See https://github.com/nex3/sass/issues/324. + original = Sequence.new([SimpleSequence.new(members, @subject, source_range)]) + original.add_sources! sources + groups.unshift original + end + groups.uniq! + groups + end + + # Unifies this selector with another {SimpleSequence}, returning + # another `SimpleSequence` that is a subselector of both input + # selectors. + # + # @param other [SimpleSequence] + # @return [SimpleSequence, nil] A {SimpleSequence} matching both `sels` and this selector, + # or `nil` if this is impossible (e.g. unifying `#foo` and `#bar`) + # @raise [Sass::SyntaxError] If this selector cannot be unified. + # This will only ever occur when a dynamic selector, + # such as {Parent} or {Interpolation}, is used in unification. + # Since these selectors should be resolved + # by the time extension and unification happen, + # this exception will only ever be raised as a result of programmer error + def unify(other) + sseq = members.inject(other.members) do |member, sel| + return unless member + sel.unify(member) + end + return unless sseq + SimpleSequence.new(sseq, other.subject? || subject?) + end + + # Returns whether or not this selector matches all elements + # that the given selector matches (as well as possibly more). + # + # @example + # (.foo).superselector?(.foo.bar) #=> true + # (.foo).superselector?(.bar) #=> false + # @param their_sseq [SimpleSequence] + # @param parents [Array] The parent selectors of `their_sseq`, if any. + # @return [Boolean] + def superselector?(their_sseq, parents = []) + return false unless base.nil? || base.eql?(their_sseq.base) + return false unless pseudo_elements.eql?(their_sseq.pseudo_elements) + our_spcs = selector_pseudo_classes + their_spcs = their_sseq.selector_pseudo_classes + + # Some psuedo-selectors can be subselectors of non-pseudo selectors. + # Pull those out here so we can efficiently check against them below. + their_subselector_pseudos = %w(matches any nth-child nth-last-child). + map {|name| their_spcs[name] || []}.flatten + + # If `self`'s non-pseudo simple selectors aren't a subset of `their_sseq`'s, + # it's definitely not a superselector. This also considers being matched + # by `:matches` or `:any`. + return false unless rest.all? do |our_sel| + next true if our_sel.is_a?(Pseudo) && our_sel.selector + next true if their_sseq.rest.include?(our_sel) + their_subselector_pseudos.any? do |their_pseudo| + their_pseudo.selector.members.all? do |their_seq| + next false unless their_seq.members.length == 1 + their_sseq = their_seq.members.first + next false unless their_sseq.is_a?(SimpleSequence) + their_sseq.rest.include?(our_sel) + end + end + end + + our_spcs.all? do |_name, pseudos| + pseudos.all? {|pseudo| pseudo.superselector?(their_sseq, parents)} + end + end + + # @see Simple#to_s + def to_s(opts = {}) + res = @members.map {|m| m.to_s(opts)}.join + + # :not(%foo) may resolve to the empty string, but it should match every + # selector so we replace it with "*". + res = '*' if res.empty? + + res << '!' if subject? + res + end + + # Returns a string representation of the sequence. + # This is basically the selector string. + # + # @return [String] + def inspect + res = members.map {|m| m.inspect}.join + res << '!' if subject? + res + end + + # Return a copy of this simple sequence with `sources` merged into the + # {SimpleSequence#sources} set. + # + # @param sources [Set] + # @return [SimpleSequence] + def with_more_sources(sources) + sseq = dup + sseq.members = members.dup + sseq.sources = self.sources | sources + sseq + end + + private + + def check_directives_match!(extend, parent_directives) + dirs1 = extend.directives.map {|d| d.resolved_value} + dirs2 = parent_directives.map {|d| d.resolved_value} + return if Sass::Util.subsequence?(dirs1, dirs2) + line = extend.node.line + filename = extend.node.filename + + # TODO(nweiz): this should use the Sass stack trace of the extend node, + # not the selector. + raise Sass::SyntaxError.new(< #{output.inspect}" + end + end + + # The mapping data ordered by the location in the target. + # + # @return [Array] + attr_reader :data + + def initialize + @data = [] + end + + # Adds a new mapping from one source range to another. Multiple invocations + # of this method should have each `output` range come after all previous ranges. + # + # @param input [Sass::Source::Range] + # The source range in the input document. + # @param output [Sass::Source::Range] + # The source range in the output document. + def add(input, output) + @data.push(Mapping.new(input, output)) + end + + # Shifts all output source ranges forward one or more lines. + # + # @param delta [Integer] The number of lines to shift the ranges forward. + def shift_output_lines(delta) + return if delta == 0 + @data.each do |m| + m.output.start_pos.line += delta + m.output.end_pos.line += delta + end + end + + # Shifts any output source ranges that lie on the first line forward one or + # more characters on that line. + # + # @param delta [Integer] The number of characters to shift the ranges + # forward. + def shift_output_offsets(delta) + return if delta == 0 + @data.each do |m| + break if m.output.start_pos.line > 1 + m.output.start_pos.offset += delta + m.output.end_pos.offset += delta if m.output.end_pos.line > 1 + end + end + + # Returns the standard JSON representation of the source map. + # + # If the `:css_uri` option isn't specified, the `:css_path` and + # `:sourcemap_path` options must both be specified. Any options may also be + # specified alongside the `:css_uri` option. If `:css_uri` isn't specified, + # it will be inferred from `:css_path` and `:sourcemap_path` using the + # assumption that the local file system has the same layout as the server. + # + # Regardless of which options are passed to this method, source stylesheets + # that are imported using a non-default importer will only be linked to in + # the source map if their importers implement + # \{Sass::Importers::Base#public\_url\}. + # + # @option options :css_uri [String] + # The publicly-visible URI of the CSS output file. + # @option options :css_path [String] + # The local path of the CSS output file. + # @option options :sourcemap_path [String] + # The (eventual) local path of the sourcemap file. + # @option options :type [Symbol] + # `:auto` (default), `:file`, or `:inline`. + # @return [String] The JSON string. + # @raise [ArgumentError] If neither `:css_uri` nor `:css_path` and + # `:sourcemap_path` are specified. + def to_json(options) + css_uri, css_path, sourcemap_path = + options[:css_uri], options[:css_path], options[:sourcemap_path] + unless css_uri || (css_path && sourcemap_path) + raise ArgumentError.new("Sass::Source::Map#to_json requires either " \ + "the :css_uri option or both the :css_path and :soucemap_path options.") + end + css_path &&= Sass::Util.pathname(File.absolute_path(css_path)) + sourcemap_path &&= Sass::Util.pathname(File.absolute_path(sourcemap_path)) + css_uri ||= Sass::Util.file_uri_from_path( + Sass::Util.relative_path_from(css_path, sourcemap_path.dirname)) + + result = "{\n" + write_json_field(result, "version", 3, true) + + source_uri_to_id = {} + id_to_source_uri = {} + id_to_contents = {} if options[:type] == :inline + next_source_id = 0 + line_data = [] + segment_data_for_line = [] + + # These track data necessary for the delta coding. + previous_target_line = nil + previous_target_offset = 1 + previous_source_line = 1 + previous_source_offset = 1 + previous_source_id = 0 + + @data.each do |m| + file, importer = m.input.file, m.input.importer + + next unless importer + + if options[:type] == :inline + source_uri = file + else + sourcemap_dir = sourcemap_path && sourcemap_path.dirname.to_s + sourcemap_dir = nil if options[:type] == :file + source_uri = importer.public_url(file, sourcemap_dir) + next unless source_uri + end + + current_source_id = source_uri_to_id[source_uri] + unless current_source_id + current_source_id = next_source_id + next_source_id += 1 + + source_uri_to_id[source_uri] = current_source_id + id_to_source_uri[current_source_id] = source_uri + + if options[:type] == :inline + id_to_contents[current_source_id] = + importer.find(file, {}).instance_variable_get('@template') + end + end + + [ + [m.input.start_pos, m.output.start_pos], + [m.input.end_pos, m.output.end_pos] + ].each do |source_pos, target_pos| + if previous_target_line != target_pos.line + line_data.push(segment_data_for_line.join(",")) unless segment_data_for_line.empty? + (target_pos.line - 1 - (previous_target_line || 0)).times {line_data.push("")} + previous_target_line = target_pos.line + previous_target_offset = 1 + segment_data_for_line = [] + end + + # `segment` is a data chunk for a single position mapping. + segment = "" + + # Field 1: zero-based starting offset. + segment << Sass::Util.encode_vlq(target_pos.offset - previous_target_offset) + previous_target_offset = target_pos.offset + + # Field 2: zero-based index into the "sources" list. + segment << Sass::Util.encode_vlq(current_source_id - previous_source_id) + previous_source_id = current_source_id + + # Field 3: zero-based starting line in the original source. + segment << Sass::Util.encode_vlq(source_pos.line - previous_source_line) + previous_source_line = source_pos.line + + # Field 4: zero-based starting offset in the original source. + segment << Sass::Util.encode_vlq(source_pos.offset - previous_source_offset) + previous_source_offset = source_pos.offset + + segment_data_for_line.push(segment) + + previous_target_line = target_pos.line + end + end + line_data.push(segment_data_for_line.join(",")) + write_json_field(result, "mappings", line_data.join(";")) + + source_names = [] + (0...next_source_id).each {|id| source_names.push(id_to_source_uri[id].to_s)} + write_json_field(result, "sources", source_names) + + if options[:type] == :inline + write_json_field(result, "sourcesContent", + (0...next_source_id).map {|id| id_to_contents[id]}) + end + + write_json_field(result, "names", []) + write_json_field(result, "file", css_uri) + + result << "\n}" + result + end + + private + + def write_json_field(out, name, value, is_first = false) + out << (is_first ? "" : ",\n") << + "\"" << + Sass::Util.json_escape_string(name) << + "\": " << + Sass::Util.json_value_of(value) + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/source/position.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/source/position.rb new file mode 100644 index 00000000..a62af452 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/source/position.rb @@ -0,0 +1,39 @@ +module Sass::Source + class Position + # The one-based line of the document associated with the position. + # + # @return [Integer] + attr_accessor :line + + # The one-based offset in the line of the document associated with the + # position. + # + # @return [Integer] + attr_accessor :offset + + # @param line [Integer] The source line + # @param offset [Integer] The source offset + def initialize(line, offset) + @line = line + @offset = offset + end + + # @return [String] A string representation of the source position. + def inspect + "#{line.inspect}:#{offset.inspect}" + end + + # @param str [String] The string to move through. + # @return [Position] The source position after proceeding forward through + # `str`. + def after(str) + newlines = str.count("\n") + Position.new(line + newlines, + if newlines == 0 + offset + str.length + else + str.length - str.rindex("\n") - 1 + end) + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/source/range.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/source/range.rb new file mode 100644 index 00000000..de687f97 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/source/range.rb @@ -0,0 +1,41 @@ +module Sass::Source + class Range + # The starting position of the range in the document (inclusive). + # + # @return [Sass::Source::Position] + attr_accessor :start_pos + + # The ending position of the range in the document (exclusive). + # + # @return [Sass::Source::Position] + attr_accessor :end_pos + + # The file in which this source range appears. This can be nil if the file + # is unknown or not yet generated. + # + # @return [String] + attr_accessor :file + + # The importer that imported the file in which this source range appears. + # This is nil for target ranges. + # + # @return [Sass::Importers::Base] + attr_accessor :importer + + # @param start_pos [Sass::Source::Position] See \{#start_pos} + # @param end_pos [Sass::Source::Position] See \{#end_pos} + # @param file [String] See \{#file} + # @param importer [Sass::Importers::Base] See \{#importer} + def initialize(start_pos, end_pos, file, importer = nil) + @start_pos = start_pos + @end_pos = end_pos + @file = file + @importer = importer + end + + # @return [String] A string representation of the source range. + def inspect + "(#{start_pos.inspect} to #{end_pos.inspect}#{" in #{@file}" if @file})" + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/stack.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/stack.rb new file mode 100644 index 00000000..fb57a978 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/stack.rb @@ -0,0 +1,140 @@ +module Sass + # A class representing the stack when compiling a Sass file. + class Stack + # TODO: use this to generate stack information for Sass::SyntaxErrors. + + # A single stack frame. + class Frame + # The filename of the file in which this stack frame was created. + # + # @return [String] + attr_reader :filename + + # The line number on which this stack frame was created. + # + # @return [String] + attr_reader :line + + # The type of this stack frame. This can be `:import`, `:mixin`, or + # `:base`. + # + # `:base` indicates that this is the bottom-most frame, meaning that it + # represents a single line of code rather than a nested context. The stack + # will only ever have one base frame, and it will always be the most + # deeply-nested frame. + # + # @return [Symbol?] + attr_reader :type + + # The name of the stack frame. For mixin frames, this is the mixin name; + # otherwise, it's `nil`. + # + # @return [String?] + attr_reader :name + + def initialize(filename, line, type, name = nil) + @filename = filename + @line = line + @type = type + @name = name + end + + # Whether this frame represents an import. + # + # @return [Boolean] + def is_import? + type == :import + end + + # Whether this frame represents a mixin. + # + # @return [Boolean] + def is_mixin? + type == :mixin + end + + # Whether this is the base frame. + # + # @return [Boolean] + def is_base? + type == :base + end + end + + # The stack frames. The last frame is the most deeply-nested. + # + # @return [Array] + attr_reader :frames + + def initialize + @frames = [] + end + + # Pushes a base frame onto the stack. + # + # @param filename [String] See \{Frame#filename}. + # @param line [String] See \{Frame#line}. + # @yield [] A block in which the new frame is on the stack. + def with_base(filename, line) + with_frame(filename, line, :base) {yield} + end + + # Pushes an import frame onto the stack. + # + # @param filename [String] See \{Frame#filename}. + # @param line [String] See \{Frame#line}. + # @yield [] A block in which the new frame is on the stack. + def with_import(filename, line) + with_frame(filename, line, :import) {yield} + end + + # Pushes a mixin frame onto the stack. + # + # @param filename [String] See \{Frame#filename}. + # @param line [String] See \{Frame#line}. + # @param name [String] See \{Frame#name}. + # @yield [] A block in which the new frame is on the stack. + def with_mixin(filename, line, name) + with_frame(filename, line, :mixin, name) {yield} + end + + # Pushes a function frame onto the stack. + # + # @param filename [String] See \{Frame#filename}. + # @param line [String] See \{Frame#line}. + # @param name [String] See \{Frame#name}. + # @yield [] A block in which the new frame is on the stack. + def with_function(filename, line, name) + with_frame(filename, line, :function, name) {yield} + end + + # Pushes a function frame onto the stack. + # + # @param filename [String] See \{Frame#filename}. + # @param line [String] See \{Frame#line}. + # @param name [String] See \{Frame#name}. + # @yield [] A block in which the new frame is on the stack. + def with_directive(filename, line, name) + with_frame(filename, line, :directive, name) {yield} + end + + def to_s + (frames.reverse + [nil]).each_cons(2).each_with_index. + map do |(frame, caller), i| + "#{i == 0 ? 'on' : 'from'} line #{frame.line}" + + " of #{frame.filename || 'an unknown file'}" + + (caller && caller.name ? ", in `#{caller.name}'" : "") + end.join("\n") + end + + private + + def with_frame(filename, line, type, name = nil) + @frames.pop if @frames.last && @frames.last.type == :base + @frames.push(Frame.new(filename, line, type, name)) + yield + ensure + @frames.pop unless type == :base && @frames.last && @frames.last.type != :base + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/supports.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/supports.rb new file mode 100644 index 00000000..6869c4d6 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/supports.rb @@ -0,0 +1,225 @@ +# A namespace for the `@supports` condition parse tree. +module Sass::Supports + # The abstract superclass of all Supports conditions. + class Condition + # Runs the SassScript in the supports condition. + # + # @param environment [Sass::Environment] The environment in which to run the script. + def perform(environment); Sass::Util.abstract(self); end + + # Returns the CSS for this condition. + # + # @return [String] + def to_css; Sass::Util.abstract(self); end + + # Returns the Sass/CSS code for this condition. + # + # @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}). + # @return [String] + def to_src(options); Sass::Util.abstract(self); end + + # Returns a deep copy of this condition and all its children. + # + # @return [Condition] + def deep_copy; Sass::Util.abstract(self); end + + # Sets the options hash for the script nodes in the supports condition. + # + # @param options [{Symbol => Object}] The options has to set. + def options=(options); Sass::Util.abstract(self); end + end + + # An operator condition (e.g. `CONDITION1 and CONDITION2`). + class Operator < Condition + # The left-hand condition. + # + # @return [Sass::Supports::Condition] + attr_accessor :left + + # The right-hand condition. + # + # @return [Sass::Supports::Condition] + attr_accessor :right + + # The operator ("and" or "or"). + # + # @return [String] + attr_accessor :op + + def initialize(left, right, op) + @left = left + @right = right + @op = op + end + + def perform(env) + @left.perform(env) + @right.perform(env) + end + + def to_css + "#{parens @left, @left.to_css} #{op} #{parens @right, @right.to_css}" + end + + def to_src(options) + "#{parens @left, @left.to_src(options)} #{op} #{parens @right, @right.to_src(options)}" + end + + def deep_copy + copy = dup + copy.left = @left.deep_copy + copy.right = @right.deep_copy + copy + end + + def options=(options) + @left.options = options + @right.options = options + end + + private + + def parens(condition, str) + if condition.is_a?(Negation) || (condition.is_a?(Operator) && condition.op != op) + return "(#{str})" + else + return str + end + end + end + + # A negation condition (`not CONDITION`). + class Negation < Condition + # The condition being negated. + # + # @return [Sass::Supports::Condition] + attr_accessor :condition + + def initialize(condition) + @condition = condition + end + + def perform(env) + @condition.perform(env) + end + + def to_css + "not #{parens @condition.to_css}" + end + + def to_src(options) + "not #{parens @condition.to_src(options)}" + end + + def deep_copy + copy = dup + copy.condition = condition.deep_copy + copy + end + + def options=(options) + condition.options = options + end + + private + + def parens(str) + return "(#{str})" if @condition.is_a?(Negation) || @condition.is_a?(Operator) + str + end + end + + # A declaration condition (e.g. `(feature: value)`). + class Declaration < Condition + # @return [Sass::Script::Tree::Node] The feature name. + attr_accessor :name + + # @!attribute resolved_name + # The name of the feature after any SassScript has been resolved. + # Only set once \{Tree::Visitors::Perform} has been run. + # + # @return [String] + attr_accessor :resolved_name + + # The feature value. + # + # @return [Sass::Script::Tree::Node] + attr_accessor :value + + # The value of the feature after any SassScript has been resolved. + # Only set once \{Tree::Visitors::Perform} has been run. + # + # @return [String] + attr_accessor :resolved_value + + def initialize(name, value) + @name = name + @value = value + end + + def perform(env) + @resolved_name = name.perform(env) + @resolved_value = value.perform(env) + end + + def to_css + "(#{@resolved_name}: #{@resolved_value})" + end + + def to_src(options) + "(#{@name.to_sass(options)}: #{@value.to_sass(options)})" + end + + def deep_copy + copy = dup + copy.name = @name.deep_copy + copy.value = @value.deep_copy + copy + end + + def options=(options) + @name.options = options + @value.options = options + end + end + + # An interpolation condition (e.g. `#{$var}`). + class Interpolation < Condition + # The SassScript expression in the interpolation. + # + # @return [Sass::Script::Tree::Node] + attr_accessor :value + + # The value of the expression after it's been resolved. + # Only set once \{Tree::Visitors::Perform} has been run. + # + # @return [String] + attr_accessor :resolved_value + + def initialize(value) + @value = value + end + + def perform(env) + @resolved_value = value.perform(env).to_s(:quote => :none) + end + + def to_css + @resolved_value + end + + def to_src(options) + @value.to_sass(options) + end + + def deep_copy + copy = dup + copy.value = @value.deep_copy + copy + end + + def options=(options) + @value.options = options + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/at_root_node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/at_root_node.rb new file mode 100644 index 00000000..e44d7aaa --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/at_root_node.rb @@ -0,0 +1,83 @@ +module Sass + module Tree + # A dynamic node representing an `@at-root` directive. + # + # An `@at-root` directive with a selector is converted to an \{AtRootNode} + # containing a \{RuleNode} at parse time. + # + # @see Sass::Tree + class AtRootNode < Node + # The query for this node (e.g. `(without: media)`), + # interspersed with {Sass::Script::Tree::Node}s representing + # `#{}`-interpolation. Any adjacent strings will be merged + # together. + # + # This will be nil if the directive didn't have a query. In this + # case, {#resolved\_type} will automatically be set to + # `:without` and {#resolved\_rule} will automatically be set to `["rule"]`. + # + # @return [Array] + attr_accessor :query + + # The resolved type of this directive. `:with` or `:without`. + # + # @return [Symbol] + attr_accessor :resolved_type + + # The resolved value of this directive -- a list of directives + # to either include or exclude. + # + # @return [Array] + attr_accessor :resolved_value + + # The number of additional tabs that the contents of this node + # should be indented. + # + # @return [Number] + attr_accessor :tabs + + # Whether the last child of this node should be considered the + # end of a group. + # + # @return [Boolean] + attr_accessor :group_end + + def initialize(query = nil) + super() + @query = Sass::Util.strip_string_array(Sass::Util.merge_adjacent_strings(query)) if query + @tabs = 0 + end + + # Returns whether or not the given directive is excluded by this + # node. `directive` may be "rule", which indicates whether + # normal CSS rules should be excluded. + # + # @param directive [String] + # @return [Boolean] + def exclude?(directive) + if resolved_type == :with + return false if resolved_value.include?('all') + !resolved_value.include?(directive) + else # resolved_type == :without + return true if resolved_value.include?('all') + resolved_value.include?(directive) + end + end + + # Returns whether the given node is excluded by this node. + # + # @param node [Sass::Tree::Node] + # @return [Boolean] + def exclude_node?(node) + return exclude?(node.name.gsub(/^@/, '')) if node.is_a?(Sass::Tree::DirectiveNode) + return exclude?('keyframes') if node.is_a?(Sass::Tree::KeyframeRuleNode) + exclude?('rule') && node.is_a?(Sass::Tree::RuleNode) + end + + # @see Node#bubbles? + def bubbles? + true + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/charset_node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/charset_node.rb new file mode 100644 index 00000000..8204d885 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/charset_node.rb @@ -0,0 +1,22 @@ +module Sass::Tree + # A static node representing an unprocessed Sass `@charset` directive. + # + # @see Sass::Tree + class CharsetNode < Node + # The name of the charset. + # + # @return [String] + attr_accessor :name + + # @param name [String] see \{#name} + def initialize(name) + @name = name + super() + end + + # @see Node#invisible? + def invisible? + true + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/comment_node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/comment_node.rb new file mode 100644 index 00000000..1b73a159 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/comment_node.rb @@ -0,0 +1,82 @@ +require 'sass/tree/node' + +module Sass::Tree + # A static node representing a Sass comment (silent or loud). + # + # @see Sass::Tree + class CommentNode < Node + # The text of the comment, not including `/*` and `*/`. + # Interspersed with {Sass::Script::Tree::Node}s representing `#{}`-interpolation + # if this is a loud comment. + # + # @return [Array] + attr_accessor :value + + # The text of the comment + # after any interpolated SassScript has been resolved. + # Only set once \{Tree::Visitors::Perform} has been run. + # + # @return [String] + attr_accessor :resolved_value + + # The type of the comment. `:silent` means it's never output to CSS, + # `:normal` means it's output in every compile mode except `:compressed`, + # and `:loud` means it's output even in `:compressed`. + # + # @return [Symbol] + attr_accessor :type + + # @param value [Array] See \{#value} + # @param type [Symbol] See \{#type} + def initialize(value, type) + @value = Sass::Util.with_extracted_values(value) {|str| normalize_indentation str} + @type = type + super() + end + + # Compares the contents of two comments. + # + # @param other [Object] The object to compare with + # @return [Boolean] Whether or not this node and the other object + # are the same + def ==(other) + self.class == other.class && value == other.value && type == other.type + end + + # Returns `true` if this is a silent comment + # or the current style doesn't render comments. + # + # Comments starting with ! are never invisible (and the ! is removed from the output.) + # + # @return [Boolean] + def invisible? + case @type + when :loud; false + when :silent; true + else; style == :compressed + end + end + + # Returns the number of lines in the comment. + # + # @return [Integer] + def lines + @value.inject(0) do |s, e| + next s + e.count("\n") if e.is_a?(String) + next s + end + end + + private + + def normalize_indentation(str) + ind = str.split("\n").inject(str[/^[ \t]*/].split("")) do |pre, line| + line[/^[ \t]*/].split("").zip(pre).inject([]) do |arr, (a, b)| + break arr if a != b + arr << a + end + end.join + str.gsub(/^#{ind}/, '') + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/content_node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/content_node.rb new file mode 100644 index 00000000..3f6528f8 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/content_node.rb @@ -0,0 +1,9 @@ +module Sass + module Tree + # A node representing the placement within a mixin of the include statement's content. + # + # @see Sass::Tree + class ContentNode < Node + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/css_import_node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/css_import_node.rb new file mode 100644 index 00000000..d9a59269 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/css_import_node.rb @@ -0,0 +1,68 @@ +module Sass::Tree + # A node representing an `@import` rule that's importing plain CSS. + # + # @see Sass::Tree + class CssImportNode < DirectiveNode + # The URI being imported, either as a plain string or an interpolated + # script string. + # + # @return [String, Sass::Script::Tree::Node] + attr_accessor :uri + + # The text of the URI being imported after any interpolated SassScript has + # been resolved. Only set once {Tree::Visitors::Perform} has been run. + # + # @return [String] + attr_accessor :resolved_uri + + # The supports condition for this import. + # + # @return [Sass::Supports::Condition] + attr_accessor :supports_condition + + # The media query for this rule, interspersed with + # {Sass::Script::Tree::Node}s representing `#{}`-interpolation. Any adjacent + # strings will be merged together. + # + # @return [Array] + attr_accessor :query + + # The media query for this rule, without any unresolved interpolation. + # It's only set once {Tree::Visitors::Perform} has been run. + # + # @return [Sass::Media::QueryList] + attr_accessor :resolved_query + + # @param uri [String, Sass::Script::Tree::Node] See \{#uri} + # @param query [Array] See \{#query} + # @param supports_condition [Sass::Supports::Condition] See \{#supports_condition} + def initialize(uri, query = [], supports_condition = nil) + @uri = uri + @query = query + @supports_condition = supports_condition + super('') + end + + # @param uri [String] See \{#resolved_uri} + # @return [CssImportNode] + def self.resolved(uri) + node = new(uri) + node.resolved_uri = uri + node + end + + # @see DirectiveNode#value + def value; raise NotImplementedError; end + + # @see DirectiveNode#resolved_value + def resolved_value + @resolved_value ||= + begin + str = "@import #{resolved_uri}" + str << " supports(#{supports_condition.to_css})" if supports_condition + str << " #{resolved_query.to_css}" if resolved_query + str + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/debug_node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/debug_node.rb new file mode 100644 index 00000000..5cc28422 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/debug_node.rb @@ -0,0 +1,18 @@ +module Sass + module Tree + # A dynamic node representing a Sass `@debug` statement. + # + # @see Sass::Tree + class DebugNode < Node + # The expression to print. + # @return [Script::Tree::Node] + attr_accessor :expr + + # @param expr [Script::Tree::Node] The expression to print + def initialize(expr) + @expr = expr + super() + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/directive_node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/directive_node.rb new file mode 100644 index 00000000..315bb701 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/directive_node.rb @@ -0,0 +1,59 @@ +module Sass::Tree + # A static node representing an unprocessed Sass `@`-directive. + # Directives known to Sass, like `@for` and `@debug`, + # are handled by their own nodes; + # only CSS directives like `@media` and `@font-face` become {DirectiveNode}s. + # + # `@import` and `@charset` are special cases; + # they become {ImportNode}s and {CharsetNode}s, respectively. + # + # @see Sass::Tree + class DirectiveNode < Node + # The text of the directive, `@` and all, with interpolation included. + # + # @return [Array] + attr_accessor :value + + # The text of the directive after any interpolated SassScript has been resolved. + # Only set once \{Tree::Visitors::Perform} has been run. + # + # @return [String] + attr_accessor :resolved_value + + # @see RuleNode#tabs + attr_accessor :tabs + + # @see RuleNode#group_end + attr_accessor :group_end + + # @param value [Array] See \{#value} + def initialize(value) + @value = value + @tabs = 0 + super() + end + + # @param value [String] See \{#resolved_value} + # @return [DirectiveNode] + def self.resolved(value) + node = new([value]) + node.resolved_value = value + node + end + + # @return [String] The name of the directive, including `@`. + def name + @name ||= value.first.gsub(/ .*$/, '') + end + + # Strips out any vendor prefixes and downcases the directive name. + # @return [String] The normalized name of the directive. + def normalized_name + @normalized_name ||= name.gsub(/^(@)(?:-[a-zA-Z0-9]+-)?/, '\1').downcase + end + + def bubbles? + has_children + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/each_node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/each_node.rb new file mode 100644 index 00000000..586cfa7f --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/each_node.rb @@ -0,0 +1,24 @@ +require 'sass/tree/node' + +module Sass::Tree + # A dynamic node representing a Sass `@each` loop. + # + # @see Sass::Tree + class EachNode < Node + # The names of the loop variables. + # @return [Array] + attr_reader :vars + + # The parse tree for the list. + # @return [Script::Tree::Node] + attr_accessor :list + + # @param vars [Array] The names of the loop variables + # @param list [Script::Tree::Node] The parse tree for the list + def initialize(vars, list) + @vars = vars + @list = list + super() + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/error_node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/error_node.rb new file mode 100644 index 00000000..203fd621 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/error_node.rb @@ -0,0 +1,18 @@ +module Sass + module Tree + # A dynamic node representing a Sass `@error` statement. + # + # @see Sass::Tree + class ErrorNode < Node + # The expression to print. + # @return [Script::Tree::Node] + attr_accessor :expr + + # @param expr [Script::Tree::Node] The expression to print + def initialize(expr) + @expr = expr + super() + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/extend_node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/extend_node.rb new file mode 100644 index 00000000..817c20ce --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/extend_node.rb @@ -0,0 +1,43 @@ +require 'sass/tree/node' + +module Sass::Tree + # A static node representing an `@extend` directive. + # + # @see Sass::Tree + class ExtendNode < Node + # The parsed selector after interpolation has been resolved. + # Only set once {Tree::Visitors::Perform} has been run. + # + # @return [Selector::CommaSequence] + attr_accessor :resolved_selector + + # The CSS selector to extend, interspersed with {Sass::Script::Tree::Node}s + # representing `#{}`-interpolation. + # + # @return [Array] + attr_accessor :selector + + # The extended selector source range. + # + # @return [Sass::Source::Range] + attr_accessor :selector_source_range + + # Whether the `@extend` is allowed to match no selectors or not. + # + # @return [Boolean] + def optional?; @optional; end + + # @param selector [Array] + # The CSS selector to extend, + # interspersed with {Sass::Script::Tree::Node}s + # representing `#{}`-interpolation. + # @param optional [Boolean] See \{ExtendNode#optional?} + # @param selector_source_range [Sass::Source::Range] The extended selector source range. + def initialize(selector, optional, selector_source_range) + @selector = selector + @optional = optional + @selector_source_range = selector_source_range + super() + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/for_node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/for_node.rb new file mode 100644 index 00000000..da3f655a --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/for_node.rb @@ -0,0 +1,36 @@ +require 'sass/tree/node' + +module Sass::Tree + # A dynamic node representing a Sass `@for` loop. + # + # @see Sass::Tree + class ForNode < Node + # The name of the loop variable. + # @return [String] + attr_reader :var + + # The parse tree for the initial expression. + # @return [Script::Tree::Node] + attr_accessor :from + + # The parse tree for the final expression. + # @return [Script::Tree::Node] + attr_accessor :to + + # Whether to include `to` in the loop or stop just before. + # @return [Boolean] + attr_reader :exclusive + + # @param var [String] See \{#var} + # @param from [Script::Tree::Node] See \{#from} + # @param to [Script::Tree::Node] See \{#to} + # @param exclusive [Boolean] See \{#exclusive} + def initialize(var, from, to, exclusive) + @var = var + @from = from + @to = to + @exclusive = exclusive + super() + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/function_node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/function_node.rb new file mode 100644 index 00000000..c2ca18ab --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/function_node.rb @@ -0,0 +1,44 @@ +module Sass + module Tree + # A dynamic node representing a function definition. + # + # @see Sass::Tree + class FunctionNode < Node + # The name of the function. + # @return [String] + attr_reader :name + + # The arguments to the function. Each element is a tuple + # containing the variable for argument and the parse tree for + # the default value of the argument + # + # @return [Array] + attr_accessor :args + + # The splat argument for this function, if one exists. + # + # @return [Script::Tree::Node?] + attr_accessor :splat + + # Strips out any vendor prefixes. + # @return [String] The normalized name of the directive. + def normalized_name + @normalized_name ||= name.gsub(/^(?:-[a-zA-Z0-9]+-)?/, '\1') + end + + # @param name [String] The function name + # @param args [Array<(Script::Tree::Node, Script::Tree::Node)>] + # The arguments for the function. + # @param splat [Script::Tree::Node] See \{#splat} + def initialize(name, args, splat) + @name = name + @args = args + @splat = splat + super() + + return unless %w(and or not).include?(name) + raise Sass::SyntaxError.new("Invalid function name \"#{name}\".") + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/if_node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/if_node.rb new file mode 100644 index 00000000..ebfec7c3 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/if_node.rb @@ -0,0 +1,52 @@ +require 'sass/tree/node' + +module Sass::Tree + # A dynamic node representing a Sass `@if` statement. + # + # {IfNode}s are a little odd, in that they also represent `@else` and `@else if`s. + # This is done as a linked list: + # each {IfNode} has a link (\{#else}) to the next {IfNode}. + # + # @see Sass::Tree + class IfNode < Node + # The conditional expression. + # If this is nil, this is an `@else` node, not an `@else if`. + # + # @return [Script::Expr] + attr_accessor :expr + + # The next {IfNode} in the if-else list, or `nil`. + # + # @return [IfNode] + attr_accessor :else + + # @param expr [Script::Expr] See \{#expr} + def initialize(expr) + @expr = expr + @last_else = self + super() + end + + # Append an `@else` node to the end of the list. + # + # @param node [IfNode] The `@else` node to append + def add_else(node) + @last_else.else = node + @last_else = node + end + + def _dump(f) + Marshal.dump([expr, self.else, children]) + end + + def self._load(data) + expr, else_, children = Marshal.load(data) + node = IfNode.new(expr) + node.else = else_ + node.children = children + node.instance_variable_set('@last_else', + node.else ? node.else.instance_variable_get('@last_else') : node) + node + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/import_node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/import_node.rb new file mode 100644 index 00000000..955bd392 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/import_node.rb @@ -0,0 +1,75 @@ +module Sass + module Tree + # A static node that wraps the {Sass::Tree} for an `@import`ed file. + # It doesn't have a functional purpose other than to add the `@import`ed file + # to the backtrace if an error occurs. + class ImportNode < RootNode + # The name of the imported file as it appears in the Sass document. + # + # @return [String] + attr_reader :imported_filename + + # Sets the imported file. + attr_writer :imported_file + + # @param imported_filename [String] The name of the imported file + def initialize(imported_filename) + @imported_filename = imported_filename + super(nil) + end + + def invisible?; to_s.empty?; end + + # Returns the imported file. + # + # @return [Sass::Engine] + # @raise [Sass::SyntaxError] If no file could be found to import. + def imported_file + @imported_file ||= import + end + + # Returns whether or not this import should emit a CSS @import declaration + # + # @return [Boolean] Whether or not this is a simple CSS @import declaration. + def css_import? + if @imported_filename =~ /\.css$/ + @imported_filename + elsif imported_file.is_a?(String) && imported_file =~ /\.css$/ + imported_file + end + end + + private + + def import + paths = @options[:load_paths] + + if @options[:importer] + f = @options[:importer].find_relative( + @imported_filename, @options[:filename], options_for_importer) + return f if f + end + + paths.each do |p| + f = p.find(@imported_filename, options_for_importer) + return f if f + end + + lines = ["File to import not found or unreadable: #{@imported_filename}."] + + if paths.size == 1 + lines << "Load path: #{paths.first}" + elsif !paths.empty? + lines << "Load paths:\n #{paths.join("\n ")}" + end + raise SyntaxError.new(lines.join("\n")) + rescue SyntaxError => e + raise SyntaxError.new(e.message, :line => line, :filename => @filename) + end + + def options_for_importer + @options.merge(:_from_import_node => true) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/keyframe_rule_node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/keyframe_rule_node.rb new file mode 100644 index 00000000..9f75f947 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/keyframe_rule_node.rb @@ -0,0 +1,15 @@ +module Sass::Tree + class KeyframeRuleNode < Node + # The text of the directive after any interpolated SassScript has been resolved. + # Since this is only a static node, this is the only value property. + # + # @return [String] + attr_accessor :resolved_value + + # @param resolved_value [String] See \{#resolved_value} + def initialize(resolved_value) + @resolved_value = resolved_value + super() + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/media_node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/media_node.rb new file mode 100644 index 00000000..3178de03 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/media_node.rb @@ -0,0 +1,48 @@ +module Sass::Tree + # A static node representing a `@media` rule. + # `@media` rules behave differently from other directives + # in that when they're nested within rules, + # they bubble up to top-level. + # + # @see Sass::Tree + class MediaNode < DirectiveNode + # TODO: parse and cache the query immediately if it has no dynamic elements + + # The media query for this rule, interspersed with {Sass::Script::Tree::Node}s + # representing `#{}`-interpolation. Any adjacent strings will be merged + # together. + # + # @return [Array] + attr_accessor :query + + # The media query for this rule, without any unresolved interpolation. It's + # only set once {Tree::Visitors::Perform} has been run. + # + # @return [Sass::Media::QueryList] + attr_accessor :resolved_query + + # @param query [Array] See \{#query} + def initialize(query) + @query = query + super('') + end + + # @see DirectiveNode#value + def value; raise NotImplementedError; end + + # @see DirectiveNode#name + def name; '@media'; end + + # @see DirectiveNode#resolved_value + def resolved_value + @resolved_value ||= "@media #{resolved_query.to_css}" + end + + # True when the directive has no visible children. + # + # @return [Boolean] + def invisible? + children.all? {|c| c.invisible?} + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/mixin_def_node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/mixin_def_node.rb new file mode 100644 index 00000000..9ed8bfb6 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/mixin_def_node.rb @@ -0,0 +1,38 @@ +module Sass + module Tree + # A dynamic node representing a mixin definition. + # + # @see Sass::Tree + class MixinDefNode < Node + # The mixin name. + # @return [String] + attr_reader :name + + # The arguments for the mixin. + # Each element is a tuple containing the variable for argument + # and the parse tree for the default value of the argument. + # + # @return [Array<(Script::Tree::Node, Script::Tree::Node)>] + attr_accessor :args + + # The splat argument for this mixin, if one exists. + # + # @return [Script::Tree::Node?] + attr_accessor :splat + + # Whether the mixin uses `@content`. Set during the nesting check phase. + # @return [Boolean] + attr_accessor :has_content + + # @param name [String] The mixin name + # @param args [Array<(Script::Tree::Node, Script::Tree::Node)>] See \{#args} + # @param splat [Script::Tree::Node] See \{#splat} + def initialize(name, args, splat) + @name = name + @args = args + @splat = splat + super() + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/mixin_node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/mixin_node.rb new file mode 100644 index 00000000..48592c1c --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/mixin_node.rb @@ -0,0 +1,52 @@ +require 'sass/tree/node' + +module Sass::Tree + # A static node representing a mixin include. + # When in a static tree, the sole purpose is to wrap exceptions + # to add the mixin to the backtrace. + # + # @see Sass::Tree + class MixinNode < Node + # The name of the mixin. + # @return [String] + attr_reader :name + + # The arguments to the mixin. + # @return [Array] + attr_accessor :args + + # A hash from keyword argument names to values. + # @return [Sass::Util::NormalizedMap] + attr_accessor :keywords + + # The first splat argument for this mixin, if one exists. + # + # This could be a list of positional arguments, a map of keyword + # arguments, or an arglist containing both. + # + # @return [Node?] + attr_accessor :splat + + # The second splat argument for this mixin, if one exists. + # + # If this exists, it's always a map of keyword arguments, and + # \{#splat} is always either a list or an arglist. + # + # @return [Node?] + attr_accessor :kwarg_splat + + # @param name [String] The name of the mixin + # @param args [Array] See \{#args} + # @param splat [Script::Tree::Node] See \{#splat} + # @param kwarg_splat [Script::Tree::Node] See \{#kwarg_splat} + # @param keywords [Sass::Util::NormalizedMap] See \{#keywords} + def initialize(name, args, keywords, splat, kwarg_splat) + @name = name + @args = args + @keywords = keywords + @splat = splat + @kwarg_splat = kwarg_splat + super() + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/node.rb new file mode 100644 index 00000000..06932f55 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/node.rb @@ -0,0 +1,240 @@ +module Sass + # A namespace for nodes in the Sass parse tree. + # + # The Sass parse tree has three states: dynamic, static Sass, and static CSS. + # + # When it's first parsed, a Sass document is in the dynamic state. + # It has nodes for mixin definitions and `@for` loops and so forth, + # in addition to nodes for CSS rules and properties. + # Nodes that only appear in this state are called **dynamic nodes**. + # + # {Tree::Visitors::Perform} creates a static Sass tree, which is + # different. It still has nodes for CSS rules and properties but it + # doesn't have any dynamic-generation-related nodes. The nodes in + # this state are in a similar structure to the Sass document: rules + # and properties are nested beneath one another, although the + # {Tree::RuleNode} selectors are already in their final state. Nodes + # that can be in this state or in the dynamic state are called + # **static nodes**; nodes that can only be in this state are called + # **solely static nodes**. + # + # {Tree::Visitors::Cssize} is then used to create a static CSS tree. + # This is like a static Sass tree, + # but the structure exactly mirrors that of the generated CSS. + # Rules and properties can't be nested beneath one another in this state. + # + # Finally, {Tree::Visitors::ToCss} can be called on a static CSS tree + # to get the actual CSS code as a string. + module Tree + # The abstract superclass of all parse-tree nodes. + class Node + include Enumerable + + def self.inherited(base) + node_name = base.name.gsub(/.*::(.*?)Node$/, '\\1').downcase + base.instance_eval <<-METHODS + # @return [Symbol] The name that is used for this node when visiting. + def node_name + :#{node_name} + end + + # @return [Symbol] The method that is used on the visitor to visit nodes of this type. + def visit_method + :visit_#{node_name} + end + + # @return [Symbol] The method name that determines if the parent is invalid. + def invalid_child_method_name + :"invalid_#{node_name}_child?" + end + + # @return [Symbol] The method name that determines if the node is an invalid parent. + def invalid_parent_method_name + :"invalid_#{node_name}_parent?" + end + METHODS + end + + # The child nodes of this node. + # + # @return [Array] + attr_reader :children + + # Whether or not this node has child nodes. + # This may be true even when \{#children} is empty, + # in which case this node has an empty block (e.g. `{}`). + # + # @return [Boolean] + attr_accessor :has_children + + # The line of the document on which this node appeared. + # + # @return [Integer] + attr_accessor :line + + # The source range in the document on which this node appeared. + # + # @return [Sass::Source::Range] + attr_accessor :source_range + + # The name of the document on which this node appeared. + # + # @return [String] + attr_writer :filename + + # The options hash for the node. + # See {file:SASS_REFERENCE.md#Options the Sass options documentation}. + # + # @return [{Symbol => Object}] + attr_reader :options + + def initialize + @children = [] + @filename = nil + @options = nil + end + + # Sets the options hash for the node and all its children. + # + # @param options [{Symbol => Object}] The options + # @see #options + def options=(options) + Sass::Tree::Visitors::SetOptions.visit(self, options) + end + + # @private + def children=(children) + self.has_children ||= !children.empty? + @children = children + end + + # The name of the document on which this node appeared. + # + # @return [String] + def filename + @filename || (@options && @options[:filename]) + end + + # Appends a child to the node. + # + # @param child [Tree::Node, Array] The child node or nodes + # @raise [Sass::SyntaxError] if `child` is invalid + def <<(child) + return if child.nil? + if child.is_a?(Array) + child.each {|c| self << c} + else + self.has_children = true + @children << child + end + end + + # Compares this node and another object (only other {Tree::Node}s will be equal). + # This does a structural comparison; + # if the contents of the nodes and all the child nodes are equivalent, + # then the nodes are as well. + # + # Only static nodes need to override this. + # + # @param other [Object] The object to compare with + # @return [Boolean] Whether or not this node and the other object + # are the same + # @see Sass::Tree + def ==(other) + self.class == other.class && other.children == children + end + + # True if \{#to\_s} will return `nil`; + # that is, if the node shouldn't be rendered. + # Should only be called in a static tree. + # + # @return [Boolean] + def invisible?; false; end + + # The output style. See {file:SASS_REFERENCE.md#Options the Sass options documentation}. + # + # @return [Symbol] + def style + @options[:style] + end + + # Computes the CSS corresponding to this static CSS tree. + # + # @return [String] The resulting CSS + # @see Sass::Tree + def css + Sass::Tree::Visitors::ToCss.new.visit(self) + end + + # Computes the CSS corresponding to this static CSS tree, along with + # the respective source map. + # + # @return [(String, Sass::Source::Map)] The resulting CSS and the source map + # @see Sass::Tree + def css_with_sourcemap + visitor = Sass::Tree::Visitors::ToCss.new(:build_source_mapping) + result = visitor.visit(self) + return result, visitor.source_mapping + end + + # Returns a representation of the node for debugging purposes. + # + # @return [String] + def inspect + return self.class.to_s unless has_children + "(#{self.class} #{children.map {|c| c.inspect}.join(' ')})" + end + + # Iterates through each node in the tree rooted at this node + # in a pre-order walk. + # + # @yield node + # @yieldparam node [Node] a node in the tree + def each + yield self + children.each {|c| c.each {|n| yield n}} + end + + # Converts a node to Sass code that will generate it. + # + # @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}) + # @return [String] The Sass code corresponding to the node + def to_sass(options = {}) + Sass::Tree::Visitors::Convert.visit(self, options, :sass) + end + + # Converts a node to SCSS code that will generate it. + # + # @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}) + # @return [String] The Sass code corresponding to the node + def to_scss(options = {}) + Sass::Tree::Visitors::Convert.visit(self, options, :scss) + end + + # Return a deep clone of this node. + # The child nodes are cloned, but options are not. + # + # @return [Node] + def deep_copy + Sass::Tree::Visitors::DeepCopy.visit(self) + end + + # Whether or not this node bubbles up through RuleNodes. + # + # @return [Boolean] + def bubbles? + false + end + + protected + + # @see Sass::Shared.balance + # @raise [Sass::SyntaxError] if the brackets aren't balanced + def balance(*args) + res = Sass::Shared.balance(*args) + return res if res + raise Sass::SyntaxError.new("Unbalanced brackets.", :line => line) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/prop_node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/prop_node.rb new file mode 100644 index 00000000..7ce8bdde --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/prop_node.rb @@ -0,0 +1,162 @@ +module Sass::Tree + # A static node representing a CSS property. + # + # @see Sass::Tree + class PropNode < Node + # The name of the property, + # interspersed with {Sass::Script::Tree::Node}s + # representing `#{}`-interpolation. + # Any adjacent strings will be merged together. + # + # @return [Array] + attr_accessor :name + + # The name of the property + # after any interpolated SassScript has been resolved. + # Only set once \{Tree::Visitors::Perform} has been run. + # + # @return [String] + attr_accessor :resolved_name + + # The value of the property. + # + # For most properties, this will just contain a single Node. However, for + # CSS variables, it will contain multiple strings and nodes representing + # interpolation. Any adjacent strings will be merged together. + # + # @return [Array] + attr_accessor :value + + # The value of the property + # after any interpolated SassScript has been resolved. + # Only set once \{Tree::Visitors::Perform} has been run. + # + # @return [String] + attr_accessor :resolved_value + + # How deep this property is indented + # relative to a normal property. + # This is only greater than 0 in the case that: + # + # * This node is in a CSS tree + # * The style is :nested + # * This is a child property of another property + # * The parent property has a value, and thus will be rendered + # + # @return [Integer] + attr_accessor :tabs + + # The source range in which the property name appears. + # + # @return [Sass::Source::Range] + attr_accessor :name_source_range + + # The source range in which the property value appears. + # + # @return [Sass::Source::Range] + attr_accessor :value_source_range + + # Whether this represents a CSS custom property. + # + # @return [Boolean] + def custom_property? + name.first.is_a?(String) && name.first.start_with?("--") + end + + # @param name [Array] See \{#name} + # @param value [Array] See \{#value} + # @param prop_syntax [Symbol] `:new` if this property uses `a: b`-style syntax, + # `:old` if it uses `:a b`-style syntax + def initialize(name, value, prop_syntax) + @name = Sass::Util.strip_string_array( + Sass::Util.merge_adjacent_strings(name)) + @value = Sass::Util.merge_adjacent_strings(value) + @value = Sass::Util.strip_string_array(@value) unless custom_property? + @tabs = 0 + @prop_syntax = prop_syntax + super() + end + + # Compares the names and values of two properties. + # + # @param other [Object] The object to compare with + # @return [Boolean] Whether or not this node and the other object + # are the same + def ==(other) + self.class == other.class && name == other.name && value == other.value && super + end + + # Returns a appropriate message indicating how to escape pseudo-class selectors. + # This only applies for old-style properties with no value, + # so returns the empty string if this is new-style. + # + # @return [String] The message + def pseudo_class_selector_message + if @prop_syntax == :new || + custom_property? || + !value.first.is_a?(Sass::Script::Tree::Literal) || + !value.first.value.is_a?(Sass::Script::Value::String) || + !value.first.value.value.empty? + return "" + end + + "\nIf #{declaration.dump} should be a selector, use \"\\#{declaration}\" instead." + end + + # Computes the Sass or SCSS code for the variable declaration. + # This is like \{#to\_scss} or \{#to\_sass}, + # except it doesn't print any child properties or a trailing semicolon. + # + # @param opts [{Symbol => Object}] The options hash for the tree. + # @param fmt [Symbol] `:scss` or `:sass`. + def declaration(opts = {:old => @prop_syntax == :old}, fmt = :sass) + name = self.name.map {|n| n.is_a?(String) ? n : n.to_sass(opts)}.join + value = self.value.map {|n| n.is_a?(String) ? n : n.to_sass(opts)}.join + value = "(#{value})" if value_needs_parens? + + if name[0] == ?: + raise Sass::SyntaxError.new("The \"#{name}: #{value}\"" + + " hack is not allowed in the Sass indented syntax") + end + + # The indented syntax doesn't support newlines in custom property values, + # but we can losslessly convert them to spaces instead. + value = value.tr("\n", " ") if fmt == :sass + + old = opts[:old] && fmt == :sass + "#{old ? ':' : ''}#{name}#{old ? '' : ':'}#{custom_property? ? '' : ' '}#{value}".rstrip + end + + # A property node is invisible if its value is empty. + # + # @return [Boolean] + def invisible? + !custom_property? && resolved_value.empty? + end + + private + + # Returns whether \{#value} neesd parentheses in order to be parsed + # properly as division. + def value_needs_parens? + return false if custom_property? + + root = value.first + root.is_a?(Sass::Script::Tree::Operation) && + root.operator == :div && + root.operand1.is_a?(Sass::Script::Tree::Literal) && + root.operand1.value.is_a?(Sass::Script::Value::Number) && + root.operand1.value.original.nil? && + root.operand2.is_a?(Sass::Script::Tree::Literal) && + root.operand2.value.is_a?(Sass::Script::Value::Number) && + root.operand2.value.original.nil? + end + + def check! + return unless @options[:property_syntax] && @options[:property_syntax] != @prop_syntax + raise Sass::SyntaxError.new( + "Illegal property syntax: can't use #{@prop_syntax} syntax when " + + ":property_syntax => #{@options[:property_syntax].inspect} is set.") + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/return_node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/return_node.rb new file mode 100644 index 00000000..3056406b --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/return_node.rb @@ -0,0 +1,19 @@ +module Sass + module Tree + # A dynamic node representing returning from a function. + # + # @see Sass::Tree + class ReturnNode < Node + # The expression to return. + # + # @return [Script::Tree::Node] + attr_accessor :expr + + # @param expr [Script::Tree::Node] The expression to return + def initialize(expr) + @expr = expr + super() + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/root_node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/root_node.rb new file mode 100644 index 00000000..1f02cbd0 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/root_node.rb @@ -0,0 +1,44 @@ +module Sass + module Tree + # A static node that is the root node of the Sass document. + class RootNode < Node + # The Sass template from which this node was created + # + # @param template [String] + attr_reader :template + + # @param template [String] The Sass template from which this node was created + def initialize(template) + super() + @template = template + end + + # Runs the dynamic Sass code and computes the CSS for the tree. + # + # @return [String] The compiled CSS. + def render + css_tree.css + end + + # Runs the dynamic Sass code and computes the CSS for the tree, along with + # the sourcemap. + # + # @return [(String, Sass::Source::Map)] The compiled CSS, as well as + # the source map. @see #render + def render_with_sourcemap + css_tree.css_with_sourcemap + end + + private + + def css_tree + Visitors::CheckNesting.visit(self) + result = Visitors::Perform.visit(self) + Visitors::CheckNesting.visit(result) # Check again to validate mixins + result, extends = Visitors::Cssize.visit(result) + Visitors::Extend.visit(result, extends) + result + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/rule_node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/rule_node.rb new file mode 100644 index 00000000..e621939a --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/rule_node.rb @@ -0,0 +1,153 @@ +require 'pathname' + +module Sass::Tree + # A static node representing a CSS rule. + # + # @see Sass::Tree + class RuleNode < Node + # The character used to include the parent selector + PARENT = '&' + + # The CSS selector for this rule, + # interspersed with {Sass::Script::Tree::Node}s + # representing `#{}`-interpolation. + # Any adjacent strings will be merged together. + # + # @return [Array] + attr_accessor :rule + + # The CSS selector for this rule, without any unresolved + # interpolation but with parent references still intact. It's only + # guaranteed to be set once {Tree::Visitors::Perform} has been + # run, but it may be set before then for optimization reasons. + # + # @return [Selector::CommaSequence] + attr_accessor :parsed_rules + + # The CSS selector for this rule, without any unresolved + # interpolation or parent references. It's only set once + # {Tree::Visitors::Perform} has been run. + # + # @return [Selector::CommaSequence] + attr_accessor :resolved_rules + + # How deep this rule is indented + # relative to a base-level rule. + # This is only greater than 0 in the case that: + # + # * This node is in a CSS tree + # * The style is :nested + # * This is a child rule of another rule + # * The parent rule has properties, and thus will be rendered + # + # @return [Integer] + attr_accessor :tabs + + # The entire selector source range for this rule. + # @return [Sass::Source::Range] + attr_accessor :selector_source_range + + # Whether or not this rule is the last rule in a nested group. + # This is only set in a CSS tree. + # + # @return [Boolean] + attr_accessor :group_end + + # The stack trace. + # This is only readable in a CSS tree as it is written during the perform step + # and only when the :trace_selectors option is set. + # + # @return [String] + attr_accessor :stack_trace + + # @param rule [Array, Sass::Selector::CommaSequence] + # The CSS rule, either unparsed or parsed. + # @param selector_source_range [Sass::Source::Range] + def initialize(rule, selector_source_range = nil) + if rule.is_a?(Sass::Selector::CommaSequence) + @rule = [rule.to_s] + @parsed_rules = rule + else + merged = Sass::Util.merge_adjacent_strings(rule) + @rule = Sass::Util.strip_string_array(merged) + try_to_parse_non_interpolated_rules + end + @selector_source_range = selector_source_range + @tabs = 0 + super() + end + + # If we've precached the parsed selector, set the line on it, too. + def line=(line) + @parsed_rules.line = line if @parsed_rules + super + end + + # If we've precached the parsed selector, set the filename on it, too. + def filename=(filename) + @parsed_rules.filename = filename if @parsed_rules + super + end + + # Compares the contents of two rules. + # + # @param other [Object] The object to compare with + # @return [Boolean] Whether or not this node and the other object + # are the same + def ==(other) + self.class == other.class && rule == other.rule && super + end + + # Adds another {RuleNode}'s rules to this one's. + # + # @param node [RuleNode] The other node + def add_rules(node) + @rule = Sass::Util.strip_string_array( + Sass::Util.merge_adjacent_strings(@rule + ["\n"] + node.rule)) + try_to_parse_non_interpolated_rules + end + + # @return [Boolean] Whether or not this rule is continued on the next line + def continued? + last = @rule.last + last.is_a?(String) && last[-1] == ?, + end + + # A hash that will be associated with this rule in the CSS document + # if the {file:SASS_REFERENCE.md#debug_info-option `:debug_info` option} is enabled. + # This data is used by e.g. [the FireSass Firebug + # extension](https://addons.mozilla.org/en-US/firefox/addon/103988). + # + # @return [{#to_s => #to_s}] + def debug_info + {:filename => filename && + ("file://" + URI::DEFAULT_PARSER.escape(File.expand_path(filename))), + :line => line} + end + + # A rule node is invisible if it has only placeholder selectors. + def invisible? + resolved_rules.members.all? {|seq| seq.invisible?} + end + + private + + def try_to_parse_non_interpolated_rules + @parsed_rules = nil + return unless @rule.all? {|t| t.is_a?(String)} + + # We don't use real filename/line info because we don't have it yet. + # When we get it, we'll set it on the parsed rules if possible. + parser = nil + warnings = Sass.logger.capture do + parser = Sass::SCSS::StaticParser.new( + Sass::Util.strip_except_escapes(@rule.join), nil, nil, 1) + @parsed_rules = parser.parse_selector rescue nil + end + + # If parsing produces a warning, throw away the result so we can parse + # later with the real filename info. + @parsed_rules = nil unless warnings.empty? + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/supports_node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/supports_node.rb new file mode 100644 index 00000000..1a2f04b6 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/supports_node.rb @@ -0,0 +1,38 @@ +module Sass::Tree + # A static node representing a `@supports` rule. + # + # @see Sass::Tree + class SupportsNode < DirectiveNode + # The name, which may include a browser prefix. + # + # @return [String] + attr_accessor :name + + # The supports condition. + # + # @return [Sass::Supports::Condition] + attr_accessor :condition + + # @param condition [Sass::Supports::Condition] See \{#condition} + def initialize(name, condition) + @name = name + @condition = condition + super('') + end + + # @see DirectiveNode#value + def value; raise NotImplementedError; end + + # @see DirectiveNode#resolved_value + def resolved_value + @resolved_value ||= "@#{name} #{condition.to_css}" + end + + # True when the directive has no visible children. + # + # @return [Boolean] + def invisible? + children.all? {|c| c.invisible?} + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/trace_node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/trace_node.rb new file mode 100644 index 00000000..2c71e889 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/trace_node.rb @@ -0,0 +1,33 @@ +require 'sass/tree/node' + +module Sass::Tree + # A solely static node left over after a mixin include or @content has been performed. + # Its sole purpose is to wrap exceptions to add to the backtrace. + # + # @see Sass::Tree + class TraceNode < Node + # The name of the trace entry to add. + # + # @return [String] + attr_reader :name + + # @param name [String] The name of the trace entry to add. + def initialize(name) + @name = name + self.has_children = true + super() + end + + # Initializes this node from an existing node. + # @param name [String] The name of the trace entry to add. + # @param node [Node] The node to copy information from. + # @return [TraceNode] + def self.from_node(name, node) + trace = new(name) + trace.line = node.line + trace.filename = node.filename + trace.options = node.options + trace + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/variable_node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/variable_node.rb new file mode 100644 index 00000000..2c0ed552 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/variable_node.rb @@ -0,0 +1,36 @@ +module Sass + module Tree + # A dynamic node representing a variable definition. + # + # @see Sass::Tree + class VariableNode < Node + # The name of the variable. + # @return [String] + attr_reader :name + + # The parse tree for the variable value. + # @return [Script::Tree::Node] + attr_accessor :expr + + # Whether this is a guarded variable assignment (`!default`). + # @return [Boolean] + attr_reader :guarded + + # Whether this is a global variable assignment (`!global`). + # @return [Boolean] + attr_reader :global + + # @param name [String] The name of the variable + # @param expr [Script::Tree::Node] See \{#expr} + # @param guarded [Boolean] See \{#guarded} + # @param global [Boolean] See \{#global} + def initialize(name, expr, guarded, global) + @name = name + @expr = expr + @guarded = guarded + @global = global + super() + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/visitors/base.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/visitors/base.rb new file mode 100644 index 00000000..2c8e1343 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/visitors/base.rb @@ -0,0 +1,72 @@ +# Visitors are used to traverse the Sass parse tree. +# Visitors should extend {Visitors::Base}, +# which provides a small amount of scaffolding for traversal. +module Sass::Tree::Visitors + # The abstract base class for Sass visitors. + # Visitors should extend this class, + # then implement `visit_*` methods for each node they care about + # (e.g. `visit_rule` for {RuleNode} or `visit_for` for {ForNode}). + # These methods take the node in question as argument. + # They may `yield` to visit the child nodes of the current node. + # + # *Note*: due to the unusual nature of {Sass::Tree::IfNode}, + # special care must be taken to ensure that it is properly handled. + # In particular, there is no built-in scaffolding + # for dealing with the return value of `@else` nodes. + # + # @abstract + class Base + # Runs the visitor on a tree. + # + # @param root [Tree::Node] The root node of the Sass tree. + # @return [Object] The return value of \{#visit} for the root node. + def self.visit(root) + new.send(:visit, root) + end + + protected + + # Runs the visitor on the given node. + # This can be overridden by subclasses that need to do something for each node. + # + # @param node [Tree::Node] The node to visit. + # @return [Object] The return value of the `visit_*` method for this node. + def visit(node) + if respond_to?(node.class.visit_method, true) + send(node.class.visit_method, node) {visit_children(node)} + else + visit_children(node) + end + end + + # Visit the child nodes for a given node. + # This can be overridden by subclasses that need to do something + # with the child nodes' return values. + # + # This method is run when `visit_*` methods `yield`, + # and its return value is returned from the `yield`. + # + # @param parent [Tree::Node] The parent node of the children to visit. + # @return [Array] The return values of the `visit_*` methods for the children. + def visit_children(parent) + parent.children.map {|c| visit(c)} + end + + # Returns the name of a node as used in the `visit_*` method. + # + # @param [Tree::Node] node The node. + # @return [String] The name. + def self.node_name(node) + Sass::Util.deprecated(self, "Call node.class.node_name instead.") + node.class.node_name + end + + # `yield`s, then runs the visitor on the `@else` clause if the node has one. + # This exists to ensure that the contents of the `@else` clause get visited. + def visit_if(node) + yield + visit(node.else) if node.else + node + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/visitors/check_nesting.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/visitors/check_nesting.rb new file mode 100644 index 00000000..05541ef5 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/visitors/check_nesting.rb @@ -0,0 +1,173 @@ +# A visitor for checking that all nodes are properly nested. +class Sass::Tree::Visitors::CheckNesting < Sass::Tree::Visitors::Base + protected + + def initialize + @parents = [] + @parent = nil + @current_mixin_def = nil + end + + def visit(node) + if (error = @parent && ( + try_send(@parent.class.invalid_child_method_name, @parent, node) || + try_send(node.class.invalid_parent_method_name, @parent, node))) + raise Sass::SyntaxError.new(error) + end + super + rescue Sass::SyntaxError => e + e.modify_backtrace(:filename => node.filename, :line => node.line) + raise e + end + + CONTROL_NODES = [Sass::Tree::EachNode, Sass::Tree::ForNode, Sass::Tree::IfNode, + Sass::Tree::WhileNode, Sass::Tree::TraceNode] + SCRIPT_NODES = [Sass::Tree::ImportNode] + CONTROL_NODES + def visit_children(parent) + old_parent = @parent + + # When checking a static tree, resolve at-roots to be sure they won't send + # nodes where they don't belong. + if parent.is_a?(Sass::Tree::AtRootNode) && parent.resolved_value + old_parents = @parents + @parents = @parents.reject {|p| parent.exclude_node?(p)} + @parent = @parents.reverse.each_with_index. + find {|p, i| !transparent_parent?(p, @parents[-i - 2])}.first + + begin + return super + ensure + @parents = old_parents + @parent = old_parent + end + end + + unless transparent_parent?(parent, old_parent) + @parent = parent + end + + @parents.push parent + begin + super + ensure + @parent = old_parent + @parents.pop + end + end + + def visit_root(node) + yield + rescue Sass::SyntaxError => e + e.sass_template ||= node.template + raise e + end + + def visit_import(node) + yield + rescue Sass::SyntaxError => e + e.modify_backtrace(:filename => node.children.first.filename) + e.add_backtrace(:filename => node.filename, :line => node.line) + raise e + end + + def visit_mixindef(node) + @current_mixin_def, old_mixin_def = node, @current_mixin_def + yield + ensure + @current_mixin_def = old_mixin_def + end + + def invalid_content_parent?(parent, child) + if @current_mixin_def + @current_mixin_def.has_content = true + nil + else + "@content may only be used within a mixin." + end + end + + def invalid_charset_parent?(parent, child) + "@charset may only be used at the root of a document." unless parent.is_a?(Sass::Tree::RootNode) + end + + VALID_EXTEND_PARENTS = [Sass::Tree::RuleNode, Sass::Tree::MixinDefNode, Sass::Tree::MixinNode] + def invalid_extend_parent?(parent, child) + return if is_any_of?(parent, VALID_EXTEND_PARENTS) + "Extend directives may only be used within rules." + end + + INVALID_IMPORT_PARENTS = CONTROL_NODES + + [Sass::Tree::MixinDefNode, Sass::Tree::MixinNode] + def invalid_import_parent?(parent, child) + unless (@parents.map {|p| p.class} & INVALID_IMPORT_PARENTS).empty? + return "Import directives may not be used within control directives or mixins." + end + return if parent.is_a?(Sass::Tree::RootNode) + return "CSS import directives may only be used at the root of a document." if child.css_import? + rescue Sass::SyntaxError => e + e.modify_backtrace(:filename => child.imported_file.options[:filename]) + e.add_backtrace(:filename => child.filename, :line => child.line) + raise e + end + + def invalid_mixindef_parent?(parent, child) + return if (@parents.map {|p| p.class} & INVALID_IMPORT_PARENTS).empty? + "Mixins may not be defined within control directives or other mixins." + end + + def invalid_function_parent?(parent, child) + return if (@parents.map {|p| p.class} & INVALID_IMPORT_PARENTS).empty? + "Functions may not be defined within control directives or other mixins." + end + + VALID_FUNCTION_CHILDREN = [ + Sass::Tree::CommentNode, Sass::Tree::DebugNode, Sass::Tree::ReturnNode, + Sass::Tree::VariableNode, Sass::Tree::WarnNode, Sass::Tree::ErrorNode + ] + CONTROL_NODES + def invalid_function_child?(parent, child) + return if is_any_of?(child, VALID_FUNCTION_CHILDREN) + "Functions can only contain variable declarations and control directives." + end + + VALID_PROP_CHILDREN = CONTROL_NODES + [Sass::Tree::CommentNode, + Sass::Tree::PropNode, + Sass::Tree::MixinNode] + def invalid_prop_child?(parent, child) + return if is_any_of?(child, VALID_PROP_CHILDREN) + "Illegal nesting: Only properties may be nested beneath properties." + end + + VALID_PROP_PARENTS = [Sass::Tree::RuleNode, Sass::Tree::KeyframeRuleNode, Sass::Tree::PropNode, + Sass::Tree::MixinDefNode, Sass::Tree::DirectiveNode, Sass::Tree::MixinNode] + def invalid_prop_parent?(parent, child) + return if is_any_of?(parent, VALID_PROP_PARENTS) + "Properties are only allowed within rules, directives, mixin includes, or other properties." + + child.pseudo_class_selector_message + end + + def invalid_return_parent?(parent, child) + "@return may only be used within a function." unless parent.is_a?(Sass::Tree::FunctionNode) + end + + private + + # Whether `parent` should be assigned to `@parent`. + def transparent_parent?(parent, grandparent) + is_any_of?(parent, SCRIPT_NODES) || + (parent.bubbles? && + !grandparent.is_a?(Sass::Tree::RootNode) && + !grandparent.is_a?(Sass::Tree::AtRootNode)) + end + + def is_any_of?(val, classes) + classes.each do |c| + return true if val.is_a?(c) + end + false + end + + def try_send(method, *args) + return unless respond_to?(method, true) + send(method, *args) + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/visitors/convert.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/visitors/convert.rb new file mode 100644 index 00000000..ded21a23 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/visitors/convert.rb @@ -0,0 +1,350 @@ +# A visitor for converting a Sass tree into a source string. +class Sass::Tree::Visitors::Convert < Sass::Tree::Visitors::Base + # Runs the visitor on a tree. + # + # @param root [Tree::Node] The root node of the Sass tree. + # @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}). + # @param format [Symbol] `:sass` or `:scss`. + # @return [String] The Sass or SCSS source for the tree. + def self.visit(root, options, format) + new(options, format).send(:visit, root) + end + + protected + + def initialize(options, format) + @options = options + @format = format + @tabs = 0 + # 2 spaces by default + @tab_chars = @options[:indent] || " " + @is_else = false + end + + def visit_children(parent) + @tabs += 1 + return @format == :sass ? "\n" : " {}\n" if parent.children.empty? + + res = visit_rule_level(parent.children) + + if @format == :sass + "\n" + res.rstrip + "\n" + else + " {\n" + res.rstrip + "\n#{@tab_chars * (@tabs - 1)}}\n" + end + ensure + @tabs -= 1 + end + + # Ensures proper spacing between top-level nodes. + def visit_root(node) + visit_rule_level(node.children) + end + + def visit_charset(node) + "#{tab_str}@charset \"#{node.name}\"#{semi}\n" + end + + def visit_comment(node) + value = interp_to_src(node.value) + if @format == :sass + content = value.gsub(%r{\*/$}, '').rstrip + if content =~ /\A[ \t]/ + # Re-indent SCSS comments like this: + # /* foo + # bar + # baz */ + content.gsub!(/^/, ' ') + content.sub!(%r{\A([ \t]*)/\*}, '/*\1') + end + + if content.include?("\n") + content.gsub!(/\n \*/, "\n ") + spaces = content.scan(/\n( *)/).map {|s| s.first.size}.min + sep = node.type == :silent ? "\n//" : "\n *" + if spaces >= 2 + content.gsub!(/\n /, sep) + else + content.gsub!(/\n#{' ' * spaces}/, sep) + end + end + + content.gsub!(%r{\A/\*}, '//') if node.type == :silent + content.gsub!(/^/, tab_str) + content = content.rstrip + "\n" + else + spaces = (@tab_chars * [@tabs - value[/^ */].size, 0].max) + content = if node.type == :silent + value.gsub(%r{^[/ ]\*}, '//').gsub(%r{ *\*/$}, '') + else + value + end.gsub(/^/, spaces) + "\n" + end + content + end + + def visit_debug(node) + "#{tab_str}@debug #{node.expr.to_sass(@options)}#{semi}\n" + end + + def visit_error(node) + "#{tab_str}@error #{node.expr.to_sass(@options)}#{semi}\n" + end + + def visit_directive(node) + res = "#{tab_str}#{interp_to_src(node.value)}" + res.gsub!(/^@import \#\{(.*)\}([^}]*)$/, '@import \1\2') + return res + "#{semi}\n" unless node.has_children + res + yield + end + + def visit_each(node) + vars = node.vars.map {|var| "$#{dasherize(var)}"}.join(", ") + "#{tab_str}@each #{vars} in #{node.list.to_sass(@options)}#{yield}" + end + + def visit_extend(node) + "#{tab_str}@extend #{selector_to_src(node.selector).lstrip}" + + "#{' !optional' if node.optional?}#{semi}\n" + end + + def visit_for(node) + "#{tab_str}@for $#{dasherize(node.var)} from #{node.from.to_sass(@options)} " + + "#{node.exclusive ? 'to' : 'through'} #{node.to.to_sass(@options)}#{yield}" + end + + def visit_function(node) + args = node.args.map do |v, d| + d ? "#{v.to_sass(@options)}: #{d.to_sass(@options)}" : v.to_sass(@options) + end.join(", ") + if node.splat + args << ", " unless node.args.empty? + args << node.splat.to_sass(@options) << "..." + end + + "#{tab_str}@function #{dasherize(node.name)}(#{args})#{yield}" + end + + def visit_if(node) + name = + if !@is_else + "if" + elsif node.expr + "else if" + else + "else" + end + @is_else = false + str = "#{tab_str}@#{name}" + str << " #{node.expr.to_sass(@options)}" if node.expr + str << yield + @is_else = true + str << visit(node.else) if node.else + str + ensure + @is_else = false + end + + def visit_import(node) + quote = @format == :scss ? '"' : '' + "#{tab_str}@import #{quote}#{node.imported_filename}#{quote}#{semi}\n" + end + + def visit_media(node) + "#{tab_str}@media #{query_interp_to_src(node.query)}#{yield}" + end + + def visit_supports(node) + "#{tab_str}@#{node.name} #{node.condition.to_src(@options)}#{yield}" + end + + def visit_cssimport(node) + if node.uri.is_a?(Sass::Script::Tree::Node) + str = "#{tab_str}@import #{node.uri.to_sass(@options)}" + else + str = "#{tab_str}@import #{node.uri}" + end + str << " supports(#{node.supports_condition.to_src(@options)})" if node.supports_condition + str << " #{interp_to_src(node.query)}" unless node.query.empty? + "#{str}#{semi}\n" + end + + def visit_mixindef(node) + args = + if node.args.empty? && node.splat.nil? + "" + else + str = '(' + str << node.args.map do |v, d| + if d + "#{v.to_sass(@options)}: #{d.to_sass(@options)}" + else + v.to_sass(@options) + end + end.join(", ") + + if node.splat + str << ", " unless node.args.empty? + str << node.splat.to_sass(@options) << '...' + end + + str << ')' + end + + "#{tab_str}#{@format == :sass ? '=' : '@mixin '}#{dasherize(node.name)}#{args}#{yield}" + end + + def visit_mixin(node) + arg_to_sass = lambda do |arg| + sass = arg.to_sass(@options) + sass = "(#{sass})" if arg.is_a?(Sass::Script::Tree::ListLiteral) && arg.separator == :comma + sass + end + + unless node.args.empty? && node.keywords.empty? && node.splat.nil? + args = node.args.map(&arg_to_sass) + keywords = node.keywords.as_stored.to_a.map {|k, v| "$#{dasherize(k)}: #{arg_to_sass[v]}"} + + if node.splat + splat = "#{arg_to_sass[node.splat]}..." + kwarg_splat = "#{arg_to_sass[node.kwarg_splat]}..." if node.kwarg_splat + end + + arglist = "(#{[args, splat, keywords, kwarg_splat].flatten.compact.join(', ')})" + end + "#{tab_str}#{@format == :sass ? '+' : '@include '}" + + "#{dasherize(node.name)}#{arglist}#{node.has_children ? yield : semi}\n" + end + + def visit_content(node) + "#{tab_str}@content#{semi}\n" + end + + def visit_prop(node) + res = tab_str + node.declaration(@options, @format) + return res + semi + "\n" if node.children.empty? + res + yield.rstrip + semi + "\n" + end + + def visit_return(node) + "#{tab_str}@return #{node.expr.to_sass(@options)}#{semi}\n" + end + + def visit_rule(node) + rule = node.parsed_rules ? [node.parsed_rules.to_s] : node.rule + if @format == :sass + name = selector_to_sass(rule) + name = "\\" + name if name[0] == ?: + name.gsub(/^/, tab_str) + yield + elsif @format == :scss + name = selector_to_scss(rule) + res = name + yield + if node.children.last.is_a?(Sass::Tree::CommentNode) && node.children.last.type == :silent + res.slice!(-3..-1) + res << "\n" << tab_str << "}\n" + end + res + end + end + + def visit_variable(node) + "#{tab_str}$#{dasherize(node.name)}: #{node.expr.to_sass(@options)}" + + "#{' !global' if node.global}#{' !default' if node.guarded}#{semi}\n" + end + + def visit_warn(node) + "#{tab_str}@warn #{node.expr.to_sass(@options)}#{semi}\n" + end + + def visit_while(node) + "#{tab_str}@while #{node.expr.to_sass(@options)}#{yield}" + end + + def visit_atroot(node) + if node.query + "#{tab_str}@at-root #{query_interp_to_src(node.query)}#{yield}" + elsif node.children.length == 1 && node.children.first.is_a?(Sass::Tree::RuleNode) + rule = node.children.first + "#{tab_str}@at-root #{selector_to_src(rule.rule).lstrip}#{visit_children(rule)}" + else + "#{tab_str}@at-root#{yield}" + end + end + + def visit_keyframerule(node) + "#{tab_str}#{node.resolved_value}#{yield}" + end + + private + + # Visit rule-level nodes and return their conversion with appropriate + # whitespace added. + def visit_rule_level(nodes) + (nodes + [nil]).each_cons(2).map do |child, nxt| + visit(child) + + if nxt && + (child.is_a?(Sass::Tree::CommentNode) && child.line + child.lines + 1 == nxt.line) || + (child.is_a?(Sass::Tree::ImportNode) && nxt.is_a?(Sass::Tree::ImportNode) && + child.line + 1 == nxt.line) || + (child.is_a?(Sass::Tree::VariableNode) && nxt.is_a?(Sass::Tree::VariableNode) && + child.line + 1 == nxt.line) || + (child.is_a?(Sass::Tree::PropNode) && nxt.is_a?(Sass::Tree::PropNode)) || + (child.is_a?(Sass::Tree::MixinNode) && nxt.is_a?(Sass::Tree::MixinNode) && + child.line + 1 == nxt.line) + "" + else + "\n" + end + end.join.rstrip + "\n" + end + + def interp_to_src(interp) + interp.map {|r| r.is_a?(String) ? r : r.to_sass(@options)}.join + end + + # Like interp_to_src, but removes the unnecessary `#{}` around the keys and + # values in query expressions. + def query_interp_to_src(interp) + interp = interp.map do |e| + next e unless e.is_a?(Sass::Script::Tree::Literal) + next e unless e.value.is_a?(Sass::Script::Value::String) + e.value.value + end + + interp_to_src(interp) + end + + def selector_to_src(sel) + @format == :sass ? selector_to_sass(sel) : selector_to_scss(sel) + end + + def selector_to_sass(sel) + sel.map do |r| + if r.is_a?(String) + r.gsub(/(,)?([ \t]*)\n\s*/) {$1 ? "#{$1}#{$2}\n" : " "} + else + r.to_sass(@options) + end + end.join + end + + def selector_to_scss(sel) + interp_to_src(sel).gsub(/^[ \t]*/, tab_str).gsub(/[ \t]*$/, '') + end + + def semi + @format == :sass ? "" : ";" + end + + def tab_str + @tab_chars * @tabs + end + + def dasherize(s) + if @options[:dasherize] + s.tr('_', '-') + else + s + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/visitors/cssize.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/visitors/cssize.rb new file mode 100644 index 00000000..6851286e --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/visitors/cssize.rb @@ -0,0 +1,362 @@ +# A visitor for converting a static Sass tree into a static CSS tree. +class Sass::Tree::Visitors::Cssize < Sass::Tree::Visitors::Base + # @param root [Tree::Node] The root node of the tree to visit. + # @return [(Tree::Node, Sass::Util::SubsetMap)] The resulting tree of static nodes + # *and* the extensions defined for this tree + def self.visit(root); super; end + + protected + + # Returns the immediate parent of the current node. + # @return [Tree::Node] + def parent + @parents.last + end + + def initialize + @parents = [] + @extends = Sass::Util::SubsetMap.new + end + + # If an exception is raised, this adds proper metadata to the backtrace. + def visit(node) + super(node) + rescue Sass::SyntaxError => e + e.modify_backtrace(:filename => node.filename, :line => node.line) + raise e + end + + # Keeps track of the current parent node. + def visit_children(parent) + with_parent parent do + parent.children = visit_children_without_parent(parent) + parent + end + end + + # Like {#visit\_children}, but doesn't set {#parent}. + # + # @param node [Sass::Tree::Node] + # @return [Array] the flattened results of + # visiting all the children of `node` + def visit_children_without_parent(node) + node.children.map {|c| visit(c)}.flatten + end + + # Runs a block of code with the current parent node + # replaced with the given node. + # + # @param parent [Tree::Node] The new parent for the duration of the block. + # @yield A block in which the parent is set to `parent`. + # @return [Object] The return value of the block. + def with_parent(parent) + @parents.push parent + yield + ensure + @parents.pop + end + + # Converts the entire document to CSS. + # + # @return [(Tree::Node, Sass::Util::SubsetMap)] The resulting tree of static nodes + # *and* the extensions defined for this tree + def visit_root(node) + yield + + if parent.nil? + imports_to_move = [] + import_limit = nil + i = -1 + node.children.reject! do |n| + i += 1 + if import_limit + next false unless n.is_a?(Sass::Tree::CssImportNode) + imports_to_move << n + next true + end + + if !n.is_a?(Sass::Tree::CommentNode) && + !n.is_a?(Sass::Tree::CharsetNode) && + !n.is_a?(Sass::Tree::CssImportNode) + import_limit = i + end + + false + end + + if import_limit + node.children = node.children[0...import_limit] + imports_to_move + + node.children[import_limit..-1] + end + end + + return node, @extends + rescue Sass::SyntaxError => e + e.sass_template ||= node.template + raise e + end + + # A simple struct wrapping up information about a single `@extend` instance. A + # single {ExtendNode} can have multiple Extends if either the parent node or + # the extended selector is a comma sequence. + # + # @attr extender [Sass::Selector::Sequence] + # The selector of the CSS rule containing the `@extend`. + # @attr target [Array] The selector being `@extend`ed. + # @attr node [Sass::Tree::ExtendNode] The node that produced this extend. + # @attr directives [Array] + # The directives containing the `@extend`. + # @attr success [Boolean] + # Whether this extend successfully matched a selector. + Extend = Struct.new(:extender, :target, :node, :directives, :success) + + # Registers an extension in the `@extends` subset map. + def visit_extend(node) + parent.resolved_rules.populate_extends(@extends, node.resolved_selector, node, + @parents.select {|p| p.is_a?(Sass::Tree::DirectiveNode)}) + [] + end + + # Modifies exception backtraces to include the imported file. + def visit_import(node) + visit_children_without_parent(node) + rescue Sass::SyntaxError => e + e.modify_backtrace(:filename => node.children.first.filename) + e.add_backtrace(:filename => node.filename, :line => node.line) + raise e + end + + # Asserts that all the traced children are valid in their new location. + def visit_trace(node) + visit_children_without_parent(node) + rescue Sass::SyntaxError => e + e.modify_backtrace(:mixin => node.name, :filename => node.filename, :line => node.line) + e.add_backtrace(:filename => node.filename, :line => node.line) + raise e + end + + # Converts nested properties into flat properties + # and updates the indentation of the prop node based on the nesting level. + def visit_prop(node) + if parent.is_a?(Sass::Tree::PropNode) + node.resolved_name = "#{parent.resolved_name}-#{node.resolved_name}" + node.tabs = parent.tabs + (parent.resolved_value.empty? ? 0 : 1) if node.style == :nested + end + + yield + + result = node.children.dup + if !node.resolved_value.empty? || node.children.empty? + node.send(:check!) + result.unshift(node) + end + + result + end + + def visit_atroot(node) + # If there aren't any more directives or rules that this @at-root needs to + # exclude, we can get rid of it and just evaluate the children. + if @parents.none? {|n| node.exclude_node?(n)} + results = visit_children_without_parent(node) + results.each {|c| c.tabs += node.tabs if bubblable?(c)} + if !results.empty? && bubblable?(results.last) + results.last.group_end = node.group_end + end + return results + end + + # If this @at-root excludes the immediate parent, return it as-is so that it + # can be bubbled up by the parent node. + return Bubble.new(node) if node.exclude_node?(parent) + + # Otherwise, duplicate the current parent and move it into the @at-root + # node. As above, returning an @at-root node signals to the parent directive + # that it should be bubbled upwards. + bubble(node) + end + + # The following directives are visible and have children. This means they need + # to be able to handle bubbling up nodes such as @at-root and @media. + + # Updates the indentation of the rule node based on the nesting + # level. The selectors were resolved in {Perform}. + def visit_rule(node) + yield + + rules = node.children.select {|c| bubblable?(c)} + props = node.children.reject {|c| bubblable?(c) || c.invisible?} + + unless props.empty? + node.children = props + rules.each {|r| r.tabs += 1} if node.style == :nested + rules.unshift(node) + end + + rules = debubble(rules) + unless parent.is_a?(Sass::Tree::RuleNode) || rules.empty? || !bubblable?(rules.last) + rules.last.group_end = true + end + rules + end + + def visit_keyframerule(node) + return node unless node.has_children + + yield + + debubble(node.children, node) + end + + # Bubbles a directive up through RuleNodes. + def visit_directive(node) + return node unless node.has_children + if parent.is_a?(Sass::Tree::RuleNode) + # @keyframes shouldn't include the rule nodes, so we manually create a + # bubble that doesn't have the parent's contents for them. + return node.normalized_name == '@keyframes' ? Bubble.new(node) : bubble(node) + end + + yield + + # Since we don't know if the mere presence of an unknown directive may be + # important, we should keep an empty version around even if all the contents + # are removed via @at-root. However, if the contents are just bubbled out, + # we don't need to do so. + directive_exists = node.children.any? do |child| + next true unless child.is_a?(Bubble) + next false unless child.node.is_a?(Sass::Tree::DirectiveNode) + child.node.resolved_value == node.resolved_value + end + + # We know empty @keyframes directives do nothing. + if directive_exists || node.name == '@keyframes' + [] + else + empty_node = node.dup + empty_node.children = [] + [empty_node] + end + debubble(node.children, node) + end + + # Bubbles the `@media` directive up through RuleNodes + # and merges it with other `@media` directives. + def visit_media(node) + return bubble(node) if parent.is_a?(Sass::Tree::RuleNode) + return Bubble.new(node) if parent.is_a?(Sass::Tree::MediaNode) + + yield + + debubble(node.children, node) do |child| + next child unless child.is_a?(Sass::Tree::MediaNode) + # Copies of `node` can be bubbled, and we don't want to merge it with its + # own query. + next child if child.resolved_query == node.resolved_query + next child if child.resolved_query = child.resolved_query.merge(node.resolved_query) + end + end + + # Bubbles the `@supports` directive up through RuleNodes. + def visit_supports(node) + return node unless node.has_children + return bubble(node) if parent.is_a?(Sass::Tree::RuleNode) + + yield + + debubble(node.children, node) + end + + private + + # "Bubbles" `node` one level by copying the parent and wrapping `node`'s + # children with it. + # + # @param node [Sass::Tree::Node]. + # @return [Bubble] + def bubble(node) + new_rule = parent.dup + new_rule.children = node.children + node.children = [new_rule] + Bubble.new(node) + end + + # Pops all bubbles in `children` and intersperses the results with the other + # values. + # + # If `parent` is passed, it's copied and used as the parent node for the + # nested portions of `children`. + # + # @param children [List] + # @param parent [Sass::Tree::Node] + # @yield [node] An optional block for processing bubbled nodes. Each bubbled + # node will be passed to this block. + # @yieldparam node [Sass::Tree::Node] A bubbled node. + # @yieldreturn [Sass::Tree::Node?] A node to use in place of the bubbled node. + # This can be the node itself, or `nil` to indicate that the node should be + # omitted. + # @return [List] + def debubble(children, parent = nil) + # Keep track of the previous parent so that we don't divide `parent` + # unnecessarily if the `@at-root` doesn't produce any new nodes (e.g. + # `@at-root {@extend %foo}`). + previous_parent = nil + + Sass::Util.slice_by(children) {|c| c.is_a?(Bubble)}.map do |(is_bubble, slice)| + unless is_bubble + next slice unless parent + if previous_parent + previous_parent.children.push(*slice) + next [] + else + previous_parent = new_parent = parent.dup + new_parent.children = slice + next new_parent + end + end + + slice.map do |bubble| + next unless (node = block_given? ? yield(bubble.node) : bubble.node) + node.tabs += bubble.tabs + node.group_end = bubble.group_end + results = [visit(node)].flatten + previous_parent = nil unless results.empty? + results + end.compact + end.flatten + end + + # Returns whether or not a node can be bubbled up through the syntax tree. + # + # @param node [Sass::Tree::Node] + # @return [Boolean] + def bubblable?(node) + node.is_a?(Sass::Tree::RuleNode) || node.bubbles? + end + + # A wrapper class for a node that indicates to the parent that it should + # treat the wrapped node as a sibling rather than a child. + # + # Nodes should be wrapped before they're passed to \{Cssize.visit}. They will + # be automatically visited upon calling \{#pop}. + # + # This duck types as a [Sass::Tree::Node] for the purposes of + # tree-manipulation operations. + class Bubble + attr_accessor :node + attr_accessor :tabs + attr_accessor :group_end + + def initialize(node) + @node = node + @tabs = 0 + end + + def bubbles? + true + end + + def inspect + "(Bubble #{node.inspect})" + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/visitors/deep_copy.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/visitors/deep_copy.rb new file mode 100644 index 00000000..85d7ba53 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/visitors/deep_copy.rb @@ -0,0 +1,107 @@ +# A visitor for copying the full structure of a Sass tree. +class Sass::Tree::Visitors::DeepCopy < Sass::Tree::Visitors::Base + protected + + def visit(node) + super(node.dup) + end + + def visit_children(parent) + parent.children = parent.children.map {|c| visit(c)} + parent + end + + def visit_debug(node) + node.expr = node.expr.deep_copy + yield + end + + def visit_error(node) + node.expr = node.expr.deep_copy + yield + end + + def visit_each(node) + node.list = node.list.deep_copy + yield + end + + def visit_extend(node) + node.selector = node.selector.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c} + yield + end + + def visit_for(node) + node.from = node.from.deep_copy + node.to = node.to.deep_copy + yield + end + + def visit_function(node) + node.args = node.args.map {|k, v| [k.deep_copy, v && v.deep_copy]} + yield + end + + def visit_if(node) + node.expr = node.expr.deep_copy if node.expr + node.else = visit(node.else) if node.else + yield + end + + def visit_mixindef(node) + node.args = node.args.map {|k, v| [k.deep_copy, v && v.deep_copy]} + yield + end + + def visit_mixin(node) + node.args = node.args.map {|a| a.deep_copy} + node.keywords = Sass::Util::NormalizedMap.new(Hash[node.keywords.map {|k, v| [k, v.deep_copy]}]) + yield + end + + def visit_prop(node) + node.name = node.name.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c} + node.value = node.value.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c} + yield + end + + def visit_return(node) + node.expr = node.expr.deep_copy + yield + end + + def visit_rule(node) + node.rule = node.rule.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c} + yield + end + + def visit_variable(node) + node.expr = node.expr.deep_copy + yield + end + + def visit_warn(node) + node.expr = node.expr.deep_copy + yield + end + + def visit_while(node) + node.expr = node.expr.deep_copy + yield + end + + def visit_directive(node) + node.value = node.value.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c} + yield + end + + def visit_media(node) + node.query = node.query.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c} + yield + end + + def visit_supports(node) + node.condition = node.condition.deep_copy + yield + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/visitors/extend.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/visitors/extend.rb new file mode 100644 index 00000000..ffc3e216 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/visitors/extend.rb @@ -0,0 +1,64 @@ +# A visitor for performing selector inheritance on a static CSS tree. +# +# Destructively modifies the tree. +class Sass::Tree::Visitors::Extend < Sass::Tree::Visitors::Base + # Performs the given extensions on the static CSS tree based in `root`, then + # validates that all extends matched some selector. + # + # @param root [Tree::Node] The root node of the tree to visit. + # @param extends [Sass::Util::SubsetMap{Selector::Simple => + # Sass::Tree::Visitors::Cssize::Extend}] + # The extensions to perform on this tree. + # @return [Object] The return value of \{#visit} for the root node. + def self.visit(root, extends) + return if extends.empty? + new(extends).send(:visit, root) + check_extends_fired! extends + end + + protected + + def initialize(extends) + @parent_directives = [] + @extends = extends + end + + # If an exception is raised, this adds proper metadata to the backtrace. + def visit(node) + super(node) + rescue Sass::SyntaxError => e + e.modify_backtrace(:filename => node.filename, :line => node.line) + raise e + end + + # Keeps track of the current parent directives. + def visit_children(parent) + @parent_directives.push parent if parent.is_a?(Sass::Tree::DirectiveNode) + super + ensure + @parent_directives.pop if parent.is_a?(Sass::Tree::DirectiveNode) + end + + # Applies the extend to a single rule's selector. + def visit_rule(node) + node.resolved_rules = node.resolved_rules.do_extend(@extends, @parent_directives) + end + + class << self + private + + def check_extends_fired!(extends) + extends.each_value do |ex| + next if ex.success || ex.node.optional? + message = "\"#{ex.extender}\" failed to @extend \"#{ex.target.join}\"." + + # TODO(nweiz): this should use the Sass stack trace of the extend node. + raise Sass::SyntaxError.new(< ex.node.filename, :line => ex.node.line) +#{message} +The selector "#{ex.target.join}" was not found. +Use "@extend #{ex.target.join} !optional" if the extend should be able to fail. +MESSAGE + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/visitors/perform.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/visitors/perform.rb new file mode 100644 index 00000000..c9970142 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/visitors/perform.rb @@ -0,0 +1,572 @@ +# A visitor for converting a dynamic Sass tree into a static Sass tree. +class Sass::Tree::Visitors::Perform < Sass::Tree::Visitors::Base + @@function_name_deprecation = Sass::Deprecation.new + + class << self + # @param root [Tree::Node] The root node of the tree to visit. + # @param environment [Sass::Environment] The lexical environment. + # @return [Tree::Node] The resulting tree of static nodes. + def visit(root, environment = nil) + new(environment).send(:visit, root) + end + + # @api private + def perform_arguments(callable, args, splat, environment) + desc = "#{callable.type.capitalize} #{callable.name}" + downcase_desc = "#{callable.type} #{callable.name}" + + # All keywords are contained in splat.keywords for consistency, + # even if there were no splats passed in. + old_keywords_accessed = splat.keywords_accessed + keywords = splat.keywords + splat.keywords_accessed = old_keywords_accessed + + begin + unless keywords.empty? + unknown_args = Sass::Util.array_minus(keywords.keys, + callable.args.map {|var| var.first.underscored_name}) + if callable.splat && unknown_args.include?(callable.splat.underscored_name) + raise Sass::SyntaxError.new("Argument $#{callable.splat.name} of #{downcase_desc} " + + "cannot be used as a named argument.") + elsif unknown_args.any? + description = unknown_args.length > 1 ? 'the following arguments:' : 'an argument named' + raise Sass::SyntaxError.new("#{desc} doesn't have #{description} " + + "#{unknown_args.map {|name| "$#{name}"}.join ', '}.") + end + end + rescue Sass::SyntaxError => keyword_exception + end + + # If there's no splat, raise the keyword exception immediately. The actual + # raising happens in the ensure clause at the end of this function. + return if keyword_exception && !callable.splat + + splat_sep = :comma + if splat + args += splat.to_a + splat_sep = splat.separator + end + + if args.size > callable.args.size && !callable.splat + extra_args_because_of_splat = splat && args.size - splat.to_a.size <= callable.args.size + + takes = callable.args.size + passed = args.size + message = "#{desc} takes #{takes} argument#{'s' unless takes == 1} " + + "but #{passed} #{passed == 1 ? 'was' : 'were'} passed." + raise Sass::SyntaxError.new(message) unless extra_args_because_of_splat + # TODO: when the deprecation period is over, make this an error. + Sass::Util.sass_warn("WARNING: #{message}\n" + + environment.stack.to_s.gsub(/^/m, " " * 8) + "\n" + + "This will be an error in future versions of Sass.") + end + + env = Sass::Environment.new(callable.environment) + callable.args.zip(args[0...callable.args.length]) do |(var, default), value| + if value && keywords.has_key?(var.name) + raise Sass::SyntaxError.new("#{desc} was passed argument $#{var.name} " + + "both by position and by name.") + end + + value ||= keywords.delete(var.name) + value ||= default && default.perform(env) + raise Sass::SyntaxError.new("#{desc} is missing argument #{var.inspect}.") unless value + env.set_local_var(var.name, value) + end + + if callable.splat + rest = args[callable.args.length..-1] || [] + arg_list = Sass::Script::Value::ArgList.new(rest, keywords, splat_sep) + arg_list.options = env.options + env.set_local_var(callable.splat.name, arg_list) + end + + yield env + rescue StandardError => e + ensure + # If there's a keyword exception, we don't want to throw it immediately, + # because the invalid keywords may be part of a glob argument that should be + # passed on to another function. So we only raise it if we reach the end of + # this function *and* the keywords attached to the argument list glob object + # haven't been accessed. + # + # The keyword exception takes precedence over any Sass errors, but not over + # non-Sass exceptions. + if keyword_exception && + !(arg_list && arg_list.keywords_accessed) && + (e.nil? || e.is_a?(Sass::SyntaxError)) + raise keyword_exception + elsif e + raise e + end + end + + # @api private + # @return [Sass::Script::Value::ArgList] + def perform_splat(splat, performed_keywords, kwarg_splat, environment) + args, kwargs, separator = [], nil, :comma + + if splat + splat = splat.perform(environment) + separator = splat.separator || separator + if splat.is_a?(Sass::Script::Value::ArgList) + args = splat.to_a + kwargs = splat.keywords + elsif splat.is_a?(Sass::Script::Value::Map) + kwargs = arg_hash(splat) + else + args = splat.to_a + end + end + kwargs ||= Sass::Util::NormalizedMap.new + kwargs.update(performed_keywords) + + if kwarg_splat + kwarg_splat = kwarg_splat.perform(environment) + unless kwarg_splat.is_a?(Sass::Script::Value::Map) + raise Sass::SyntaxError.new("Variable keyword arguments must be a map " + + "(was #{kwarg_splat.inspect}).") + end + kwargs.update(arg_hash(kwarg_splat)) + end + + Sass::Script::Value::ArgList.new(args, kwargs, separator) + end + + private + + def arg_hash(map) + Sass::Util.map_keys(map.to_h) do |key| + next key.value if key.is_a?(Sass::Script::Value::String) + raise Sass::SyntaxError.new("Variable keyword argument map must have string keys.\n" + + "#{key.inspect} is not a string in #{map.inspect}.") + end + end + end + + protected + + def initialize(env) + @environment = env + @in_keyframes = false + @at_root_without_rule = false + end + + # If an exception is raised, this adds proper metadata to the backtrace. + def visit(node) + return super(node.dup) unless @environment + @environment.stack.with_base(node.filename, node.line) {super(node.dup)} + rescue Sass::SyntaxError => e + e.modify_backtrace(:filename => node.filename, :line => node.line) + raise e + end + + # Keeps track of the current environment. + def visit_children(parent) + with_environment Sass::Environment.new(@environment, parent.options) do + parent.children = super.flatten + parent + end + end + + # Runs a block of code with the current environment replaced with the given one. + # + # @param env [Sass::Environment] The new environment for the duration of the block. + # @yield A block in which the environment is set to `env`. + # @return [Object] The return value of the block. + def with_environment(env) + old_env, @environment = @environment, env + yield + ensure + @environment = old_env + end + + # Sets the options on the environment if this is the top-level root. + def visit_root(node) + yield + rescue Sass::SyntaxError => e + e.sass_template ||= node.template + raise e + end + + # Removes this node from the tree if it's a silent comment. + def visit_comment(node) + return [] if node.invisible? + node.resolved_value = run_interp_no_strip(node.value) + node.resolved_value.gsub!(/\\([\\#])/, '\1') + node + end + + # Prints the expression to STDERR. + def visit_debug(node) + res = node.expr.perform(@environment) + if res.is_a?(Sass::Script::Value::String) + res = res.value + else + res = res.to_sass + end + if node.filename + Sass::Util.sass_warn "#{node.filename}:#{node.line} DEBUG: #{res}" + else + Sass::Util.sass_warn "Line #{node.line} DEBUG: #{res}" + end + [] + end + + # Throws the expression as an error. + def visit_error(node) + res = node.expr.perform(@environment) + if res.is_a?(Sass::Script::Value::String) + res = res.value + else + res = res.to_sass + end + raise Sass::SyntaxError.new(res) + end + + # Runs the child nodes once for each value in the list. + def visit_each(node) + list = node.list.perform(@environment) + + with_environment Sass::SemiGlobalEnvironment.new(@environment) do + list.to_a.map do |value| + if node.vars.length == 1 + @environment.set_local_var(node.vars.first, value) + else + node.vars.zip(value.to_a) do |(var, sub_value)| + @environment.set_local_var(var, sub_value || Sass::Script::Value::Null.new) + end + end + node.children.map {|c| visit(c)} + end.flatten + end + end + + # Runs SassScript interpolation in the selector, + # and then parses the result into a {Sass::Selector::CommaSequence}. + def visit_extend(node) + parser = Sass::SCSS::StaticParser.new(run_interp(node.selector), + node.filename, node.options[:importer], node.line) + node.resolved_selector = parser.parse_selector + node + end + + # Runs the child nodes once for each time through the loop, varying the variable each time. + def visit_for(node) + from = node.from.perform(@environment) + to = node.to.perform(@environment) + from.assert_int! + to.assert_int! + + to = to.coerce(from.numerator_units, from.denominator_units) + direction = from.to_i > to.to_i ? -1 : 1 + range = Range.new(direction * from.to_i, direction * to.to_i, node.exclusive) + + with_environment Sass::SemiGlobalEnvironment.new(@environment) do + range.map do |i| + @environment.set_local_var(node.var, + Sass::Script::Value::Number.new(direction * i, + from.numerator_units, from.denominator_units)) + node.children.map {|c| visit(c)} + end.flatten + end + end + + # Loads the function into the environment. + def visit_function(node) + env = Sass::Environment.new(@environment, node.options) + + if node.normalized_name == 'calc' || node.normalized_name == 'element' || + node.name == 'expression' || node.name == 'url' + @@function_name_deprecation.warn(node.filename, node.line, < e + e.modify_backtrace(:filename => node.imported_file.options[:filename]) + e.add_backtrace(:filename => node.filename, :line => node.line) + raise e + end + end + + # Loads a mixin into the environment. + def visit_mixindef(node) + env = Sass::Environment.new(@environment, node.options) + @environment.set_local_mixin(node.name, + Sass::Callable.new(node.name, node.args, node.splat, env, + node.children, node.has_content, "mixin", :stylesheet)) + [] + end + + # Runs a mixin. + def visit_mixin(node) + @environment.stack.with_mixin(node.filename, node.line, node.name) do + mixin = @environment.mixin(node.name) + raise Sass::SyntaxError.new("Undefined mixin '#{node.name}'.") unless mixin + + if node.has_children && !mixin.has_content + raise Sass::SyntaxError.new(%(Mixin "#{node.name}" does not accept a content block.)) + end + + args = node.args.map {|a| a.perform(@environment)} + keywords = Sass::Util.map_vals(node.keywords) {|v| v.perform(@environment)} + splat = self.class.perform_splat(node.splat, keywords, node.kwarg_splat, @environment) + + self.class.perform_arguments(mixin, args, splat, @environment) do |env| + env.caller = Sass::Environment.new(@environment) + env.content = [node.children, @environment] if node.has_children + + trace_node = Sass::Tree::TraceNode.from_node(node.name, node) + with_environment(env) {trace_node.children = mixin.tree.map {|c| visit(c)}.flatten} + trace_node + end + end + rescue Sass::SyntaxError => e + e.modify_backtrace(:mixin => node.name, :line => node.line) + e.add_backtrace(:line => node.line) + raise e + end + + def visit_content(node) + content, content_env = @environment.content + return [] unless content + @environment.stack.with_mixin(node.filename, node.line, '@content') do + trace_node = Sass::Tree::TraceNode.from_node('@content', node) + content_env = Sass::Environment.new(content_env) + content_env.caller = Sass::Environment.new(@environment) + with_environment(content_env) do + trace_node.children = content.map {|c| visit(c.dup)}.flatten + end + trace_node + end + rescue Sass::SyntaxError => e + e.modify_backtrace(:mixin => '@content', :line => node.line) + e.add_backtrace(:line => node.line) + raise e + end + + # Runs any SassScript that may be embedded in a property. + def visit_prop(node) + node.resolved_name = run_interp(node.name) + + # If the node's value is just a variable or similar, we may get a useful + # source range from evaluating it. + if node.value.length == 1 && node.value.first.is_a?(Sass::Script::Tree::Node) + result = node.value.first.perform(@environment) + node.resolved_value = result.to_s + node.value_source_range = result.source_range if result.source_range + elsif node.custom_property? + node.resolved_value = run_interp_no_strip(node.value) + else + node.resolved_value = run_interp(node.value) + end + + yield + end + + # Returns the value of the expression. + def visit_return(node) + throw :_sass_return, node.expr.perform(@environment) + end + + # Runs SassScript interpolation in the selector, + # and then parses the result into a {Sass::Selector::CommaSequence}. + def visit_rule(node) + old_at_root_without_rule = @at_root_without_rule + parser = Sass::SCSS::StaticParser.new(run_interp(node.rule), + node.filename, node.options[:importer], node.line) + if @in_keyframes + keyframe_rule_node = Sass::Tree::KeyframeRuleNode.new(parser.parse_keyframes_selector) + keyframe_rule_node.options = node.options + keyframe_rule_node.line = node.line + keyframe_rule_node.filename = node.filename + keyframe_rule_node.source_range = node.source_range + keyframe_rule_node.has_children = node.has_children + with_environment Sass::Environment.new(@environment, node.options) do + keyframe_rule_node.children = node.children.map {|c| visit(c)}.flatten + end + keyframe_rule_node + else + @at_root_without_rule = false + node.parsed_rules ||= parser.parse_selector + node.resolved_rules = node.parsed_rules.resolve_parent_refs( + @environment.selector, !old_at_root_without_rule) + node.stack_trace = @environment.stack.to_s if node.options[:trace_selectors] + with_environment Sass::Environment.new(@environment, node.options) do + @environment.selector = node.resolved_rules + node.children = node.children.map {|c| visit(c)}.flatten + end + node + end + ensure + @at_root_without_rule = old_at_root_without_rule + end + + # Sets a variable that indicates that the first level of rule nodes + # shouldn't include the parent selector by default. + def visit_atroot(node) + if node.query + parser = Sass::SCSS::StaticParser.new(run_interp(node.query), + node.filename, node.options[:importer], node.line) + node.resolved_type, node.resolved_value = parser.parse_static_at_root_query + else + node.resolved_type, node.resolved_value = :without, ['rule'] + end + + old_at_root_without_rule = @at_root_without_rule + old_in_keyframes = @in_keyframes + @at_root_without_rule = true if node.exclude?('rule') + @in_keyframes = false if node.exclude?('keyframes') + yield + ensure + @in_keyframes = old_in_keyframes + @at_root_without_rule = old_at_root_without_rule + end + + # Loads the new variable value into the environment. + def visit_variable(node) + env = @environment + env = env.global_env if node.global + if node.guarded + var = env.var(node.name) + return [] if var && !var.null? + end + + val = node.expr.perform(@environment) + if node.expr.source_range + val.source_range = node.expr.source_range + else + val.source_range = node.source_range + end + env.set_var(node.name, val) + [] + end + + # Prints the expression to STDERR with a stylesheet trace. + def visit_warn(node) + res = node.expr.perform(@environment) + res = res.value if res.is_a?(Sass::Script::Value::String) + @environment.stack.with_directive(node.filename, node.line, "@warn") do + msg = "WARNING: #{res}\n " + msg << @environment.stack.to_s.gsub("\n", "\n ") << "\n" + Sass::Util.sass_warn msg + end + [] + end + + # Runs the child nodes until the continuation expression becomes false. + def visit_while(node) + children = [] + with_environment Sass::SemiGlobalEnvironment.new(@environment) do + children += node.children.map {|c| visit(c)} while node.expr.perform(@environment).to_bool + end + children.flatten + end + + def visit_directive(node) + node.resolved_value = run_interp(node.value) + old_in_keyframes, @in_keyframes = @in_keyframes, node.normalized_name == "@keyframes" + with_environment Sass::Environment.new(@environment) do + node.children = node.children.map {|c| visit(c)}.flatten + node + end + ensure + @in_keyframes = old_in_keyframes + end + + def visit_media(node) + parser = Sass::SCSS::StaticParser.new(run_interp(node.query), + node.filename, node.options[:importer], node.line) + node.resolved_query ||= parser.parse_media_query_list + yield + end + + def visit_supports(node) + node.condition = node.condition.deep_copy + node.condition.perform(@environment) + yield + end + + def visit_cssimport(node) + node.resolved_uri = run_interp([node.uri]) + if node.query && !node.query.empty? + parser = Sass::SCSS::StaticParser.new(run_interp(node.query), + node.filename, node.options[:importer], node.line) + node.resolved_query ||= parser.parse_media_query_list + end + if node.supports_condition + node.supports_condition.perform(@environment) + end + yield + end + + private + + def run_interp_no_strip(text) + text.map do |r| + next r if r.is_a?(String) + r.perform(@environment).to_s(:quote => :none) + end.join + end + + def run_interp(text) + Sass::Util.strip_except_escapes(run_interp_no_strip(text)) + end + + def handle_import_loop!(node) + msg = "An @import loop has been found:" + files = @environment.stack.frames.select {|f| f.is_import?}.map {|f| f.filename}.compact + if node.filename == node.imported_file.options[:filename] + raise Sass::SyntaxError.new("#{msg} #{node.filename} imports itself") + end + + files << node.filename << node.imported_file.options[:filename] + msg << "\n" << files.each_cons(2).map do |m1, m2| + " #{m1} imports #{m2}" + end.join("\n") + raise Sass::SyntaxError.new(msg) + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/visitors/set_options.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/visitors/set_options.rb new file mode 100644 index 00000000..75f4a2bd --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/visitors/set_options.rb @@ -0,0 +1,139 @@ +# A visitor for setting options on the Sass tree +class Sass::Tree::Visitors::SetOptions < Sass::Tree::Visitors::Base + # @param root [Tree::Node] The root node of the tree to visit. + # @param options [{Symbol => Object}] The options has to set. + def self.visit(root, options); new(options).send(:visit, root); end + + protected + + def initialize(options) + @options = options + end + + def visit(node) + node.instance_variable_set('@options', @options) + super + end + + def visit_comment(node) + node.value.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)} + yield + end + + def visit_debug(node) + node.expr.options = @options + yield + end + + def visit_error(node) + node.expr.options = @options + yield + end + + def visit_each(node) + node.list.options = @options + yield + end + + def visit_extend(node) + node.selector.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)} + yield + end + + def visit_for(node) + node.from.options = @options + node.to.options = @options + yield + end + + def visit_function(node) + node.args.each do |k, v| + k.options = @options + v.options = @options if v + end + node.splat.options = @options if node.splat + yield + end + + def visit_if(node) + node.expr.options = @options if node.expr + visit(node.else) if node.else + yield + end + + def visit_import(node) + # We have no good way of propagating the new options through an Engine + # instance, so we just null it out. This also lets us avoid caching an + # imported Engine along with the importing source tree. + node.imported_file = nil + yield + end + + def visit_mixindef(node) + node.args.each do |k, v| + k.options = @options + v.options = @options if v + end + node.splat.options = @options if node.splat + yield + end + + def visit_mixin(node) + node.args.each {|a| a.options = @options} + node.keywords.each {|_k, v| v.options = @options} + node.splat.options = @options if node.splat + node.kwarg_splat.options = @options if node.kwarg_splat + yield + end + + def visit_prop(node) + node.name.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)} + node.value.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)} + yield + end + + def visit_return(node) + node.expr.options = @options + yield + end + + def visit_rule(node) + node.rule.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)} + yield + end + + def visit_variable(node) + node.expr.options = @options + yield + end + + def visit_warn(node) + node.expr.options = @options + yield + end + + def visit_while(node) + node.expr.options = @options + yield + end + + def visit_directive(node) + node.value.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)} + yield + end + + def visit_media(node) + node.query.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)} + yield + end + + def visit_cssimport(node) + node.query.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)} if node.query + yield + end + + def visit_supports(node) + node.condition.options = @options + yield + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/visitors/to_css.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/visitors/to_css.rb new file mode 100644 index 00000000..29ec285e --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/visitors/to_css.rb @@ -0,0 +1,436 @@ +# A visitor for converting a Sass tree into CSS. +class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base + # The source mapping for the generated CSS file. This is only set if + # `build_source_mapping` is passed to the constructor and \{Sass::Engine#render} has been + # run. + attr_reader :source_mapping + + # @param build_source_mapping [Boolean] Whether to build a + # \{Sass::Source::Map} while creating the CSS output. The mapping will + # be available from \{#source\_mapping} after the visitor has completed. + def initialize(build_source_mapping = false) + @tabs = 0 + @line = 1 + @offset = 1 + @result = String.new("") + @source_mapping = build_source_mapping ? Sass::Source::Map.new : nil + @lstrip = nil + @in_directive = false + end + + # Runs the visitor on `node`. + # + # @param node [Sass::Tree::Node] The root node of the tree to convert to CSS> + # @return [String] The CSS output. + def visit(node) + super + rescue Sass::SyntaxError => e + e.modify_backtrace(:filename => node.filename, :line => node.line) + raise e + end + + protected + + def with_tabs(tabs) + old_tabs, @tabs = @tabs, tabs + yield + ensure + @tabs = old_tabs + end + + # Associate all output produced in a block with a given node. Used for source + # mapping. + def for_node(node, attr_prefix = nil) + return yield unless @source_mapping + start_pos = Sass::Source::Position.new(@line, @offset) + yield + + range_attr = attr_prefix ? :"#{attr_prefix}_source_range" : :source_range + return if node.invisible? || !node.send(range_attr) + source_range = node.send(range_attr) + target_end_pos = Sass::Source::Position.new(@line, @offset) + target_range = Sass::Source::Range.new(start_pos, target_end_pos, nil) + @source_mapping.add(source_range, target_range) + end + + def trailing_semicolon? + @result.end_with?(";") && !@result.end_with?('\;') + end + + # Move the output cursor back `chars` characters. + def erase!(chars) + return if chars == 0 + str = @result.slice!(-chars..-1) + newlines = str.count("\n") + if newlines > 0 + @line -= newlines + @offset = @result[@result.rindex("\n") || 0..-1].size + else + @offset -= chars + end + end + + # Avoid allocating lots of new strings for `#output`. This is important + # because `#output` is called all the time. + NEWLINE = "\n" + + # Add `s` to the output string and update the line and offset information + # accordingly. + def output(s) + if @lstrip + s = s.gsub(/\A\s+/, "") + @lstrip = false + end + + newlines = s.count(NEWLINE) + if newlines > 0 + @line += newlines + @offset = s[s.rindex(NEWLINE)..-1].size + else + @offset += s.size + end + + @result << s + end + + # Strip all trailing whitespace from the output string. + def rstrip! + erase! @result.length - 1 - (@result.rindex(/[^\s]/) || -1) + end + + # lstrip the first output in the given block. + def lstrip + old_lstrip = @lstrip + @lstrip = true + yield + ensure + @lstrip &&= old_lstrip + end + + # Prepend `prefix` to the output string. + def prepend!(prefix) + @result.insert 0, prefix + return unless @source_mapping + + line_delta = prefix.count("\n") + offset_delta = prefix.gsub(/.*\n/, '').size + @source_mapping.shift_output_offsets(offset_delta) + @source_mapping.shift_output_lines(line_delta) + end + + def visit_root(node) + node.children.each do |child| + next if child.invisible? + visit(child) + next if node.style == :compressed + output "\n" + next unless child.is_a?(Sass::Tree::DirectiveNode) && child.has_children && !child.bubbles? + output "\n" + end + rstrip! + if node.style == :compressed && trailing_semicolon? + erase! 1 + end + return "" if @result.empty? + + output "\n" + + unless @result.ascii_only? + if node.style == :compressed + # A byte order mark is sufficient to tell browsers that this + # file is UTF-8 encoded, and will override any other detection + # methods as per http://encoding.spec.whatwg.org/#decode-and-encode. + prepend! "\uFEFF" + else + prepend! "@charset \"UTF-8\";\n" + end + end + + @result + rescue Sass::SyntaxError => e + e.sass_template ||= node.template + raise e + end + + def visit_charset(node) + for_node(node) {output("@charset \"#{node.name}\";")} + end + + def visit_comment(node) + return if node.invisible? + spaces = (' ' * [@tabs - node.resolved_value[/^ */].size, 0].max) + output(spaces) + + content = node.resolved_value.split("\n").join("\n" + spaces) + if node.type == :silent + content.gsub!(%r{^(\s*)//(.*)$}) {"#{$1}/*#{$2} */"} + end + if (node.style == :compact || node.style == :compressed) && node.type != :loud + content.gsub!(%r{\n +(\* *(?!/))?}, ' ') + end + for_node(node) {output(content)} + end + + def visit_directive(node) + was_in_directive = @in_directive + tab_str = ' ' * @tabs + if !node.has_children || node.children.empty? + output(tab_str) + for_node(node) {output(node.resolved_value)} + if node.has_children + output("#{' ' unless node.style == :compressed}{}") + elsif node.children.empty? + output(";") + end + return + end + + @in_directive ||= !node.is_a?(Sass::Tree::MediaNode) + output(tab_str) if node.style != :compressed + for_node(node) {output(node.resolved_value)} + output(node.style == :compressed ? "{" : " {") + output(node.style == :compact ? ' ' : "\n") if node.style != :compressed + + had_children = true + first = true + node.children.each do |child| + next if child.invisible? + if node.style == :compact + if child.is_a?(Sass::Tree::PropNode) + with_tabs(first || !had_children ? 0 : @tabs + 1) do + visit(child) + output(' ') + end + else + unless had_children + erase! 1 + output "\n" + end + + if first + lstrip {with_tabs(@tabs + 1) {visit(child)}} + else + with_tabs(@tabs + 1) {visit(child)} + end + + rstrip! + output "\n" + end + had_children = child.has_children + first = false + elsif node.style == :compressed + unless had_children + output(";") unless trailing_semicolon? + end + with_tabs(0) {visit(child)} + had_children = child.has_children + else + with_tabs(@tabs + 1) {visit(child)} + output "\n" + end + end + rstrip! + if node.style == :compressed && trailing_semicolon? + erase! 1 + end + if node.style == :expanded + output("\n#{tab_str}") + elsif node.style != :compressed + output(" ") + end + output("}") + ensure + @in_directive = was_in_directive + end + + def visit_media(node) + with_tabs(@tabs + node.tabs) {visit_directive(node)} + output("\n") if node.style != :compressed && node.group_end + end + + def visit_supports(node) + visit_media(node) + end + + def visit_cssimport(node) + visit_directive(node) + end + + def visit_prop(node) + return if node.resolved_value.empty? && !node.custom_property? + tab_str = ' ' * (@tabs + node.tabs) + output(tab_str) + for_node(node, :name) {output(node.resolved_name)} + output(":") + output(" ") unless node.style == :compressed || node.custom_property? + for_node(node, :value) do + output(if node.custom_property? + format_custom_property_value(node) + else + node.resolved_value + end) + end + output(";") unless node.style == :compressed + end + + def visit_rule(node) + with_tabs(@tabs + node.tabs) do + rule_separator = node.style == :compressed ? ',' : ', ' + line_separator = + case node.style + when :nested, :expanded; "\n" + when :compressed; "" + else; " " + end + rule_indent = ' ' * @tabs + per_rule_indent, total_indent = if [:nested, :expanded].include?(node.style) + [rule_indent, ''] + else + ['', rule_indent] + end + + joined_rules = node.resolved_rules.members.map do |seq| + next if seq.invisible? + rule_part = seq.to_s(style: node.style, placeholder: false) + if node.style == :compressed + rule_part.gsub!(/([^,])\s*\n\s*/m, '\1 ') + rule_part.gsub!(/\s*([+>])\s*/m, '\1') + rule_part.gsub!(/nth([^( ]*)\(([^)]*)\)/m) do |match| + match.tr(" \t\n", "") + end + rule_part = Sass::Util.strip_except_escapes(rule_part) + end + rule_part + end.compact.join(rule_separator) + + joined_rules.lstrip! + joined_rules.gsub!(/\s*\n\s*/, "#{line_separator}#{per_rule_indent}") + + old_spaces = ' ' * @tabs + if node.style != :compressed + if node.options[:debug_info] && !@in_directive + visit(debug_info_rule(node.debug_info, node.options)) + output "\n" + elsif node.options[:trace_selectors] + output("#{old_spaces}/* ") + output(node.stack_trace.gsub("\n", "\n #{old_spaces}")) + output(" */\n") + elsif node.options[:line_comments] + output("#{old_spaces}/* line #{node.line}") + + if node.filename + relative_filename = + if node.options[:css_filename] + begin + Sass::Util.relative_path_from( + node.filename, File.dirname(node.options[:css_filename])).to_s + rescue ArgumentError + nil + end + end + relative_filename ||= node.filename + output(", #{relative_filename}") + end + + output(" */\n") + end + end + + end_props, trailer, tabs = '', '', 0 + if node.style == :compact + separator, end_props, bracket = ' ', ' ', ' { ' + trailer = "\n" if node.group_end + elsif node.style == :compressed + separator, bracket = ';', '{' + else + tabs = @tabs + 1 + separator, bracket = "\n", " {\n" + trailer = "\n" if node.group_end + end_props = (node.style == :expanded ? "\n" + old_spaces : ' ') + end + output(total_indent + per_rule_indent) + for_node(node, :selector) {output(joined_rules)} + output(bracket) + + with_tabs(tabs) do + node.children.each_with_index do |child, i| + if i > 0 + if separator.start_with?(";") && trailing_semicolon? + erase! 1 + end + output(separator) + end + visit(child) + end + end + if node.style == :compressed && trailing_semicolon? + erase! 1 + end + + output(end_props) + output("}" + trailer) + end + end + + def visit_keyframerule(node) + visit_directive(node) + end + + private + + # Reformats the value of `node` so that it's nicely indented, preserving its + # existing relative indentation. + # + # @param node [Sass::Script::Tree::PropNode] A custom property node. + # @return [String] + def format_custom_property_value(node) + value = node.resolved_value.sub(/\n[ \t\r\f\n]*\Z/, ' ') + if node.style == :compact || node.style == :compressed || !value.include?("\n") + # Folding not involving newlines was done in the parser. We can safely + # fold newlines here because tokens like strings can't contain literal + # newlines, so we know any adjacent whitespace is tokenized as whitespace. + return node.resolved_value.gsub(/[ \t\r\f]*\n[ \t\r\f\n]*/, ' ') + end + + # Find the smallest amount of indentation in the custom property and use + # that as the base indentation level. + lines = value.split("\n") + indented_lines = lines[1..-1] + min_indentation = indented_lines. + map {|line| line[/^[ \t]*/]}. + reject {|line| line.empty?}. + min_by {|line| line.length} + + # Limit the base indentation to the same indentation level as the node name + # so that if *every* line is indented relative to the property name that's + # preserved. + if node.name_source_range + base_indentation = min_indentation[0...node.name_source_range.start_pos.offset - 1] + end + + lines.first + "\n" + indented_lines.join("\n").gsub(/^#{base_indentation}/, ' ' * @tabs) + end + + def debug_info_rule(debug_info, options) + node = Sass::Tree::DirectiveNode.resolved("@media -sass-debug-info") + debug_info.map {|k, v| [k.to_s, v.to_s]}.to_a.each do |k, v| + rule = Sass::Tree::RuleNode.new([""]) + rule.resolved_rules = Sass::Selector::CommaSequence.new( + [Sass::Selector::Sequence.new( + [Sass::Selector::SimpleSequence.new( + [Sass::Selector::Element.new(k.to_s.gsub(/[^\w-]/, "\\\\\\0"), nil)], + false) + ]) + ]) + prop = Sass::Tree::PropNode.new([""], [""], :new) + prop.resolved_name = "font-family" + prop.resolved_value = Sass::SCSS::RX.escape_ident(v.to_s) + rule << prop + node << rule + end + node.options = options.merge(:debug_info => false, + :line_comments => false, + :style => :compressed) + node + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/warn_node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/warn_node.rb new file mode 100644 index 00000000..4af4789a --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/warn_node.rb @@ -0,0 +1,18 @@ +module Sass + module Tree + # A dynamic node representing a Sass `@warn` statement. + # + # @see Sass::Tree + class WarnNode < Node + # The expression to print. + # @return [Script::Tree::Node] + attr_accessor :expr + + # @param expr [Script::Tree::Node] The expression to print + def initialize(expr) + @expr = expr + super() + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/while_node.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/while_node.rb new file mode 100644 index 00000000..93529f0f --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/tree/while_node.rb @@ -0,0 +1,18 @@ +require 'sass/tree/node' + +module Sass::Tree + # A dynamic node representing a Sass `@while` loop. + # + # @see Sass::Tree + class WhileNode < Node + # The parse tree for the continuation expression. + # @return [Script::Tree::Node] + attr_accessor :expr + + # @param expr [Script::Tree::Node] See \{#expr} + def initialize(expr) + @expr = expr + super() + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/util.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/util.rb new file mode 100644 index 00000000..54606fdd --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/util.rb @@ -0,0 +1,1137 @@ +# -*- coding: utf-8 -*- +require 'erb' +require 'set' +require 'enumerator' +require 'stringio' +require 'rbconfig' +require 'uri' +require 'thread' +require 'pathname' + +require 'sass/root' +require 'sass/util/subset_map' + +module Sass + # A module containing various useful functions. + module Util + extend self + + # An array of ints representing the Ruby version number. + # @api public + RUBY_VERSION_COMPONENTS = RUBY_VERSION.split(".").map {|s| s.to_i} + + # The Ruby engine we're running under. Defaults to `"ruby"` + # if the top-level constant is undefined. + # @api public + RUBY_ENGINE = defined?(::RUBY_ENGINE) ? ::RUBY_ENGINE : "ruby" + + # Returns the path of a file relative to the Sass root directory. + # + # @param file [String] The filename relative to the Sass root + # @return [String] The filename relative to the the working directory + def scope(file) + File.join(Sass::ROOT_DIR, file) + end + + # Maps the keys in a hash according to a block. + # + # @example + # map_keys({:foo => "bar", :baz => "bang"}) {|k| k.to_s} + # #=> {"foo" => "bar", "baz" => "bang"} + # @param hash [Hash] The hash to map + # @yield [key] A block in which the keys are transformed + # @yieldparam key [Object] The key that should be mapped + # @yieldreturn [Object] The new value for the key + # @return [Hash] The mapped hash + # @see #map_vals + # @see #map_hash + def map_keys(hash) + map_hash(hash) {|k, v| [yield(k), v]} + end + + # Maps the values in a hash according to a block. + # + # @example + # map_values({:foo => "bar", :baz => "bang"}) {|v| v.to_sym} + # #=> {:foo => :bar, :baz => :bang} + # @param hash [Hash] The hash to map + # @yield [value] A block in which the values are transformed + # @yieldparam value [Object] The value that should be mapped + # @yieldreturn [Object] The new value for the value + # @return [Hash] The mapped hash + # @see #map_keys + # @see #map_hash + def map_vals(hash) + # We don't delegate to map_hash for performance here + # because map_hash does more than is necessary. + rv = hash.class.new + hash = hash.as_stored if hash.is_a?(NormalizedMap) + hash.each do |k, v| + rv[k] = yield(v) + end + rv + end + + # Maps the key-value pairs of a hash according to a block. + # + # @example + # map_hash({:foo => "bar", :baz => "bang"}) {|k, v| [k.to_s, v.to_sym]} + # #=> {"foo" => :bar, "baz" => :bang} + # @param hash [Hash] The hash to map + # @yield [key, value] A block in which the key-value pairs are transformed + # @yieldparam [key] The hash key + # @yieldparam [value] The hash value + # @yieldreturn [(Object, Object)] The new value for the `[key, value]` pair + # @return [Hash] The mapped hash + # @see #map_keys + # @see #map_vals + def map_hash(hash) + # Copy and modify is more performant than mapping to an array and using + # to_hash on the result. + rv = hash.class.new + hash.each do |k, v| + new_key, new_value = yield(k, v) + new_key = hash.denormalize(new_key) if hash.is_a?(NormalizedMap) && new_key == k + rv[new_key] = new_value + end + rv + end + + # Computes the powerset of the given array. + # This is the set of all subsets of the array. + # + # @example + # powerset([1, 2, 3]) #=> + # Set[Set[], Set[1], Set[2], Set[3], Set[1, 2], Set[2, 3], Set[1, 3], Set[1, 2, 3]] + # @param arr [Enumerable] + # @return [Set] The subsets of `arr` + def powerset(arr) + arr.inject([Set.new].to_set) do |powerset, el| + new_powerset = Set.new + powerset.each do |subset| + new_powerset << subset + new_powerset << subset + [el] + end + new_powerset + end + end + + # Restricts a number to falling within a given range. + # Returns the number if it falls within the range, + # or the closest value in the range if it doesn't. + # + # @param value [Numeric] + # @param range [Range] + # @return [Numeric] + def restrict(value, range) + [[value, range.first].max, range.last].min + end + + # Like [Fixnum.round], but leaves rooms for slight floating-point + # differences. + # + # @param value [Numeric] + # @return [Numeric] + def round(value) + # If the number is within epsilon of X.5, round up (or down for negative + # numbers). + mod = value % 1 + mod_is_half = (mod - 0.5).abs < Script::Value::Number.epsilon + if value > 0 + !mod_is_half && mod < 0.5 ? value.floor : value.ceil + else + mod_is_half || mod < 0.5 ? value.floor : value.ceil + end + end + + # Concatenates all strings that are adjacent in an array, + # while leaving other elements as they are. + # + # @example + # merge_adjacent_strings([1, "foo", "bar", 2, "baz"]) + # #=> [1, "foobar", 2, "baz"] + # @param arr [Array] + # @return [Array] The enumerable with strings merged + def merge_adjacent_strings(arr) + # Optimize for the common case of one element + return arr if arr.size < 2 + arr.inject([]) do |a, e| + if e.is_a?(String) + if a.last.is_a?(String) + a.last << e + else + a << e.dup + end + else + a << e + end + a + end + end + + # Non-destructively replaces all occurrences of a subsequence in an array + # with another subsequence. + # + # @example + # replace_subseq([1, 2, 3, 4, 5], [2, 3], [:a, :b]) + # #=> [1, :a, :b, 4, 5] + # + # @param arr [Array] The array whose subsequences will be replaced. + # @param subseq [Array] The subsequence to find and replace. + # @param replacement [Array] The sequence that `subseq` will be replaced with. + # @return [Array] `arr` with `subseq` replaced with `replacement`. + def replace_subseq(arr, subseq, replacement) + new = [] + matched = [] + i = 0 + arr.each do |elem| + if elem != subseq[i] + new.push(*matched) + matched = [] + i = 0 + new << elem + next + end + + if i == subseq.length - 1 + matched = [] + i = 0 + new.push(*replacement) + else + matched << elem + i += 1 + end + end + new.push(*matched) + new + end + + # Intersperses a value in an enumerable, as would be done with `Array#join` + # but without concatenating the array together afterwards. + # + # @param enum [Enumerable] + # @param val + # @return [Array] + def intersperse(enum, val) + enum.inject([]) {|a, e| a << e << val}[0...-1] + end + + def slice_by(enum) + results = [] + enum.each do |value| + key = yield(value) + if !results.empty? && results.last.first == key + results.last.last << value + else + results << [key, [value]] + end + end + results + end + + # Substitutes a sub-array of one array with another sub-array. + # + # @param ary [Array] The array in which to make the substitution + # @param from [Array] The sequence of elements to replace with `to` + # @param to [Array] The sequence of elements to replace `from` with + def substitute(ary, from, to) + res = ary.dup + i = 0 + while i < res.size + if res[i...i + from.size] == from + res[i...i + from.size] = to + end + i += 1 + end + res + end + + # Destructively strips whitespace from the beginning and end of the first + # and last elements, respectively, in the array (if those elements are + # strings). Preserves CSS escapes at the end of the array. + # + # @param arr [Array] + # @return [Array] `arr` + def strip_string_array(arr) + arr.first.lstrip! if arr.first.is_a?(String) + arr[-1] = Sass::Util.rstrip_except_escapes(arr[-1]) if arr.last.is_a?(String) + arr + end + + # Normalizes identifier escapes. + # + # See https://github.com/sass/language/blob/master/accepted/identifier-escapes.md. + # + # @param ident [String] + # @return [String] + def normalize_ident_escapes(ident, start: true) + ident.gsub(/(^)?(#{Sass::SCSS::RX::ESCAPE})/) do |s| + at_start = start && $1 + char = escaped_char(s) + next char if char =~ (at_start ? Sass::SCSS::RX::NMSTART : Sass::SCSS::RX::NMCHAR) + if char =~ (at_start ? /[\x0-\x1F\x7F0-9]/ : /[\x0-\x1F\x7F]/) + "\\#{char.ord.to_s(16)} " + else + "\\#{char}" + end + end + end + + # Returns the character encoded by the given escape sequence. + # + # @param escape [String] + # @return [String] + def escaped_char(escape) + if escape =~ /^\\([0-9a-fA-F]{1,6})[ \t\r\n\f]?/ + $1.to_i(16).chr(Encoding::UTF_8) + else + escape[1] + end + end + + # Like [String#strip], but preserves escaped whitespace at the end of the + # string. + # + # @param string [String] + # @return [String] + def strip_except_escapes(string) + rstrip_except_escapes(string.lstrip) + end + + # Like [String#rstrip], but preserves escaped whitespace at the end of the + # string. + # + # @param string [String] + # @return [String] + def rstrip_except_escapes(string) + string.sub(/(?] + # @return [Array] + # + # @example + # paths([[1, 2], [3, 4], [5]]) #=> + # # [[1, 3, 5], + # # [2, 3, 5], + # # [1, 4, 5], + # # [2, 4, 5]] + def paths(arrs) + arrs.inject([[]]) do |paths, arr| + arr.map {|e| paths.map {|path| path + [e]}}.flatten(1) + end + end + + # Computes a single longest common subsequence for `x` and `y`. + # If there are more than one longest common subsequences, + # the one returned is that which starts first in `x`. + # + # @param x [Array] + # @param y [Array] + # @yield [a, b] An optional block to use in place of a check for equality + # between elements of `x` and `y`. + # @yieldreturn [Object, nil] If the two values register as equal, + # this will return the value to use in the LCS array. + # @return [Array] The LCS + def lcs(x, y, &block) + x = [nil, *x] + y = [nil, *y] + block ||= proc {|a, b| a == b && a} + lcs_backtrace(lcs_table(x, y, &block), x, y, x.size - 1, y.size - 1, &block) + end + + # Like `String.upcase`, but only ever upcases ASCII letters. + def upcase(string) + return string.upcase unless ruby2_4? + string.upcase(:ascii) + end + + # Like `String.downcase`, but only ever downcases ASCII letters. + def downcase(string) + return string.downcase unless ruby2_4? + string.downcase(:ascii) + end + + # Returns a sub-array of `minuend` containing only elements that are also in + # `subtrahend`. Ensures that the return value has the same order as + # `minuend`, even on Rubinius where that's not guaranteed by `Array#-`. + # + # @param minuend [Array] + # @param subtrahend [Array] + # @return [Array] + def array_minus(minuend, subtrahend) + return minuend - subtrahend unless rbx? + set = Set.new(minuend) - subtrahend + minuend.select {|e| set.include?(e)} + end + + # Returns the maximum of `val1` and `val2`. We use this over \{Array.max} to + # avoid unnecessary garbage collection. + def max(val1, val2) + val1 > val2 ? val1 : val2 + end + + # Returns the minimum of `val1` and `val2`. We use this over \{Array.min} to + # avoid unnecessary garbage collection. + def min(val1, val2) + val1 <= val2 ? val1 : val2 + end + + # Returns a string description of the character that caused an + # `Encoding::UndefinedConversionError`. + # + # @param e [Encoding::UndefinedConversionError] + # @return [String] + def undefined_conversion_error_char(e) + # Rubinius (as of 2.0.0.rc1) pre-quotes the error character. + return e.error_char if rbx? + # JRuby (as of 1.7.2) doesn't have an error_char field on + # Encoding::UndefinedConversionError. + return e.error_char.dump unless jruby? + e.message[/^"[^"]+"/] # " + end + + # Asserts that `value` falls within `range` (inclusive), leaving + # room for slight floating-point errors. + # + # @param name [String] The name of the value. Used in the error message. + # @param range [Range] The allowed range of values. + # @param value [Numeric, Sass::Script::Value::Number] The value to check. + # @param unit [String] The unit of the value. Used in error reporting. + # @return [Numeric] `value` adjusted to fall within range, if it + # was outside by a floating-point margin. + def check_range(name, range, value, unit = '') + grace = (-0.00001..0.00001) + str = value.to_s + value = value.value if value.is_a?(Sass::Script::Value::Number) + return value if range.include?(value) + return range.first if grace.include?(value - range.first) + return range.last if grace.include?(value - range.last) + raise ArgumentError.new( + "#{name} #{str} must be between #{range.first}#{unit} and #{range.last}#{unit}") + end + + # Returns whether or not `seq1` is a subsequence of `seq2`. That is, whether + # or not `seq2` contains every element in `seq1` in the same order (and + # possibly more elements besides). + # + # @param seq1 [Array] + # @param seq2 [Array] + # @return [Boolean] + def subsequence?(seq1, seq2) + i = j = 0 + loop do + return true if i == seq1.size + return false if j == seq2.size + i += 1 if seq1[i] == seq2[j] + j += 1 + end + end + + # Returns information about the caller of the previous method. + # + # @param entry [String] An entry in the `#caller` list, or a similarly formatted string + # @return [[String, Integer, (String, nil)]] + # An array containing the filename, line, and method name of the caller. + # The method name may be nil + def caller_info(entry = nil) + # JRuby evaluates `caller` incorrectly when it's in an actual default argument. + entry ||= caller[1] + info = entry.scan(/^((?:[A-Za-z]:)?.*?):(-?.*?)(?::.*`(.+)')?$/).first + info[1] = info[1].to_i + # This is added by Rubinius to designate a block, but we don't care about it. + info[2].sub!(/ \{\}\Z/, '') if info[2] + info + end + + # Returns whether one version string represents a more recent version than another. + # + # @param v1 [String] A version string. + # @param v2 [String] Another version string. + # @return [Boolean] + def version_gt(v1, v2) + # Construct an array to make sure the shorter version is padded with nil + Array.new([v1.length, v2.length].max).zip(v1.split("."), v2.split(".")) do |_, p1, p2| + p1 ||= "0" + p2 ||= "0" + release1 = p1 =~ /^[0-9]+$/ + release2 = p2 =~ /^[0-9]+$/ + if release1 && release2 + # Integer comparison if both are full releases + p1, p2 = p1.to_i, p2.to_i + next if p1 == p2 + return p1 > p2 + elsif !release1 && !release2 + # String comparison if both are prereleases + next if p1 == p2 + return p1 > p2 + else + # If only one is a release, that one is newer + return release1 + end + end + end + + # Returns whether one version string represents the same or a more + # recent version than another. + # + # @param v1 [String] A version string. + # @param v2 [String] Another version string. + # @return [Boolean] + def version_geq(v1, v2) + version_gt(v1, v2) || !version_gt(v2, v1) + end + + # Throws a NotImplementedError for an abstract method. + # + # @param obj [Object] `self` + # @raise [NotImplementedError] + def abstract(obj) + raise NotImplementedError.new("#{obj.class} must implement ##{caller_info[2]}") + end + + # Prints a deprecation warning for the caller method. + # + # @param obj [Object] `self` + # @param message [String] A message describing what to do instead. + def deprecated(obj, message = nil) + obj_class = obj.is_a?(Class) ? "#{obj}." : "#{obj.class}#" + full_message = "DEPRECATION WARNING: #{obj_class}#{caller_info[2]} " + + "will be removed in a future version of Sass.#{("\n" + message) if message}" + Sass::Util.sass_warn full_message + end + + # Silences all Sass warnings within a block. + # + # @yield A block in which no Sass warnings will be printed + def silence_sass_warnings + old_level, Sass.logger.log_level = Sass.logger.log_level, :error + yield + ensure + Sass.logger.log_level = old_level + end + + # The same as `Kernel#warn`, but is silenced by \{#silence\_sass\_warnings}. + # + # @param msg [String] + def sass_warn(msg) + Sass.logger.warn("#{msg}\n") + end + + ## Cross Rails Version Compatibility + + # Returns the root of the Rails application, + # if this is running in a Rails context. + # Returns `nil` if no such root is defined. + # + # @return [String, nil] + def rails_root + if defined?(::Rails.root) + return ::Rails.root.to_s if ::Rails.root + raise "ERROR: Rails.root is nil!" + end + return RAILS_ROOT.to_s if defined?(RAILS_ROOT) + nil + end + + # Returns the environment of the Rails application, + # if this is running in a Rails context. + # Returns `nil` if no such environment is defined. + # + # @return [String, nil] + def rails_env + return ::Rails.env.to_s if defined?(::Rails.env) + return RAILS_ENV.to_s if defined?(RAILS_ENV) + nil + end + + # Returns whether this environment is using ActionPack + # version 3.0.0 or greater. + # + # @return [Boolean] + def ap_geq_3? + ap_geq?("3.0.0.beta1") + end + + # Returns whether this environment is using ActionPack + # of a version greater than or equal to that specified. + # + # @param version [String] The string version number to check against. + # Should be greater than or equal to Rails 3, + # because otherwise ActionPack::VERSION isn't autoloaded + # @return [Boolean] + def ap_geq?(version) + # The ActionPack module is always loaded automatically in Rails >= 3 + return false unless defined?(ActionPack) && defined?(ActionPack::VERSION) && + defined?(ActionPack::VERSION::STRING) + + version_geq(ActionPack::VERSION::STRING, version) + end + + # Returns an ActionView::Template* class. + # In pre-3.0 versions of Rails, most of these classes + # were of the form `ActionView::TemplateFoo`, + # while afterwards they were of the form `ActionView;:Template::Foo`. + # + # @param name [#to_s] The name of the class to get. + # For example, `:Error` will return `ActionView::TemplateError` + # or `ActionView::Template::Error`. + def av_template_class(name) + return ActionView.const_get("Template#{name}") if ActionView.const_defined?("Template#{name}") + ActionView::Template.const_get(name.to_s) + end + + ## Cross-OS Compatibility + # + # These methods are cached because some of them are called quite frequently + # and even basic checks like String#== are too costly to be called repeatedly. + + # Whether or not this is running on Windows. + # + # @return [Boolean] + def windows? + return @windows if defined?(@windows) + @windows = (RbConfig::CONFIG['host_os'] =~ /mswin|windows|mingw/i) + end + + # Whether or not this is running on IronRuby. + # + # @return [Boolean] + def ironruby? + return @ironruby if defined?(@ironruby) + @ironruby = RUBY_ENGINE == "ironruby" + end + + # Whether or not this is running on Rubinius. + # + # @return [Boolean] + def rbx? + return @rbx if defined?(@rbx) + @rbx = RUBY_ENGINE == "rbx" + end + + # Whether or not this is running on JRuby. + # + # @return [Boolean] + def jruby? + return @jruby if defined?(@jruby) + @jruby = RUBY_PLATFORM =~ /java/ + end + + # Returns an array of ints representing the JRuby version number. + # + # @return [Array] + def jruby_version + @jruby_version ||= ::JRUBY_VERSION.split(".").map {|s| s.to_i} + end + + # Like `Dir.glob`, but works with backslash-separated paths on Windows. + # + # @param path [String] + def glob(path) + path = path.tr('\\', '/') if windows? + if block_given? + Dir.glob(path) {|f| yield(f)} + else + Dir.glob(path) + end + end + + # Like `Pathname.new`, but normalizes Windows paths to always use backslash + # separators. + # + # `Pathname#relative_path_from` can break if the two pathnames aren't + # consistent in their slash style. + # + # @param path [String] + # @return [Pathname] + def pathname(path) + path = path.tr("/", "\\") if windows? + Pathname.new(path) + end + + # Like `Pathname#cleanpath`, but normalizes Windows paths to always use + # backslash separators. Normally, `Pathname#cleanpath` actually does the + # reverse -- it will convert backslashes to forward slashes, which can break + # `Pathname#relative_path_from`. + # + # @param path [String, Pathname] + # @return [Pathname] + def cleanpath(path) + path = Pathname.new(path) unless path.is_a?(Pathname) + pathname(path.cleanpath.to_s) + end + + # Returns `path` with all symlinks resolved. + # + # @param path [String, Pathname] + # @return [Pathname] + def realpath(path) + path = Pathname.new(path) unless path.is_a?(Pathname) + + # Explicitly DON'T run #pathname here. We don't want to convert + # to Windows directory separators because we're comparing these + # against the paths returned by Listen, which use forward + # slashes everywhere. + begin + path.realpath + rescue SystemCallError + # If [path] doesn't actually exist, don't bail, just + # return the original. + path + end + end + + # Returns `path` relative to `from`. + # + # This is like `Pathname#relative_path_from` except it accepts both strings + # and pathnames, it handles Windows path separators correctly, and it throws + # an error rather than crashing if the paths use different encodings + # (https://github.com/ruby/ruby/pull/713). + # + # @param path [String, Pathname] + # @param from [String, Pathname] + # @return [Pathname?] + def relative_path_from(path, from) + pathname(path.to_s).relative_path_from(pathname(from.to_s)) + rescue NoMethodError => e + raise e unless e.name == :zero? + + # Work around https://github.com/ruby/ruby/pull/713. + path = path.to_s + from = from.to_s + raise ArgumentError("Incompatible path encodings: #{path.inspect} is #{path.encoding}, " + + "#{from.inspect} is #{from.encoding}") + end + + # Converts `path` to a "file:" URI. This handles Windows paths correctly. + # + # @param path [String, Pathname] + # @return [String] + def file_uri_from_path(path) + path = path.to_s if path.is_a?(Pathname) + path = path.tr('\\', '/') if windows? + path = URI::DEFAULT_PARSER.escape(path) + return path.start_with?('/') ? "file://" + path : path unless windows? + return "file:///" + path.tr("\\", "/") if path =~ %r{^[a-zA-Z]:[/\\]} + return "file:" + path.tr("\\", "/") if path =~ %r{\\\\[^\\]+\\[^\\/]+} + path.tr("\\", "/") + end + + # Retries a filesystem operation if it fails on Windows. Windows + # has weird and flaky locking rules that can cause operations to fail. + # + # @yield [] The filesystem operation. + def retry_on_windows + return yield unless windows? + + begin + yield + rescue SystemCallError + sleep 0.1 + yield + end + end + + # Prepare a value for a destructuring assignment (e.g. `a, b = + # val`). This works around a performance bug when using + # ActiveSupport, and only needs to be called when `val` is likely + # to be `nil` reasonably often. + # + # See [this bug report](http://redmine.ruby-lang.org/issues/4917). + # + # @param val [Object] + # @return [Object] + def destructure(val) + val || [] + end + + CHARSET_REGEXP = /\A@charset "([^"]+)"/ + bom = "\uFEFF" + UTF_8_BOM = bom.encode("UTF-8").force_encoding('BINARY') + UTF_16BE_BOM = bom.encode("UTF-16BE").force_encoding('BINARY') + UTF_16LE_BOM = bom.encode("UTF-16LE").force_encoding('BINARY') + + ## Cross-Ruby-Version Compatibility + + # Whether or not this is running under Ruby 2.4 or higher. + # + # @return [Boolean] + def ruby2_4? + return @ruby2_4 if defined?(@ruby2_4) + @ruby2_4 = + if RUBY_VERSION_COMPONENTS[0] == 2 + RUBY_VERSION_COMPONENTS[1] >= 4 + else + RUBY_VERSION_COMPONENTS[0] > 2 + end + end + + # Like {\#check\_encoding}, but also checks for a `@charset` declaration + # at the beginning of the file and uses that encoding if it exists. + # + # Sass follows CSS's decoding rules. + # + # @param str [String] The string of which to check the encoding + # @return [(String, Encoding)] The original string encoded as UTF-8, + # and the source encoding of the string + # @raise [Encoding::UndefinedConversionError] if the source encoding + # cannot be converted to UTF-8 + # @raise [ArgumentError] if the document uses an unknown encoding with `@charset` + # @raise [Sass::SyntaxError] If the document declares an encoding that + # doesn't match its contents, or it doesn't declare an encoding and its + # contents are invalid in the native encoding. + def check_sass_encoding(str) + # Determine the fallback encoding following section 3.2 of CSS Syntax Level 3 and Encodings: + # http://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#determine-the-fallback-encoding + # http://encoding.spec.whatwg.org/#decode + binary = str.dup.force_encoding("BINARY") + if binary.start_with?(UTF_8_BOM) + binary.slice! 0, UTF_8_BOM.length + str = binary.force_encoding('UTF-8') + elsif binary.start_with?(UTF_16BE_BOM) + binary.slice! 0, UTF_16BE_BOM.length + str = binary.force_encoding('UTF-16BE') + elsif binary.start_with?(UTF_16LE_BOM) + binary.slice! 0, UTF_16LE_BOM.length + str = binary.force_encoding('UTF-16LE') + elsif binary =~ CHARSET_REGEXP + charset = $1.force_encoding('US-ASCII') + encoding = Encoding.find(charset) + if encoding.name == 'UTF-16' || encoding.name == 'UTF-16BE' + encoding = Encoding.find('UTF-8') + end + str = binary.force_encoding(encoding) + elsif str.encoding.name == "ASCII-8BIT" + # Normally we want to fall back on believing the Ruby string + # encoding, but if that's just binary we want to make sure + # it's valid UTF-8. + str = str.force_encoding('utf-8') + end + + find_encoding_error(str) unless str.valid_encoding? + + begin + # If the string is valid, preprocess it according to section 3.3 of CSS Syntax Level 3. + return str.encode("UTF-8").gsub(/\r\n?|\f/, "\n").tr("\u0000", "�"), str.encoding + rescue EncodingError + find_encoding_error(str) + end + end + + # Destructively removes all elements from an array that match a block, and + # returns the removed elements. + # + # @param array [Array] The array from which to remove elements. + # @yield [el] Called for each element. + # @yieldparam el [*] The element to test. + # @yieldreturn [Boolean] Whether or not to extract the element. + # @return [Array] The extracted elements. + def extract!(array) + out = [] + array.reject! do |e| + next false unless yield e + out << e + true + end + out + end + + # Flattens the first level of nested arrays in `arrs`. Unlike + # `Array#flatten`, this orders the result by taking the first + # values from each array in order, then the second, and so on. + # + # @param arrs [Array] The array to flatten. + # @return [Array] The flattened array. + def flatten_vertically(arrs) + result = [] + arrs = arrs.map {|sub| sub.is_a?(Array) ? sub.dup : Array(sub)} + until arrs.empty? + arrs.reject! do |arr| + result << arr.shift + arr.empty? + end + end + result + end + + # Like `Object#inspect`, but preserves non-ASCII characters rather than + # escaping them under Ruby 1.9.2. This is necessary so that the + # precompiled Haml template can be `#encode`d into `@options[:encoding]` + # before being evaluated. + # + # @param obj {Object} + # @return {String} + def inspect_obj(obj) + return obj.inspect unless version_geq(RUBY_VERSION, "1.9.2") + return ':' + inspect_obj(obj.to_s) if obj.is_a?(Symbol) + return obj.inspect unless obj.is_a?(String) + '"' + obj.gsub(/[\x00-\x7F]+/) {|s| s.inspect[1...-1]} + '"' + end + + # Extracts the non-string vlaues from an array containing both strings and non-strings. + # These values are replaced with escape sequences. + # This can be undone using \{#inject\_values}. + # + # This is useful e.g. when we want to do string manipulation + # on an interpolated string. + # + # The precise format of the resulting string is not guaranteed. + # However, it is guaranteed that newlines and whitespace won't be affected. + # + # @param arr [Array] The array from which values are extracted. + # @return [(String, Array)] The resulting string, and an array of extracted values. + def extract_values(arr) + values = [] + mapped = arr.map do |e| + next e.gsub('{', '{{') if e.is_a?(String) + values << e + next "{#{values.count - 1}}" + end + return mapped.join, values + end + + # Undoes \{#extract\_values} by transforming a string with escape sequences + # into an array of strings and non-string values. + # + # @param str [String] The string with escape sequences. + # @param values [Array] The array of values to inject. + # @return [Array] The array of strings and values. + def inject_values(str, values) + return [str.gsub('{{', '{')] if values.empty? + # Add an extra { so that we process the tail end of the string + result = (str + '{{').scan(/(.*?)(?:(\{\{)|\{(\d+)\})/m).map do |(pre, esc, n)| + [pre, esc ? '{' : '', n ? values[n.to_i] : ''] + end.flatten(1) + result[-2] = '' # Get rid of the extra { + merge_adjacent_strings(result).reject {|s| s == ''} + end + + # Allows modifications to be performed on the string form + # of an array containing both strings and non-strings. + # + # @param arr [Array] The array from which values are extracted. + # @yield [str] A block in which string manipulation can be done to the array. + # @yieldparam str [String] The string form of `arr`. + # @yieldreturn [String] The modified string. + # @return [Array] The modified, interpolated array. + def with_extracted_values(arr) + str, vals = extract_values(arr) + str = yield str + inject_values(str, vals) + end + + # Builds a sourcemap file name given the generated CSS file name. + # + # @param css [String] The generated CSS file name. + # @return [String] The source map file name. + def sourcemap_name(css) + css + ".map" + end + + # Escapes certain characters so that the result can be used + # as the JSON string value. Returns the original string if + # no escaping is necessary. + # + # @param s [String] The string to be escaped + # @return [String] The escaped string + def json_escape_string(s) + return s if s !~ /["\\\b\f\n\r\t]/ + + result = "" + s.split("").each do |c| + case c + when '"', "\\" + result << "\\" << c + when "\n" then result << "\\n" + when "\t" then result << "\\t" + when "\r" then result << "\\r" + when "\f" then result << "\\f" + when "\b" then result << "\\b" + else + result << c + end + end + result + end + + # Converts the argument into a valid JSON value. + # + # @param v [Integer, String, Array, Boolean, nil] + # @return [String] + def json_value_of(v) + case v + when Integer + v.to_s + when String + "\"" + json_escape_string(v) + "\"" + when Array + "[" + v.map {|x| json_value_of(x)}.join(",") + "]" + when NilClass + "null" + when TrueClass + "true" + when FalseClass + "false" + else + raise ArgumentError.new("Unknown type: #{v.class.name}") + end + end + + VLQ_BASE_SHIFT = 5 + VLQ_BASE = 1 << VLQ_BASE_SHIFT + VLQ_BASE_MASK = VLQ_BASE - 1 + VLQ_CONTINUATION_BIT = VLQ_BASE + + BASE64_DIGITS = ('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a + ['+', '/'] + BASE64_DIGIT_MAP = begin + map = {} + BASE64_DIGITS.each_with_index.map do |digit, i| + map[digit] = i + end + map + end + + # Encodes `value` as VLQ (http://en.wikipedia.org/wiki/VLQ). + # + # @param value [Integer] + # @return [String] The encoded value + def encode_vlq(value) + if value < 0 + value = ((-value) << 1) | 1 + else + value <<= 1 + end + + result = '' + begin + digit = value & VLQ_BASE_MASK + value >>= VLQ_BASE_SHIFT + if value > 0 + digit |= VLQ_CONTINUATION_BIT + end + result << BASE64_DIGITS[digit] + end while value > 0 + result + end + + ## Static Method Stuff + + # The context in which the ERB for \{#def\_static\_method} will be run. + class StaticConditionalContext + # @param set [#include?] The set of variables that are defined for this context. + def initialize(set) + @set = set + end + + # Checks whether or not a variable is defined for this context. + # + # @param name [Symbol] The name of the variable + # @return [Boolean] + def method_missing(name, *args) + super unless args.empty? && !block_given? + @set.include?(name) + end + end + + # @private + ATOMIC_WRITE_MUTEX = Mutex.new + + # This creates a temp file and yields it for writing. When the + # write is complete, the file is moved into the desired location. + # The atomicity of this operation is provided by the filesystem's + # rename operation. + # + # @param filename [String] The file to write to. + # @param perms [Integer] The permissions used for creating this file. + # Will be masked by the process umask. Defaults to readable/writeable + # by all users however the umask usually changes this to only be writable + # by the process's user. + # @yieldparam tmpfile [Tempfile] The temp file that can be written to. + # @return The value returned by the block. + def atomic_create_and_write_file(filename, perms = 0666) + require 'tempfile' + tmpfile = Tempfile.new(File.basename(filename), File.dirname(filename)) + tmpfile.binmode if tmpfile.respond_to?(:binmode) + result = yield tmpfile + tmpfile.close + ATOMIC_WRITE_MUTEX.synchronize do + begin + File.chmod(perms & ~File.umask, tmpfile.path) + rescue Errno::EPERM + # If we don't have permissions to chmod the file, don't let that crash + # the compilation. See issue 1215. + end + File.rename tmpfile.path, filename + end + result + ensure + # close and remove the tempfile if it still exists, + # presumably due to an error during write + tmpfile.close if tmpfile + tmpfile.unlink if tmpfile + end + + private + + def find_encoding_error(str) + encoding = str.encoding + cr = Regexp.quote("\r".encode(encoding).force_encoding('BINARY')) + lf = Regexp.quote("\n".encode(encoding).force_encoding('BINARY')) + ff = Regexp.quote("\f".encode(encoding).force_encoding('BINARY')) + line_break = /#{cr}#{lf}?|#{ff}|#{lf}/ + + str.force_encoding("binary").split(line_break).each_with_index do |line, i| + begin + line.encode(encoding) + rescue Encoding::UndefinedConversionError => e + raise Sass::SyntaxError.new( + "Invalid #{encoding.name} character #{undefined_conversion_error_char(e)}", + :line => i + 1) + end + end + + # We shouldn't get here, but it's possible some weird encoding stuff causes it. + return str, str.encoding + end + + # Calculates the memoization table for the Least Common Subsequence algorithm. + # Algorithm from [Wikipedia](http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Computing_the_length_of_the_LCS) + def lcs_table(x, y) + # This method does not take a block as an explicit parameter for performance reasons. + c = Array.new(x.size) {[]} + x.size.times {|i| c[i][0] = 0} + y.size.times {|j| c[0][j] = 0} + (1...x.size).each do |i| + (1...y.size).each do |j| + c[i][j] = + if yield x[i], y[j] + c[i - 1][j - 1] + 1 + else + [c[i][j - 1], c[i - 1][j]].max + end + end + end + c + end + + # Computes a single longest common subsequence for arrays x and y. + # Algorithm from [Wikipedia](http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Reading_out_an_LCS) + def lcs_backtrace(c, x, y, i, j, &block) + return [] if i == 0 || j == 0 + if (v = yield(x[i], y[j])) + return lcs_backtrace(c, x, y, i - 1, j - 1, &block) << v + end + + return lcs_backtrace(c, x, y, i, j - 1, &block) if c[i][j - 1] > c[i - 1][j] + lcs_backtrace(c, x, y, i - 1, j, &block) + end + + singleton_methods.each {|method| module_function method} + end +end + +require 'sass/util/multibyte_string_scanner' +require 'sass/util/normalized_map' diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/util/multibyte_string_scanner.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/util/multibyte_string_scanner.rb new file mode 100644 index 00000000..27e07f09 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/util/multibyte_string_scanner.rb @@ -0,0 +1,151 @@ +require 'strscan' + +if Sass::Util.rbx? + # Rubinius's StringScanner class implements some of its methods in terms of + # others, which causes us to double-count bytes in some cases if we do + # straightforward inheritance. To work around this, we use a delegate class. + require 'delegate' + class Sass::Util::MultibyteStringScanner < DelegateClass(StringScanner) + def initialize(str) + super(StringScanner.new(str)) + @mb_pos = 0 + @mb_matched_size = nil + @mb_last_pos = nil + end + + def is_a?(klass) + __getobj__.is_a?(klass) || super + end + end +else + class Sass::Util::MultibyteStringScanner < StringScanner + def initialize(str) + super + @mb_pos = 0 + @mb_matched_size = nil + @mb_last_pos = nil + end + end +end + +# A wrapper of the native StringScanner class that works correctly with +# multibyte character encodings. The native class deals only in bytes, not +# characters, for methods like [#pos] and [#matched_size]. This class deals +# only in characters, instead. +class Sass::Util::MultibyteStringScanner + def self.new(str) + return StringScanner.new(str) if str.ascii_only? + super + end + + alias_method :byte_pos, :pos + alias_method :byte_matched_size, :matched_size + + def check(pattern); _match super; end + def check_until(pattern); _matched super; end + def getch; _forward _match super; end + def match?(pattern); _size check(pattern); end + def matched_size; @mb_matched_size; end + def peek(len); string[@mb_pos, len]; end + alias_method :peep, :peek + def pos; @mb_pos; end + alias_method :pointer, :pos + def rest_size; rest.size; end + def scan(pattern); _forward _match super; end + def scan_until(pattern); _forward _matched super; end + def skip(pattern); _size scan(pattern); end + def skip_until(pattern); _matched _size scan_until(pattern); end + + def get_byte + raise "MultibyteStringScanner doesn't support #get_byte." + end + + def getbyte + raise "MultibyteStringScanner doesn't support #getbyte." + end + + def pos=(n) + @mb_last_pos = nil + + # We set position kind of a lot during parsing, so we want it to be as + # efficient as possible. This is complicated by the fact that UTF-8 is a + # variable-length encoding, so it's difficult to find the byte length that + # corresponds to a given character length. + # + # Our heuristic here is to try to count the fewest possible characters. So + # if the new position is close to the current one, just count the + # characters between the two; if the new position is closer to the + # beginning of the string, just count the characters from there. + if @mb_pos - n < @mb_pos / 2 + # New position is close to old position + byte_delta = @mb_pos > n ? -string[n...@mb_pos].bytesize : string[@mb_pos...n].bytesize + super(byte_pos + byte_delta) + else + # New position is close to BOS + super(string[0...n].bytesize) + end + @mb_pos = n + end + + def reset + @mb_pos = 0 + @mb_matched_size = nil + @mb_last_pos = nil + super + end + + def scan_full(pattern, advance_pointer_p, return_string_p) + res = _match super(pattern, advance_pointer_p, true) + _forward res if advance_pointer_p + return res if return_string_p + end + + def search_full(pattern, advance_pointer_p, return_string_p) + res = super(pattern, advance_pointer_p, true) + _forward res if advance_pointer_p + _matched((res if return_string_p)) + end + + def string=(str) + @mb_pos = 0 + @mb_matched_size = nil + @mb_last_pos = nil + super + end + + def terminate + @mb_pos = string.size + @mb_matched_size = nil + @mb_last_pos = nil + super + end + alias_method :clear, :terminate + + def unscan + super + @mb_pos = @mb_last_pos + @mb_last_pos = @mb_matched_size = nil + end + + private + + def _size(str) + str && str.size + end + + def _match(str) + @mb_matched_size = str && str.size + str + end + + def _matched(res) + _match matched + res + end + + def _forward(str) + @mb_last_pos = @mb_pos + @mb_pos += str.size if str + str + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/util/normalized_map.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/util/normalized_map.rb new file mode 100644 index 00000000..d2e3b876 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/util/normalized_map.rb @@ -0,0 +1,122 @@ +require 'delegate' + +module Sass + module Util + # A hash that normalizes its string keys while still allowing you to get back + # to the original keys that were stored. If several different values normalize + # to the same value, whichever is stored last wins. + class NormalizedMap + # Create a normalized map + def initialize(map = nil) + @key_strings = {} + @map = {} + + map.each {|key, value| self[key] = value} if map + end + + # Specifies how to transform the key. + # + # This can be overridden to create other normalization behaviors. + def normalize(key) + key.tr("-", "_") + end + + # Returns the version of `key` as it was stored before + # normalization. If `key` isn't in the map, returns it as it was + # passed in. + # + # @return [String] + def denormalize(key) + @key_strings[normalize(key)] || key + end + + # @private + def []=(k, v) + normalized = normalize(k) + @map[normalized] = v + @key_strings[normalized] = k + v + end + + # @private + def [](k) + @map[normalize(k)] + end + + # @private + def has_key?(k) + @map.has_key?(normalize(k)) + end + + # @private + def delete(k) + normalized = normalize(k) + @key_strings.delete(normalized) + @map.delete(normalized) + end + + # @return [Hash] Hash with the keys as they were stored (before normalization). + def as_stored + Sass::Util.map_keys(@map) {|k| @key_strings[k]} + end + + def empty? + @map.empty? + end + + def values + @map.values + end + + def keys + @map.keys + end + + def each + @map.each {|k, v| yield(k, v)} + end + + def size + @map.size + end + + def to_hash + @map.dup + end + + def to_a + @map.to_a + end + + def map + @map.map {|k, v| yield(k, v)} + end + + def dup + d = super + d.send(:instance_variable_set, "@map", @map.dup) + d + end + + def sort_by + @map.sort_by {|k, v| yield k, v} + end + + def update(map) + map = map.as_stored if map.is_a?(NormalizedMap) + map.each {|k, v| self[k] = v} + end + + def method_missing(method, *args, &block) + if Sass.tests_running + raise ArgumentError.new("The method #{method} must be implemented explicitly") + end + @map.send(method, *args, &block) + end + + def respond_to_missing?(method, include_private = false) + @map.respond_to?(method, include_private) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/util/subset_map.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/util/subset_map.rb new file mode 100644 index 00000000..cd401f23 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/util/subset_map.rb @@ -0,0 +1,109 @@ +require 'set' + +module Sass + module Util + # A map from sets to values. + # A value is \{#\[]= set} by providing a set (the "set-set") and a value, + # which is then recorded as corresponding to that set. + # Values are \{#\[] accessed} by providing a set (the "get-set") + # and returning all values that correspond to set-sets + # that are subsets of the get-set. + # + # SubsetMap preserves the order of values as they're inserted. + # + # @example + # ssm = SubsetMap.new + # ssm[Set[1, 2]] = "Foo" + # ssm[Set[2, 3]] = "Bar" + # ssm[Set[1, 2, 3]] = "Baz" + # + # ssm[Set[1, 2, 3]] #=> ["Foo", "Bar", "Baz"] + class SubsetMap + # Creates a new, empty SubsetMap. + def initialize + @hash = {} + @vals = [] + end + + # Whether or not this SubsetMap has any key-value pairs. + # + # @return [Boolean] + def empty? + @hash.empty? + end + + # Associates a value with a set. + # When `set` or any of its supersets is accessed, + # `value` will be among the values returned. + # + # Note that if the same `set` is passed to this method multiple times, + # all given `value`s will be associated with that `set`. + # + # This runs in `O(n)` time, where `n` is the size of `set`. + # + # @param set [#to_set] The set to use as the map key. May not be empty. + # @param value [Object] The value to associate with `set`. + # @raise [ArgumentError] If `set` is empty. + def []=(set, value) + raise ArgumentError.new("SubsetMap keys may not be empty.") if set.empty? + + index = @vals.size + @vals << value + set.each do |k| + @hash[k] ||= [] + @hash[k] << [set, set.to_set, index] + end + end + + # Returns all values associated with subsets of `set`. + # + # In the worst case, this runs in `O(m*max(n, log m))` time, + # where `n` is the size of `set` + # and `m` is the number of associations in the map. + # However, unless many keys in the map overlap with `set`, + # `m` will typically be much smaller. + # + # @param set [Set] The set to use as the map key. + # @return [Array<(Object, #to_set)>] An array of pairs, + # where the first value is the value associated with a subset of `set`, + # and the second value is that subset of `set` + # (or whatever `#to_set` object was used to set the value) + # This array is in insertion order. + # @see #[] + def get(set) + res = set.map do |k| + subsets = @hash[k] + next unless subsets + subsets.map do |subenum, subset, index| + next unless subset.subset?(set) + [index, subenum] + end + end.flatten(1) + res.compact! + res.uniq! + res.sort! + res.map! {|i, s| [@vals[i], s]} + res + end + + # Same as \{#get}, but doesn't return the subsets of the argument + # for which values were found. + # + # @param set [Set] The set to use as the map key. + # @return [Array] The array of all values + # associated with subsets of `set`, in insertion order. + # @see #get + def [](set) + get(set).map {|v, _| v} + end + + # Iterates over each value in the subset map. Ignores keys completely. If + # multiple keys have the same value, this will return them multiple times. + # + # @yield [Object] Each value in the map. + def each_value + @vals.each {|v| yield v} + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/util/test.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/util/test.rb new file mode 100644 index 00000000..905e81f2 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/util/test.rb @@ -0,0 +1,9 @@ +module Sass + module Util + module Test + def skip(msg = nil, bt = caller) + super if defined?(super) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/version.rb b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/version.rb new file mode 100644 index 00000000..1a50696a --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/lib/sass/version.rb @@ -0,0 +1,120 @@ +require 'date' +require 'sass/util' + +module Sass + # Handles Sass version-reporting. + # Sass not only reports the standard three version numbers, + # but its Git revision hash as well, + # if it was installed from Git. + module Version + # Returns a hash representing the version of Sass. + # The `:major`, `:minor`, and `:teeny` keys have their respective numbers as Integers. + # The `:name` key has the name of the version. + # The `:string` key contains a human-readable string representation of the version. + # The `:number` key is the major, minor, and teeny keys separated by periods. + # The `:date` key, which is not guaranteed to be defined, is the `DateTime` + # at which this release was cut. + # If Sass is checked out from Git, the `:rev` key will have the revision hash. + # For example: + # + # { + # :string => "2.1.0.9616393", + # :rev => "9616393b8924ef36639c7e82aa88a51a24d16949", + # :number => "2.1.0", + # :date => DateTime.parse("Apr 30 13:52:01 2009 -0700"), + # :major => 2, :minor => 1, :teeny => 0 + # } + # + # If a prerelease version of Sass is being used, + # the `:string` and `:number` fields will reflect the full version + # (e.g. `"2.2.beta.1"`), and the `:teeny` field will be `-1`. + # A `:prerelease` key will contain the name of the prerelease (e.g. `"beta"`), + # and a `:prerelease_number` key will contain the rerelease number. + # For example: + # + # { + # :string => "3.0.beta.1", + # :number => "3.0.beta.1", + # :date => DateTime.parse("Mar 31 00:38:04 2010 -0700"), + # :major => 3, :minor => 0, :teeny => -1, + # :prerelease => "beta", + # :prerelease_number => 1 + # } + # + # @return [{Symbol => String/Integer}] The version hash + def version + return @@version if defined?(@@version) + + numbers = File.read(Sass::Util.scope('VERSION')).strip.split('.'). + map {|n| n =~ /^[0-9]+$/ ? n.to_i : n} + name = File.read(Sass::Util.scope('VERSION_NAME')).strip + @@version = { + :major => numbers[0], + :minor => numbers[1], + :teeny => numbers[2], + :name => name + } + + if (date = version_date) + @@version[:date] = date + end + + if numbers[3].is_a?(String) + @@version[:teeny] = -1 + @@version[:prerelease] = numbers[3] + @@version[:prerelease_number] = numbers[4] + end + + @@version[:number] = numbers.join('.') + @@version[:string] = @@version[:number].dup + + if (rev = revision_number) + @@version[:rev] = rev + unless rev[0] == ?( + @@version[:string] << "." << rev[0...7] + end + end + + @@version + end + + private + + def revision_number + if File.exist?(Sass::Util.scope('REVISION')) + rev = File.read(Sass::Util.scope('REVISION')).strip + return rev unless rev =~ /^([a-f0-9]+|\(.*\))$/ || rev == '(unknown)' + end + + return unless File.exist?(Sass::Util.scope('.git/HEAD')) + rev = File.read(Sass::Util.scope('.git/HEAD')).strip + return rev unless rev =~ /^ref: (.*)$/ + + ref_name = $1 + ref_file = Sass::Util.scope(".git/#{ref_name}") + info_file = Sass::Util.scope(".git/info/refs") + return File.read(ref_file).strip if File.exist?(ref_file) + return unless File.exist?(info_file) + File.open(info_file) do |f| + f.each do |l| + sha, ref = l.strip.split("\t", 2) + next unless ref == ref_name + return sha + end + end + nil + end + + def version_date + return unless File.exist?(Sass::Util.scope('VERSION_DATE')) + DateTime.parse(File.read(Sass::Util.scope('VERSION_DATE')).strip) + end + end + + extend Sass::Version + + # A string representing the version of Sass. + # A more fine-grained representation is available from Sass.version. + # @api public + VERSION = version[:string] unless defined?(Sass::VERSION) +end diff --git a/path/ruby/2.6.0/gems/sass-3.7.4/rails/init.rb b/path/ruby/2.6.0/gems/sass-3.7.4/rails/init.rb new file mode 100644 index 00000000..13d5baa3 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-3.7.4/rails/init.rb @@ -0,0 +1 @@ +Kernel.load File.join(File.dirname(__FILE__), '..', 'init.rb') diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/CHANGELOG.md b/path/ruby/2.6.0/gems/sass-listen-4.0.0/CHANGELOG.md new file mode 100644 index 00000000..232adbd8 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/CHANGELOG.md @@ -0,0 +1 @@ +# Moved to [GitHub releases](https://github.com/guard/listen/releases) page. diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/CONTRIBUTING.md b/path/ruby/2.6.0/gems/sass-listen-4.0.0/CONTRIBUTING.md new file mode 100644 index 00000000..5a5d6de6 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/CONTRIBUTING.md @@ -0,0 +1,38 @@ +Contribute to Listen +=================== + +File an issue +------------- + +If you haven't already, first see [TROUBLESHOOTING](https://github.com/guard/listen/wiki/Troubleshooting) for known issues, solutions and workarounds. + +You can report bugs and feature requests to [GitHub Issues](https://github.com/guard/listen/issues). + +**Please don't ask question in the issue tracker**, instead ask them in our +[Google group](http://groups.google.com/group/guard-dev) or on `#guard` (irc.freenode.net). + +Try to figure out where the issue belongs to: Is it an issue with Listen itself or with Guard? + + +**It's most likely that your bug gets resolved faster if you provide as much information as possible!** + +The MOST useful information is debugging output from Listen (`LISTEN_GEM_DEBUGGING=1`) - see [TROUBLESHOOTING](https://github.com/guard/listen/wiki/Troubleshooting) for details. + + +Development +----------- + +* Documentation hosted at [RubyDoc](http://rubydoc.info/github/guard/listen/master/frames). +* Source hosted at [GitHub](https://github.com/guard/listen). + +Pull requests are very welcome! Please try to follow these simple rules if applicable: + +* Please create a topic branch for every separate change you make. +* Make sure your patches are well tested. All specs run with `rake spec` must pass. +* Update the [Yard](http://yardoc.org/) documentation. +* Update the [README](https://github.com/guard/listen/blob/master/README.md). +* Update the [CHANGELOG](https://github.com/guard/listen/blob/master/CHANGELOG.md) for noteworthy changes. +* Please **do not change** the version number. + +For questions please join us in our [Google group](http://groups.google.com/group/guard-dev) or on +`#guard` (irc.freenode.net). diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/LICENSE.txt b/path/ruby/2.6.0/gems/sass-listen-4.0.0/LICENSE.txt new file mode 100644 index 00000000..b5f71c8b --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2013 Thibaud Guillaume-Gentil + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/README.md b/path/ruby/2.6.0/gems/sass-listen-4.0.0/README.md new file mode 100644 index 00000000..05f0b898 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/README.md @@ -0,0 +1,297 @@ +# This is a Fork + +This is a fork of the official version `3.0.x` branch. Sass need to support older +versions of ruby than Guard wants to support on an ongoing basis, so we are releasing +updates as needed for critical fixes and will support ruby 2.0 and +greater for as long as Sass users need it. Our blog has more information about +ths [Ruby version policy for Sass](http://blog.sass-lang.com/posts/560719). + +# Listen + +The Listen gem listens to file modifications and notifies you about the changes. + +## Features + +* OS-optimized adapters on MRI for Mac OS X 10.6+, Linux, \*BSD and Windows, [more info](#listen-adapters) below. +* Detects file modification, addition and removal. +* You can watch multiple directories. +* Regexp-patterns for ignoring paths for more accuracy and speed +* Increased change detection accuracy on OS X HFS and VFAT volumes. +* Tested on MRI Ruby environments (2.0+ only) via [Travis CI](https://travis-ci.org/guard/listen), + +## Issues / limitations + +* Limited support for symlinked directories ([#279](https://github.com/guard/listen/issues/279)): + * Symlinks are always followed ([#25](https://github.com/guard/listen/issues/25)). + * Symlinked directories pointing within a watched directory are not supported ([#273](https://github.com/guard/listen/pull/273)- see [Duplicate directory errors](https://github.com/guard/listen/wiki/Duplicate-directory-errors)). +* No directory/adapter-specific configuration options. +* Support for plugins planned for future. +* TCP functionality was removed in Listen [3.0.0](https://github.com/guard/listen/releases/tag/v3.0.0) ([#319](https://github.com/guard/listen/issues/319), [#218](https://github.com/guard/listen/issues/218)). There are plans to extract this feature to separate gems ([#258](https://github.com/guard/listen/issues/258)), until this is finished, you can use by locking the `listen` gem to version `'~> 2.10'`. +* Some filesystems won't work without polling (VM/Vagrant Shared folders, NFS, Samba, sshfs, etc.). +* Specs suite on JRuby and Rubinius aren't reliable on Travis CI, but should work. +* Windows and \*BSD adapter aren't continuously and automatically tested. +* OSX adapter has some performance limitations ([#342](https://github.com/guard/listen/issues/342)). +* Ruby 1.9.3 is no longer maintained (and may not work with Listen) - it's best to upgrade to Ruby 2.2.2. + +Pull requests or help is very welcome for these. + +## Install + +The simplest way to install Listen is to use [Bundler](http://bundler.io). + +```ruby +gem 'listen', '~> 3.0' # NOTE: for TCP functionality, use '~> 2.10' for now +``` + +## Usage + +Call `Listen.to` with either a single directory or multiple directories, then define the "changes" callback in a block. + +``` ruby +listener = Listen.to('dir/to/listen', 'dir/to/listen2') do |modified, added, removed| + puts "modified absolute path: #{modified}" + puts "added absolute path: #{added}" + puts "removed absolute path: #{removed}" +end +listener.start # not blocking +sleep +``` + +### Pause / unpause / stop + +Listeners can also be easily paused/unpaused: + +``` ruby +listener = Listen.to('dir/path/to/listen') { |modified, added, removed| puts 'handle changes here...' } + +listener.start +listener.paused? # => false +listener.processing? # => true + +listener.pause # stops processing changes (but keeps on collecting them) +listener.paused? # => true +listener.processing? # => false + +listener.unpause # resumes processing changes ("start" would do the same) +listener.stop # stop both listening to changes and processing them +``` + + Note: While paused, Listen keeps on collecting changes in the background - to clear them, call "stop" + + Note: You should keep track of all started listeners and stop them properly on finish. + +### Ignore / ignore! + +Listen ignores some directories and extensions by default (See DEFAULT_IGNORED_DIRECTORIES and DEFAULT_IGNORED_EXTENSIONS in Listen::Silencer), you can add ignoring patterns with the `ignore` option/method or overwrite default with `ignore!` option/method. + +``` ruby +listener = Listen.to('dir/path/to/listen', ignore: /\.txt/) { |modified, added, removed| # ... } +listener.start +listener.ignore! /\.pkg/ # overwrite all patterns and only ignore pkg extension. +listener.ignore /\.rb/ # ignore rb extension in addition of pkg. +sleep +``` + +Note: `:ignore` regexp patterns are evaluated against relative paths. + +Note: Ignoring paths does not improve performance, except when Polling ([#274](https://github.com/guard/listen/issues/274)) + +### Only + +Listen catches all files (less the ignored ones) by default. If you want to only listen to a specific type of file (i.e., just `.rb` extension), you should use the `only` option/method. + +``` ruby +listener = Listen.to('dir/path/to/listen', only: /\.rb$/) { |modified, added, removed| # ... } +listener.start +listener.only /_spec\.rb$/ # overwrite all existing only patterns. +sleep +``` + +Note: `:only` regexp patterns are evaluated only against relative **file** paths. + + +## Changes callback + +Changes to the listened-to directories gets reported back to the user in a callback. +The registered callback gets invoked, when there are changes, with **three** parameters: +`modified`, `added` and `removed` paths, in that particular order. +Paths are always returned in their absolute form. + +Example: + +```ruby +listener = Listen.to('path/to/app') do |modified, added, removed| + # This block will be called when there are changes. +end +listener.start +sleep +``` + +or ... + +```ruby +# Create a callback +callback = Proc.new do |modified, added, removed| + # This proc will be called when there are changes. +end +listener = Listen.to('dir', &callback) +listener.start +sleep +``` + +## Options + +All the following options can be set through the `Listen.to` after the directory path(s) params. + +```ruby +ignore: [%r{/foo/bar}, /\.pid$/, /\.coffee$/] # Ignore a list of paths + # default: See DEFAULT_IGNORED_DIRECTORIES and DEFAULT_IGNORED_EXTENSIONS in Listen::Silencer + +ignore!: %r{/foo/bar} # Same as ignore options, but overwrite default ignored paths. + +only: %r{.rb$} # Only listen to specific files + # default: none + +latency: 0.5 # Set the delay (**in seconds**) between checking for changes + # default: 0.25 sec (1.0 sec for polling) + +wait_for_delay: 4 # Set the delay (**in seconds**) between calls to the callback when changes exist + # default: 0.10 sec + +force_polling: true # Force the use of the polling adapter + # default: none + +relative: false # Whether changes should be relative to current dir or not + # default: false + +polling_fallback_message: 'custom message' # Set a custom polling fallback message (or disable it with false) + # default: "Listen will be polling for changes. Learn more at https://github.com/guard/listen#listen-adapters." +``` + +## Debugging + +Setting the environment variable `LISTEN_GEM_DEBUGGING=1` sets up the INFO level logger, while `LISTEN_GEM_DEBUGGING=2` sets up the DEBUG level logger. + +You can also set `Listen.logger` to a custom logger. + + +## Listen adapters + +The Listen gem has a set of adapters to notify it when there are changes. + +There are 4 OS-specific adapters to support Darwin, Linux, \*BSD and Windows. +These adapters are fast as they use some system-calls to implement the notifying function. + +There is also a polling adapter - although it's much slower than other adapters, +it works on every platform/system and scenario (including network filesystems such as VM shared folders). + +The Darwin and Linux adapters are dependencies of the Listen gem so they work out of the box. For other adapters a specific gem will have to be added to your Gemfile, please read below. + +The Listen gem will choose the best adapter automatically, if present. If you +want to force the use of the polling adapter, use the `:force_polling` option +while initializing the listener. + +### On Windows + +If you are on Windows, it's recommended to use the [`wdm`](https://github.com/Maher4Ever/wdm) adapter instead of polling. + +Please add the following to your Gemfile: + +```ruby +gem 'wdm', '>= 0.1.0' if Gem.win_platform? +``` + +### On \*BSD + +If you are on \*BSD you can try to use the [`rb-kqueue`](https://github.com/mat813/rb-kqueue) adapter instead of polling. + +Please add the following to your Gemfile: + +```ruby +require 'rbconfig' +if RbConfig::CONFIG['target_os'] =~ /bsd|dragonfly/i + gem 'rb-kqueue', '>= 0.2' +end + +``` + +### Getting the [polling fallback message](#options)? + +Please visit the [installation section of the Listen WIKI](https://github.com/guard/listen/wiki#installation) for more information and options for potential fixes. + +### Issues and troubleshooting + +*NOTE: without providing the output after setting the `LISTEN_GEM_DEBUGGING=1` environment variable, it can be almost impossible to guess why listen is not working as expected.* + +See [TROUBLESHOOTING](https://github.com/guard/listen/wiki/Troubleshooting) + +## Performance + +If Listen seems slow or unresponsive, make sure you're not using the Polling adapter (you should see a warning upon startup if you are). + +Also, if the directories you're watching contain many files, make sure you're: + +* not using Polling (ideally) +* using `:ignore` and `:only` options to avoid tracking directories you don't care about (important with Polling and on MacOS) +* running Listen with the `:latency` and `:wait_for_delay` options not too small or too big (depends on needs) +* not watching directories with log files, database files or other frequently changing files +* not using a version of Listen prior to 2.7.7 +* not getting silent crashes within Listen (see LISTEN_GEM_DEBUGGING=2) +* not running multiple instances of Listen in the background +* using a file system with atime modification disabled (ideally) +* not using a filesystem with inaccurate file modification times (ideally), e.g. HFS, VFAT +* not buffering to a slow terminal (e.g. transparency + fancy font + slow gfx card + lots of output) +* ideally not running a slow encryption stack, e.g. btrfs + ecryptfs + +When in doubt, LISTEN_GEM_DEBUGGING=2 can help discover the actual events and time they happened. + +See also [Tips and Techniques](https://github.com/guard/listen/wiki/Tips-and-Techniques). + +## Development + +* Documentation hosted at [RubyDoc](http://rubydoc.info/github/guard/listen/master/frames). +* Source hosted at [GitHub](https://github.com/guard/listen). + +Pull requests are very welcome! Please try to follow these simple rules if applicable: + +* Please create a topic branch for every separate change you make. +* Make sure your patches are well tested. All specs must pass on [Travis CI](https://travis-ci.org/guard/listen). +* Update the [Yard](http://yardoc.org/) documentation. +* Update the [README](https://github.com/guard/listen/blob/master/README.md). +* Please **do not change** the version number. + +For questions please join us in our [Google group](http://groups.google.com/group/guard-dev) or on +`#guard` (irc.freenode.net). + +## Acknowledgments + +* [Michael Kessler (netzpirat)][] for having written the [initial specs](https://github.com/guard/listen/commit/1e457b13b1bb8a25d2240428ce5ed488bafbed1f). +* [Travis Tilley (ttilley)][] for this awesome work on [fssm][] & [rb-fsevent][]. +* [Nathan Weizenbaum (nex3)][] for [rb-inotify][], a thorough inotify wrapper. +* [Mathieu Arnold (mat813)][] for [rb-kqueue][], a simple kqueue wrapper. +* [Maher Sallam][] for [wdm][], windows support wouldn't exist without him. +* [Yehuda Katz (wycats)][] for [vigilo][], that has been a great source of inspiration. + +## Author + +[Thibaud Guillaume-Gentil](https://github.com/thibaudgg) ([@thibaudgg](https://twitter.com/thibaudgg)) + +## Contributors + +[https://github.com/guard/listen/graphs/contributors](https://github.com/guard/listen/graphs/contributors) + +[Thibaud Guillaume-Gentil (thibaudgg)]: https://github.com/thibaudgg +[Maher Sallam]: https://github.com/Maher4Ever +[Michael Kessler (netzpirat)]: https://github.com/netzpirat +[Travis Tilley (ttilley)]: https://github.com/ttilley +[fssm]: https://github.com/ttilley/fssm +[rb-fsevent]: https://github.com/thibaudgg/rb-fsevent +[Mathieu Arnold (mat813)]: https://github.com/mat813 +[Nathan Weizenbaum (nex3)]: https://github.com/nex3 +[rb-inotify]: https://github.com/nex3/rb-inotify +[stereobooster]: https://github.com/stereobooster +[rb-fchange]: https://github.com/stereobooster/rb-fchange +[rb-kqueue]: https://github.com/mat813/rb-kqueue +[Yehuda Katz (wycats)]: https://github.com/wycats +[vigilo]: https://github.com/wycats/vigilo +[wdm]: https://github.com/Maher4Ever/wdm diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen.rb new file mode 100644 index 00000000..dac914fb --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen.rb @@ -0,0 +1,55 @@ +require 'logger' +require 'sass-listen/logger' +require 'sass-listen/listener' + +require 'sass-listen/internals/thread_pool' + +# Always set up logging by default first time file is required +# +# NOTE: If you need to clear the logger completely, do so *after* +# requiring this file. If you need to set a custom logger, +# require the listen/logger file and set the logger before requiring +# this file. +SassListen.setup_default_logger_if_unset + +# Won't print anything by default because of level - unless you've set +# LISTEN_GEM_DEBUGGING or provided your own logger with a high enough level +SassListen::Logger.info "SassListen loglevel set to: #{SassListen.logger.level}" +SassListen::Logger.info "SassListen version: #{SassListen::VERSION}" + +module SassListen + class << self + # Listens to file system modifications on a either single directory or + # multiple directories. + # + # @param (see SassListen::Listener#new) + # + # @yield [modified, added, removed] the changed files + # @yieldparam [Array] modified the list of modified files + # @yieldparam [Array] added the list of added files + # @yieldparam [Array] removed the list of removed files + # + # @return [SassListen::Listener] the listener + # + def to(*args, &block) + @listeners ||= [] + Listener.new(*args, &block).tap do |listener| + @listeners << listener + end + end + + # This is used by the `listen` binary to handle Ctrl-C + # + def stop + Internals::ThreadPool.stop + @listeners ||= [] + + # TODO: should use a mutex for this + @listeners.each do |listener| + # call stop to halt the main loop + listener.stop + end + @listeners = nil + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/adapter.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/adapter.rb new file mode 100644 index 00000000..1f949712 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/adapter.rb @@ -0,0 +1,43 @@ +require 'sass-listen/adapter/base' +require 'sass-listen/adapter/bsd' +require 'sass-listen/adapter/darwin' +require 'sass-listen/adapter/linux' +require 'sass-listen/adapter/polling' +require 'sass-listen/adapter/windows' + +module SassListen + module Adapter + OPTIMIZED_ADAPTERS = [Darwin, Linux, BSD, Windows] + POLLING_FALLBACK_MESSAGE = 'SassListen will be polling for changes.'\ + 'Learn more at https://github.com/guard/listen#listen-adapters.' + + def self.select(options = {}) + _log :debug, 'Adapter: considering polling ...' + return Polling if options[:force_polling] + _log :debug, 'Adapter: considering optimized backend...' + return _usable_adapter_class if _usable_adapter_class + _log :debug, 'Adapter: falling back to polling...' + _warn_polling_fallback(options) + Polling + rescue + _log :warn, format('Adapter: failed: %s:%s', $ERROR_POSITION.inspect, + $ERROR_POSITION * "\n") + raise + end + + private + + def self._usable_adapter_class + OPTIMIZED_ADAPTERS.detect(&:usable?) + end + + def self._warn_polling_fallback(options) + msg = options.fetch(:polling_fallback_message, POLLING_FALLBACK_MESSAGE) + Kernel.warn "[SassListen warning]:\n #{msg}" if msg + end + + def self._log(type, message) + SassListen::Logger.send(type, message) + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/adapter/base.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/adapter/base.rb new file mode 100644 index 00000000..94a90719 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/adapter/base.rb @@ -0,0 +1,137 @@ +require 'sass-listen/options' +require 'sass-listen/record' +require 'sass-listen/change' + +module SassListen + module Adapter + class Base + attr_reader :options + + # TODO: only used by tests + DEFAULTS = {} + + attr_reader :config + + def initialize(config) + @started = false + @config = config + + @configured = nil + + fail 'No directories to watch!' if config.directories.empty? + + defaults = self.class.const_get('DEFAULTS') + @options = SassListen::Options.new(config.adapter_options, defaults) + rescue + _log_exception 'adapter config failed: %s:%s called from: %s', caller + raise + end + + # TODO: it's a separate method as a temporary workaround for tests + def configure + if @configured + _log(:warn, 'Adapter already configured!') + return + end + + @configured = true + + @callbacks ||= {} + config.directories.each do |dir| + callback = @callbacks[dir] || lambda do |event| + _process_event(dir, event) + end + @callbacks[dir] = callback + _configure(dir, &callback) + end + + @snapshots ||= {} + # TODO: separate config per directory (some day maybe) + change_config = Change::Config.new(config.queue, config.silencer) + config.directories.each do |dir| + record = Record.new(dir) + snapshot = Change.new(change_config, record) + @snapshots[dir] = snapshot + end + end + + def started? + @started + end + + def start + configure + + if started? + _log(:warn, 'Adapter already started!') + return + end + + @started = true + + calling_stack = caller.dup + SassListen::Internals::ThreadPool.add do + begin + @snapshots.values.each do |snapshot| + _timed('Record.build()') { snapshot.record.build } + end + _run + rescue + msg = 'run() in thread failed: %s:\n'\ + ' %s\n\ncalled from:\n %s' + _log_exception(msg, calling_stack) + raise # for unit tests mostly + end + end + end + + def stop + _stop + end + + def self.usable? + const_get('OS_REGEXP') =~ RbConfig::CONFIG['target_os'] + end + + private + + def _stop + end + + def _timed(title) + start = Time.now.to_f + yield + diff = Time.now.to_f - start + SassListen::Logger.info format('%s: %.05f seconds', title, diff) + rescue + SassListen::Logger.warn "#{title} crashed: #{$ERROR_INFO.inspect}" + raise + end + + # TODO: allow backend adapters to pass specific invalidation objects + # e.g. Darwin -> DirRescan, INotify -> MoveScan, etc. + def _queue_change(type, dir, rel_path, options) + @snapshots[dir].invalidate(type, rel_path, options) + end + + def _log(*args, &block) + self.class.send(:_log, *args, &block) + end + + def _log_exception(msg, caller_stack) + formatted = format( + msg, + $ERROR_INFO, + $ERROR_POSITION * "\n", + caller_stack * "\n" + ) + + _log(:error, formatted) + end + + def self._log(*args, &block) + SassListen::Logger.send(*args, &block) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/adapter/bsd.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/adapter/bsd.rb new file mode 100644 index 00000000..90e5f58d --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/adapter/bsd.rb @@ -0,0 +1,106 @@ +# Listener implementation for BSD's `kqueue`. +# @see http://www.freebsd.org/cgi/man.cgi?query=kqueue +# @see https://github.com/mat813/rb-kqueue/blob/master/lib/rb-kqueue/queue.rb +# +module SassListen + module Adapter + class BSD < Base + OS_REGEXP = /bsd|dragonfly/i + + DEFAULTS = { + events: [ + :delete, + :write, + :extend, + :attrib, + :rename + # :link, :revoke + ] + } + + BUNDLER_DECLARE_GEM = <<-EOS.gsub(/^ {6}/, '') + Please add the following to your Gemfile to avoid polling for changes: + require 'rbconfig' + if RbConfig::CONFIG['target_os'] =~ /#{OS_REGEXP}/ + gem 'rb-kqueue', '>= 0.2' + end + EOS + + def self.usable? + return false unless super + require 'rb-kqueue' + require 'find' + true + rescue LoadError + Kernel.warn BUNDLER_DECLARE_GEM + false + end + + private + + def _configure(directory, &_callback) + @worker ||= KQueue::Queue.new + @callback = _callback + # use Record to make a snapshot of dir, so we + # can detect new files + _find(directory.to_s) { |path| _watch_file(path, @worker) } + end + + def _run + @worker.run + end + + def _process_event(dir, event) + full_path = _event_path(event) + if full_path.directory? + # Force dir content tracking to kick in, or we won't have + # names of added files + _queue_change(:dir, dir, '.', recursive: true) + elsif full_path.exist? + path = full_path.relative_path_from(dir) + _queue_change(:file, dir, path.to_s, change: _change(event.flags)) + end + + # If it is a directory, and it has a write flag, it means a + # file has been added so find out which and deal with it. + # No need to check for removed files, kqueue will forget them + # when the vfs does. + _watch_for_new_file(event) if full_path.directory? + end + + def _change(event_flags) + { modified: [:attrib, :extend], + added: [:write], + removed: [:rename, :delete] + }.each do |change, flags| + return change unless (flags & event_flags).empty? + end + nil + end + + def _event_path(event) + Pathname.new(event.watcher.path) + end + + def _watch_for_new_file(event) + queue = event.watcher.queue + _find(_event_path(event).to_s) do |file_path| + unless queue.watchers.detect { |_, v| v.path == file_path.to_s } + _watch_file(file_path, queue) + end + end + end + + def _watch_file(path, queue) + queue.watch_file(path, *options.events, &@callback) + rescue Errno::ENOENT => e + _log :warn, "kqueue: watch file failed: #{e.message}" + end + + # Quick rubocop workaround + def _find(*paths, &block) + Find.send(:find, *paths, &block) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/adapter/config.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/adapter/config.rb new file mode 100644 index 00000000..e1c807a7 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/adapter/config.rb @@ -0,0 +1,26 @@ +require 'pathname' + +module SassListen + module Adapter + class Config + attr_reader :directories + attr_reader :silencer + attr_reader :queue + attr_reader :adapter_options + + def initialize(directories, queue, silencer, adapter_options) + # Default to current directory if no directories are supplied + directories = [Dir.pwd] if directories.to_a.empty? + + # TODO: fix (flatten, array, compact?) + @directories = directories.map do |directory| + Pathname.new(directory.to_s).realpath + end + + @silencer = silencer + @queue = queue + @adapter_options = adapter_options + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/adapter/darwin.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/adapter/darwin.rb new file mode 100644 index 00000000..d5437263 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/adapter/darwin.rb @@ -0,0 +1,88 @@ +require 'thread' +require 'sass-listen/internals/thread_pool' + +module SassListen + module Adapter + # Adapter implementation for Mac OS X `FSEvents`. + # + class Darwin < Base + OS_REGEXP = /darwin(?1\d+)/i + + # The default delay between checking for changes. + DEFAULTS = { latency: 0.1 } + + INCOMPATIBLE_GEM_VERSION = <<-EOS.gsub(/^ {8}/, '') + rb-fsevent > 0.9.4 no longer supports OS X 10.6 through 10.8. + + Please add the following to your Gemfile to avoid polling for changes: + require 'rbconfig' + if RbConfig::CONFIG['target_os'] =~ /darwin(1[0-3])/i + gem 'rb-fsevent', '<= 0.9.4' + end + EOS + + def self.usable? + require 'rb-fsevent' + darwin_version = RbConfig::CONFIG['target_os'][OS_REGEXP, :major_version] or return false + return true if darwin_version.to_i >= 13 # darwin13 is OS X 10.9 + return true if Gem::Version.new(FSEvent::VERSION) <= Gem::Version.new('0.9.4') + Kernel.warn INCOMPATIBLE_GEM_VERSION + false + end + + private + + # NOTE: each directory gets a DIFFERENT callback! + def _configure(dir, &callback) + opts = { latency: options.latency } + + @workers ||= ::Queue.new + @workers << FSEvent.new.tap do |worker| + _log :debug, "fsevent: watching: #{dir.to_s.inspect}" + worker.watch(dir.to_s, opts, &callback) + end + end + + def _run + first = @workers.pop + + # NOTE: _run is called within a thread, so run every other + # worker in it's own thread + _run_workers_in_background(_to_array(@workers)) + _run_worker(first) + end + + def _process_event(dir, event) + _log :debug, "fsevent: processing event: #{event.inspect}" + event.each do |path| + new_path = Pathname.new(path.sub(/\/$/, '')) + _log :debug, "fsevent: #{new_path}" + # TODO: does this preserve symlinks? + rel_path = new_path.relative_path_from(dir).to_s + _queue_change(:dir, dir, rel_path, recursive: true) + end + end + + def _run_worker(worker) + _log :debug, "fsevent: running worker: #{worker.inspect}" + worker.run + rescue + _log_exception 'fsevent: running worker failed: %s:%s called from: %s', caller + end + + def _run_workers_in_background(workers) + workers.each do |worker| + # NOTE: while passing local variables to the block below is not + # thread safe, using 'worker' from the enumerator above is ok + SassListen::Internals::ThreadPool.add { _run_worker(worker) } + end + end + + def _to_array(queue) + workers = [] + workers << queue.pop until queue.empty? + workers + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/adapter/linux.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/adapter/linux.rb new file mode 100644 index 00000000..08d0d80e --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/adapter/linux.rb @@ -0,0 +1,108 @@ +module SassListen + module Adapter + # @see https://github.com/nex3/rb-inotify + class Linux < Base + OS_REGEXP = /linux/i + + DEFAULTS = { + events: [ + :recursive, + :attrib, + :create, + :delete, + :move, + :close_write + ], + wait_for_delay: 0.1 + } + + private + + WIKI_URL = 'https://github.com/guard/listen'\ + '/wiki/Increasing-the-amount-of-inotify-watchers' + + INOTIFY_LIMIT_MESSAGE = <<-EOS.gsub(/^\s*/, '') + FATAL: SassListen error: unable to monitor directories for changes. + Visit #{WIKI_URL} for info on how to fix this. + EOS + + def _configure(directory, &callback) + require 'rb-inotify' + @worker ||= ::INotify::Notifier.new + @worker.watch(directory.to_s, *options.events, &callback) + rescue Errno::ENOSPC + abort(INOTIFY_LIMIT_MESSAGE) + end + + def _run + Thread.current[:listen_blocking_read_thread] = true + @worker.run + Thread.current[:listen_blocking_read_thread] = false + end + + def _process_event(dir, event) + # NOTE: avoid using event.absolute_name since new API + # will need to have a custom recursion implemented + # to properly match events to configured directories + path = Pathname.new(event.watcher.path) + event.name + rel_path = path.relative_path_from(dir).to_s + + _log(:debug) { "inotify: #{rel_path} (#{event.flags.inspect})" } + + if /1|true/ =~ ENV['LISTEN_GEM_SIMULATE_FSEVENT'] + if (event.flags & [:moved_to, :moved_from]) || _dir_event?(event) + rel_path = path.dirname.relative_path_from(dir).to_s + _queue_change(:dir, dir, rel_path, {}) + else + _queue_change(:dir, dir, rel_path, {}) + end + return + end + + return if _skip_event?(event) + + cookie_params = event.cookie.zero? ? {} : { cookie: event.cookie } + + # Note: don't pass options to force rescanning the directory, so we can + # detect moving/deleting a whole tree + if _dir_event?(event) + _queue_change(:dir, dir, rel_path, cookie_params) + return + end + + params = cookie_params.merge(change: _change(event.flags)) + + _queue_change(:file, dir, rel_path, params) + end + + def _skip_event?(event) + # Event on root directory + return true if event.name == '' + # INotify reports changes to files inside directories as events + # on the directories themselves too. + # + # @see http://linux.die.net/man/7/inotify + _dir_event?(event) && (event.flags & [:close, :modify]).any? + end + + def _change(event_flags) + { modified: [:attrib, :close_write], + moved_to: [:moved_to], + moved_from: [:moved_from], + added: [:create], + removed: [:delete] }.each do |change, flags| + return change unless (flags & event_flags).empty? + end + nil + end + + def _dir_event?(event) + event.flags.include?(:isdir) + end + + def _stop + @worker && @worker.close + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/adapter/polling.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/adapter/polling.rb new file mode 100644 index 00000000..cd6d3c97 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/adapter/polling.rb @@ -0,0 +1,37 @@ +module SassListen + module Adapter + # Polling Adapter that works cross-platform and + # has no dependencies. This is the adapter that + # uses the most CPU processing power and has higher + # file IO than the other implementations. + # + class Polling < Base + OS_REGEXP = // # match every OS + + DEFAULTS = { latency: 1.0, wait_for_delay: 0.05 } + + private + + def _configure(_, &callback) + @polling_callbacks ||= [] + @polling_callbacks << callback + end + + def _run + loop do + start = Time.now.to_f + @polling_callbacks.each do |callback| + callback.call(nil) + nap_time = options.latency - (Time.now.to_f - start) + # TODO: warn if nap_time is negative (polling too slow) + sleep(nap_time) if nap_time > 0 + end + end + end + + def _process_event(dir, _) + _queue_change(:dir, dir, '.', recursive: true) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/adapter/windows.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/adapter/windows.rb new file mode 100644 index 00000000..7214e19e --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/adapter/windows.rb @@ -0,0 +1,99 @@ +module SassListen + module Adapter + # Adapter implementation for Windows `wdm`. + # + class Windows < Base + OS_REGEXP = /mswin|mingw|cygwin/i + + BUNDLER_DECLARE_GEM = <<-EOS.gsub(/^ {6}/, '') + Please add the following to your Gemfile to avoid polling for changes: + gem 'wdm', '>= 0.1.0' if Gem.win_platform? + EOS + + def self.usable? + return false unless super + require 'wdm' + true + rescue LoadError + _log :debug, format('wdm - load failed: %s:%s', $ERROR_INFO, + $ERROR_POSITION * "\n") + + Kernel.warn BUNDLER_DECLARE_GEM + false + end + + private + + def _configure(dir, &callback) + require 'wdm' + _log :debug, 'wdm - starting...' + @worker ||= WDM::Monitor.new + @worker.watch_recursively(dir.to_s, :files) do |change| + callback.call([:file, change]) + end + + @worker.watch_recursively(dir.to_s, :directories) do |change| + callback.call([:dir, change]) + end + + events = [:attributes, :last_write] + @worker.watch_recursively(dir.to_s, *events) do |change| + callback.call([:attr, change]) + end + end + + def _run + @worker.run! + end + + def _process_event(dir, event) + _log :debug, "wdm - callback: #{event.inspect}" + + type, change = event + + full_path = Pathname(change.path) + + rel_path = full_path.relative_path_from(dir).to_s + + options = { change: _change(change.type) } + + case type + when :file + _queue_change(:file, dir, rel_path, options) + when :attr + unless full_path.directory? + _queue_change(:file, dir, rel_path, options) + end + when :dir + if change.type == :removed + # TODO: check if watched dir? + _queue_change(:dir, dir, Pathname(rel_path).dirname.to_s, {}) + elsif change.type == :added + _queue_change(:dir, dir, rel_path, {}) + else + # do nothing - changed directory means either: + # - removed subdirs (handled above) + # - added subdirs (handled above) + # - removed files (handled by _file_callback) + # - added files (handled by _file_callback) + # so what's left? + end + end + rescue + details = event.inspect + _log :error, format('wdm - callback (%): %s:%s', details, $ERROR_INFO, + $ERROR_POSITION * "\n") + raise + end + + def _change(type) + { modified: [:modified, :attrib], # TODO: is attrib really passed? + added: [:added, :renamed_new_file], + removed: [:removed, :renamed_old_file] }.each do |change, types| + return change if types.include?(type) + end + nil + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/backend.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/backend.rb new file mode 100644 index 00000000..08dd764f --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/backend.rb @@ -0,0 +1,43 @@ +require 'sass-listen/adapter' +require 'sass-listen/adapter/base' +require 'sass-listen/adapter/config' + +require 'forwardable' + +# This class just aggregates configuration object to avoid Listener specs +# from exploding with huge test setup blocks +module SassListen + class Backend + def initialize(directories, queue, silencer, config) + adapter_select_opts = config.adapter_select_options + + adapter_class = Adapter.select(adapter_select_opts) + + # Use default from adapter if possible + @min_delay_between_events = config.min_delay_between_events + @min_delay_between_events ||= adapter_class::DEFAULTS[:wait_for_delay] + @min_delay_between_events ||= 0.1 + + adapter_opts = config.adapter_instance_options(adapter_class) + + aconfig = Adapter::Config.new(directories, queue, silencer, adapter_opts) + @adapter = adapter_class.new(aconfig) + end + + def start + adapter.start + end + + def stop + adapter.stop + end + + def min_delay_between_events + @min_delay_between_events + end + + private + + attr_reader :adapter + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/change.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/change.rb new file mode 100644 index 00000000..6b1f07c3 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/change.rb @@ -0,0 +1,78 @@ +require 'sass-listen/file' +require 'sass-listen/directory' + +module SassListen + # TODO: rename to Snapshot + class Change + # TODO: test this class for coverage + class Config + def initialize(queue, silencer) + @queue = queue + @silencer = silencer + end + + def silenced?(path, type) + @silencer.silenced?(Pathname(path), type) + end + + def queue(*args) + @queue << args + end + end + + attr_reader :record + + def initialize(config, record) + @config = config + @record = record + end + + # Invalidate some part of the snapshot/record (dir, file, subtree, etc.) + def invalidate(type, rel_path, options) + watched_dir = Pathname.new(record.root) + + change = options[:change] + cookie = options[:cookie] + + if !cookie && config.silenced?(rel_path, type) + SassListen::Logger.debug { "(silenced): #{rel_path.inspect}" } + return + end + + path = watched_dir + rel_path + + SassListen::Logger.debug do + log_details = options[:silence] && 'recording' || change || 'unknown' + "#{log_details}: #{type}:#{path} (#{options.inspect})" + end + + if change + options = cookie ? { cookie: cookie } : {} + config.queue(type, change, watched_dir, rel_path, options) + else + if type == :dir + # NOTE: POSSIBLE RECURSION + # TODO: fix - use a queue instead + Directory.scan(self, rel_path, options) + else + change = File.change(record, rel_path) + return if !change || options[:silence] + config.queue(:file, change, watched_dir, rel_path) + end + end + rescue RuntimeError => ex + msg = format( + '%s#%s crashed %s:%s', + self.class, + __method__, + exinspect, + ex.backtrace * "\n") + SassListen::Logger.error(msg) + raise + end + + private + + attr_reader :config + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/cli.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/cli.rb new file mode 100644 index 00000000..c56193d5 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/cli.rb @@ -0,0 +1,65 @@ +require 'thor' +require 'sass-listen' +require 'logger' + +module SassListen + class CLI < Thor + default_task :start + + desc 'start', 'Starts SassListen' + + class_option :verbose, + type: :boolean, + default: false, + aliases: '-v', + banner: 'Verbose' + + class_option :directory, + type: :array, + default: '.', + aliases: '-d', + banner: 'The directory to listen to' + + class_option :relative, + type: :boolean, + default: false, + aliases: '-r', + banner: 'Convert paths relative to current directory' + + def start + SassListen::Forwarder.new(options).start + end + end + + class Forwarder + attr_reader :logger + def initialize(options) + @options = options + @logger = ::Logger.new(STDOUT) + @logger.level = ::Logger::INFO + @logger.formatter = proc { |_, _, _, msg| "#{msg}\n" } + end + + def start + logger.info 'Starting listen...' + directory = @options[:directory] + relative = @options[:relative] + callback = proc do |modified, added, removed| + if @options[:verbose] + logger.info "+ #{added}" unless added.empty? + logger.info "- #{removed}" unless removed.empty? + logger.info "> #{modified}" unless modified.empty? + end + end + + listener = SassListen.to( + directory, + relative: relative, + &callback) + + listener.start + + sleep 0.5 while listener.processing? + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/directory.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/directory.rb new file mode 100644 index 00000000..b3ab7045 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/directory.rb @@ -0,0 +1,87 @@ +require 'set' + +module SassListen + # TODO: refactor (turn it into a normal object, cache the stat, etc) + class Directory + def self.scan(snapshot, rel_path, options) + record = snapshot.record + dir = Pathname.new(record.root) + previous = record.dir_entries(rel_path) + + record.add_dir(rel_path) + + # TODO: use children(with_directory: false) + path = dir + rel_path + current = Set.new(_children(path)) + + SassListen::Logger.debug do + format('%s: %s(%s): %s -> %s', + (options[:silence] ? 'Recording' : 'Scanning'), + rel_path, options.inspect, previous.inspect, current.inspect) + end + + begin + current.each do |full_path| + type = ::File.lstat(full_path.to_s).directory? ? :dir : :file + item_rel_path = full_path.relative_path_from(dir).to_s + _change(snapshot, type, item_rel_path, options) + end + rescue Errno::ENOENT + # The directory changed meanwhile, so rescan it + current = Set.new(_children(path)) + retry + end + + # TODO: this is not tested properly + previous = previous.reject { |entry, _| current.include? path + entry } + + _async_changes(snapshot, Pathname.new(rel_path), previous, options) + + rescue Errno::ENOENT, Errno::EHOSTDOWN + record.unset_path(rel_path) + _async_changes(snapshot, Pathname.new(rel_path), previous, options) + + rescue Errno::ENOTDIR + # TODO: path not tested + record.unset_path(rel_path) + _async_changes(snapshot, path, previous, options) + _change(snapshot, :file, rel_path, options) + rescue + SassListen::Logger.warn do + format('scan DIED: %s:%s', $ERROR_INFO, $ERROR_POSITION * "\n") + end + raise + end + + def self._async_changes(snapshot, path, previous, options) + fail "Not a Pathname: #{path.inspect}" unless path.respond_to?(:children) + previous.each do |entry, data| + # TODO: this is a hack with insufficient testing + type = data.key?(:mtime) ? :file : :dir + rel_path_s = (path + entry).to_s + _change(snapshot, type, rel_path_s, options) + end + end + + def self._change(snapshot, type, path, options) + return snapshot.invalidate(type, path, options) if type == :dir + + # Minor param cleanup for tests + # TODO: use a dedicated Event class + opts = options.dup + opts.delete(:recursive) + snapshot.invalidate(type, path, opts) + end + + def self._children(path) + return path.children unless RUBY_ENGINE == 'jruby' + + # JRuby inconsistency workaround, see: + # https://github.com/jruby/jruby/issues/3840 + exists = path.exist? + directory = path.directory? + return path.children unless (exists && !directory) + raise Errno::ENOTDIR, path.to_s + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/event/config.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/event/config.rb new file mode 100644 index 00000000..9709ebef --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/event/config.rb @@ -0,0 +1,59 @@ +module SassListen + module Event + class Config + def initialize( + listener, + event_queue, + queue_optimizer, + wait_for_delay, + &block) + + @listener = listener + @event_queue = event_queue + @queue_optimizer = queue_optimizer + @min_delay_between_events = wait_for_delay + @block = block + end + + def sleep(*args) + Kernel.sleep(*args) + end + + def call(*args) + @block.call(*args) if @block + end + + def timestamp + Time.now.to_f + end + + def event_queue + @event_queue + end + + def callable? + @block + end + + def optimize_changes(changes) + @queue_optimizer.smoosh_changes(changes) + end + + def min_delay_between_events + @min_delay_between_events + end + + def stopped? + listener.state == :stopped + end + + def paused? + listener.state == :paused + end + + private + + attr_reader :listener + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/event/loop.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/event/loop.rb new file mode 100644 index 00000000..c2caec23 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/event/loop.rb @@ -0,0 +1,117 @@ +require 'thread' + +require 'timeout' +require 'sass-listen/event/processor' + +module SassListen + module Event + class Loop + class Error < RuntimeError + class NotStarted < Error + end + end + + def initialize(config) + @config = config + @wait_thread = nil + @state = :paused + @reasons = ::Queue.new + end + + def wakeup_on_event + return if stopped? + return unless processing? + return unless wait_thread.alive? + _wakeup(:event) + end + + def paused? + wait_thread && state == :paused + end + + def processing? + return false if stopped? + return false if paused? + state == :processing + end + + def setup + # TODO: use a Fiber instead? + q = ::Queue.new + @wait_thread = Internals::ThreadPool.add do + _wait_for_changes(q, config) + end + + SassListen::Logger.debug('Waiting for processing to start...') + Timeout.timeout(5) { q.pop } + end + + def resume + fail Error::NotStarted if stopped? + return unless wait_thread + _wakeup(:resume) + end + + def pause + # TODO: works? + # fail NotImplementedError + end + + def teardown + return unless wait_thread + if wait_thread.alive? + _wakeup(:teardown) + wait_thread.join + end + @wait_thread = nil + end + + def stopped? + !wait_thread + end + + private + + attr_reader :config + attr_reader :wait_thread + + attr_accessor :state + + def _wait_for_changes(ready_queue, config) + processor = Event::Processor.new(config, @reasons) + + _wait_until_resumed(ready_queue) + processor.loop_for(config.min_delay_between_events) + rescue StandardError => ex + _nice_error(ex) + end + + def _sleep(*args) + Kernel.sleep(*args) + end + + def _wait_until_resumed(ready_queue) + self.state = :paused + ready_queue << :ready + sleep + self.state = :processing + end + + def _nice_error(ex) + indent = "\n -- " + msg = format( + 'exception while processing events: %s Backtrace:%s%s', + ex, + indent, + ex.backtrace * indent + ) + SassListen::Logger.error(msg) + end + + def _wakeup(reason) + @reasons << reason + wait_thread.wakeup + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/event/processor.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/event/processor.rb new file mode 100644 index 00000000..40028809 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/event/processor.rb @@ -0,0 +1,122 @@ +module SassListen + module Event + class Processor + def initialize(config, reasons) + @config = config + @reasons = reasons + _reset_no_unprocessed_events + end + + # TODO: implement this properly instead of checking the state at arbitrary + # points in time + def loop_for(latency) + @latency = latency + + loop do + _wait_until_events + _wait_until_events_calm_down + _wait_until_no_longer_paused + _process_changes + end + rescue Stopped + SassListen::Logger.debug('Processing stopped') + end + + private + + class Stopped < RuntimeError + end + + def _wait_until_events_calm_down + loop do + now = _timestamp + + # Assure there's at least latency between callbacks to allow + # for accumulating changes + diff = _deadline - now + break if diff <= 0 + + # give events a bit of time to accumulate so they can be + # compressed/optimized + _sleep(:waiting_until_latency, diff) + end + end + + def _wait_until_no_longer_paused + # TODO: may not be a good idea? + _sleep(:waiting_for_unpause) while config.paused? + end + + def _check_stopped + return unless config.stopped? + + _flush_wakeup_reasons + raise Stopped + end + + def _sleep(_local_reason, *args) + _check_stopped + sleep_duration = config.sleep(*args) + _check_stopped + + _flush_wakeup_reasons do |reason| + next unless reason == :event + _remember_time_of_first_unprocessed_event unless config.paused? + end + + sleep_duration + end + + def _remember_time_of_first_unprocessed_event + @first_unprocessed_event_time ||= _timestamp + end + + def _reset_no_unprocessed_events + @first_unprocessed_event_time = nil + end + + def _deadline + @first_unprocessed_event_time + @latency + end + + def _wait_until_events + # TODO: long sleep may not be a good idea? + _sleep(:waiting_for_events) while config.event_queue.empty? + @first_unprocessed_event_time ||= _timestamp + end + + def _flush_wakeup_reasons + reasons = @reasons + until reasons.empty? + reason = reasons.pop + yield reason if block_given? + end + end + + def _timestamp + config.timestamp + end + + # for easier testing without sleep loop + def _process_changes + _reset_no_unprocessed_events + + changes = [] + changes << config.event_queue.pop until config.event_queue.empty? + + callable = config.callable? + return unless callable + + hash = config.optimize_changes(changes) + result = [hash[:modified], hash[:added], hash[:removed]] + return if result.all?(&:empty?) + + block_start = _timestamp + config.call(*result) + SassListen::Logger.debug "Callback took #{_timestamp - block_start} sec" + end + + attr_reader :config + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/event/queue.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/event/queue.rb new file mode 100644 index 00000000..3d763671 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/event/queue.rb @@ -0,0 +1,58 @@ +require 'thread' + +require 'forwardable' + +module SassListen + module Event + class Queue + class Config + def initialize(relative) + @relative = relative + end + + def relative? + @relative + end + end + + def initialize(config, &block) + @event_queue = ::Queue.new + @block = block + @config = config + end + + def <<(args) + type, change, dir, path, options = *args + fail "Invalid type: #{type.inspect}" unless [:dir, :file].include? type + fail "Invalid change: #{change.inspect}" unless change.is_a?(Symbol) + fail "Invalid path: #{path.inspect}" unless path.is_a?(String) + + dir = _safe_relative_from_cwd(dir) + event_queue.public_send(:<<, [type, change, dir, path, options]) + + block.call(args) if block + end + + def empty? + event_queue.empty? + end + + def pop + event_queue.pop + end + + private + + attr_reader :event_queue + attr_reader :block + attr_reader :config + + def _safe_relative_from_cwd(dir) + return dir unless config.relative? + dir.relative_path_from(Pathname.pwd) + rescue ArgumentError + dir + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/file.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/file.rb new file mode 100644 index 00000000..9a60bd71 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/file.rb @@ -0,0 +1,80 @@ +require 'digest/md5' + +module SassListen + class File + def self.change(record, rel_path) + path = Pathname.new(record.root) + rel_path + lstat = path.lstat + + data = { mtime: lstat.mtime.to_f, mode: lstat.mode } + + record_data = record.file_data(rel_path) + + if record_data.empty? + record.update_file(rel_path, data) + return :added + end + + if data[:mode] != record_data[:mode] + record.update_file(rel_path, data) + return :modified + end + + if data[:mtime] != record_data[:mtime] + record.update_file(rel_path, data) + return :modified + end + + return if /1|true/ =~ ENV['LISTEN_GEM_DISABLE_HASHING'] + return unless self.inaccurate_mac_time?(lstat) + + # Check if change happened within 1 second (maybe it's even + # too much, e.g. 0.3-0.5 could be sufficient). + # + # With rb-fsevent, there's a (configurable) latency between + # when file was changed and when the event was triggered. + # + # If a file is saved at ???14.998, by the time the event is + # actually received by SassListen, the time could already be e.g. + # ???15.7. + # + # And since Darwin adapter uses directory scanning, the file + # mtime may be the same (e.g. file was changed at ???14.001, + # then at ???14.998, but the fstat time would be ???14.0 in + # both cases). + # + # If change happend at ???14.999997, the mtime is 14.0, so for + # an mtime=???14.0 we assume it could even be almost ???15.0 + # + # So if Time.now.to_f is ???15.999998 and stat reports mtime + # at ???14.0, then event was due to that file'd change when: + # + # ???15.999997 - ???14.999998 < 1.0s + # + # So the "2" is "1 + 1" (1s to cover rb-fsevent latency + + # 1s maximum difference between real mtime and that recorded + # in the file system) + # + return if data[:mtime].to_i + 2 <= Time.now.to_f + + md5 = Digest::MD5.file(path).digest + record.update_file(rel_path, data.merge(md5: md5)) + :modified if record_data[:md5] && md5 != record_data[:md5] + rescue SystemCallError + record.unset_path(rel_path) + :removed + rescue + SassListen::Logger.debug "lstat failed for: #{rel_path} (#{$ERROR_INFO})" + raise + end + + def self.inaccurate_mac_time?(stat) + # 'mac' means Modified/Accessed/Created + + # Since precision depends on mounted FS (e.g. you can have a FAT partiion + # mounted on Linux), check for fields with a remainder to detect this + + [stat.mtime, stat.ctime, stat.atime].map(&:usec).all?(&:zero?) + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/fsm.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/fsm.rb new file mode 100644 index 00000000..e253b76f --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/fsm.rb @@ -0,0 +1,131 @@ +# Code copied from https://github.com/celluloid/celluloid-fsm +module SassListen + module FSM + DEFAULT_STATE = :default # Default state name unless one is explicitly set + + # Included hook to extend class methods + def self.included(klass) + klass.send :extend, ClassMethods + end + + module ClassMethods + # Obtain or set the default state + # Passing a state name sets the default state + def default_state(new_default = nil) + if new_default + @default_state = new_default.to_sym + else + defined?(@default_state) ? @default_state : DEFAULT_STATE + end + end + + # Obtain the valid states for this FSM + def states + @states ||= {} + end + + # Declare an FSM state and optionally provide a callback block to fire + # Options: + # * to: a state or array of states this state can transition to + def state(*args, &block) + if args.last.is_a? Hash + # Stringify keys :/ + options = args.pop.each_with_object({}) { |(k, v), h| h[k.to_s] = v } + else + options = {} + end + + args.each do |name| + name = name.to_sym + default_state name if options['default'] + states[name] = State.new(name, options['to'], &block) + end + end + end + + # Be kind and call super if you must redefine initialize + def initialize + @state = self.class.default_state + end + + # Obtain the current state of the FSM + attr_reader :state + + def transition(state_name) + new_state = validate_and_sanitize_new_state(state_name) + return unless new_state + transition_with_callbacks!(new_state) + end + + # Immediate state transition with no checks, or callbacks. "Dangerous!" + def transition!(state_name) + @state = state_name + end + + protected + + def validate_and_sanitize_new_state(state_name) + state_name = state_name.to_sym + + return if current_state_name == state_name + + if current_state && !current_state.valid_transition?(state_name) + valid = current_state.transitions.map(&:to_s).join(', ') + msg = "#{self.class} can't change state from '#{@state}'"\ + " to '#{state_name}', only to: #{valid}" + fail ArgumentError, msg + end + + new_state = states[state_name] + + unless new_state + return if state_name == default_state + fail ArgumentError, "invalid state for #{self.class}: #{state_name}" + end + + new_state + end + + def transition_with_callbacks!(state_name) + transition! state_name.name + state_name.call(self) + end + + def states + self.class.states + end + + def default_state + self.class.default_state + end + + def current_state + states[@state] + end + + def current_state_name + current_state && current_state.name || '' + end + + class State + attr_reader :name, :transitions + + def initialize(name, transitions = nil, &block) + @name, @block = name, block + @transitions = nil + @transitions = Array(transitions).map(&:to_sym) if transitions + end + + def call(obj) + obj.instance_eval(&@block) if @block + end + + def valid_transition?(new_state) + # All transitions are allowed unless expressly + return true unless @transitions + + @transitions.include? new_state.to_sym + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/internals/thread_pool.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/internals/thread_pool.rb new file mode 100644 index 00000000..102b2140 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/internals/thread_pool.rb @@ -0,0 +1,29 @@ +module SassListen + # @private api + module Internals + module ThreadPool + def self.add(&block) + Thread.new { block.call }.tap do |th| + (@threads ||= Queue.new) << th + end + end + + def self.stop + return unless @threads ||= nil + return if @threads.empty? # return to avoid using possibly stubbed Queue + + killed = Queue.new + # You can't kill a read on a descriptor in JRuby, so let's just + # ignore running threads (listen rb-inotify waiting for disk activity + # before closing) pray threads die faster than they are created... + limit = RUBY_ENGINE == 'jruby' ? [1] : [] + + killed << @threads.pop.kill until @threads.empty? + until killed.empty? + th = killed.pop + th.join(*limit) unless th[:listen_blocking_read_thread] + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/listener.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/listener.rb new file mode 100644 index 00000000..6c5e2a70 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/listener.rb @@ -0,0 +1,132 @@ +require 'English' + +require 'sass-listen/version' + +require 'sass-listen/backend' + +require 'sass-listen/silencer' +require 'sass-listen/silencer/controller' + +require 'sass-listen/queue_optimizer' + +require 'sass-listen/fsm' + +require 'sass-listen/event/loop' +require 'sass-listen/event/queue' +require 'sass-listen/event/config' + +require 'sass-listen/listener/config' + +module SassListen + class Listener + include SassListen::FSM + + # Initializes the directories listener. + # + # @param [String] directory the directories to listen to + # @param [Hash] options the listen options (see SassListen::Listener::Options) + # + # @yield [modified, added, removed] the changed files + # @yieldparam [Array] modified the list of modified files + # @yieldparam [Array] added the list of added files + # @yieldparam [Array] removed the list of removed files + # + def initialize(*dirs, &block) + options = dirs.last.is_a?(Hash) ? dirs.pop : {} + + @config = Config.new(options) + + eq_config = Event::Queue::Config.new(@config.relative?) + queue = Event::Queue.new(eq_config) { @processor.wakeup_on_event } + + silencer = Silencer.new + rules = @config.silencer_rules + @silencer_controller = Silencer::Controller.new(silencer, rules) + + @backend = Backend.new(dirs, queue, silencer, @config) + + optimizer_config = QueueOptimizer::Config.new(@backend, silencer) + + pconfig = Event::Config.new( + self, + queue, + QueueOptimizer.new(optimizer_config), + @backend.min_delay_between_events, + &block) + + @processor = Event::Loop.new(pconfig) + + super() # FSM + end + + default_state :initializing + + state :initializing, to: [:backend_started, :stopped] + + state :backend_started, to: [:frontend_ready, :stopped] do + backend.start + end + + state :frontend_ready, to: [:processing_events, :stopped] do + processor.setup + end + + state :processing_events, to: [:paused, :stopped] do + processor.resume + end + + state :paused, to: [:processing_events, :stopped] do + processor.pause + end + + state :stopped, to: [:backend_started] do + backend.stop # should be before processor.teardown to halt events ASAP + processor.teardown + end + + # Starts processing events and starts adapters + # or resumes invoking callbacks if paused + def start + transition :backend_started if state == :initializing + transition :frontend_ready if state == :backend_started + transition :processing_events if state == :frontend_ready + transition :processing_events if state == :paused + end + + # Stops both listening for events and processing them + def stop + transition :stopped + end + + # Stops invoking callbacks (messages pile up) + def pause + transition :paused + end + + # processing means callbacks are called + def processing? + state == :processing_events + end + + def paused? + state == :paused + end + + def ignore(regexps) + @silencer_controller.append_ignores(regexps) + end + + def ignore!(regexps) + @silencer_controller.replace_with_bang_ignores(regexps) + end + + def only(regexps) + @silencer_controller.replace_with_only(regexps) + end + + private + + attr_reader :processor + attr_reader :backend + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/listener/config.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/listener/config.rb new file mode 100644 index 00000000..a3ace4e0 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/listener/config.rb @@ -0,0 +1,45 @@ +module SassListen + class Listener + class Config + DEFAULTS = { + # Listener options + debug: false, # TODO: is this broken? + wait_for_delay: nil, # NOTE: should be provided by adapter if possible + relative: false, + + # Backend selecting options + force_polling: false, + polling_fallback_message: nil + } + + def initialize(opts) + @options = DEFAULTS.merge(opts) + @relative = @options[:relative] + @min_delay_between_events = @options[:wait_for_delay] + @silencer_rules = @options # silencer will extract what it needs + end + + def relative? + @relative + end + + def min_delay_between_events + @min_delay_between_events + end + + def silencer_rules + @silencer_rules + end + + def adapter_instance_options(klass) + valid_keys = klass.const_get('DEFAULTS').keys + Hash[@options.select { |key, _| valid_keys.include?(key) }] + end + + def adapter_select_options + valid_keys = %w(force_polling polling_fallback_message).map(&:to_sym) + Hash[@options.select { |key, _| valid_keys.include?(key) }] + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/logger.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/logger.rb new file mode 100644 index 00000000..5abe8bdf --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/logger.rb @@ -0,0 +1,32 @@ +module SassListen + def self.logger + @logger ||= nil + end + + def self.logger=(logger) + @logger = logger + end + + def self.setup_default_logger_if_unset + self.logger ||= ::Logger.new(STDERR).tap do |logger| + debugging = ENV['LISTEN_GEM_DEBUGGING'] + logger.level = + case debugging.to_s + when /2/ + ::Logger::DEBUG + when /true|yes|1/i + ::Logger::INFO + else + ::Logger::ERROR + end + end + end + + class Logger + [:fatal, :error, :warn, :info, :debug].each do |meth| + define_singleton_method(meth) do |*args, &block| + SassListen.logger.public_send(meth, *args, &block) if SassListen.logger + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/options.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/options.rb new file mode 100644 index 00000000..82a33268 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/options.rb @@ -0,0 +1,23 @@ +module SassListen + class Options + def initialize(opts, defaults) + @options = {} + given_options = opts.dup + defaults.keys.each do |key| + @options[key] = given_options.delete(key) || defaults[key] + end + + return if given_options.empty? + + msg = "Unknown options: #{given_options.inspect}" + SassListen::Logger.warn msg + fail msg + end + + def method_missing(name, *_) + return @options[name] if @options.key?(name) + msg = "Bad option: #{name.inspect} (valid:#{@options.keys.inspect})" + fail NameError, msg + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/queue_optimizer.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/queue_optimizer.rb new file mode 100644 index 00000000..650c4430 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/queue_optimizer.rb @@ -0,0 +1,132 @@ +module SassListen + class QueueOptimizer + class Config + def initialize(adapter_class, silencer) + @adapter_class = adapter_class + @silencer = silencer + end + + def exist?(path) + Pathname(path).exist? + end + + def silenced?(path, type) + @silencer.silenced?(path, type) + end + + def debug(*args, &block) + SassListen.logger.debug(*args, &block) + end + end + + def smoosh_changes(changes) + # TODO: adapter could be nil at this point (shutdown) + cookies = changes.group_by do |_, _, _, _, options| + (options || {})[:cookie] + end + _squash_changes(_reinterpret_related_changes(cookies)) + end + + def initialize(config) + @config = config + end + + private + + attr_reader :config + + # groups changes into the expected structure expected by + # clients + def _squash_changes(changes) + # We combine here for backward compatibility + # Newer clients should receive dir and path separately + changes = changes.map { |change, dir, path| [change, dir + path] } + + actions = changes.group_by(&:last).map do |path, action_list| + [_logical_action_for(path, action_list.map(&:first)), path.to_s] + end + + config.debug("listen: raw changes: #{actions.inspect}") + + { modified: [], added: [], removed: [] }.tap do |squashed| + actions.each do |type, path| + squashed[type] << path unless type.nil? + end + config.debug("listen: final changes: #{squashed.inspect}") + end + end + + def _logical_action_for(path, actions) + actions << :added if actions.delete(:moved_to) + actions << :removed if actions.delete(:moved_from) + + modified = actions.detect { |x| x == :modified } + _calculate_add_remove_difference(actions, path, modified) + end + + def _calculate_add_remove_difference(actions, path, default_if_exists) + added = actions.count { |x| x == :added } + removed = actions.count { |x| x == :removed } + diff = added - removed + + # TODO: avoid checking if path exists and instead assume the events are + # in order (if last is :removed, it doesn't exist, etc.) + if config.exist?(path) + if diff > 0 + :added + elsif diff.zero? && added > 0 + :modified + else + default_if_exists + end + else + diff < 0 ? :removed : nil + end + end + + # remove extraneous rb-inotify events, keeping them only if it's a possible + # editor rename() call (e.g. Kate and Sublime) + def _reinterpret_related_changes(cookies) + table = { moved_to: :added, moved_from: :removed } + cookies.map do |_, changes| + data = _detect_possible_editor_save(changes) + if data + to_dir, to_file = data + [[:modified, to_dir, to_file]] + else + not_silenced = changes.reject do |type, _, _, path, _| + config.silenced?(Pathname(path), type) + end + not_silenced.map do |_, change, dir, path, _| + [table.fetch(change, change), dir, path] + end + end + end.flatten(1) + end + + def _detect_possible_editor_save(changes) + return unless changes.size == 2 + + from_type = from_change = from = nil + to_type = to_change = to_dir = to = nil + + changes.each do |data| + case data[1] + when :moved_from + from_type, from_change, _, from, _ = data + when :moved_to + to_type, to_change, to_dir, to, _ = data + else + return nil + end + end + + return unless from && to + + # Expect an ignored moved_from and non-ignored moved_to + # to qualify as an "editor modify" + return unless config.silenced?(Pathname(from), from_type) + config.silenced?(Pathname(to), to_type) ? nil : [to_dir, to] + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/record.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/record.rb new file mode 100644 index 00000000..e8875114 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/record.rb @@ -0,0 +1,120 @@ +require 'thread' +require 'sass-listen/record/entry' +require 'sass-listen/record/symlink_detector' + +module SassListen + class Record + # TODO: one Record object per watched directory? + # TODO: deprecate + + attr_reader :root + def initialize(directory) + @tree = _auto_hash + @root = directory.to_s + end + + def add_dir(rel_path) + return if [nil, '', '.'].include? rel_path + @tree[rel_path] ||= {} + end + + def update_file(rel_path, data) + dirname, basename = Pathname(rel_path).split.map(&:to_s) + _fast_update_file(dirname, basename, data) + end + + def unset_path(rel_path) + dirname, basename = Pathname(rel_path).split.map(&:to_s) + _fast_unset_path(dirname, basename) + end + + def file_data(rel_path) + dirname, basename = Pathname(rel_path).split.map(&:to_s) + if [nil, '', '.'].include? dirname + tree[basename] ||= {} + tree[basename].dup + else + tree[dirname] ||= {} + tree[dirname][basename] ||= {} + tree[dirname][basename].dup + end + end + + def dir_entries(rel_path) + subtree = + if [nil, '', '.'].include? rel_path.to_s + tree + else + tree[rel_path.to_s] ||= _auto_hash + tree[rel_path.to_s] + end + + result = {} + subtree.each do |key, values| + # only get data for file entries + result[key] = values.key?(:mtime) ? values : {} + end + result + end + + def build + @tree = _auto_hash + # TODO: test with a file name given + # TODO: test other permissions + # TODO: test with mixed encoding + symlink_detector = SymlinkDetector.new + remaining = ::Queue.new + remaining << Entry.new(root, nil, nil) + _fast_build_dir(remaining, symlink_detector) until remaining.empty? + end + + private + + def _auto_hash + Hash.new { |h, k| h[k] = Hash.new } + end + + def tree + @tree + end + + def _fast_update_file(dirname, basename, data) + if [nil, '', '.'].include? dirname + tree[basename] = (tree[basename] || {}).merge(data) + else + tree[dirname] ||= {} + tree[dirname][basename] = (tree[dirname][basename] || {}).merge(data) + end + end + + def _fast_unset_path(dirname, basename) + # this may need to be reworked to properly remove + # entries from a tree, without adding non-existing dirs to the record + if [nil, '', '.'].include? dirname + return unless tree.key?(basename) + tree.delete(basename) + else + return unless tree.key?(dirname) + tree[dirname].delete(basename) + end + end + + def _fast_build_dir(remaining, symlink_detector) + entry = remaining.pop + children = entry.children # NOTE: children() implicitly tests if dir + symlink_detector.verify_unwatched!(entry) + children.each { |child| remaining << child } + add_dir(entry.record_dir_key) + rescue Errno::ENOTDIR + _fast_try_file(entry) + rescue SystemCallError, SymlinkDetector::Error + _fast_unset_path(entry.relative, entry.name) + end + + def _fast_try_file(entry) + _fast_update_file(entry.relative, entry.name, entry.meta) + rescue SystemCallError + _fast_unset_path(entry.relative, entry.name) + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/record/entry.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/record/entry.rb new file mode 100644 index 00000000..18a1c416 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/record/entry.rb @@ -0,0 +1,62 @@ +module SassListen + # @private api + class Record + # Represents a directory entry (dir or file) + class Entry + # file: "/home/me/watched_dir", "app/models", "foo.rb" + # dir, "/home/me/watched_dir", "." + def initialize(root, relative, name = nil) + @root, @relative, @name = root, relative, name + end + + attr_reader :root, :relative, :name + + def children + child_relative = _join + (_entries(sys_path) - %w(. ..)).map do |name| + Entry.new(@root, child_relative, name) + end + end + + def meta + lstat = ::File.lstat(sys_path) + { mtime: lstat.mtime.to_f, mode: lstat.mode } + end + + # record hash is e.g. + # if @record["/home/me/watched_dir"]["project/app/models"]["foo.rb"] + # if @record["/home/me/watched_dir"]["project/app"]["models"] + # record_dir_key is "project/app/models" + def record_dir_key + ::File.join(*[@relative, @name].compact) + end + + def sys_path + # Use full path in case someone uses chdir + ::File.join(*[@root, @relative, @name].compact) + end + + def real_path + @real_path ||= ::File.realpath(sys_path) + end + + private + + def _join + args = [@relative, @name].compact + args.empty? ? nil : ::File.join(*args) + end + + def _entries(dir) + return Dir.entries(dir) unless RUBY_ENGINE == 'jruby' + + # JRuby inconsistency workaround, see: + # https://github.com/jruby/jruby/issues/3840 + exists = ::File.exist?(dir) + directory = ::File.directory?(dir) + return Dir.entries(dir) unless (exists && !directory) + raise Errno::ENOTDIR, dir + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/record/symlink_detector.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/record/symlink_detector.rb new file mode 100644 index 00000000..0aced230 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/record/symlink_detector.rb @@ -0,0 +1,39 @@ +require 'set' + +module SassListen + # @private api + class Record + class SymlinkDetector + WIKI = 'https://github.com/guard/listen/wiki/Duplicate-directory-errors' + + SYMLINK_LOOP_ERROR = <<-EOS + ** ERROR: directory is already being watched! ** + + Directory: %s + + is already being watched through: %s + + MORE INFO: #{WIKI} + EOS + + class Error < RuntimeError + end + + def initialize + @real_dirs = Set.new + end + + def verify_unwatched!(entry) + real_path = entry.real_path + @real_dirs.add?(real_path) || _fail(entry.sys_path, real_path) + end + + private + + def _fail(symlinked, real_path) + STDERR.puts format(SYMLINK_LOOP_ERROR, symlinked, real_path) + fail Error, 'Failed due to looped symlinks' + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/silencer.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/silencer.rb new file mode 100644 index 00000000..7e735faa --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/silencer.rb @@ -0,0 +1,97 @@ +module SassListen + class Silencer + # The default list of directories that get ignored. + DEFAULT_IGNORED_DIRECTORIES = %r{^(?: + \.git + | \.svn + | \.hg + | \.rbx + | \.bundle + | bundle + | vendor/bundle + | log + | tmp + |vendor/ruby + )(/|$)}x + + # The default list of files that get ignored. + DEFAULT_IGNORED_EXTENSIONS = /(?: + # Kate's tmp\/swp files + \..*\d+\.new + | \.kate-swp + + # Gedit tmp files + | \.goutputstream-.{6} + + # Intellij files + | ___jb_bak___ + | ___jb_old___ + + # Vim swap files and write test + | \.sw[px] + | \.swpx + | ^4913 + + # Sed temporary files - but without actual words, like 'sedatives' + | (?:^ + sed + + (?: + [a-zA-Z0-9]{0}[A-Z]{1}[a-zA-Z0-9]{5} | + [a-zA-Z0-9]{1}[A-Z]{1}[a-zA-Z0-9]{4} | + [a-zA-Z0-9]{2}[A-Z]{1}[a-zA-Z0-9]{3} | + [a-zA-Z0-9]{3}[A-Z]{1}[a-zA-Z0-9]{2} | + [a-zA-Z0-9]{4}[A-Z]{1}[a-zA-Z0-9]{1} | + [a-zA-Z0-9]{5}[A-Z]{1}[a-zA-Z0-9]{0} + ) + ) + + # other files + | \.DS_Store + | \.tmp + | ~ + )$/x + + attr_accessor :only_patterns, :ignore_patterns + + def initialize + configure({}) + end + + def configure(options) + @only_patterns = options[:only] ? Array(options[:only]) : nil + @ignore_patterns = _init_ignores(options[:ignore], options[:ignore!]) + end + + # Note: relative_path is temporarily expected to be a relative Pathname to + # make refactoring easier (ideally, it would take a string) + + # TODO: switch type and path places - and verify + def silenced?(relative_path, type) + path = relative_path.to_s + + if only_patterns && type == :file + return true unless only_patterns.any? { |pattern| path =~ pattern } + end + + ignore_patterns.any? { |pattern| path =~ pattern } + end + + private + + attr_reader :options + + def _init_ignores(ignores, overrides) + patterns = [] + unless overrides + patterns << DEFAULT_IGNORED_DIRECTORIES + patterns << DEFAULT_IGNORED_EXTENSIONS + end + + patterns << ignores + patterns << overrides + + patterns.compact.flatten + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/silencer/controller.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/silencer/controller.rb new file mode 100644 index 00000000..d262ec83 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/silencer/controller.rb @@ -0,0 +1,48 @@ +module SassListen + class Silencer + class Controller + def initialize(silencer, default_options) + @silencer = silencer + + opts = default_options + + @prev_silencer_options = {} + rules = [:only, :ignore, :ignore!].map do |option| + [option, opts[option]] if opts.key? option + end + + _reconfigure_silencer(Hash[rules.compact]) + end + + def append_ignores(*regexps) + prev_ignores = Array(@prev_silencer_options[:ignore]) + _reconfigure_silencer(ignore: [prev_ignores + regexps]) + end + + def replace_with_bang_ignores(regexps) + _reconfigure_silencer(ignore!: regexps) + end + + def replace_with_only(regexps) + _reconfigure_silencer(only: regexps) + end + + private + + def _reconfigure_silencer(extra_options) + opts = extra_options.dup + opts = opts.map do |key, value| + [key, Array(value).flatten.compact] + end + opts = Hash[opts] + + if opts.key?(:ignore) && opts[:ignore].empty? + opts.delete(:ignore) + end + + @prev_silencer_options = opts + @silencer.configure(@prev_silencer_options.dup.freeze) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/version.rb b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/version.rb new file mode 100644 index 00000000..05fa5d03 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-listen-4.0.0/lib/sass-listen/version.rb @@ -0,0 +1,3 @@ +module SassListen + VERSION = '4.0.0' +end diff --git a/path/ruby/2.6.0/gems/sass-rails-5.1.0/MIT-LICENSE b/path/ruby/2.6.0/gems/sass-rails-5.1.0/MIT-LICENSE new file mode 100644 index 00000000..5f8a093f --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-rails-5.1.0/MIT-LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2011 Christopher Eppstein + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/path/ruby/2.6.0/gems/sass-rails-5.1.0/README.md b/path/ruby/2.6.0/gems/sass-rails-5.1.0/README.md new file mode 100644 index 00000000..1c7453ef --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-rails-5.1.0/README.md @@ -0,0 +1,101 @@ +# Official Ruby-on-Rails Integration with Sass + +This gem provides official integration for Ruby on Rails projects with the Sass stylesheet language. + +## Installing + +Since Rails 3.1, new Rails projects will be already configured to use Sass. If you are upgrading to Rails 3.1 you will need to add the following to your Gemfile: + + gem 'sass-rails' + +## Configuration + +To configure Sass via Rails set use `config.sass` in your +application and/or environment files to set configuration +properties that will be passed to Sass. + +### Options + +- `preferred_syntax` - This option determines the default Sass syntax and file extensions that will be used by Rails generators. Can be `:scss` (default CSS-compatible SCSS syntax) or `:sass` (indented Sass syntax). + +The [list of supported Sass options](http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#options) +can be found on the Sass Website with the following caveats: + +- `:style` - This option is not supported. This is determined by the Rails environment. It's `:expanded` only on development, otherwise it's `:compressed`. +- `:never_update` - This option is not supported. Instead set `config.assets.enabled = false` +- `:always_update` - This option is not supported. Sprockets uses a controller to access stylesheets in development mode instead of a full scan for changed files. +- `:always_check` - This option is not supported. Sprockets always checks in development. +- `:syntax` - This is determined by the file's extensions. +- `:filename` - This is determined by the file's name. +- `:line` - This is provided by the template handler. + +### Example + + MyProject::Application.configure do + config.sass.preferred_syntax = :sass + config.sass.line_comments = false + config.sass.cache = false + end + +## Important Note + +Sprockets provides some directives that are placed inside of comments called `require`, `require_tree`, and +`require_self`. **DO NOT USE THEM IN YOUR SASS/SCSS FILES.** They are very +primitive and do not work well with Sass files. Instead, use Sass's native `@import` directive which +`sass-rails` has customized to integrate with the conventions of your Rails projects. + +## Features + +### Glob Imports + +When in Rails, there is a special import syntax that allows you to +glob imports relative to the folder of the stylesheet that is doing the importing. + +* `@import "mixins/*"` will import all the files in the mixins folder +* `@import "mixins/**/*"` will import all the files in the mixins tree + +Any valid ruby glob may be used. The imports are sorted alphabetically. + +**NOTE:** It is recommended that you only use this when importing pure library +files (containing mixins and variables) because it is difficult to control the +cascade ordering for imports that contain styles using this approach. + +### Asset Helpers +When using the asset pipeline, paths to assets must be rewritten. +When referencing assets use the following asset helpers (underscored in Ruby, hyphenated +in Sass): + +#### `asset-path($relative-asset-path)` +Returns a string to the asset. + +* `asset-path("rails.png")` returns `"/assets/rails.png"` + +#### `asset-url($relative-asset-path)` +Returns a url reference to the asset. + +* `asset-url("rails.png")` returns `url(/assets/rails.png)` + +As a convenience, for each of the following asset classes there are +corresponding `-path` and `-url` helpers: +image, font, video, audio, javascript, stylesheet. + +* `image-path("rails.png")` returns `"/assets/rails.png"` +* `image-url("rails.png")` returns `url(/assets/rails.png)` + +#### `asset-data-url($relative-asset-path)` +Returns a url reference to the Base64-encoded asset at the specified path. + +* `asset-data-url("rails.png")` returns `url(data:image/png;base64,iVBORw0K...)` + +## Running Tests + + $ bundle install + $ bundle exec rake test + +If you need to test against local gems, use Bundler's gem :path option in the Gemfile and also edit `test/support/test_helper.rb` and tell the tests where the gem is checked out. + +## Code Status + +* [![Travis CI](https://api.travis-ci.org/rails/sass-rails.svg)](http://travis-ci.org/rails/sass-rails) +* [![Gem Version](https://badge.fury.io/rb/sass-rails.svg)](http://badge.fury.io/rb/sass-rails) +* [![Dependencies](https://gemnasium.com/rails/sass-rails.svg)](https://gemnasium.com/rails/sass-rails) diff --git a/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/rails/generators/sass/assets/assets_generator.rb b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/rails/generators/sass/assets/assets_generator.rb new file mode 100644 index 00000000..fd792dce --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/rails/generators/sass/assets/assets_generator.rb @@ -0,0 +1,13 @@ +require "rails/generators/named_base" + +module Sass + module Generators + class AssetsGenerator < ::Rails::Generators::NamedBase + source_root File.expand_path("../templates", __FILE__) + + def copy_sass + template "stylesheet.sass", File.join('app/assets/stylesheets', class_path, "#{file_name}.sass") + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/rails/generators/sass/assets/templates/stylesheet.sass b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/rails/generators/sass/assets/templates/stylesheet.sass new file mode 100644 index 00000000..25a5df95 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/rails/generators/sass/assets/templates/stylesheet.sass @@ -0,0 +1,3 @@ +// Place all the styles related to the <%= name %> controller here. +// They will automatically be included in application.css. +// You can use Sass here: http://sass-lang.com/ diff --git a/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/rails/generators/sass/scaffold/scaffold_generator.rb b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/rails/generators/sass/scaffold/scaffold_generator.rb new file mode 100644 index 00000000..99cbbe66 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/rails/generators/sass/scaffold/scaffold_generator.rb @@ -0,0 +1,9 @@ +require "rails/generators/sass_scaffold" + +module Sass + module Generators + class ScaffoldGenerator < ::Sass::Generators::ScaffoldBase + def syntax() :sass end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/rails/generators/sass_scaffold.rb b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/rails/generators/sass_scaffold.rb new file mode 100644 index 00000000..968247b6 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/rails/generators/sass_scaffold.rb @@ -0,0 +1,15 @@ +require "sass/css" +require "rails/generators/named_base" + +module Sass + module Generators + class ScaffoldBase < ::Rails::Generators::NamedBase + def copy_stylesheet + dir = ::Rails::Generators::ScaffoldGenerator.source_root + file = File.join(dir, "scaffold.css") + converted_contents = ::Sass::CSS.new(File.read(file)).render(syntax) + create_file "app/assets/stylesheets/scaffolds.#{syntax}", converted_contents + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/rails/generators/scss/assets/assets_generator.rb b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/rails/generators/scss/assets/assets_generator.rb new file mode 100644 index 00000000..403f1ebb --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/rails/generators/scss/assets/assets_generator.rb @@ -0,0 +1,13 @@ +require "rails/generators/named_base" + +module Scss + module Generators + class AssetsGenerator < ::Rails::Generators::NamedBase + source_root File.expand_path("../templates", __FILE__) + + def copy_scss + template "stylesheet.scss", File.join('app/assets/stylesheets', class_path, "#{file_name}.scss") + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/rails/generators/scss/assets/templates/stylesheet.scss b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/rails/generators/scss/assets/templates/stylesheet.scss new file mode 100644 index 00000000..2e92369b --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/rails/generators/scss/assets/templates/stylesheet.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the <%= name %> controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/rails/generators/scss/scaffold/scaffold_generator.rb b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/rails/generators/scss/scaffold/scaffold_generator.rb new file mode 100644 index 00000000..1f9ac4e4 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/rails/generators/scss/scaffold/scaffold_generator.rb @@ -0,0 +1,10 @@ +require "rails/generators/sass_scaffold" + +module Scss + module Generators + class ScaffoldGenerator < ::Sass::Generators::ScaffoldBase + def syntax() :scss end + end + end +end + diff --git a/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/sass-rails.rb b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/sass-rails.rb new file mode 100644 index 00000000..9c505548 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/sass-rails.rb @@ -0,0 +1 @@ +require 'sass/rails' diff --git a/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/sass/rails.rb b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/sass/rails.rb new file mode 100644 index 00000000..1ff85a68 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/sass/rails.rb @@ -0,0 +1,11 @@ +module Sass + module Rails + autoload :Logger, 'sass/rails/logger' + end +end + +require 'sass/rails/version' +require 'sass/rails/helpers' +require 'sass/rails/importer' +require 'sass/rails/template' +require 'sass/rails/railtie' diff --git a/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/sass/rails/cache_store.rb b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/sass/rails/cache_store.rb new file mode 100644 index 00000000..e7ba533f --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/sass/rails/cache_store.rb @@ -0,0 +1,31 @@ +require 'sass' + +module Sass + module Rails + class CacheStore < ::Sass::CacheStores::Base + attr_reader :environment + + def initialize(environment) + @environment = environment + end + + def _store(key, version, sha, contents) + environment.cache_set("sass/#{key}", {:version => version, :sha => sha, :contents => contents}) + end + + def _retrieve(key, version, sha) + if obj = environment.cache_get("sass/#{key}") + return unless obj[:version] == version + return unless obj[:sha] == sha + obj[:contents] + else + nil + end + end + + def path_to(key) + key + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/sass/rails/helpers.rb b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/sass/rails/helpers.rb new file mode 100644 index 00000000..6ff3fa00 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/sass/rails/helpers.rb @@ -0,0 +1,13 @@ +require 'sass' +require 'sprockets/sass_functions' + +module Sprockets + module SassFunctions + remove_method :asset_data_url if method_defined?(:asset_data_url) + def asset_data_url(path) + Sass::Script::String.new("url(" + sprockets_context.asset_data_uri(path.value) + ")") + end + end +end + +::Sass::Script::Functions.send :include, Sprockets::SassFunctions diff --git a/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/sass/rails/importer.rb b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/sass/rails/importer.rb new file mode 100644 index 00000000..9d20991c --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/sass/rails/importer.rb @@ -0,0 +1,147 @@ +require 'active_support/deprecation/reporting' +require 'sass' +require 'sprockets/sass_importer' +require 'tilt' + +module Sass + module Rails + class SassImporter < Sass::Importers::Filesystem + module Globbing + GLOB = /(\A|\/)(\*|\*\*\/\*)\z/ + + def find_relative(name, base, options) + if options[:sprockets] && m = name.match(GLOB) + path = name.sub(m[0], "") + base = File.expand_path(path, File.dirname(base)) + glob_imports(base, m[2], options) + else + super + end + end + + def find(name, options) + # globs must be relative + return if name =~ GLOB + super + end + + private + def glob_imports(base, glob, options) + contents = "" + context = options[:sprockets][:context] + each_globbed_file(base, glob, context) do |filename| + next if filename == options[:filename] + contents << "@import #{filename.inspect};\n" + end + return nil if contents == "" + Sass::Engine.new(contents, options.merge( + :filename => base, + :importer => self, + :syntax => :scss + )) + end + + def each_globbed_file(base, glob, context) + raise ArgumentError unless glob == "*" || glob == "**/*" + + exts = extensions.keys.map { |ext| Regexp.escape(".#{ext}") }.join("|") + sass_re = Regexp.compile("(#{exts})$") + + context.depend_on(base) + + Dir["#{base}/#{glob}"].sort.each do |path| + if File.directory?(path) + context.depend_on(path) + elsif sass_re =~ path + yield path + end + end + end + end + + module ERB + def extensions + { + 'css.erb' => :scss_erb, + 'scss.erb' => :scss_erb, + 'sass.erb' => :sass_erb + }.merge(super) + end + + def erb_extensions + { + :scss_erb => :scss, + :sass_erb => :sass + } + end + + def find_relative(*args) + process_erb_engine(super) + end + + def find(*args) + process_erb_engine(super) + end + + private + def process_erb_engine(engine) + if engine && engine.options[:sprockets] && syntax = erb_extensions[engine.options[:syntax]] + template = Tilt::ERBTemplate.new(engine.options[:filename]) + contents = template.render(engine.options[:sprockets][:context], {}) + + Sass::Engine.new(contents, engine.options.merge(:syntax => syntax)) + else + engine + end + end + end + + module Deprecated + def extensions + { + 'css.scss' => :scss, + 'css.sass' => :sass, + 'css.scss.erb' => :scss_erb, + 'css.sass.erb' => :sass_erb + }.merge(super) + end + + def find_relative(*args) + deprecate_extra_css_extension(super) + end + + def find(*args) + deprecate_extra_css_extension(super) + end + + private + def deprecate_extra_css_extension(engine) + if engine && filename = engine.options[:filename] + if filename.end_with?('.css.scss') + msg = "Extra .css in SCSS file is unnecessary. Rename #{filename} to #{filename.sub('.css.scss', '.scss')}." + elsif filename.end_with?('.css.sass') + msg = "Extra .css in SASS file is unnecessary. Rename #{filename} to #{filename.sub('.css.sass', '.sass')}." + elsif filename.end_with?('.css.scss.erb') + msg = "Extra .css in SCSS/ERB file is unnecessary. Rename #{filename} to #{filename.sub('.css.scss.erb', '.scss.erb')}." + elsif filename.end_with?('.css.sass.erb') + msg = "Extra .css in SASS/ERB file is unnecessary. Rename #{filename} to #{filename.sub('.css.sass.erb', '.sass.erb')}." + end + + ActiveSupport::Deprecation.warn(msg) if msg + end + + engine + end + end + + include Deprecated + include ERB + include Globbing + + # Allow .css files to be @import'd + def extensions + { 'css' => :scss }.merge(super) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/sass/rails/logger.rb b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/sass/rails/logger.rb new file mode 100644 index 00000000..c277a997 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/sass/rails/logger.rb @@ -0,0 +1,22 @@ +require 'sass' +require 'sass/logger' + +module Sass + module Rails + class Logger < Sass::Logger::Base + def _log(level, message) + + case level + when :trace, :debug + ::Rails.logger.debug message + when :warn + ::Rails.logger.warn message + when :error + ::Rails.logger.error message + when :info + ::Rails.logger.info message + end + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/sass/rails/railtie.rb b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/sass/rails/railtie.rb new file mode 100644 index 00000000..9da8b401 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/sass/rails/railtie.rb @@ -0,0 +1,94 @@ +require 'sass' +require 'active_support/core_ext/class/attribute' +require 'sprockets/railtie' + +module Sass::Rails + class Railtie < ::Rails::Railtie + config.sass = ActiveSupport::OrderedOptions.new + + # Establish static configuration defaults + # Emit scss files during stylesheet generation of scaffold + config.sass.preferred_syntax = :scss + # Write sass cache files for performance + config.sass.cache = true + # Read sass cache files for performance + config.sass.read_cache = true + # Display line comments above each selector as a debugging aid + config.sass.line_comments = true + # Initialize the load paths to an empty array + config.sass.load_paths = [] + # Send Sass logs to Rails.logger + config.sass.logger = Sass::Rails::Logger.new + + # Set the default stylesheet engine + # It can be overridden by passing: + # --stylesheet_engine=sass + # to the rails generate command + config.app_generators.stylesheet_engine config.sass.preferred_syntax + + if config.respond_to?(:annotations) + config.annotations.register_extensions("scss", "sass") { |annotation| /\/\/\s*(#{annotation}):?\s*(.*)$/ } + end + + # Remove the sass middleware if it gets inadvertently enabled by applications. + config.after_initialize do |app| + app.config.middleware.delete(Sass::Plugin::Rack) if defined?(Sass::Plugin::Rack) + end + + initializer :setup_sass, group: :all do |app| + # Only emit one kind of syntax because though we have registered two kinds of generators + syntax = app.config.sass.preferred_syntax.to_sym + alt_syntax = syntax == :sass ? "scss" : "sass" + app.config.generators.hide_namespace alt_syntax + + # Override stylesheet engine to the preferred syntax + config.app_generators.stylesheet_engine syntax + + # Set the sass cache location + config.sass.cache_location = File.join(Rails.root, "tmp/cache/sass") + + # Establish configuration defaults that are evironmental in nature + if config.sass.full_exception.nil? + # Display a stack trace in the css output when in development-like environments. + config.sass.full_exception = app.config.consider_all_requests_local + end + + config.assets.configure do |env| + if env.respond_to?(:register_engine) + args = ['.sass', Sass::Rails::SassTemplate] + args << { silence_deprecation: true } if env.method(:register_engine).arity.abs > 2 + env.register_engine(*args) + + args = ['.scss', Sass::Rails::ScssTemplate] + args << { silence_deprecation: true } if env.method(:register_engine).arity.abs > 2 + env.register_engine(*args) + end + + if env.respond_to?(:register_transformer) + env.register_transformer 'text/sass', 'text/css', + Sprockets::SassProcessor.new(importer: SassImporter, sass_config: app.config.sass) + env.register_transformer 'text/scss', 'text/css', + Sprockets::ScssProcessor.new(importer: SassImporter, sass_config: app.config.sass) + end + + env.context_class.class_eval do + class_attribute :sass_config + self.sass_config = app.config.sass + end + end + + Sass.logger = app.config.sass.logger + end + + initializer :setup_compression, group: :all do |app| + if Rails.env.development? + # Use expanded output instead of the sass default of :nested unless specified + app.config.sass.style ||= :expanded + else + # config.assets.css_compressor may be set to nil in non-dev environments. + # otherwise, the default is sass compression. + app.config.assets.css_compressor = :sass unless app.config.assets.has_key?(:css_compressor) + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/sass/rails/template.rb b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/sass/rails/template.rb new file mode 100644 index 00000000..b2690c24 --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/sass/rails/template.rb @@ -0,0 +1,72 @@ +require 'sass' +require 'sass/rails/cache_store' +require 'sass/rails/helpers' +require 'sprockets/sass_functions' +require 'tilt' + +module Sass + module Rails + class SassTemplate < Tilt::Template + def self.default_mime_type + 'text/css' + end + + def self.engine_initialized? + true + end + + def initialize_engine + end + + def prepare + end + + def syntax + :sass + end + + def evaluate(context, locals, &block) + cache_store = CacheStore.new(context.environment) + + options = { + :filename => eval_file, + :line => line, + :syntax => syntax, + :cache_store => cache_store, + :importer => importer_class.new(context.pathname.to_s), + :load_paths => context.environment.paths.map { |path| importer_class.new(path.to_s) }, + :sprockets => { + :context => context, + :environment => context.environment + } + } + + sass_config = context.sass_config.merge(options) + + engine = ::Sass::Engine.new(data, sass_config) + css = engine.render + + engine.dependencies.map do |dependency| + context.depend_on(dependency.options[:filename]) + end + + css + rescue ::Sass::SyntaxError => e + context.__LINE__ = e.sass_backtrace.first[:line] + raise e + end + + private + + def importer_class + SassImporter + end + end + + class ScssTemplate < SassTemplate + def syntax + :scss + end + end + end +end diff --git a/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/sass/rails/version.rb b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/sass/rails/version.rb new file mode 100644 index 00000000..76b1a3df --- /dev/null +++ b/path/ruby/2.6.0/gems/sass-rails-5.1.0/lib/sass/rails/version.rb @@ -0,0 +1,5 @@ +module Sass + module Rails + VERSION = "5.1.0" + end +end diff --git a/path/ruby/2.6.0/gems/selenium-webdriver-3.142.6/CHANGES b/path/ruby/2.6.0/gems/selenium-webdriver-3.142.6/CHANGES new file mode 100644 index 00000000..7fee4578 --- /dev/null +++ b/path/ruby/2.6.0/gems/selenium-webdriver-3.142.6/CHANGES @@ -0,0 +1,1641 @@ +3.142.6 (2019-10-04) +==================== + +Ruby: + * Loosen ChildProcess dependency so that 3.0+ can be used (thanks @jaredbeck) + +3.142.5 (2019-10-01) +==================== + +Ruby: + * Loosen RubyZip dependency so that 1.3+ can be used (thanks @vtamara) + +3.142.4 (2019-09-02) +==================== + +Chrome: + * Added support for new command for getting logs in ChromeDriver 76+ + with W3C mode on + +3.142.3 (2019-05-21) +==================== + +Firefox: + * Fixed a regression when Firefox binary path was not sent to GeckoDriver + by default and browser could not be located (issue #7219) + + +3.142.2 (2019-05-11) +==================== + +Chrome: + * Fixed an issue when getting/setting network conditions and sending CDP + commands didn't work with Grid (issue #7174) + +Safari: + * Fixed an issue when getting/setting permissions and attaching debugger + didn't work with Grid (issue #7174) + +3.142.1 (2019-05-07) +==================== + +Firefox: + * Fixed an issue when processing error in legacy driver would result + in NoMethodError (issue #7178) + +3.142.0 (2019-04-24) +==================== + +Ruby: + * Fixed an issue when services are not shutdown properly + +Firefox: + * Fixed an issue when passing :profile string to Firefox::Options.new would + result in NoMethodError. Now it will find a profile with such name on your + system and use it accordingly (issue #7119) + * Fixed an issue when instantiating Firefox driver with capabilities having + :marionette would result in NoMethodError (issue #7120) + +3.141.5926 (2019-04-18) +======================= + +Ruby: + * Fixed an issue when Selenium itself would print deprecation warning + for TimeoutError + * Fixed a regression when socket poller would raise Errno::EBADF on JRuby + +3.141.592 (2019-04-18) +====================== + +Ruby: + * Updated minimum required Ruby version to 2.3 + * Added support for ChildProcess 1.x + * Improved socket connection waiting (thanks @N0xFF) + * Changed waiting to use monotonic clock instead of Time class to avoid + collisions with Timecop and similar gems + * Removed deprecated PortProber.random + * Added strictFileInteractability to the list of known capabilities + * Added InsecureCertificateError + * Added support for setting SOCKS version in proxy (issue #6938) + * Implemented new window command using driver.manage.new_window. The command + is supported by recent Firefox, Safari and IE drivers (thanks @dylanlive) + * Added support for passing proc to driver_path setter in Service classes + * Deprecated all errors which don't exist in WebDriver specification + * Deprecated TouchActionBuilder which is not a part of WebDriver specification + and is only supported by Chrome, but is likely to be dropped in v75. + ActionBuilder should be used instead + * Deprecated using Remote::W3C::Capabilities in favor of Remote::Capabilities + +Chrome: + * Added support for execute CDP commands using Driver#execute_cdp + * Removed GPU disabling in ChromeDriver when using Options#headless! + * Switched suggested download URL to HTTPS (thanks @JLLeitschuh) + * Added support for instantiating service class directly and moved all driver + executable configuration there (command-line arguments, port, etc.) + Passing driver_opts, driver_path and port to driver initializer is now + deprecated so use Selenium::WebDriver::Service.chrome instead, which allows + to customize executable behavior in similar way. Once initialized, this + object can be passed as :service keyword during driver initialization. + * Deprecated Chrome.driver_path= in favor of Service::Chrome.driver_path= + +Edge: + * Added support for instantiating service class directly and moved all driver + executable configuration there (command-line arguments, port, etc.) + Passing driver_opts, driver_path and port to driver initializer is now + deprecated so use Selenium::WebDriver::Service.firefox instead, which allows + to customize executable behavior in similar way. Once initialized, this + object can be passed as :service keyword during driver initialization + * Deprecated Edge.driver_path= in favor of Service::Edge.driver_path= + +Firefox: + * Deprecated legacy driver in favor of GeckoDriver + * Fixed Firefox path lookup on Cygwin (issue #6908) + * Added support for instantiating service class directly and moved all driver + executable configuration there (command-line arguments, port, etc.) + Passing driver_opts, driver_path and port to driver initializer is now + deprecated so use Selenium::WebDriver::Service.firefox instead, which allows + to customize executable behavior in similar way. Once initialized, this + object can be passed as :service keyword during driver initialization + * Deprecated Firefox.driver_path= in favor of Service::Firefox.driver_path= + * Deprecated outdated capabilities + +IE: + * Fixed an issue when native events could not be disabled using IE::Options + initializer + * Added support for instantiating service class directly and moved all driver + executable configuration there (command-line arguments, port, etc.) + Passing driver_opts, driver_path and port to driver initializer is now + deprecated so use Selenium::WebDriver::Service.ie instead, which allows + to customize executable behavior in similar way. Once initialized, this + object can be passed as :service keyword during driver initialization + * Deprecated IE.driver_path= in favor of Service::IE.driver_path= + +Safari: + * Added support for instantiating service class directly and moved all driver + executable configuration there (command-line arguments, port, etc.) + Passing driver_opts, driver_path and port to driver initializer is now + deprecated so use Selenium::WebDriver::Service.safari instead, which allows + to customize executable behavior in similar way. Once initialized, this + object can be passed as :service keyword during driver initialization + * Deprecated Safari.driver_path= in favor of Service::Safari.driver_path= + +Remote: + * Change default HTTP client to use persistent connections + +3.141.0 (2018-10-31) +==================== + +Edge: + * Added new Edge::Options class that should be used to customize browser + behavior. The instance of options class can be passed to driver + initialization using :options key. Please, note that using options require + insiders builds of Edge. + +Chrome: + * Included HasLocation to Chrome driver (thanks @sidonath). + * Updated endpoint to send Chrome Debugging Protocol commands. The old one + has been deprecated in ChromeDriver 40. + +Safari: + * Added new Safari::Options class that should be used to customize browser + behavior. The instance of options class can be passed to driver + initialization using :options key. Please, note that using options require + Safari 12+. + +Remote: + * Allow passing Options instances to remote driver initialization using + :options key. This feature allows to use browser-specific options classes + (Chrome::Options, Firefox::Options, etc.) and pass them to Server/Grid + instead of capabilities. + +3.14.1 (2018-10-03) +=================== + +Ruby: + * Added a workaround to ignore Errno::EALREADY error when starting drivers + on Windows Subsystem for Linux + * Changed default pause duration to 0 for Selenium::WebDriver::Driver#action#pause (thanks @twalpole) + * Deprecated Selenium::WebDriver::PortProber#random + in favor of Selenium::WebDriver::PortProber#above + * Fixed a bug when Selenium::WebDriver::Element#drag_and_drop would not + work in certain cases + * Updated RubyZip dependency to 1.2.2 which fixes security vulnerability + +Edge: + * Added support for --silent driver flag + * Fixed an incorrect dash symbol when passing arguments to MicrosoftWebDriver.exe + +Firefox: + * Fixed an incorrect dash symbol when passing arguments to GeckoDriver + +Safari: + * Fixed a bug when Selenium::WebDriver::Element#displayed? would raise error + in Safari 12 + +3.14.0 (2018-08-03) +=================== + +Ruby: + * Allow to customize default duration of movement of pointer actions using + Driver#action#default_move_duration= (thanks @prakharrr) + * Fixed an accidentally removed Selenium::WebDriver::Error::TimeoutError (thanks @twalpole) + +Server: + * Fixed an issue when Server.latest couldn't parse the version + +Remote: + * Added support for uploading multiple files by passing them as a string + separated by \n to Element#send_keys. Please, note that not all the drivers + have multiple file upload implemented (tested to work in ChromeDriver). + +3.13.1 (2018-07-20) +=================== + +Chrome: + * Fixed an issue when empty Chrome options would cause DevToolsActivePort issue (thanks @artplan1) + +Remote: + * Support detecting local files (thanks @mskvn) + +3.13.0 (2018-06-25) +=================== + +Ruby: + * Address warnings for redefined methods and uninitialized instance variables + +Chrome: + * Chrome options capabilities updated to use goog:chromeOptions. + Note that Selenium now requires ChromeDriver v2.31 at minimum. + * Added ability to tell headless Chrome to save files using Driver#download_path= (thanks @pelly) + +3.12.0 (2018-05-08) +=================== + +Ruby: + * Added User-Agent header to requests from Selenium to give remote + ends more visibility into distribution of clients (thanks @sah) + * Added Selenium::WebDriver::VERSION constant (thanks @sah) + * Added changelog link to RubyGems page + * Fixed a bug when requests were sent with empty Content-Type, + which should instead be application/json (issue #5615 and #5659) + * Fixed a bug when failed connection attempt was retried without + grace period for remote to resolve its problem (thanks @amckinley42) + * Fixed a bug with accidentally removed HasNetworkConnection driver extension + +Chrome: + * Fixed a bug when deprecation message for using Chrome extensions + was incorrectly shown (thanks @treby) + +Safari: + * Added support getting permissions via Driver#permissions + * Added support setting permissions via Driver#permissions= + * Added support enabling web inspector via Driver#attach_debugger + +3.11.0 (2018-03-11) +=================== + +Ruby: + * No changes in Ruby bindings for this release + +3.10.0 (2018-03-02) +=================== + +Ruby: + * Added Errno::EAFNOSUPPORT to the list of ignored errors when finding port (thanks @jtarchie) + * Added automatic conversion of noProxy to the list of strings as required + by W3C WebDriver Specification (issue #5004) + +Chrome: + * Added Chrome::Options#headless! shortcut to enable headless mode (thanks @pulkitsharma07) + +IE: + * Added support for getting local storage using Driver#local_storage + * Added support for getting session storage using Driver#session_storage + +3.9.0 (2018-02-06) +================== + +Ruby: + * Fixed a bug when omitted capabilities caused NoMethodError (issue #5185) + * Fixed a bug when getting page source in W3C dialect caused WebDriverError (thanks @KazuCocoa) + * Fixed a bug when getting backtrace of server error would case NoMethodError (thanks @mcking49) + * Updated YARD to ~> 0.9.11 + * Updated rubyzip to ~> 1.2 (thanks @michaelglass) + +Chrome: + * Added support for getting network conditions via Driver#network_conditions + * Added support for setting network conditions via Driver#network_conditions= + * Added support to allow driver respond with custom error codes (issue #5376) + +Firefox: + * Improved GeckoDriver binary lookup mechanism (issue #5240) + +3.8.0 (2017-12-01) +================== + +Ruby: + * Removed deprecated Alert#authenticate + * Removed deprecated :port initialization argument of Remote::Bridge. + Use :url instead. + * Removed deprecated Selenium::WebDriver::Remote::W3CCapabilities. + Use Selenium::WebDriver::Remote::Capabilities instead. + +IE: + * Remove deprecated :log_file driver initialization argument. + Use driver_opts: {log_file: ''} instead. + * Remove deprecated :log_level driver initialization argument. + Use driver_opts: {log_level: ''} instead. + * Remove deprecated :implementation driver initialization argument. + Use driver_opts: {implementation: ''} instead. + * Removed deprecated :service_args driver initialization argument. + Use driver_opts: {args: ['--some-switch']} instead. + +Chrome: + * Removed deprecated :service_log_path driver initialization argument. + Use driver_opts: {log_path: 'path'} instead. + * Removed deprecated :service_args driver initialization argument. + Use driver_opts: {args: ['--some-switch']} instead. + +Firefox: + * Removed deprecated :service_args driver initialization argument. + Use driver_opts: {args: ['--some-switch']} instead. + +Safari: + * Removed deprecated :service_args driver initialization argument. + Use driver_opts: {args: ['--some-switch']} instead. + +Edge: + * Removed deprecated :service_args driver initialization argument. + Use driver_opts: {args: ['--some-switch']} instead. + +3.7.0 (2017-11-03) +================== + +Ruby: + * Added //rb:lint task to check codebase using RuboCop (thanks @RustyNail) + * Fixed codebase to comply more to Ruby community style guide (thanks @RustyNail) + * Packaged all dependencies to Selenium repository so that non-Ruby committers + can build and test Ruby bindings easier + * Update errors list according to latest changes of specification (thanks @jaysonesmith) + +Firefox: + * Added Firefox::Options#headless! shortcut to enable headless mode (thanks @franzliedke) + +3.6.0 (2017-09-25) +================== + +Edge: + * Fixed a bug when execute_script failed using server + Edge (issue #4651) + +Firefox: + * Fixed a bug when web extension failed to install using profile class (issue #4093) + +PhantomJS: + * Support is deprecated in favor of headless Chrome/Firefox or HTMLUnit. + PhantomJS is no longer actively developed, and support will eventually + be dropped. + +3.5.2 (2017-09-07) +================== + +Ruby: + * Removed platformVersion from W3C payload (issue #4641) + * Fixed a bug when proxy type was not compliant to specification (issue #4574) + * Added support for passing speed to flick action (issue #4549) + * Using TouchActionBuilder no longer prints mouse/key deprecations + * Deprecated Alert#authenticate + * Added support for DEBUG environment variable which enables full debug mode in gem + +Firefox: + * Fixed a bug when page load timeout was not properly constructed in new session payload + * Fixed a bug when GeckoDriver error stacktrace was not displayed (issue #3683) + +Chrome: + * Added workaround for the case when findElements call returns null (issue #4555) + * Chrome::Driver now includes touch actions by default + +3.5.1 (2017-08-15) +================== + +Ruby: + * Fixed a bug when Chrome/Firefox (and probably other) drivers could not be + started on JRuby (issue #4453). + +3.5.0 (2017-08-10) +================== + +Firefox: + * Firefox subprocess output is now only redirected when debug is set (issue #4311) + * Fixed a bug where non-integers could be sent when setting window rect + * Fixed Firefox location detection on Windows_x64 (thanks kamenlitchev) + * Fixed passing of profile with a W3C compatible remote end + +IE: + * Added new IE::Options class that should be used to customize browser + behavior (native events, element scroll behavior, etc). + The instance of options class can be passed to driver initialization using + :options key. Old way of passing these customization directly to driver + initialization is deprecated. + +3.4.4 (2017-07-13) +================== + +Firefox: + * Added support for GeckoDriver install addon command (issue 4215). + * Added support for GeckoDriver uninstall addon command (issue 4215). + * Raise error when passing :firefox_options as capability. + +Ruby: + * Fixed a bug when childprocess were leaking /dev/null file descriptor (issue 4285). + * Make Remote::Driver class so that it can be inherited from. + +3.4.3 (2017-06-16) +================== + +Ruby: + * Fixed a regression when passing symbol as :desired_capabilities caused NoMethodError (issue 4187). + +3.4.2 (2017-06-14) +================== + +Ruby: + * Added unhandledPromptBehavior to the list of known capabilities. + * Added timeouts to the list of known capabilities. + * Fixed a regression when passing hash as :desired_capabilities caused NoMethodError (issue 4172, thanks Thomas Walpole). + * Fixed a regression when extension capabilities (the ones that have ":" inside) + were filtered out when doing protocol handshake. + * Improved handling of capability names passed as plain camelCased strings + vs Rubyish snake_cased symbols when doing protocol handshake. + +Chrome: + * Fixed a regression when passing :switches to driver initialization was ignored. + +3.4.1 (2017-06-13) +================== + +Ruby: + * Implemented a new dual-dialect mechanism for communication with drivers + (a.k.a. protocol handshake). There shouldn't be any noticeable differences + but since this is a significant refactoring of internal APIs, some bugs + could slip into the release. + * Renamed ElementClickIntercepted to ElementClickInterceptedError. + * Renamed ElementNotInteractable to ElementNotInteractableError. + * Deprecated W3CCapabilities in favor of Capabilities (it was meant to be private API). + * Added a warning when trying to save screenshot without .png extension (thanks @abotalov). + +IE: + * Added support for both old IEDriver which uses OSS dialect of JSON wire + protocol (<= 3.4.0) and new IEDriver which uses W3C dialect (not yet released). + +Safari: + * Removed runtime dependencies used for old SafariDriver (u.g. websocket). + +Chrome: + * Added new Chrome::Options class that should be used to customize browser + behavior (command line arguments, extensions, preferences, mobile emulation, etc.). + The instance of options class can be passed to driver initialization using + :options key. Old way of passing these customization directly to driver + initialization is deprecated. + +Firefox: + * Added new Firefox::Options class that should be used to customize browser + behavior (command line arguments, profile, preferences, Firefox binary, etc.). + The instance of options class can be passed to driver initialization using + :options key. Old way of passing these customization directly to driver + initialization is deprecated. + +3.4.0 (2017-04-21) +=================== + +Edge: + * Fix bug when response is not wrapped with "value" + +Firefox: + * Support geckodriver v0.16 + +Ruby: + * Support ElementClickIntercepted error from W3C spec + * Support ElementNotInteractable error from W3C spec + * Implement window rect commands + * Implement window minimize command + +3.3.0 (2017-03-07) +=================== + +Firefox: + * geckodriver v0.15 or later is required + +W3C: + * Support for command response data to be wrapped in a 'value' key + * Support for updated timeout formats + +3.2.2 (2017-03-01) +=================== + +Ruby: + * Fix bug for supporting Logger output on Ruby versions < 2.3 + * Add more logging and adjust output levels + +Remote: + * Support for creating Remote session with browser name and url parameters + +3.2.1 (2017-02-24) +=================== + +Ruby: + * Fix bug for supporting Logger on Ruby versions < 2.3 + +3.2.0 (2017-02-22) +=================== + +Ruby: + * Implement new Logger class + * Fix issue with chromedriver process leader incompatibility on Win7 (issue 3512) + +3.1.0 (2017-02-14) +=================== + +Firefox: + * implement W3C actions endpoint + +3.0.8 (2017-02-08) +=================== + +Firefox: + * Fix signature of element returned from #active_element + +3.0.7 (2017-02-06) +=================== + +Firefox: + * Fix signature of element arrays returned from #find_elements (issue 3471) + +3.0.6 (2017-02-05) +=================== + +Firefox: + * Implement W3C window position + * Update implementation for W3C send text to alert + * Implement timeout settings + * Remove default capabilities (thanks lmtierney) + * Fix signature of elements returned from #execute_script (thanks Thomas Walpole) + +3.0.5 (2016-12-27) +=================== + +Ruby: + * Support for Ruby 2.4.0 (Thanks jamespdo) + +3.0.4 (2016-12-21) +=================== + +Firefox: + * Implement profile support via geckodriver (#2933 thanks lmtierney) + +Ruby: + * Fix bug preventing use of Curb client (#2951 thanks clarkenciel) + * Support Net::HTTP::Persistent version 3 (#3219 thanks Pete Johns) + * Allow Net::HTTP::Default to set open_timout and read_timeout independently (#3264 thanks richseviora) + * Change default for Net::HTTP::Default#open_timeout to facilitate debuggers (#3264 thanks richseviora) + +3.0.3 (2016-11-26) +=================== + +Ruby: + * Allow drivers to be executed from batch files on Windows + +3.0.2 (2016-11-25) +=================== + +Ruby: + * Implement #driver_path as parameter when initializing a driver (thanks lmtierney) + * Improve Ruby syntax in driver commands (thanks joe_schulte) + * Improve performance when shutting down drivers (thanks lmtierney) + * Fix bug for finding open ports on Windows (thanks kou1okada) + * Fix bug in auto detection of drivers which allowed selection of non-executable binaries + +W3C: + * Implement #cookie_named and #delete_all_cookies methods (thanks lmtierney)
 + * Implement element #property method (thanks lmtierney)
 + +Chrome: + * Fix bug in switches (thanks danvine)
 + +3.0.1 (2016-11-06) +=================== + +Ruby: + * Always send JSON Wire Protocol commands to server instead of W3C commands + +3.0.0 (2016-10-13) +=================== + +Firefox: + * Update :firefox_options support for geckodriver 0.11 + +3.0.0.beta4 (2016-09-29) +=================== + +Ruby: + * Remove support for deprecated driver options + * Add support for latest driver options + * Add support for :port parameter for launching driver + * Add support for :service_args parameter for driver command line switches + * Improve reliability by increasing service shutdown timeout (#2815; thanks John Barbuto )
 + +Firefox: + * Add support for :firefox_options in geckodriver + +Safari: + * Remove support for legacy Safari driver (use Apple's driver built in to Safari 10+) + +Chrome: + * Set chromedriver to not log by default + +3.0.0.beta3.1 (2016-09-03) +=================== + +Firefox: + * Fixed bug - legacy firefox extension included in gem + +3.0.0.beta3 (2016-09-01) +=================== + +Firefox: + * Implemented w3c getAttribute with javascript atom + +3.0.0.beta2.1 (2016-08-03) +=================== + +Ruby: + * Fixed bug in collections + +3.0.0.beta2 (2016-08-02) +=================== + +Firefox: + * Fixed bug with form submission + * Improved w3c element handling + +3.0.0.beta1 (2016-07-28) +=================== + +Ruby: + * Remove support for RC client + * Remove support for Ruby < 2.0 + * Update code to support designated style guidelines + * Chrome/GeckoDriver/PhantomJS/IE/Edge drivers are refactored to use standard + service class (issue 1797) + * Option `:timeout` was removed from IE server (issue 1797) + +Chrome: + * Remove override of default chromedriver behavior for chrome.detach (issue 2418) + +Firefox: + * Rename wires to geckodriver + * Change default usage from FirefoxDriver to geckodriver + +Safari: + * Initial support for Apple's Safari Driver in Sierra (issue #2475) + +Android and iPhone: + * Remove support for deprecated classes (Issue #2476) + +2.53.0 (2016-03-15) +=================== + +Ruby: + * Removed dependency on "multi_json" (issue 1632) + * Properly handle namespaces in install manifest of Firefox add-ons (issue 1143) + * Improve error handling when stopping browsers (thanks bsedat) + * Fix deselecting options in select lists (thanks glib-briia) + * Fix w3c error handling + * Update w3c Capabilities support + +IE: + * support for alert credentials (issue #1698, thanks Alan Baird & trabulmonkee) + +2.52.0 (2016-02-12) +=================== + +No Ruby changes in this release. + +2.51.0 (2016-02-05) +=================== + +No Ruby changes in this release. + +2.50.0 (2016-01-27) +=================== + +Firefox: + * Fix bug for locating binary in local path (issue 1523, thanks Gopal Patel) + +2.49.0 (2016-01-13) +=================== + +Ruby: + * support for SessionNotCreatedError (thanks Alexander Bayandin) + +Safari: + * Limit support to OS X (issue 1186) + * support for multiple concurrent drivers + +PhantomJS: + * Implement Socket locking + +IE: + * support for multiple concurrent drivers + +Chrome: + * prevent 404 error when shutting down Chrome service (thanks Fumiaki MATSUSHIMA) + * logging turned on by default + * support local storage capabilities + +Firefox: + * support setting the location of Firefox binary to use when run locally + * add default lookup of Homebrew Cask default directory (issue 1437) + +W3C Specification: + * support for using with Remote WebDriver + * implement window size command + * implement window position command + * implement element size command + * implement element position command + * implement full screen window command + * implement page source command + +2.48.1 (2015-10-13) +=================== + +Firefox: + * Mozilla's Wires Driver for Marionette works with Remote WebDriver + +2.48.0 (2015-10-07) +=================== + +Firefox: + * Initial implementation of Mozilla's Wires Driver for Marionette; Supported in version 43 and higher + +Edge: + * Fix execution with Remote Server + * Fix Javascript Execution + * Implement Alert Handling + * Implement Window Switching + +Ruby: + * Initial implementation of W3C WebDriver syntax to support Mozilla Wires + * Change to RSpec expect syntax + * Specs can be run from relative directories + + +2.47.1 (2015-07-31) +=================== + +Edge: + * Initial implementation of Microsoft's EdgeDriver + + +2.47.0 (2015-07-29) +=================== + +Safari: + * Remove support for installing additional extensions due to architectural changes introduced with Safari 7 + +2.46.2 (2015-06-05) +=================== +* Fix encoding issue which prevents Element#send_keys to work on Ruby < 2.0 (#621) + +2.46.1 (2015-06-04) +=================== + * Fix aborted rubygems.org release + +2.46.0 (2015-06-04) +=================== + +Firefox: + * Support for Firefox 38 + * Fix performance bug by not forcing garbage collection in httpd.js + +Chrome: + * Fixed ChromeDriver port race condition (#449 - thanks Jason Anderson) + +Ruby changes: + * Retry binding to ports unavailable by EADDRNOTAVAIL (#394). + * Remove Presto-Opera support (Blink-based Opera still supported) + +2.45.0 (2015-02-28) +=================== + +Firefox: + * Native events in Firefox relied on an API that Mozilla no longer + provides. As such, fall back to synthesized events on recent Firefox + versions. + +Ruby changes: + * Allow switching windows when current window is closed (thanks Titus Fortner). + * Add :javascript_enabled to Android capabilities. + +2.44.0 (2014-10-05) +=================== + +No Ruby changes in this release. + +Firefox: + * Native event support for Firefox 24, 31, 32 and 33 + +2.43.0 (2014-09-09) +=================== + +* Make sure UnhandledAlertErrors includes the alert text if provided by the driver. +* Firefox + - Make sure the browser process is properly killed if silent startup hangs (#7392) + - native events support for Firefox 24, 31 and 32 +* Loosen websocket dependency to ~> 1.0 +* Add support for `switch_to.parent_frame` (thanks abotalov) +* Fix download location for Selenium::Server.{latest,get} (#7049 - thanks marekj) + +2.42.0 (2014-05-23) +=================== + +Firefox: + * Fix for extensions whose install.rdf uses an attribute for em:id (#5978) + * Support for Firefox 29 Native Events + +2.41.0 (2014-03-28) +=================== + +* Removed dead browser visibility methods. +* Firefox: + * Native events support for Firefox 28 (removed support for 26) + +2.40.0 (2014-02-19) +=================== + +* Fix bug where FileReaper would not reap files added in a child process +* Document AbstractEventListener (#5994) +* Safari: + * Add Safari::Options + clean up Safari extension handling (#6382) + * Add support for user extensions (#6815) +* Firefox: + * Support native events for Firefox 27 + removed native event support for Firefox 25 + +2.39.0 (2013-12-17) +=================== + +* Firefox: Native events support for Firefox 26. +* Add deprecation warning to the Android driver. +* Make sure selenium/client doesn't require Rake (#6709) + +2.38.0 (2013-12-05) +=================== + +* Enforce required Ruby version in gemspec, not just transitively through rubyzip. +* Expose the logging API (beta API, subject to change) in the Ruby client: driver.manage.logs #=> Selenium::WebDriver::Logs +* Update to support native events for Firefox 25 + + +2.37.0 (2013-10-18) +=================== + +* As of this version, selenium-webdriver no longer supports Ruby < 1.9.2 +* Depend on rubyzip ~> 1.0.0 +* Added SOCKS proxy support +* Fixed support for SVG documents in the atoms. +* Fixed computing an element's container dimensions to account for the scrollbar size when scrolling +* Added Capabilities.htmlunitwithjs + +Chrome: + * Pass through the prefs option as a Chrome capability (#5929). +Firefox: + * Updated Firefox native event components to support Firefox 24. + * New elementScrollBehavior capability. + * Fixed getLocation to work on scrolled pages. + * Fixed autoscrolling for elements located in frames. + * Fixed drag-n-drop for elements in frames with native events +IE: + * Use native events by default, also for remote IE (#4695) +Safari: + * Enable screenshots and input devices in the client. + +2.35.1 (2013-08-26) +=================== + +* Depend on rubyzip < 1.0.0 + +2.35.0 (2013-08-14) +=================== + +Firefox: + * Updated Firefox native event components to support Firefox 23. + +2.34.0 (2013-08-06) +=================== + +Remote: + * Add `Driver#remote_status` (remote driver only) (#5669) +Firefox: + * Updated Firefox native event components to support Firefox 22. +iPhone: + * The driver is now deprecated (see http://appium.io/ or http://ios-driver.github.io/ios-driver/ for a replacement) +Various: + * Updated our copy of the Closure compiler and library to the most + recent versions. + * Updated the atoms library, including support for MS pointer events + and refinements to element visibility tests. + * Update synthesized mouse implementation. Mouse moves are + implemented using nsIDOMWindowUtils. + * Added support for the HTML5 "hidden" attribute. If an element, or + ancestor, has hidden attribute make, it is not shown. + +2.33.0 (2013-05-26) +=================== + +Remote: + * Support rotating devices, such as iPhone & iPad in simulator and Android browser in emulator + * Support for interacting with touch screen devices, such as iPhone & iPad in simulator and Android browser in emulator + * Improve error messages for invalid wire protocol responses +Chrome: + * Accept :service_log_path for Chrome. (#3475) +IE: + * IE >=9 versions triggerMouseEvent like other browsers (#2218). +Various: + * Element#text ignores elements in + + +2.32.1 (2013-04-11) +=================== + +Safari: + * Fix typo when encoding the Safari server redirect URL (#5472) + +2.32.0 (2013-04-09) +=================== + +Safari: + * The Safari extension is now packaged with the gem and automatically installed (#5322) + * Improved detection of the binary location on 64-bit windows (#5273) +Opera: + * Allow passing :desired_capabailities (#5279) +Firefox: + * This release supports versions 10esr, 17esr, 19, 20. + * Improved SVG support +Other: + * Allow #click_and_hold without target (#5410). + * Remove assumptions about returned capabilities (for ios-driver) + +2.31.0 (2013-03-02) +=================== + +Remote: + * Expose session_id on the remote driver when used directly (#5240). +Firefox: + * Native events in Firefox 19 +Opera: + * Treat UNSPECIFIED proxy type as a nil proxy (#5081). +Other: + * Add ability to pass :desired_capabilities to all the Ruby drivers, not just for :remote. + Direct arguments take presendence. (#5078, see also https://github.com/SeleniumHQ/selenium/pull/8, https://github.com/SeleniumHQ/selenium/pull/11) + +2.30.0 (2013-02-20) +=================== + +Firefox: + * Firefox 19 support (for synthesized events) +Remote: + * Pass along firefox_binary correctly (#5152) +Safari: + * Don't overwrite Driver#browser +Other + * Alert#text should raise on closed alert. + +2.29.0 (2013-01-21) +=================== + +Firefox: + * Firefox 18 support (for native events). +IE: + * New 'requireWindowFocus' desired capability. + * IE view port calculation take scroll bars into account (#3602) +Safari: + * Replace 'libwebsocket' with 'websocket' gem. This should ensure + support with recent Safari. +Other: + * Fix Cygwin issue in PortProber/Firefox::Bianry (#4963) + +2.27.2 (2012-12-11) +=================== + +Firefox: + * Fix for native events in v17 (packaging mistake) + +2.27.1 (2012-12-07) +=================== + +Firefox: + * Fix "g[b] is not an object" error when passing null to execute_script. + +2.27.0 (2012-12-06) +=================== + +Firefox: + * Support for Firefox 17. +IE: + * Path to the server executable can be specified (S::W::IE.path=) +Other: + * Added :phantomjs driver + +2.26.0 (2012-11-02) +=================== + +Firefox: + * Added support for native events for Firefox 15 and 16. + * Modified FirefoxDriver to use atoms to switch between frames. + * FIXED: 4309: 'Could not convert Native argument arg 0' error with Firefox. + * FIXED: 4375: Executing javascript hangs Firefox. + * FIXED: 4165: WebDriver fails on a machine with no IP address. +Safari: + * Fixed SafariDriver to allow calling .quit() consecutively without error. + * FIXED: 4676: Unable to fire javascript events into SVG's. + * FIXED: 3969: SafariDriver should auto-dismiss alerts. +IE: + * FIXED: 4535: Hover still does not work perfectly in IE. + * FIXED: 4593: Alert.accept() Cancels the Resend Alert/Dialog Box. + +2.25.0 (2012-07-19) +=================== + +* Respect no_proxy / NO_PROXY env vars (#4007). +* Improve error message if a configured proxy refuses the connection. +* Ignored exception can be configured on the Wait class. +* Add Selenium::WebDriver::Support::Color class. +* Ignore Errno::ENETUNREACH when trying to detect our local IP (#4165). +* Ignore Errno::EADDRNOTAVAIL in PortProber (#3987). +* Firefox: + * Enumerate through client rects until finding one with non-zero dimensions when clicking. + * Updated supported versions of Firefox to 17. + * Allow windows to be resized from a frame (#3897). + * Fix an issue where a call to submit could hang the driver. +* IE: + * Ability to configure logging through the :log_file and :log_level options. + * Increasing stability of file upload dialog handling (#3858) +* Better handling of overflow edge cases when determining element visibility. + +2.24.0 (2012-06-19) +=================== + +* bot.dom.getVisibleText does not properly handle display:run-in or display:table (#1584). +* CSS selectors now support compound selectors. +* IE: + * Failure to click on an element in the IE Driver will yield a more meaningful error. + * Crash on IE8 when S_FALSE is returned from get_Document (#4064) + * DLLs are no longer bundled with the gem, users must use the standalone server from now on. +* Firefox: + * Support for Firefox 13 + * Ability to pass :proxy directly as Firefox option (no Profile needed). + +2.22.2 (2012-06-05) +=================== + +* Improve method for determining the local IP (#3987). + +2.22.1 (2012-06-01) +=================== + +* Fix for 1.8.7 behaviour of Socket.getaddrinfo. +* Automatically reap Firefox profile on exit, not just in #quit. + +2.22.0 (2012-05-29) +=================== + +* Fix conflict with ActiveSupport's Object#load (#3819) +* IE: + * Default to standalone server executable, fall back to bundled DLLs. + * The 'nativeEvents' capabilitiy is exposed as :native_events in the Ruby client (mode still experimental). +* Firefox: + * Native events for Firefox 12. + * Native events retained for Firefox 3.x, 10 and 11. + * Fix for typing in Firefox 12 + * Fix for typing on XHTML pages (#3647) + * Fix for maximizing windows when switched to a frame (#3758) + * Handle alerts from nested iframes (#3825) +* Remote: + * Honor HTTP_PROXY env vars (#3838). +* Element#attribute returns nil if a boolean attribute is not present. +* NoSuchWindowError will be thrown if the currently selected window is closed and another command is sent. +* Safari: + * support for frame switching, snapshot taking, execute script + * message protocol changed, not backwards compatible with 2.21. +* Style attributes are no longer lower-cased by default (#1089). +* Make sure the Ruby client kills Firefox even when the RPC fails. +* Make sure the Ruby client checks all network interfaces when finding free ports. + + +2.21.2 (2012-04-18) +=================== + +* Check MultiJson.respond_to?, depend on ~> 1.0 + +2.21.1 (2012-04-16) +=================== + +* Set multi_json dependency to < 1.3 to avoid deprecation warnings. + +2.21.0 (2012-04-11) +=================== + +* Add Selenium::WebDriver::Window#maximize (#3489) +* Safari: + * New driver! See https://github.com/SeleniumHQ/selenium/wiki/SafariDriver. +* Firefox: + * Significant stability improvements. + * Native events support for Firefox 11 + * Dropped native events support for Firefox 4-9 + * Window maximize implementation. +* IE: + * Ignore invisible dialogs (#3360). + * Window maximize implementation. +* Android: + * Accept SSL certificates (#3504). + +2.20.0 (2012-02-28) +=================== + +* Loosen the multi_json and ffi dependencies (#3446) +* Firefox: + * Introduce a timeout for page loads. This needs to be used in + conjunction with the unstable page load detection. Exposed as + Timeouts#page_load= +* Scroll containing elements, not just windows (#3391). +* Element#{style => css_value}, with an alias for backwards compatibility. +* Atoms: + * Submit a form when the enter button is pressed in its input + element. + * Add a "mouse pixel scroll" event to the atoms events module. + * Adding a public "mouseOver" action and a little internal + refactoring around the mouseOver functionality. +* Selenium::WebDriver::Wait: polling interval reduced from 0.5 to 0.2 seconds. + +2.19.0 (2012-02-08) +=================== + +* RC client supports server-side WebDriver-backed Selenium. +* Touch APIs implemented in the Ruby client (Android only) +* Firefox: + * Fix deserialization of user.js in Firefox::Profile + * Native events implemented for Firefox 10. + * Renamed the experimental "fast" page loaded strategy "unstable" + and disable queuing of commands when it's enabled. + * Disabled native events for Firefox 8 as it's deprecated by Mozilla. + * Fix for exceptions thrown when an alert occurs during script execution. +* Chrome: + * New download link for the server component: + http://code.google.com/p/chromedriver/downloads/list + +2.18.0 (2012-01-27) +=================== + +* Fix for getting value attribute of option elements. (#3169) +* Firefox and IE: + * Raise UnhandledAlertError if an alert is present during an operation. The unhandled alert is also dismissed to mitigate repeat exceptions. +* Firefox: + * Better handling of getText invocations on SVG elements. + * Fix for Element#click in Firefox 4. (#3253) + * Fixed bug when deserializing user.js in Firefox::Profile. + * Default profile preferences now read from shared JSON blob. +* Android and iPhone: + * Client support for the geolocation API (see Selenium::WebDriver::DriverExtensions::HasLocation) + * Client support for the web storage API (see Selenium::WebDriver::DriverExtensions::HasWebStorage) +* iPhone: + * Server now supports frame switching (for frames in the same domain). + +2.17.0 (2012-01-16) +=================== + +* Firefox: + * Fix excessive unwrapping when switching windows. + * Set toolkit.telemetry.{enabled,rejected} in the default Firefox profile. + * Support up to version 12 with synthesized events. + * Fixed issues launching Firefox 9 due to modal dialog (#3154, #3144) +* Chrome: + * Now accepts a :url option, pointing to an already running Chrome server. + * Now accepts a :proxy option (i.e. Selenium::WebDriver::Proxy instance). +* iPhone: + * iWebDriver will auto-play HTML5 video (#3152) +* Element#attribute("value") falls back to the text of option tags, if no value attribute is specified (#3169) + +2.16.0 (2012-01-04) +=================== + +* Firefox: + * Native events support for Firefox 9 + * Allow apps to use offline storage by default + * Fix excessive unwrapping when executing mouseMove + * Click in the middle, rather than the top-left of elements (#3034, #2700) +* IE: + * Raise StaleElementReferenceError when interacting with elements outside the currently focused frame + * Handle frames and iframes in showModalDialog windows +* Chrome: + * Improve client handling of invalid error responses +* iPhone: + * Updated to latest CocoaHTTPServer +* Remote: + * Improve performance of Element#== by only making an RPC when necessary. + * Disallow caching of GET requests +* Various: + * Raise ArgumentError instead of UnsupportedOperationError on invalid modifier key + * Improve docs for Server, ActionBuilder and Window + * Update to latest Sizzle (used for :css selectors in older browsers) + +2.15.0 (2011-12-08) +=================== + +* Firefox: + * Now supports up to Firefox 11 (for syntesized events) + * Implicit waits now change how long we wait for alerts. This + functionality will change in 2.16 + * Fix scrolling issue (#2700) +* IE: + * Fix issue with getWindowHandles() and showModalDialog() (#1828) + * Add support for the window sizing and positioning API +* Make Platform.bitsize use the correct constant from FFI +* WebDriver internals: + * Atoms code contribution from Google + * Closure updated + + +2.14.0 (2011-11-29) +=================== + +* Add Selenium::WebDriver::Support::Select class to ease working with select lists. +* Add Capabilities.ipad and Capabilities.iphone (#2895) +* Replace json_pure dependency with multi_json. (#2901) +* Don't leave sockets hanging in SYN_SENT state while polling. +* Allow selecting option elements even if the enclosing select has zero opacity. +* Exception renames (old names aliased and will still work in rescues): + * ObsoleteElementError -> StaleElementReferenceError + * UnhandledError -> UnknownError + * UnexpectedJavascriptError -> JavascriptError + * NoAlertOpenError -> NoAlertPresentError + * ElementNotDisplayedError -> ElementNotVisibleError +* Firefox: + * Fixed issue with scrolling on small viewports with native events. + * Fix CSS selector support in Firefox 3.0 +* IE: + * Implement the window control API + +2.13.0 (2011-11-18) +=================== + +* Firefox: + * Recovering from null window references in the Firefox driver (#1438) + +2.12.2 (2011-11-13) +=================== + +* Firefox + * Fix issue where Firefox 8 would throw permission errors after document.domain was modified (#2863). + * Force window to the foreground on launch (see https://bugzilla.mozilla.org/show_bug.cgi?id=566671) + +2.12.1 (2011-11-11) +=================== + +* Fix regression when typing into contenteditable elements. + +2.12.0 (2011-11-10) +=================== + +* Loosened the ffi dependency now that Windows support is back (#2755). +* Fix shutdown issues when using net-http-persistent against remote servers. +* Added beta window control API (see Selenium::WebDriver::Options#window). +* Firefox + * Support for Firefox 8. + * Better reporting of page size when attempting to move out of bounds. + * Beta implementation of window control. + +2.11.0 +====== + +(release skipped since it included only server-side changes) + +2.10.0 (2011-10-27) +=================== + +* Improve native keyboard emulation on Windows. +* Allow commas in CSS selectors (#2301) +* Firefox: + * Fix intermittent error when finding elements by XPath (#2099) + * Fix detection of maximum page size (#2700) + * Avoid extension selection dialog (extensions.autoDisableScopes) + * Fix invalid wire responses from the synthetic mouse (#2695) + * Don't scroll unnecessarily (#1771). +* IE: + * Improve handling of #clear on disabled or readonly elements. + +2.9.1 (2011-10-24) +================== + +* Workaround for invalid error responses (#2695). + +2.9.0 (2011-10-21) +================== + +* Support file uploads to the remote server (see Selenium::WebDriver::DriverExtensions::UploadsFiles) +* New exception: Selenium::WebDriver::Error::MoveTargetOutOfBoundsError +* Implemented Chrome::Profile#add_extension +* Better respect for preformatted text in Element#text +* Reduced scrolling during tests for IE and Firefox. +* Firefox: + * Support for experimental page load detection. +* IE: + * Better detection of where to click on links (#2675). + * Improve handling of modal dialogs (showModalDialog). + +2.8.0 (2011-10-06) +================== + +* Firefox + - Avoid telemetry prompt on Firefox 7 + - Allow parallel execution with native events on Linux (#1326) + - Added native events support for Firefox 7 on Linux/Windows + - Improve search for libX11 for native events on Linux (#384) + - Fix double click with native events +* Chrome + - Fail fast if the chromedriver server is not executable. +* IE + - Fix find_element bug (#2563) + + +2.7.0 (2011-09-23) +================== + +* Firefox + - no longer types in the URL bar (#2487) + - fixed native events click() issue when element is out of view + - double click + get no longer hangs firefox (#2456) + - make sure escaped backslashes are properly escaped when serializing a Firefox profile (#2485) +* IE + - fix event firing issue (#2516) +* Opera + - don't start the remote service if the driver is passed bad arguments + +2.6.0 (2011-09-13) +================== + +* Rescue and retry on Errno::EADDRINUSE to work around ephemeral ports issue on Windows. +* Use correct default URL for the Android driver. +* Profile zipping now follows symlinks (#2416). +* Firefox + - Disable malware check + - Various #click improvements + - Don't scroll if element is already in view +* IE: + - improve scrolling to elements with 'overflow: scroll' + - properly trigger jQuery change events (#2207) + - improve handling of nested modal dialogs +* Opera: + - capabilities exposed as options to Selenium::WebDriver.for +* Various iPhone driver fixes (e.g. #1396) + +2.5.0 (2011-08-23) +================== + +* IE: support for double click and fix for clicks close to edge of window. +* Fix for clicking links that overflow into multiple lines (#1020). +* Correct initial cursor position when typing into text fields with Firefox 6. +* Native events support for Firefox 6 on Windows and Linux. +* Fix bug in Android::Bridge when no :http_client option was passed. +* Set chrome.detach to tell chromedriver to leave browser running on exit. + +2.4.0 (2011-08-11) +================== + +* Firefox 6 support. +* Raise in switch_to.alert when no alert is present. +* Improved handling of non-breaking spaces for Element#text. + +2.3.2 (2011-08-01) +================== + +* Re-releasing since 2.3.1 was a buggy build. + +2.3.1 (2011-08-01) +================== + +* Fix bug where Firefox would hang if Firefox::Profile#log_file= was set. + +2.3.0 (2011-08-01) +================== + +* Add Selenium::WebDriver::Chrome::Profile +* Better detection of clickable areas in Firefox. +* Merge of Google-contributed code into the underlying Atoms. +* IE click issue fixed (#1650) +* No longer raise in Element#inspect if the element is obsolete. + + +2.2.0 (2011-07-26) +================== + +* Add ability to listen for WebDriver events +* Fix Android/iPhone bridges to work similar to others (https://github.com/jnicklas/capybara/issues/425) +* Various atoms fixes +* Element equality now works properly with the remote server (#251). + +2.1.0 (2011-07-18) +================== + +* Various improvments to the IE driver (#2049, #1870) +* Atoms fixes (#1776, #1972). +* Synthetic mouse clicks do not propagate errors in onmouseover. + +2.0.1 (2011-07-11) +================== + +* Make sure at_exit hooks aren't inherited by child processes. + +2.0.0 (2011-07-08) +================== + +* Remove deprecated methods Element#{toggle,select,drag_and_drop_*,value}. +* Add ability to pass :verbose, :native_events to the Chrome driver. +* Synthetic mouse implementation for Firefox - improves drag and drop support platforms without native events. +* Added Selenium::WebDriver::Opera (requires the remote server). +* Fix for locating Firefox on 64-bit Windows when missing from the registry. +* Fix native events on Firefox 4, 5. + +0.2.2 (2011-06-22) +================== + +* Deprecate Element#{toggle,select,drag_and_drop_*} +* Chrome.path= now sets the path to Chrome, Chrome.driver_path= the path to the chromedriver server. +* Fix argument names in Mouse#move_to and Mouse#move_by. +* Support Firefox 5 +* Chrome and Firefox drivers now includes the HasInputDevices module. +* Selenium::Rake::ServerTask now works with Rake 0.9. + +0.2.1 (2011-06-01) +================== + +* Allow passing custom command line switches to Chrome (requires today's release of the Chrome server) +* Avoid mutating arguments to find_element (issue #1273). +* Avoid conflicts when SUT modifies Array.prototype +* Allow setting arbitrary capabilities by adding Capabilities#[]= +* The Chrome driver is extended with TakesScreenshot. +* IE driver detects bad protected mode settings. +* Firefox driver no longer considers opacity when determining visibility. +* Fix for ActionBuilder#move_by. +* Treat Errno::EBADF as an indication that we failed to grab the socket lock (issue #1611). +* Ensure Firefox launches don't hang on some Ruby versions (by improving Selenium::WebDriver::SocketPoller). +* Various internal driver improvements. + +0.2.0 (2011-04-22) +================== + +* Update Ruby bindings to use the rewritten Chrome driver (see https://github.com/SeleniumHQ/selenium/wiki/ChromeDriver). +* Add deprecation warning for WebDriver::Element#value (use Element#attribute("value") instead). +* Change the default timeout for Wait instances to 5 seconds. +* Fix bug where locators would fail if Object.prototype had been modified. +* Various IE fixes + - Issues: #698, #1444 + - Improved handling of showModalDialog() + - Full-size screenshots +* Allow users to override the dom.max_script_run_time preference in Firefox. +* Removed DesiredCapabilities.safari, which was never supported anyway. +* Add DesiredCapabilities.opera, which will be supported in the Remote server if OperaDriver is on the classpath. +* Print warnings for deprecated constants in the RC client: + - Selenium::SeleniumDriver => Selenium::Client::Driver + - Selenium::CommandError => Selenium::Client::CommandError +* Removed top-level constants: + - SeleniumHelper (available as Selenium::Client::SeleniumHelper) + - SeleniumCommandError (available as Selenium::Client::CommandError) + +0.1.4 (2011-03-21) +================== + +* Support for Firefox 4. +* Search PATH for Firefox / Chrome on OS X as well. +* Bump dependencies for ffi and childprocess (#1356). +* Find module renamed to SearchContext +* Deprecated methods Driver#hover and Options#{speed,speed=} removed. +* Improve IE driver stability, IE9 support +* Added basic ActionBuilder and HasInputDevices#action. Not applicable for all browsers. +* Added Driver#execute_async_script +* Some WebDriver exception classes have been renamed to match its Java equivalents: + ElementNotEnabledError -> InvalidElementStateError + UnknownScriptResultError -> XpathLookupError +* Fix bug where Element#disabled? would alternate between true/false (r11438) + +0.1.3 (2011-02-14) +================== + +* Several crashing bugs fixed in the IE driver. +* Alert API available through the remote driver. +* Driver#refresh fixed in the IE driver. +* Fixed paths for IE DLLs on Cygwin. +* Screenshot support in the IE and Remote drivers. +* Fix #1152 by avoiding IPv6 loopback. +* Added Mouse and Keyboard classes, accessible as Driver#{mouse,keyboard}. Considered experimental (IE + HtmlUnit only at the moment). +* Automation atoms now used extensively in the IE driver. +* Firefox::Bridge is now easier to extend (i.e. with a custom launcher). +* Add S::W::Remote::Http::Persistent (currently only usable with the remote server). +* IE driver passes along options like the other remote drivers, enabling user-specified HTTP clients. +* :firefox_profile added to Remote::Capabilities, enabling passing a profile to remote Firefoxes. +* IE driver now supports launching multiple instances of the browser. +* Remove some Ruby warnings (uninitialized ivars, URI.escape). + + +0.1.2 (2010-12-22) +================== + +* Changed frame switching behaviour (http://groups.google.com/group/selenium-developers/browse_thread/thread/8dc7938c35bb3968) +* IE driver rewrite landed. +* Initial support for alerts/prompts (in Firefox). +* Cygwin support. +* Driver#execute_script now properly wraps elements inside Hashes. +* Various fixes for Firefox 4. + +0.1.1 (2010-11-29) +================== + +* Fix for Chrome.path= +* Remote drivers always add Content-Length for POST requests (thanks joshuachisholm) +* Fix for JS execution bug in the IE driver +* Add ability to specify a proxy on the Http::Default client. +* The remote drivers' :http_client argument now take a configured instance. + +0.1.0 (2010-11-11) +=================== + +* selenium-client code (Se1/RC client) is now included in the gem (require "selenium/client"). +* Add Selenium::WebDriver::Proxy, used to configure proxies for Firefox::Profile and the remote driver. +* Tweaked Firefox profile preferences, improve logging, disabled crash reporter. +* Reap Firefox profiles on close, not just on exit. +* Add selenium/rake/server_task and selenium/server which wraps the Selenium server jar. +* Various Firefox driver improvements (GC race conditions ++). +* IE::Bridge#initialize now takes an options hash like the other bridges. +* Added basic iPhone and Android driver classes. +* Firefox driver now works on FreeBSD. + + +0.0.29 (2010-10-09) +=================== + +* Element#find_element with :xpath follows the XPath spec (i.e. results are not limited to the receiver's subtree). +* Element#attribute(attribute) now returns "false" instead of nil. +* Firefox::Profile instances can now be reused for multiple drivers. +* Redirect Firefox console logs to a file with Firefox::Profile.log_file= +* Added a simple Wait class, based on WebDriverWait in Java. +* Search PATH for Firefox executable on Windows also. +* Added Capabilities.android +* Fix saving of screenshots on Windows and Ruby 1.9 (using "wb" mode string) +* CSS selector support in the remote driver +* CSS selector support for IE (using querySelector when available, Sizzle elsewhere) +* CSS selector support for older versions of Firefox (through Sizzle) +* Cookie expiration dates are now handled correctly (#730) +* Make Driver#bridge private, since this seems to be a common cause of confusion. +* Add {Element,Remote::Capabilities}#as_json for Rails 3 (http://jonathanjulian.com/2010/04/rails-to_json-or-as_json/) +* User can configure path to exectuables with {Firefox,Chrome}.path = "/some/path" +* Added "chromium" as a possible name for the Chrome binary (#769) +* Correctly set the HTTP client timeout (#768) +* switch_to.window with block now handles exceptions and non-local returns. +* switch_to.window with block returns the result of the block. +* Extracted handling of child processes to a separate gem: http://github.com/jarib/childprocess + + +0.0.28 (2010-08-23) +=================== + +* Fix behaviour of Element#==, Element#eql? and Element#hash (#hash still has issues on IE / remote). +* Include remote server backtrace in raised errors (if available). +* Chrome: Untrusted certificate support. +* IE: Fix NoMethodError when getElementAttribute returns nil. +* Driver#[] shorthand can take a locator hash, not just an id string. + +0.0.27 (2010-07-22) +=================== + +* Fixes for Element#attribute on IE / Firefox + +0.0.26 (2010-07-19) +=================== + +* Work around Curb issue: http://github.com/taf2/curb/issues/issue/40 + +0.0.25 (2010-07-19) +=================== + +* Prevent Firefox from launching in offline mode (issue #587). +* Add ability to set Firefox' binary path through Selenium::WebDriver::Firefox::Binary.path= +* Add ability to install Firefox XPIs through Profile#add_extension. +* Better packaging/building of Firefox/Chrome extensions, which adds rubyzip as a dependency. +* Remote client supports HTTPS (issue #613 - thanks kkaempf). +* Fix error message for TimeOutError in the IE driver (issue #602) +* Add ability to use Chrome's default profile. +* Fix for frame behaviour in Chrome (issue #273). +* Standard gem directory structure (issue #475). + +0.0.24 (2010-06-17) +================== + +* Fix issues with quitting Firefox/Chrome on Windows + MRI. + + +0.0.23 (2010-06-15) +=================== + +* Improved the HTTP clients: + - hopefully fix some occasional socket errors on Windows + - rescue error on driver.close() with curb + +0.0.22 (2010-06-11) +=================== + +* Bugfix: Workaround for http://github.com/taf2/curb/issues/issue/33 - curb would sometimes use DELETE for GET requests. +* Minor doc fix +* Add ability to set timeout for HTTP clients + +0.0.21 (2010-06-11) +=================== + +* User can specify :http_client for the Firefox driver. +* Refactor HTTP client code +* Add Remote::Http::Curb as an alternative to the default (net/http) client. + + +0.0.20 (2010-06-03) +=================== + +* Fix bug where Firefox would hang on quit(). + +0.0.19 (2010-05-31) +=================== + +* Add a max redirect check to the remote driver +* Add Firefox::Profile#assume_untrusted_certificate_issuer= +* Add implicit waits (Selenium::WebDriver::Timeouts) +* at_exit hook to clean temporary profiles +* Fix for Errno::ECONNABORTED errors on Windows +* Fix issue where Firefox::Profile#secure_ssl= would have no effect +* Fix issue where locating elements by :css would fail in the Chrome driver. +* IE driver now works on 64-bit rubies. diff --git a/path/ruby/2.6.0/gems/selenium-webdriver-3.142.6/Gemfile b/path/ruby/2.6.0/gems/selenium-webdriver-3.142.6/Gemfile new file mode 100644 index 00000000..5f10ba8c --- /dev/null +++ b/path/ruby/2.6.0/gems/selenium-webdriver-3.142.6/Gemfile @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' +gemspec diff --git a/path/ruby/2.6.0/gems/selenium-webdriver-3.142.6/LICENSE b/path/ruby/2.6.0/gems/selenium-webdriver-3.142.6/LICENSE new file mode 100644 index 00000000..68daa6eb --- /dev/null +++ b/path/ruby/2.6.0/gems/selenium-webdriver-3.142.6/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019 Software Freedom Conservancy (SFC) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/path/ruby/2.6.0/gems/selenium-webdriver-3.142.6/README.md b/path/ruby/2.6.0/gems/selenium-webdriver-3.142.6/README.md new file mode 100644 index 00000000..26295c3e --- /dev/null +++ b/path/ruby/2.6.0/gems/selenium-webdriver-3.142.6/README.md @@ -0,0 +1,35 @@ +# selenium-webdriver + +This gem provides Ruby bindings for WebDriver +and has been tested to work on MRI (2.0 through 2.2), + +## Install + + gem install selenium-webdriver + +## Links + +* http://rubygems.org/gems/selenium-webdriver +* http://seleniumhq.github.io/selenium/docs/api/rb/index.html +* https://github.com/SeleniumHQ/selenium/wiki/Ruby-Bindings +* https://github.com/SeleniumHQ/selenium/issues + +## License + +Copyright 2009-2018 Software Freedom Conservancy + +Licensed to the Software Freedom Conservancy (SFC) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The SFC licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/path/ruby/2.6.0/gems/selenium-webdriver-3.142.6/lib/selenium-webdriver.rb b/path/ruby/2.6.0/gems/selenium-webdriver-3.142.6/lib/selenium-webdriver.rb new file mode 100644 index 00000000..d2d7eccf --- /dev/null +++ b/path/ruby/2.6.0/gems/selenium-webdriver-3.142.6/lib/selenium-webdriver.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +require 'selenium/webdriver' diff --git a/path/ruby/2.6.0/gems/selenium-webdriver-3.142.6/lib/selenium/server.rb b/path/ruby/2.6.0/gems/selenium-webdriver-3.142.6/lib/selenium/server.rb new file mode 100644 index 00000000..0984fa91 --- /dev/null +++ b/path/ruby/2.6.0/gems/selenium-webdriver-3.142.6/lib/selenium/server.rb @@ -0,0 +1,272 @@ +# frozen_string_literal: true + +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +require 'childprocess' +require 'selenium/webdriver/common/socket_poller' +require 'net/http' + +module Selenium + # + # Wraps the remote server jar + # + # Usage: + # + # server = Selenium::Server.new('/path/to/selenium-server-standalone.jar') + # server.start + # + # Automatically download the given version: + # + # server = Selenium::Server.get '2.6.0' + # server.start + # + # or the latest version: + # + # server = Selenium::Server.get :latest + # server.start + # + # Run the server in the background: + # + # server = Selenium::Server.new(jar, :background => true) + # server.start + # + # Add additional arguments: + # + # server = Selenium::Server.new(jar) + # server << ["--additional", "args"] + # server.start + # + + class Server + class Error < StandardError; end + + CL_RESET = WebDriver::Platform.windows? ? '' : "\r\e[0K" + + def self.get(required_version, opts = {}) + new(download(required_version), opts) + end + + # + # Download the given version of the selenium-server-standalone jar. + # + + class << self + def download(required_version) + required_version = latest if required_version == :latest + download_file_name = "selenium-server-standalone-#{required_version}.jar" + + return download_file_name if File.exist? download_file_name + + begin + File.open(download_file_name, 'wb') do |destination| + net_http.start('selenium-release.storage.googleapis.com') do |http| + resp = http.request_get("/#{required_version[/(\d+\.\d+)\./, 1]}/#{download_file_name}") do |response| + total = response.content_length + progress = 0 + segment_count = 0 + + response.read_body do |segment| + progress += segment.length + segment_count += 1 + + if (segment_count % 15).zero? + percent = (progress.to_f / total.to_f) * 100 + print "#{CL_RESET}Downloading #{download_file_name}: #{percent.to_i}% (#{progress} / #{total})" + segment_count = 0 + end + + destination.write(segment) + end + end + + raise Error, "#{resp.code} for #{download_file_name}" unless resp.is_a? Net::HTTPSuccess + end + end + rescue + FileUtils.rm download_file_name if File.exist? download_file_name + raise + end + + download_file_name + end + + # + # Ask Google Code what the latest selenium-server-standalone version is. + # + + def latest + require 'rexml/document' + net_http.start('selenium-release.storage.googleapis.com') do |http| + versions = REXML::Document.new(http.get('/').body).root.get_elements('//Contents/Key').map do |e| + e.text[/selenium-server-standalone-(\d+\.\d+\.\d+)\.jar/, 1] + end + + versions.compact.map { |version| Gem::Version.new(version) }.max.version + end + end + + def net_http + http_proxy = ENV['http_proxy'] || ENV['HTTP_PROXY'] + + if http_proxy + http_proxy = "http://#{http_proxy}" unless http_proxy.start_with?('http://') + uri = URI.parse(http_proxy) + + Net::HTTP::Proxy(uri.host, uri.port) + else + Net::HTTP + end + end + end + + # + # The server port + # + + attr_accessor :port + + # + # The server timeout + # + + attr_accessor :timeout + + # + # Whether to launch the server in the background + # + + attr_accessor :background + + # + # Path to log file, or 'true' for stdout. + # + + attr_accessor :log + + # + # @param [String] jar Path to the server jar. + # @param [Hash] opts the options to create the server process with + # + # @option opts [Integer] :port Port the server should listen on (default: 4444). + # @option opts [Integer] :timeout Seconds to wait for server launch/shutdown (default: 30) + # @option opts [true,false] :background Run the server in the background (default: false) + # @option opts [true,false,String] :log Either a path to a log file, + # or true to pass server log to stdout. + # @raise [Errno::ENOENT] if the jar file does not exist + # + + def initialize(jar, opts = {}) + raise Errno::ENOENT, jar unless File.exist?(jar) + + @jar = jar + @host = '127.0.0.1' + @port = opts.fetch(:port, 4444) + @timeout = opts.fetch(:timeout, 30) + @background = opts.fetch(:background, false) + @log = opts[:log] + + @additional_args = [] + end + + def start + process.start + poll_for_service + + process.wait unless @background + end + + def stop + begin + Net::HTTP.get(@host, '/selenium-server/driver/?cmd=shutDownSeleniumServer', @port) + rescue Errno::ECONNREFUSED + end + + stop_process if @process + poll_for_shutdown + + @log_file&.close + end + + def webdriver_url + "http://#{@host}:#{@port}/wd/hub" + end + + def <<(arg) + if arg.is_a?(Array) + @additional_args += arg + else + @additional_args << arg.to_s + end + end + + private + + def stop_process + return unless @process.alive? + + begin + @process.poll_for_exit(5) + rescue ChildProcess::TimeoutError + @process.stop + end + rescue Errno::ECHILD + # already dead + ensure + @process = nil + end + + def process + @process ||= begin + # extract any additional_args that start with -D as options + properties = @additional_args.dup - @additional_args.delete_if { |arg| arg[/^-D/] } + server_command = ['java'] + properties + ['-jar', @jar, '-port', @port.to_s] + @additional_args + cp = ChildProcess.build(*server_command) + WebDriver.logger.debug("Executing Process #{server_command}") + + io = cp.io + + if @log.is_a?(String) + @log_file = File.open(@log, 'w') + io.stdout = io.stderr = @log_file + elsif @log + io.inherit! + end + + cp.detach = @background + + cp + end + end + + def poll_for_service + return if socket.connected? + + raise Error, "remote server not launched in #{@timeout} seconds" + end + + def poll_for_shutdown + return if socket.closed? + + raise Error, "remote server not stopped in #{@timeout} seconds" + end + + def socket + @socket ||= WebDriver::SocketPoller.new(@host, @port, @timeout) + end + end # Server +end # Selenium diff --git a/path/ruby/2.6.0/gems/selenium-webdriver-3.142.6/lib/selenium/webdriver.rb b/path/ruby/2.6.0/gems/selenium-webdriver-3.142.6/lib/selenium/webdriver.rb new file mode 100644 index 00000000..975de859 --- /dev/null +++ b/path/ruby/2.6.0/gems/selenium-webdriver-3.142.6/lib/selenium/webdriver.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +require 'childprocess' +require 'tmpdir' +require 'fileutils' +require 'date' +require 'json' +require 'set' + +require 'selenium/webdriver/common' +require 'selenium/webdriver/atoms' +require 'selenium/webdriver/version' + +module Selenium + module WebDriver + Point = Struct.new(:x, :y) + Dimension = Struct.new(:width, :height) + Rectangle = Struct.new(:x, :y, :width, :height) + Location = Struct.new(:latitude, :longitude, :altitude) + + autoload :Chrome, 'selenium/webdriver/chrome' + autoload :Edge, 'selenium/webdriver/edge' + autoload :Firefox, 'selenium/webdriver/firefox' + autoload :IE, 'selenium/webdriver/ie' + autoload :PhantomJS, 'selenium/webdriver/phantomjs' + autoload :Remote, 'selenium/webdriver/remote' + autoload :Safari, 'selenium/webdriver/safari' + autoload :Support, 'selenium/webdriver/support' + + # @api private + + def self.root + @root ||= File.expand_path('..', __dir__) + end + + # + # Create a new Driver instance with the correct bridge for the given browser + # + # @overload for(browser) + # @param [:ie, :internet_explorer, :edge, :remote, :chrome, :firefox, :ff, :phantomjs, :safari] browser The browser to + # create the driver for + # @overload for(browser, opts) + # @param [:ie, :internet_explorer, :edge, :remote, :chrome, :firefox, :ff, :phantomjs, :safari] browser The browser to + # create the driver for + # @param [Hash] opts Options passed to Driver.new + # + # @return [Driver] + # + # @see Selenium::WebDriver::Remote::Driver + # @see Selenium::WebDriver::Firefox::Driver + # @see Selenium::WebDriver::IE::Driver + # @see Selenium::WebDriver::Edge::Driver + # @see Selenium::WebDriver::Chrome::Driver + # @see Selenium::WebDriver::PhantomJS::Driver + # @see Selenium::WebDriver::Safari::Driver + # + # @example + # + # WebDriver.for :firefox, profile: 'some-profile' + # WebDriver.for :firefox, profile: Profile.new + # WebDriver.for :remote, url: "http://localhost:4444/wd/hub", desired_capabilities: caps + # + # One special argument is not passed on to the bridges, :listener. + # You can pass a listener for this option to get notified of WebDriver events. + # The passed object must respond to #call or implement the methods from AbstractEventListener. + # + # @see Selenium::WebDriver::Support::AbstractEventListener + # + + def self.for(*args) + WebDriver::Driver.for(*args) + end + + # + # Returns logger instance that can be used across the whole Selenium. + # + # @return [Logger] + # + + def self.logger + @logger ||= WebDriver::Logger.new + end + end # WebDriver +end # Selenium diff --git a/path/ruby/2.6.0/gems/selenium-webdriver-3.142.6/lib/selenium/webdriver/atoms.rb b/path/ruby/2.6.0/gems/selenium-webdriver-3.142.6/lib/selenium/webdriver/atoms.rb new file mode 100644 index 00000000..8a220e5a --- /dev/null +++ b/path/ruby/2.6.0/gems/selenium-webdriver-3.142.6/lib/selenium/webdriver/atoms.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +module Selenium + module WebDriver + module Atoms + + private + + def read_atom(function) + File.read(File.expand_path("../atoms/#{function}.js", __FILE__)) + end + + def execute_atom(function_name, *arguments) + script = format("return (%s).apply(null, arguments)", atom: read_atom(function_name)) + execute_script(script, *arguments) + end + + end # Atoms + end # WebDriver +end # Selenium diff --git a/path/ruby/2.6.0/gems/selenium-webdriver-3.142.6/lib/selenium/webdriver/atoms/getAttribute.js b/path/ruby/2.6.0/gems/selenium-webdriver-3.142.6/lib/selenium/webdriver/atoms/getAttribute.js new file mode 100644 index 00000000..81289ec0 --- /dev/null +++ b/path/ruby/2.6.0/gems/selenium-webdriver-3.142.6/lib/selenium/webdriver/atoms/getAttribute.js @@ -0,0 +1,7 @@ +function(){return function(){var d=this;function f(a){return"string"==typeof a}function h(a,b){function e(){}e.prototype=b.prototype;a.c=b.prototype;a.prototype=new e;a.prototype.constructor=a;a.b=function(a,e,m){for(var c=Array(arguments.length-2),g=2;gb||a.indexOf("Error",b)!=b)a+="Error";this.name=a;a=Error(this.message);a.name=this.name;this.stack=a.stack||""}h(k,Error);var n="unknown error",l={15:"element not selectable",11:"element not visible"};l[31]=n;l[30]=n;l[24]="invalid cookie domain";l[29]="invalid element coordinates";l[12]="invalid element state";l[32]="invalid selector"; +l[51]="invalid selector";l[52]="invalid selector";l[17]="javascript error";l[405]="unsupported operation";l[34]="move target out of bounds";l[27]="no such alert";l[7]="no such element";l[8]="no such frame";l[23]="no such window";l[28]="script timeout";l[33]="session not created";l[10]="stale element reference";l[21]="timeout";l[25]="unable to set cookie";l[26]="unexpected alert open";l[13]=n;l[9]="unknown command";k.prototype.toString=function(){return this.name+": "+this.message};var p;a:{var q=d.navigator;if(q){var r=q.userAgent;if(r){p=r;break a}}p=""}function t(a){return-1!=p.indexOf(a)};function aa(a,b){for(var e=a.length,c=f(a)?a.split(""):a,g=0;gparseFloat(D)){C=String(F);break a}}C=D}var G;var H=d.document;G=H&&x?B()||("CSS1Compat"==H.compatMode?parseInt(C,10):5):void 0;var ca=t("Firefox"),da=u()||t("iPod"),ea=t("iPad"),I=t("Android")&&!(v()||t("Firefox")||t("Opera")||t("Silk")),fa=v(),J=t("Safari")&&!(v()||t("Coast")||t("Opera")||t("Edge")||t("Silk")||t("Android"))&&!(u()||t("iPad")||t("iPod"));function K(a){return(a=a.exec(p))?a[1]:""}(function(){if(ca)return K(/Firefox\/([0-9.]+)/);if(x||z||w)return C;if(fa)return u()||t("iPad")||t("iPod")?K(/CriOS\/([0-9.]+)/):K(/Chrome\/([0-9.]+)/);if(J&&!(u()||t("iPad")||t("iPod")))return K(/Version\/([0-9.]+)/);if(da||ea){var a=/Version\/(\S+).*Mobile\/(\S+)/.exec(p);if(a)return a[1]+"."+a[2]}else if(I)return(a=K(/Android\s+([0-9.]+)/))?a:K(/Version\/([0-9.]+)/);return""})();var L,M=function(){if(!A)return!1;var a=d.Components;if(!a)return!1;try{if(!a.classes)return!1}catch(g){return!1}var b=a.classes;a=a.interfaces;var e=b["@mozilla.org/xpcom/version-comparator;1"].getService(a.nsIVersionComparator),c=b["@mozilla.org/xre/app-info;1"].getService(a.nsIXULAppInfo).version;L=function(a){e.compare(c,""+a)};return!0}(),N=x&&!(8<=Number(G)),ha=x&&!(9<=Number(G));I&&M&&L(2.3);I&&M&&L(4);J&&M&&L(6);var ia={SCRIPT:1,STYLE:1,HEAD:1,IFRAME:1,OBJECT:1},O={IMG:" ",BR:"\n"};function P(a,b,e){if(!(a.nodeName in ia))if(3==a.nodeType)e?b.push(String(a.nodeValue).replace(/(\r\n|\r|\n)/g,"")):b.push(a.nodeValue);else if(a.nodeName in O)b.push(O[a.nodeName]);else for(a=a.firstChild;a;)P(a,b,e),a=a.nextSibling};function Q(a,b){b=b.toLowerCase();return"style"==b?ja(a.style.cssText):N&&"value"==b&&R(a,"INPUT")?a.value:ha&&!0===a[b]?String(a.getAttribute(b)):(a=a.getAttributeNode(b))&&a.specified?a.value:null}var ka=/[;]+(?=(?:(?:[^"]*"){2})*[^"]*$)(?=(?:(?:[^']*'){2})*[^']*$)(?=(?:[^()]*\([^()]*\))*[^()]*$)/; +function ja(a){var b=[];aa(a.split(ka),function(a){var c=a.indexOf(":");0