pax_global_header00006660000000000000000000000064140723241540014514gustar00rootroot0000000000000052 comment=d494298d5afb0a78ea76283a064e179920e85b20 dry-core-0.7.1/000077500000000000000000000000001407232415400132455ustar00rootroot00000000000000dry-core-0.7.1/.action_hero.yml000066400000000000000000000010431407232415400163360ustar00rootroot00000000000000# This is a config synced from dry-rb/template-gem repo sources: - repo: dry-rb/template-gem sync: - ".action_hero.yml.erb" - ".devtools/templates/*.sync:${{dir}}/${{name}}" - ".github/**/*.*" - "gemspec.erb:dry-core.gemspec" - "spec/support/*" - "CODE_OF_CONDUCT.md" - "CONTRIBUTING.md" - "CODEOWNERS" - "LICENSE.erb" - "README.md.erb" - "Gemfile.devtools" - ".rspec" - ".rubocop.yml" - repo: action-hero/workflows sync: - ".github/workflows/*.yml" dry-core-0.7.1/.devtools/000077500000000000000000000000001407232415400151625ustar00rootroot00000000000000dry-core-0.7.1/.devtools/templates/000077500000000000000000000000001407232415400171605ustar00rootroot00000000000000dry-core-0.7.1/.devtools/templates/changelog.erb000066400000000000000000000013711407232415400216030ustar00rootroot00000000000000 <% releases.each_with_index do |r, idx| %> ## <%= r.version %> <%= r.date %> <% if r.summary %> <%= r.summary %> <% end %> <% if r.added? %> ### Added <% r.added.each do |log| %> - <%= log %> <% end %> <% end %> <% if r.fixed? %> ### Fixed <% r.fixed.each do |log| %> - <%= log %> <% end %> <% end %> <% if r.changed? %> ### Changed <% r.changed.each do |log| %> - <%= log %> <% end %> <% end %> <% curr_ver = r.date ? "v#{r.version}" : 'master' %> <% prev_rel = releases[idx + 1] %> <% if prev_rel %> <% ver_range = "v#{prev_rel.version}...#{curr_ver}" %> [Compare <%=ver_range%>](https://github.com/dry-rb/<%= project.name %>/compare/<%=ver_range%>) <% end %> <% end %> dry-core-0.7.1/.devtools/templates/release.erb000066400000000000000000000013171407232415400212740ustar00rootroot00000000000000 <% if latest_release.summary %> <%= latest_release.summary %> <% end %> <% if latest_release.added? %> ### Added <% latest_release.added.each do |log| %> - <%= log %> <% end %> <% end %> <% if latest_release.fixed? %> ### Fixed <% latest_release.fixed.each do |log| %> - <%= log %> <% end %> <% end %> <% if latest_release.changed? %> ### Changed <% latest_release.changed.each do |log| %> - <%= log %> <% end %> <% end %> <% if previous_release %> <% ver_range = "v#{previous_release.version}...v#{latest_release.version}" %> [Compare <%=ver_range%>](https://github.com/dry-rb/<%= project.name %>/compare/<%=ver_range%>) <% end %> dry-core-0.7.1/.github/000077500000000000000000000000001407232415400146055ustar00rootroot00000000000000dry-core-0.7.1/.github/FUNDING.yml000066400000000000000000000000441407232415400164200ustar00rootroot00000000000000github: [jodosha, solnic, timriley] dry-core-0.7.1/.github/ISSUE_TEMPLATE/000077500000000000000000000000001407232415400167705ustar00rootroot00000000000000dry-core-0.7.1/.github/ISSUE_TEMPLATE/bug-report.md000066400000000000000000000007521407232415400214040ustar00rootroot00000000000000--- name: "\U0001F41B Bug report" about: See CONTRIBUTING.md for more information title: '' labels: bug, help wanted assignees: '' --- ## Describe the bug A clear and concise description of what the bug is. ## To Reproduce Provide detailed steps to reproduce, **an executable script would be best**. ## Expected behavior A clear and concise description of what you expected to happen. ## My environment - Affects my production application: **YES/NO** - Ruby version: ... - OS: ... dry-core-0.7.1/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000002361407232415400207610ustar00rootroot00000000000000blank_issues_enabled: false contact_links: - name: Community Support url: https://discourse.dry-rb.org about: Please ask and answer questions here. dry-core-0.7.1/.github/SUPPORT.md000066400000000000000000000004441407232415400163050ustar00rootroot00000000000000## Support If you need help with any of the dry-rb libraries, feel free to ask questions on our [discussion forum](https://discourse.dry-rb.org/). This is the best place to seek help. Make sure to search for a potential solution in past threads before posting your question. Thanks! :heart: dry-core-0.7.1/.github/workflows/000077500000000000000000000000001407232415400166425ustar00rootroot00000000000000dry-core-0.7.1/.github/workflows/ci.yml000066400000000000000000000043321407232415400177620ustar00rootroot00000000000000# This file is synced from dry-rb/template-gem repo name: CI on: push: paths: - ".github/workflows/ci.yml" - "lib/**" - "*.gemspec" - "spec/**" - "Rakefile" - "Gemfile" - "Gemfile.devtools" - ".rubocop.yml" - "project.yml" pull_request: branches: - master create: jobs: tests: runs-on: ubuntu-latest name: Tests strategy: fail-fast: false matrix: ruby: - "3.0" - "2.7" - "2.6" include: - ruby: "3.0" coverage: "true" env: COVERAGE: ${{matrix.coverage}} COVERAGE_TOKEN: ${{secrets.CODACY_PROJECT_TOKEN}} steps: - name: Checkout uses: actions/checkout@v1 - name: Install package dependencies run: "[ -e $APT_DEPS ] || sudo apt-get install -y --no-install-recommends $APT_DEPS" - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} bundler-cache: true - name: Run all tests run: bundle exec rake - name: Run codacy-coverage-reporter uses: codacy/codacy-coverage-reporter-action@master if: env.COVERAGE == 'true' && env.COVERAGE_TOKEN != '' with: project-token: ${{secrets.CODACY_PROJECT_TOKEN}} coverage-reports: coverage/coverage.xml release: runs-on: ubuntu-latest if: contains(github.ref, 'tags') && github.event_name == 'create' needs: tests env: GITHUB_LOGIN: dry-bot GITHUB_TOKEN: ${{secrets.GH_PAT}} steps: - uses: actions/checkout@v1 - name: Install package dependencies run: "[ -e $APT_DEPS ] || sudo apt-get install -y --no-install-recommends $APT_DEPS" - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: 2.6 - name: Install dependencies run: gem install ossy --no-document - name: Trigger release workflow run: | tag=$(echo $GITHUB_REF | cut -d / -f 3) ossy gh w dry-rb/devtools release --payload "{\"tag\":\"$tag\",\"sha\":\"${{github.sha}}\",\"tag_creator\":\"$GITHUB_ACTOR\",\"repo\":\"$GITHUB_REPOSITORY\",\"repo_name\":\"${{github.event.repository.name}}\"}" dry-core-0.7.1/.github/workflows/docsite.yml000066400000000000000000000032651407232415400210250ustar00rootroot00000000000000# this file is managed by dry-rb/devtools project name: docsite on: push: paths: - docsite/** - .github/workflows/docsite.yml branches: - master - release-** tags: jobs: update-docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: fetch-depth: 0 - run: | git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/* - name: Set up Ruby uses: actions/setup-ruby@v1 with: ruby-version: "2.6.x" - name: Set up git user run: | git config --local user.email "dry-bot@dry-rb.org" git config --local user.name "dry-bot" - name: Install dependencies run: gem install ossy --no-document - name: Update release branches run: | branches=`git log --format=%B -n 1 $GITHUB_SHA | grep "docsite:release-" || echo "nothing"` if [[ ! $branches -eq "nothing" ]]; then for b in $branches do name=`echo $b | ruby -e 'puts gets[/:(.+)/, 1].gsub(/\s+/, "")'` echo "merging $GITHUB_SHA to $name" git checkout -b $name --track origin/$name echo `git log -n 1` git cherry-pick $GITHUB_SHA -m 1 done git push --all "https://dry-bot:${{secrets.GH_PAT}}@github.com/$GITHUB_REPOSITORY.git" git checkout master else echo "no need to update branches" fi - name: Trigger dry-rb.org deploy env: GITHUB_LOGIN: dry-bot GITHUB_TOKEN: ${{secrets.GH_PAT}} run: ossy github workflow dry-rb/dry-rb.org ci dry-core-0.7.1/.github/workflows/rubocop.yml000066400000000000000000000017071407232415400210430ustar00rootroot00000000000000name: "RuboCop" on: push: branches: - "main" - "master" paths: - "**/*.rb" - "**/*.rake" - "Rakefile" - "Gemfile*" - ".rubocop.yml" pull_request: branches: - "main" - "master" types: - "opened" - "synchronize" workflow_dispatch: jobs: run: runs-on: ubuntu-latest name: ${{ matrix.type }} strategy: fail-fast: false matrix: type: ["Style", "Layout", "Naming", "Lint", "Metrics", "Security"] steps: - name: Clone uses: actions/checkout@v2 - name: Get git diff id: get_diff uses: technote-space/get-diff-action@v4 with: PATTERNS: | **/*.rb **/*.rake Gemfile Rakefile - name: Check ${{ matrix.type }} uses: action-hero/actions/rubocop@main if: ${{ env.GIT_DIFF != '' }} with: diff: ${{ env.GIT_DIFF }} type: ${{ matrix.type }} dry-core-0.7.1/.github/workflows/sync_configs.yml000066400000000000000000000027161407232415400220570ustar00rootroot00000000000000# This file is synced from dry-rb/template-gem repo name: sync on: repository_dispatch: push: branches: - "master" jobs: main: runs-on: ubuntu-latest if: (github.event_name == 'repository_dispatch' && github.event.action == 'sync_configs') || github.event_name != 'repository_dispatch' env: GITHUB_LOGIN: dry-bot GITHUB_TOKEN: ${{ secrets.GH_PAT }} steps: - name: Checkout ${{github.repository}} uses: actions/checkout@v1 - name: Checkout devtools uses: actions/checkout@v2 with: repository: dry-rb/devtools path: tmp/devtools - name: Setup git user run: | git config --local user.email "dry-bot@dry-rb.org" git config --local user.name "dry-bot" - name: Set up Ruby uses: actions/setup-ruby@v1 with: ruby-version: "2.6" - name: Install dependencies run: gem install ossy --no-document - name: Update changelog.yml from commit run: tmp/devtools/bin/update-changelog-from-commit $GITHUB_SHA - name: Compile CHANGELOG.md run: tmp/devtools/bin/compile-changelog - name: Commit run: | git add -A git commit -m "[devtools] sync" || echo "nothing to commit" - name: Push changes run: | git pull --rebase origin master git push https://dry-bot:${{secrets.GH_PAT}}@github.com/${{github.repository}}.git HEAD:master dry-core-0.7.1/.gitignore000066400000000000000000000001521407232415400152330ustar00rootroot00000000000000/.bundle/ /.yardoc /Gemfile.lock /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ spec/examples.txt dry-core-0.7.1/.inch.yml000066400000000000000000000001071407232415400147650ustar00rootroot00000000000000files: excluded: - lib/dry/core.rb - lib/dry/core/version.rb dry-core-0.7.1/.rspec000066400000000000000000000000701407232415400143570ustar00rootroot00000000000000--color --require spec_helper --order random --warnings dry-core-0.7.1/.rubocop.yml000066400000000000000000000056731407232415400155320ustar00rootroot00000000000000# This is a config synced from dry-rb/template-gem repo AllCops: TargetRubyVersion: 2.6 NewCops: enable Exclude: - spec/support/coverage.rb - spec/support/warnings.rb - spec/support/rspec_options.rb - Gemfile.devtools - "*.gemspec" Layout/SpaceAroundMethodCallOperator: Enabled: false Layout/SpaceInLambdaLiteral: Enabled: false Layout/MultilineMethodCallIndentation: Enabled: true EnforcedStyle: indented Layout/FirstArrayElementIndentation: EnforcedStyle: consistent Layout/SpaceInsideHashLiteralBraces: Enabled: true EnforcedStyle: no_space EnforcedStyleForEmptyBraces: no_space Layout/LineLength: Max: 100 Exclude: - "spec/**/*.rb" Lint/AmbiguousBlockAssociation: Enabled: true # because 'expect { foo }.to change { bar }' is fine Exclude: - "spec/**/*.rb" Lint/BooleanSymbol: Enabled: false Lint/ConstantDefinitionInBlock: Exclude: - "spec/**/*.rb" Lint/RaiseException: Enabled: false Lint/StructNewOverride: Enabled: false Lint/SuppressedException: Exclude: - "spec/spec_helper.rb" Naming/PredicateName: Enabled: false Naming/FileName: Exclude: - "lib/*-*.rb" Naming/MethodName: Enabled: false Naming/MethodParameterName: Enabled: true Exclude: - "spec/**/*.rb" Naming/MemoizedInstanceVariableName: Enabled: false Metrics/MethodLength: Enabled: false Metrics/ClassLength: Enabled: false Metrics/BlockLength: Enabled: false Metrics/AbcSize: Max: 25 Metrics/CyclomaticComplexity: Enabled: true Max: 12 Style/ExponentialNotation: Enabled: false Style/HashEachMethods: Enabled: false Style/HashTransformKeys: Enabled: false Style/HashTransformValues: Enabled: false Style/AccessModifierDeclarations: Enabled: false Style/Alias: Enabled: true EnforcedStyle: prefer_alias_method Style/AsciiComments: Enabled: false Style/BlockDelimiters: Enabled: false Style/ClassAndModuleChildren: Exclude: - "spec/**/*.rb" Style/ConditionalAssignment: Enabled: false Style/DateTime: Enabled: false Style/Documentation: Enabled: false Style/EachWithObject: Enabled: false Style/FormatString: Enabled: false Style/GuardClause: Enabled: false Style/IfUnlessModifier: Enabled: false Style/Lambda: Enabled: false Style/LambdaCall: Enabled: false Style/ParallelAssignment: Enabled: false Style/StabbyLambdaParentheses: Enabled: false Style/StringLiterals: Enabled: true EnforcedStyle: double_quotes ConsistentQuotesInMultiline: false Style/StringLiteralsInInterpolation: Enabled: true EnforcedStyle: double_quotes Style/SymbolArray: Exclude: - "spec/**/*.rb" Style/TrailingUnderscoreVariable: Enabled: false Style/MultipleComparison: Enabled: false Style/Next: Enabled: false Lint/UnusedBlockArgument: Exclude: - "spec/**/*.rb" Metrics/ParameterLists: Exclude: - "spec/**/*.rb" Lint/EmptyBlock: Exclude: - "spec/**/*.rb" Naming/ConstantName: Exclude: - "spec/**/*.rb" dry-core-0.7.1/CHANGELOG.md000066400000000000000000000161531407232415400150640ustar00rootroot00000000000000 ## 0.7.1 2021-07-10 ### Fixed - [memoizable] memoizable correctly handles cases where a method has unnamed params (e.g. happens when the new `...` syntax is used) (@flash-gordon) [Compare v0.7.0...v0.7.1](https://github.com/dry-rb/dry-core/compare/v0.7.0...v0.7.1) ## 0.7.0 2021-07-08 ### Fixed - [memoizable] warnings when using keyword arguments (@flash-gordon) - [deprecations] warnings show more relevant information about caller by default (@timriley) ### Changed - Minimal Ruby version is 2.6 - [memoizable] memoization of block-accepting methods is deprecated (@flash-gordon) [Compare v0.6.0...v0.7.0](https://github.com/dry-rb/dry-core/compare/v0.6.0...v0.7.0) ## 0.6.0 2021-06-03 ### Added - [memoizable] support for `BasicObject` (@oleander) - [memoizable] support for methods that accept blocks (@oleander) - [deprecations] allow printing frame info on warn when setting up Deprecation module (via #52) (@waiting-for-dev) ### Fixed - [memoizable] works with MRI 2.7+ keyword arguments now (@oleander) [Compare v0.5.0...v0.6.0](https://github.com/dry-rb/dry-core/compare/v0.5.0...v0.6.0) ## 0.5.0 2012-12-12 ### Added - dry-equalizer has been imported into dry-core as `Dry::Core::Equalizer` but the interface remains the same, which is `include Dry.Equalizer(...)` - we'll be porting all other gems that depend on dry-equalizer to the latest dry-core with equalizer included *gradually*. Eventually dry-equalizer usage will be gone completely in rom-rb/dry-rb/hanami projects (@solnic) [Compare v0.4.10...v0.5.0](https://github.com/dry-rb/dry-core/compare/v0.4.10...v0.5.0) ## 0.4.10 2020-11-19 ### Added - `ClassAttributes.defines` gets a new option for coercing values (tallica) ```ruby class Builder extend Dry::Core::ClassAttributes defines :nodes, coerce: -> value { Integer(value) } end ``` `:coerce` works with any callable as well as types from dry-types ```ruby defines :nodes, coerce: Dry::Types['coercible.integer'] ``` - `Constants::IDENTITY` which is the identity function (flash-gordon) [Compare v0.4.9...v0.4.10](https://github.com/dry-rb/dry-core/compare/v0.4.9...v0.4.10) ## 0.4.9 2019-08-09 ### Added - `Undefined.coalesce` takes a variable number of arguments and returns the first non-`Undefined` value (flash-gordon) ```ruby Undefined.coalesce(Undefined, Undefined, :foo) # => :foo ``` ### Fixed - `Undefined.{dup,clone}` returns `Undefined` back, `Undefined` is a singleton (flash-gordon) [Compare v0.4.8...v0.4.9](https://github.com/dry-rb/dry-core/compare/v0.4.8...v0.4.9) ## 0.4.8 2019-06-23 ### Added - `Undefined.map` for mapping non-undefined values (flash-gordon) ```ruby something = 1 Undefined.map(something) { |v| v + 1 } # => 2 something = Undefined Undefined.map(something) { |v| v + 1 } # => Undefined ``` [Compare v0.4.7...v0.4.8](https://github.com/dry-rb/dry-core/compare/v0.4.7...v0.4.8) ## 0.4.7 2018-06-25 ### Fixed - Fix default logger for deprecations, it now uses `$stderr` by default, as it should (flash-gordon) [Compare v0.4.6...v0.4.7](https://github.com/dry-rb/dry-core/compare/v0.4.6...v0.4.7) ## 0.4.6 2018-05-15 ### Changed - Trigger constant autoloading in the class builder (radar) [Compare v0.4.5...v0.4.6](https://github.com/dry-rb/dry-core/compare/v0.4.5...v0.4.6) ## 0.4.5 2018-03-14 ### Added - `Dry::Core::Memoizable`, which provides a `memoize` macro for memoizing results of instance methods (timriley) [Compare v0.4.4...v0.4.5](https://github.com/dry-rb/dry-core/compare/v0.4.4...v0.4.5) ## 0.4.4 2018-02-10 ### Added - `deprecate_constant` overrides `Module#deprecate_constant` and issues a labeled message on accessing a deprecated constant (flash-gordon) - `Undefined.default` which accepts two arguments and returns the first if it's not `Undefined`; otherwise, returns the second one or yields a block (flash-gordon) [Compare v0.4.3...v0.4.4](https://github.com/dry-rb/dry-core/compare/v0.4.3...v0.4.4) ## 0.4.3 2018-02-03 ### Added - `Dry::Core::DescendantsTracker` which is a maintained version of the [`descendants_tracker`](https://github.com/dkubb/descendants_tracker) gem (flash-gordon) [Compare v0.4.2...v0.4.3](https://github.com/dry-rb/dry-core/compare/v0.4.2...v0.4.3) ## 0.4.2 2017-12-16 ### Fixed - Class attributes now support private setters/getters (flash-gordon) [Compare v0.4.1...v0.4.2](https://github.com/dry-rb/dry-core/compare/v0.4.1...v0.4.2) ## 0.4.1 2017-11-04 ### Changed - Improved error message on invalid attribute value (GustavoCaso) [Compare v0.4.0...v0.4.1](https://github.com/dry-rb/dry-core/compare/v0.4.0...v0.4.1) ## 0.4.0 2017-11-02 ### Added - Added the `:type` option to class attributes, you can now restrict attribute values with a type. You can either use plain ruby types (`Integer`, `String`, etc) or `dry-types` (GustavoCaso) ```ruby class Foo extend Dry::Core::ClassAttributes defines :ruby_attr, type: Integer defines :dry_attr, type: Dry::Types['strict.int'] end ``` [Compare v0.3.4...v0.4.0](https://github.com/dry-rb/dry-core/compare/v0.3.4...v0.4.0) ## 0.3.4 2017-09-29 ### Fixed - `Deprecations` output is set to `$stderr` by default now (solnic) [Compare v0.3.3...v0.3.4](https://github.com/dry-rb/dry-core/compare/v0.3.3...v0.3.4) ## 0.3.3 2017-08-31 ### Fixed - The Deprecations module now shows the right caller line (flash-gordon) [Compare v0.3.2...v0.3.3](https://github.com/dry-rb/dry-core/compare/v0.3.2...v0.3.3) ## 0.3.2 2017-08-31 ### Added - Accept an existing logger object in `Dry::Core::Deprecations.set_logger!` (flash-gordon) [Compare v0.3.1...v0.3.2](https://github.com/dry-rb/dry-core/compare/v0.3.1...v0.3.2) ## 0.3.1 2017-05-27 ### Added - Support for building classes within an existing namespace (flash-gordon) [Compare v0.3.0...v0.3.1](https://github.com/dry-rb/dry-core/compare/v0.3.0...v0.3.1) ## 0.3.0 2017-05-05 ### Changed - Class attributes are initialized _before_ running the `inherited` hook. It's slightly more convenient behavior and it's very unlikely anyone will be affected by this, but technically this is a breaking change (flash-gordon) [Compare v0.2.4...v0.3.0](https://github.com/dry-rb/dry-core/compare/v0.2.4...v0.3.0) ## 0.2.4 2017-01-26 ### Fixed - Do not require deprecated method to be defined (flash-gordon) [Compare v0.2.3...v0.2.4](https://github.com/dry-rb/dry-core/compare/v0.2.3...v0.2.4) ## 0.2.3 2016-12-30 ### Fixed - Fix warnings on using uninitialized class attributes (flash-gordon) [Compare v0.2.2...v0.2.3](https://github.com/dry-rb/dry-core/compare/v0.2.2...v0.2.3) ## 0.2.2 2016-12-30 ### Added - `ClassAttributes` which provides `defines` method for defining get-or-set methods (flash-gordon) [Compare v0.2.1...v0.2.2](https://github.com/dry-rb/dry-core/compare/v0.2.1...v0.2.2) ## 0.2.1 2016-11-18 ### Added - `Constants` are now available in nested scopes (flash-gordon) [Compare v0.2.0...v0.2.1](https://github.com/dry-rb/dry-core/compare/v0.2.0...v0.2.1) ## 0.2.0 2016-11-01 [Compare v0.1.0...v0.2.0](https://github.com/dry-rb/dry-core/compare/v0.1.0...v0.2.0) ## 0.1.0 2016-09-17 Initial release dry-core-0.7.1/CODEOWNERS000066400000000000000000000000121407232415400146310ustar00rootroot00000000000000* @solnic dry-core-0.7.1/CODE_OF_CONDUCT.md000066400000000000000000000026611407232415400160510ustar00rootroot00000000000000# Contributor Code of Conduct As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion. Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. 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. Project maintainers who do not follow the Code of Conduct may be removed from the project team. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.4.0, available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct](https://www.contributor-covenant.org/version/1/4/code-of-conduct) dry-core-0.7.1/CONTRIBUTING.md000066400000000000000000000026471407232415400155070ustar00rootroot00000000000000# Issue Guidelines ## Reporting bugs If you found a bug, report an issue and describe what's the expected behavior versus what actually happens. If the bug causes a crash, attach a full backtrace. If possible, a reproduction script showing the problem is highly appreciated. ## Reporting feature requests Report a feature request **only after discussing it first on [discourse.dry-rb.org](https://discourse.dry-rb.org)** where it was accepted. Please provide a concise description of the feature. ## Reporting questions, support requests, ideas, concerns etc. **PLEASE DON'T** - use [discourse.dry-rb.org](https://discourse.dry-rb.org) instead. # Pull Request Guidelines A Pull Request will only be accepted if it addresses a specific issue that was reported previously, or fixes typos, mistakes in documentation etc. Other requirements: 1) Do not open a pull request if you can't provide tests along with it. If you have problems writing tests, ask for help in the related issue. 2) Follow the style conventions of the surrounding code. In most cases, this is standard ruby style. 3) Add API documentation if it's a new feature 4) Update API documentation if it changes an existing feature 5) Bonus points for sending a PR which updates user documentation in the `docsite` directory # Asking for help If these guidelines aren't helpful, and you're stuck, please post a message on [discourse.dry-rb.org](https://discourse.dry-rb.org). dry-core-0.7.1/Gemfile000066400000000000000000000005001407232415400145330ustar00rootroot00000000000000# frozen_string_literal: true source "https://rubygems.org" eval_gemfile "Gemfile.devtools" gemspec group :test do gem "activesupport" gem "dry-inflector" gem "dry-types", "~> 1.0" gem "inflecto", "~> 0.0", ">= 0.0.2" end group :tools do gem "pry", platform: :jruby gem "pry-byebug", platform: :mri end dry-core-0.7.1/Gemfile.devtools000066400000000000000000000006031407232415400163750ustar00rootroot00000000000000# frozen_string_literal: true # This file is synced from dry-rb/template-gem repo gem "rake", ">= 12.3.3" group :test do gem "simplecov", require: false, platforms: :ruby gem "simplecov-cobertura", require: false, platforms: :ruby gem "rexml", require: false gem "warning" end group :tools do # this is the same version that we use on codacy gem "rubocop", "1.16.0" end dry-core-0.7.1/LICENSE000066400000000000000000000020731407232415400142540ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015-2021 dry-rb team 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. dry-core-0.7.1/LICENSE.txt000066400000000000000000000020731407232415400150720ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2016 Nikita Shilnikov 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. dry-core-0.7.1/README.md000066400000000000000000000023361407232415400145300ustar00rootroot00000000000000 [gem]: https://rubygems.org/gems/dry-core [actions]: https://github.com/dry-rb/dry-core/actions [codacy]: https://www.codacy.com/gh/dry-rb/dry-core [chat]: https://dry-rb.zulipchat.com [inchpages]: http://inch-ci.org/github/dry-rb/dry-core # dry-core [![Join the chat at https://dry-rb.zulipchat.com](https://img.shields.io/badge/dry--rb-join%20chat-%23346b7a.svg)][chat] [![Gem Version](https://badge.fury.io/rb/dry-core.svg)][gem] [![CI Status](https://github.com/dry-rb/dry-core/workflows/ci/badge.svg)][actions] [![Codacy Badge](https://api.codacy.com/project/badge/Grade/40946292b9094624beec604a149a6023)][codacy] [![Codacy Badge](https://api.codacy.com/project/badge/Coverage/40946292b9094624beec604a149a6023)][codacy] [![Inline docs](http://inch-ci.org/github/dry-rb/dry-core.svg?branch=master)][inchpages] ## Links * [User documentation](https://dry-rb.org/gems/dry-core) * [API documentation](http://rubydoc.info/gems/dry-core) ## Supported Ruby versions This library officially supports the following Ruby versions: * MRI `>= 2.6.0` * ~~jruby~~ `>= 9.3` (we are waiting for [2.6 support](https://github.com/jruby/jruby/issues/6161)) ## License See `LICENSE` file. dry-core-0.7.1/Rakefile000066400000000000000000000002211407232415400147050ustar00rootroot00000000000000# frozen_string_literal: true require "bundler/gem_tasks" require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) task default: :spec dry-core-0.7.1/bin/000077500000000000000000000000001407232415400140155ustar00rootroot00000000000000dry-core-0.7.1/bin/.gitkeep000066400000000000000000000000001407232415400154340ustar00rootroot00000000000000dry-core-0.7.1/bin/console000077500000000000000000000004441407232415400154070ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true require "bundler/setup" require "dry/core" # 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. binding.irb # rubocop:disable Lint/Debugger dry-core-0.7.1/bin/setup000077500000000000000000000002031407232415400150760ustar00rootroot00000000000000#!/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 dry-core-0.7.1/changelog.yml000066400000000000000000000130731407232415400157230ustar00rootroot00000000000000--- - version: 0.7.1 date: 2021-07-10 fixed: - | [memoizable] memoizable correctly handles cases where a method has unnamed params (e.g. happens when the new `...` syntax is used) (@flash-gordon) - version: 0.7.0 date: 2021-07-08 fixed: - "[memoizable] warnings when using keyword arguments (@flash-gordon)" - "[deprecations] warnings show more relevant information about caller by default (@timriley)" changed: - Minimal Ruby version is 2.6 - "[memoizable] memoization of block-accepting methods is deprecated (@flash-gordon)" - version: 0.6.0 summary: date: 2021-06-03 fixed: - "[memoizable] works with MRI 2.7+ keyword arguments now (@oleander)" added: - "[memoizable] support for `BasicObject` (@oleander)" - "[memoizable] support for methods that accept blocks (@oleander)" - "[deprecations] allow printing frame info on warn when setting up Deprecation module (via #52) (@waiting-for-dev)" changed: - version: 0.5.0 summary: date: '2012-12-12' fixed: added: - dry-equalizer has been imported into dry-core as `Dry::Core::Equalizer` but the interface remains the same, which is `include Dry.Equalizer(...)` - we'll be porting all other gems that depend on dry-equalizer to the latest dry-core with equalizer included *gradually*. Eventually dry-equalizer usage will be gone completely in rom-rb/dry-rb/hanami projects (@solnic) changed: - version: 0.4.10 date: '2020-11-19' added: - |- `ClassAttributes.defines` gets a new option for coercing values (tallica) ```ruby class Builder extend Dry::Core::ClassAttributes defines :nodes, coerce: -> value { Integer(value) } end ``` `:coerce` works with any callable as well as types from dry-types ```ruby defines :nodes, coerce: Dry::Types['coercible.integer'] ``` - "`Constants::IDENTITY` which is the identity function (flash-gordon)" - version: 0.4.9 date: '2019-08-09' added: - |- `Undefined.coalesce` takes a variable number of arguments and returns the first non-`Undefined` value (flash-gordon) ```ruby Undefined.coalesce(Undefined, Undefined, :foo) # => :foo ``` fixed: - "`Undefined.{dup,clone}` returns `Undefined` back, `Undefined` is a singleton (flash-gordon)" - version: 0.4.8 date: '2019-06-23' added: - |- `Undefined.map` for mapping non-undefined values (flash-gordon) ```ruby something = 1 Undefined.map(something) { |v| v + 1 } # => 2 something = Undefined Undefined.map(something) { |v| v + 1 } # => Undefined ``` - version: 0.4.7 date: '2018-06-25' fixed: - Fix default logger for deprecations, it now uses `$stderr` by default, as it should (flash-gordon) - version: 0.4.6 date: '2018-05-15' changed: - Trigger constant autoloading in the class builder (radar) - version: 0.4.5 date: '2018-03-14' added: - "`Dry::Core::Memoizable`, which provides a `memoize` macro for memoizing results of instance methods (timriley)" - version: 0.4.4 date: '2018-02-10' added: - "`deprecate_constant` overrides `Module#deprecate_constant` and issues a labeled message on accessing a deprecated constant (flash-gordon)" - "`Undefined.default` which accepts two arguments and returns the first if it's not `Undefined`; otherwise, returns the second one or yields a block (flash-gordon)" - version: 0.4.3 date: '2018-02-03' added: - "`Dry::Core::DescendantsTracker` which is a maintained version of the [`descendants_tracker`](https://github.com/dkubb/descendants_tracker) gem (flash-gordon)" - version: 0.4.2 date: '2017-12-16' fixed: - Class attributes now support private setters/getters (flash-gordon) - version: 0.4.1 date: '2017-11-04' changed: - Improved error message on invalid attribute value (GustavoCaso) - version: 0.4.0 date: '2017-11-02' added: - |- Added the `:type` option to class attributes, you can now restrict attribute values with a type. You can either use plain ruby types (`Integer`, `String`, etc) or `dry-types` (GustavoCaso) ```ruby class Foo extend Dry::Core::ClassAttributes defines :ruby_attr, type: Integer defines :dry_attr, type: Dry::Types['strict.int'] end ``` - version: 0.3.4 date: '2017-09-29' fixed: - "`Deprecations` output is set to `$stderr` by default now (solnic)" - version: 0.3.3 date: '2017-08-31' fixed: - The Deprecations module now shows the right caller line (flash-gordon) - version: 0.3.2 date: '2017-08-31' added: - Accept an existing logger object in `Dry::Core::Deprecations.set_logger!` (flash-gordon) - version: 0.3.1 date: '2017-05-27' added: - Support for building classes within an existing namespace (flash-gordon) - version: 0.3.0 date: '2017-05-05' changed: - Class attributes are initialized _before_ running the `inherited` hook. It's slightly more convenient behavior and it's very unlikely anyone will be affected by this, but technically this is a breaking change (flash-gordon) - version: 0.2.4 date: '2017-01-26' fixed: - Do not require deprecated method to be defined (flash-gordon) - version: 0.2.3 date: '2016-12-30' fixed: - Fix warnings on using uninitialized class attributes (flash-gordon) - version: 0.2.2 date: '2016-12-30' added: - "`ClassAttributes` which provides `defines` method for defining get-or-set methods (flash-gordon)" - version: 0.2.1 date: '2016-11-18' added: - "`Constants` are now available in nested scopes (flash-gordon)" - version: 0.2.0 date: '2016-11-01' - version: 0.1.0 date: '2016-09-17' summary: Initial release dry-core-0.7.1/docsite/000077500000000000000000000000001407232415400146775ustar00rootroot00000000000000dry-core-0.7.1/docsite/source/000077500000000000000000000000001407232415400161775ustar00rootroot00000000000000dry-core-0.7.1/docsite/source/cache.html.md000066400000000000000000000011561407232415400205320ustar00rootroot00000000000000--- title: Cache layout: gem-single name: dry-core --- Allows you to cache call results that are solely determined by arguments. ```ruby require 'dry/core/cache' class Foo extend Dry::Core::Cache attr_reader :source def initialize(source) @source = source end def heavy_computation(arg1, arg2) fetch_or_store(source, arg1, arg2) { source ^ arg1 ^ arg2 } end end ``` ### Note Beware Proc instance hashes are not equal, i.e. `-> { 1 }.hash != -> { 1 }.hash`. This means you shouldn't pass Procs in args unless you're sure they are always the same instances, otherwise you introduce a memory leak dry-core-0.7.1/docsite/source/classes.html.md000066400000000000000000000004461407232415400211250ustar00rootroot00000000000000--- title: Working with Classes layout: gem-single name: dry-core sections: - class-attributes - class-builder --- You can enhance your classes using the [Class Attributes](docs::classes/class-attributes) or eliminate extra boilerplate using the [Class Builder](docs::classes/class-builder) dry-core-0.7.1/docsite/source/classes/000077500000000000000000000000001407232415400176345ustar00rootroot00000000000000dry-core-0.7.1/docsite/source/classes/class-attributes.html.md000066400000000000000000000013131407232415400244100ustar00rootroot00000000000000--- title: Class Attributes layout: gem-single name: dry-core --- ```ruby require 'dry/core/class_attributes' class ExtraClass extend Dry::Core::ClassAttributes defines :hello hello 'world' end # example with inheritance and type checking # setting up an invalid value will raise Dry::Core::InvalidClassAttributeValue class MyClass extend Dry::Core::ClassAttributes defines :one, :two, type: Integer one 1 two 2 end class OtherClass < MyClass two 3 end MyClass.one # => 1 MyClass.two # => 2 OtherClass.one # => 1 OtherClass.two # => 3 # example type checking with dry-types class Foo extend Dry::Core::ClassAttributes defines :one, :two, type: Dry::Types['strict.integer'] end ``` dry-core-0.7.1/docsite/source/classes/class-builder.html.md000066400000000000000000000003251407232415400236520ustar00rootroot00000000000000--- title: Class Builder layout: gem-single name: dry-core --- ```ruby require 'dry/core/class_builder' builder = Dry::Core::ClassBuilder.new(name: 'MyClass') klass = builder.call klass.name # => "MyClass" ``` dry-core-0.7.1/docsite/source/constants.html.md000066400000000000000000000005051407232415400215000ustar00rootroot00000000000000--- title: Constants layout: gem-single name: dry-core --- A list of constants you can use to avoid memory allocations or identity checks. * `EMPTY_ARRAY` * `EMPTY_HASH` * `EMPTY_OPTS` * `EMPTY_SET` * `EMPTY_STRING` * `Undefined` - A special value you can use as a default to know if no arguments were passed to you method dry-core-0.7.1/docsite/source/deprecations.html.md000066400000000000000000000015201407232415400221420ustar00rootroot00000000000000--- title: Deprecations layout: gem-single name: dry-core --- To deprecate ruby methods you need to extend the `Dry::Core::Deprecations` module with a tag that will be displayed in the output. For example: ```ruby require 'dry/core/deprecations' class Foo extend Dry::Core::Deprecations[:tag] def self.old_class_api; end def self.new_class_api; end deprecate_class_method :old_class_api, :new_class_api def old_api; end def new_api; end deprecate :old_api, :new_api end Foo.old_class_api # => [tag] Foo.old_class_api is deprecated and will be removed in the next major version # => Please use Foo.new_class_api instead. # => file.rb:9:in `' Foo.new.old_api # => [tag] Foo#old_api is deprecated and will be removed in the next major version # => Please use Foo#new_api instead. # => file.rb:14:in `' ``` dry-core-0.7.1/docsite/source/extensions.html.md000066400000000000000000000005331407232415400216640ustar00rootroot00000000000000--- title: Extensions layout: gem-single name: dry-core --- Define extensions that can be later enabled by the user. ```ruby require 'dry/core/extensions' class Foo extend Dry::Core::Extensions register_extension(:bar) do def bar; :bar end end end Foo.new.bar # => NoMethodError Foo.load_extensions(:bar) Foo.new.bar # => :bar ``` dry-core-0.7.1/docsite/source/index.html.md000066400000000000000000000013101407232415400205660ustar00rootroot00000000000000--- title: Introduction description: A toolset of small support modules used throughout the @dry-rb & @rom-rb ecosystems layout: gem-single order: 5 type: gem name: dry-core sections: - cache - constants - classes - deprecations - extensions --- `dry-core` is a simple toolset that can be used in many places. ## Features - [Cache](docs::cache) - allows you to cache call results that are solely determined by arguments. - [Class Attributes](docs::classes/class-attributes) - [Class Builder](docs::classes/class-builder) - [Constants](docs::constants) - a list of constants you can use to avoid memory allocations or identity checks. - [Deprecations](docs::deprecations) - [Extensions](docs::extensions) dry-core-0.7.1/dry-core.gemspec000066400000000000000000000026131407232415400163400ustar00rootroot00000000000000# frozen_string_literal: true # this file is synced from dry-rb/template-gem project lib = File.expand_path("lib", __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require "dry/core/version" Gem::Specification.new do |spec| spec.name = "dry-core" spec.authors = ["Nikita Shilnikov"] spec.email = ["fg@flashgordon.ru"] spec.license = "MIT" spec.version = Dry::Core::VERSION.dup spec.summary = "A toolset of small support modules used throughout the dry-rb ecosystem" spec.description = spec.summary spec.homepage = "https://dry-rb.org/gems/dry-core" spec.files = Dir["CHANGELOG.md", "LICENSE", "README.md", "dry-core.gemspec", "lib/**/*"] spec.bindir = "bin" spec.executables = [] spec.require_paths = ["lib"] spec.metadata["allowed_push_host"] = "https://rubygems.org" spec.metadata["changelog_uri"] = "https://github.com/dry-rb/dry-core/blob/master/CHANGELOG.md" spec.metadata["source_code_uri"] = "https://github.com/dry-rb/dry-core" spec.metadata["bug_tracker_uri"] = "https://github.com/dry-rb/dry-core/issues" spec.required_ruby_version = ">= 2.6.0" # to update dependencies edit project.yml spec.add_runtime_dependency "concurrent-ruby", "~> 1.0" spec.add_development_dependency "bundler" spec.add_development_dependency "rake" spec.add_development_dependency "rspec" end dry-core-0.7.1/lib/000077500000000000000000000000001407232415400140135ustar00rootroot00000000000000dry-core-0.7.1/lib/dry-core.rb000066400000000000000000000000621407232415400160620ustar00rootroot00000000000000# frozen_string_literal: true require "dry/core" dry-core-0.7.1/lib/dry/000077500000000000000000000000001407232415400146115ustar00rootroot00000000000000dry-core-0.7.1/lib/dry/core.rb000066400000000000000000000001641407232415400160670ustar00rootroot00000000000000# frozen_string_literal: true require "dry/core/version" # :nodoc: module Dry # :nodoc: module Core end end dry-core-0.7.1/lib/dry/core/000077500000000000000000000000001407232415400155415ustar00rootroot00000000000000dry-core-0.7.1/lib/dry/core/cache.rb000066400000000000000000000035021407232415400171310ustar00rootroot00000000000000# frozen_string_literal: true require "concurrent/map" module Dry module Core # Allows you to cache call results that are solely determined by arguments. # # @example # require 'dry/core/cache' # # class Foo # extend Dry::Core::Cache # # def heavy_computation(arg1, arg2) # fetch_or_store(arg1, arg2) { arg1 ^ arg2 } # end # end # # @api public module Cache # @api private def self.extended(klass) super klass.include(Methods) klass.instance_variable_set(:@__cache__, Concurrent::Map.new) end # @api private def inherited(klass) super klass.instance_variable_set(:@__cache__, cache) end # @api private def cache @__cache__ end # Caches a result of the block evaluation # # @param [Array] args List of hashable objects # @yield An arbitrary block # # @note beware Proc instance hashes are not equal, i.e. -> { 1 }.hash != -> { 1 }.hash, # this means you shouldn't pass Procs in args unless you're sure # they are always the same instances, otherwise you introduce a memory leak # # @return [Object] block's return value (cached for subsequent calls with # the same argument values) def fetch_or_store(*args, &block) cache.fetch_or_store(args.hash, &block) end # Instance methods module Methods # Delegates call to the class-level method # # @param [Array] args List of hashable objects # @yield An arbitrary block # # @return [Object] block's return value def fetch_or_store(*args, &block) self.class.fetch_or_store(*args, &block) end end end end end dry-core-0.7.1/lib/dry/core/class_attributes.rb000066400000000000000000000051221407232415400214410ustar00rootroot00000000000000# frozen_string_literal: true require "dry/core/constants" require "dry/core/errors" module Dry module Core # Internal support module for class-level settings # # @api public module ClassAttributes include Constants # Specify what attributes a class will use # # @example # class ExtraClass # extend Dry::Core::ClassAttributes # # defines :hello # # hello 'world' # end # # @example with inheritance and type checking # # class MyClass # extend Dry::Core::ClassAttributes # # defines :one, :two, type: Integer # # one 1 # two 2 # end # # class OtherClass < MyClass # two 3 # end # # MyClass.one # => 1 # MyClass.two # => 2 # # OtherClass.one # => 1 # OtherClass.two # => 3 # # @example with dry-types # # class Foo # extend Dry::Core::ClassAttributes # # defines :one, :two, type: Dry::Types['strict.int'] # end # # @example with coercion using Proc # # class Bar # extend Dry::Core::ClassAttributes # # defines :one, coerce: proc { |value| value.to_s } # end # # @example with coercion using dry-types # # class Bar # extend Dry::Core::ClassAttributes # # defines :one, coerce: Dry::Types['coercible.string'] # end # def defines(*args, type: ::Object, coerce: IDENTITY) # rubocop:disable Metrics/PerceivedComplexity unless coerce.respond_to?(:call) raise ::ArgumentError, "Non-callable coerce option: #{coerce.inspect}" end mod = ::Module.new do args.each do |name| ivar = :"@#{name}" define_method(name) do |value = Undefined| if Undefined.equal?(value) if instance_variable_defined?(ivar) instance_variable_get(ivar) else # rubocop:disable Style/EmptyElse nil end elsif type === value # rubocop:disable Style/CaseEquality instance_variable_set(ivar, coerce.call(value)) else raise InvalidClassAttributeValue.new(name, value) end end end define_method(:inherited) do |klass| args.each { |name| klass.send(name, send(name)) } super(klass) end end extend(mod) end end end end dry-core-0.7.1/lib/dry/core/class_builder.rb000066400000000000000000000047041407232415400207060ustar00rootroot00000000000000# frozen_string_literal: true module Dry module Core # Class for generating more classes class ClassBuilder ParentClassMismatch = Class.new(TypeError) attr_reader :name, :parent, :namespace def initialize(name:, parent: nil, namespace: nil) @name = name @namespace = namespace @parent = parent || Object end # Generate a class based on options # # @example Create anonymous class # builder = Dry::Core::ClassBuilder.new(name: 'MyClass') # # klass = builder.call # klass.name # => "MyClass" # # @example Create named class # builder = Dry::Core::ClassBuilder.new(name: 'User', namespace: Entities) # # klass = builder.call # klass.name # => "Entities::User" # klass.superclass.name # => "Entities::User" # Entities::User # => "Entities::User" # klass.superclass == Entities::User # => true # # @return [Class] def call klass = if namespace create_named else create_anonymous end yield(klass) if block_given? klass end private # @api private def create_anonymous klass = Class.new(parent) name = self.name klass.singleton_class.class_eval do define_method(:name) { name } alias_method :inspect, :name alias_method :to_s, :name end klass end # @api private def create_named name = self.name base = create_base(namespace, name, parent) klass = Class.new(base) namespace.module_eval do remove_const(name) const_set(name, klass) remove_const(name) const_set(name, base) end klass end # @api private def create_base(namespace, name, parent) begin namespace.const_get(name) rescue NameError # rubocop:disable Lint/SuppressedException end if namespace.const_defined?(name, false) existing = namespace.const_get(name) unless existing <= parent raise ParentClassMismatch, "#{existing.name} must be a subclass of #{parent.name}" end existing else klass = Class.new(parent || Object) namespace.const_set(name, klass) klass end end end end end dry-core-0.7.1/lib/dry/core/constants.rb000066400000000000000000000052571407232415400201130ustar00rootroot00000000000000# frozen_string_literal: true require "set" module Dry module Core # A list of constants you can use to avoid memory allocations or identity checks. # # @example Just include this module to your class or module # class Foo # include Dry::Core::Constants # def call(value = EMPTY_ARRAY) # value.map(&:to_s) # end # end # # @api public module Constants # An empty array EMPTY_ARRAY = [].freeze # An empty hash EMPTY_HASH = {}.freeze # An empty list of options EMPTY_OPTS = {}.freeze # An empty set EMPTY_SET = ::Set.new.freeze # An empty string EMPTY_STRING = "" # Identity function IDENTITY = (-> x { x }).freeze # A special value you can use as a default to know if no arguments # were passed to the method # # @example # def method(value = Undefined) # if Undefined.equal?(value) # puts 'no args' # else # puts value # end # end Undefined = Object.new.tap do |undefined| # @api private Self = -> { Undefined } # rubocop:disable Lint/ConstantDefinitionInBlock # @api public def undefined.to_s "Undefined" end # @api public def undefined.inspect "Undefined" end # Pick a value, if the first argument is not Undefined, return it back, # otherwise return the second arg or yield the block. # # @example # def method(val = Undefined) # 1 + Undefined.default(val, 2) # end # def undefined.default(x, y = self) # rubocop:disable Naming/MethodParameterName if equal?(x) if equal?(y) yield else y end else x end end # Map a non-undefined value # # @example # def add_five(val = Undefined) # Undefined.map(val) { |x| x + 5 } # end # def undefined.map(value) if equal?(value) self else yield(value) end end # @api public def undefined.dup self end # @api public def undefined.clone self end # @api public def undefined.coalesce(*args) args.find(Self) { |x| !equal?(x) } end end.freeze def self.included(base) super constants.each do |const_name| base.const_set(const_name, const_get(const_name)) end end end end end dry-core-0.7.1/lib/dry/core/deprecations.rb000066400000000000000000000157031407232415400205540ustar00rootroot00000000000000# frozen_string_literal: true require "logger" module Dry module Core # An extension for issueing warnings on using deprecated methods. # # @example # # class Foo # def self.old_class_api; end # def self.new_class_api; end # # deprecate_class_method :old_class_api, :new_class_api # # def old_api; end # def new_api; end # # deprecate :old_api, :new_api, message: "old_api is no-no" # end # # @example You also can use this module for your custom messages # # Dry::Core::Deprecations.announce("Foo", "use bar instead") # Dry::Core::Deprecations.warn("Baz is going to be removed soon") # # @api public module Deprecations STACK = -> { caller.find { |l| l !~ %r{(lib/dry/core)|(gems)} } } class << self # Prints a warning # # @param [String] msg Warning string # @param [String] tag Tag to help identify the source of the warning. # Defaults to "deprecated" # @param [Integer] Caller frame to add to the message def warn(msg, tag: nil, uplevel: nil) caller_info = uplevel.nil? ? nil : "#{caller_locations(uplevel + 2, 1)[0]} " tag = "[#{tag || "deprecated"}] " hint = msg.gsub(/^\s+/, "") logger.warn("#{caller_info}#{tag}#{hint}") end # Wraps arguments with a standard message format and prints a warning # # @param [Object] name what is deprecated # @param [String] msg additional message usually containing upgrade instructions def announce(name, msg, tag: nil, uplevel: nil) # Bump the uplevel (if provided) by one to account for the uplevel calculation # taking place one frame deeper in `.warn` uplevel += 1 if uplevel warn(deprecation_message(name, msg), tag: tag, uplevel: uplevel) end # @api private def deprecation_message(name, msg) <<-MSG #{name} is deprecated and will be removed in the next major version #{msg} MSG end # @api private def deprecated_name_message(old, new = nil, msg = nil) if new deprecation_message(old, <<-MSG) Please use #{new} instead. #{msg} MSG else deprecation_message(old, msg) end end # Returns the logger used for printing warnings. # You can provide your own with .set_logger! # # @param [IO] output output stream # # @return [Logger] def logger(output = $stderr) if defined?(@logger) @logger else set_logger!(output) end end # Sets a custom logger. This is a global setting. # # @overload set_logger!(output) # @param [IO] output Stream for messages # # @overload set_logger! # Stream messages to stdout # # @overload set_logger!(logger) # @param [#warn] logger # # @api public def set_logger!(output = $stderr) # rubocop:disable Naming/AccessorMethodName if output.respond_to?(:warn) @logger = output else @logger = Logger.new(output).tap do |logger| logger.formatter = proc { |_, _, _, msg| "#{msg}\n" } end end end def [](tag) Tagged.new(tag) end end # @api private class Tagged < ::Module def initialize(tag) super() @tag = tag end def extended(base) base.extend Interface base.deprecation_tag @tag end end module Interface # Sets/gets deprecation tag # # @option [String,Symbol] tag tag def deprecation_tag(tag = nil) if defined?(@deprecation_tag) @deprecation_tag else @deprecation_tag = tag end end # Issue a tagged warning message # # @param [String] msg warning message def warn(msg) Deprecations.warn(msg, tag: deprecation_tag) end # Mark instance method as deprecated # # @param [Symbol] old_name deprecated method # @param [Symbol] new_name replacement (not required) # @option [String] message optional deprecation message def deprecate(old_name, new_name = nil, message: nil) full_msg = Deprecations.deprecated_name_message( "#{name}##{old_name}", new_name ? "#{name}##{new_name}" : nil, message ) mod = self if new_name undef_method old_name if method_defined?(old_name) define_method(old_name) do |*args, &block| mod.warn("#{full_msg}\n#{STACK.()}") __send__(new_name, *args, &block) end else aliased_name = :"#{old_name}_without_deprecation" alias_method aliased_name, old_name private aliased_name undef_method old_name define_method(old_name) do |*args, &block| mod.warn("#{full_msg}\n#{STACK.()}") __send__(aliased_name, *args, &block) end end end # Mark class-level method as deprecated # # @param [Symbol] old_name deprecated method # @param [Symbol] new_name replacement (not required) # @option [String] message optional deprecation message def deprecate_class_method(old_name, new_name = nil, message: nil) full_msg = Deprecations.deprecated_name_message( "#{name}.#{old_name}", new_name ? "#{name}.#{new_name}" : nil, message ) meth = new_name ? method(new_name) : method(old_name) singleton_class.instance_exec do undef_method old_name if method_defined?(old_name) define_method(old_name) do |*args, &block| warn("#{full_msg}\n#{STACK.()}") meth.call(*args, &block) end end end # Mark a constant as deprecated # @param [Symbol] constant_name constant name to be deprecated # @option [String] message optional deprecation message def deprecate_constant(constant_name, message: nil) value = const_get(constant_name) remove_const(constant_name) full_msg = Deprecations.deprecated_name_message( "#{name}::#{constant_name}", message ) mod = Module.new do define_method(:const_missing) do |missing| if missing == constant_name warn("#{full_msg}\n#{STACK.()}") value else super(missing) end end end extend(mod) end end end end end dry-core-0.7.1/lib/dry/core/descendants_tracker.rb000066400000000000000000000027761407232415400221100ustar00rootroot00000000000000# frozen_string_literal: true require "concurrent/array" module Dry module Core # An implementation of descendants tracker, heavily inspired # by the descendants_tracker gem. # # @example # # class Base # extend Dry::Core::DescendantsTracker # end # # class A < Base # end # # class B < Base # end # # class C < A # end # # Base.descendants # => [C, B, A] # A.descendants # => [C] # B.descendants # => [] # module DescendantsTracker class << self # @api private def setup(target) target.instance_variable_set(:@descendants, Concurrent::Array.new) end private # @api private def extended(base) super DescendantsTracker.setup(base) end end # Return the descendants of this class # # @example # descendants = Parent.descendants # # @return [Array] # # @api public attr_reader :descendants protected # @api private def add_descendant(descendant) ancestor = superclass if ancestor.respond_to?(:add_descendant, true) ancestor.add_descendant(descendant) end descendants.unshift(descendant) end private # @api private def inherited(descendant) super DescendantsTracker.setup(descendant) add_descendant(descendant) end end end end dry-core-0.7.1/lib/dry/core/equalizer.rb000066400000000000000000000077531407232415400201030ustar00rootroot00000000000000# frozen_string_literal: true module Dry # Build an equalizer module for the inclusion in other class # # ## Credits # # Equalizer has been originally imported from the equalizer gem created by Dan Kubb # # @api public def self.Equalizer(*keys, **options) Dry::Core::Equalizer.new(*keys, **options) end module Core # Define equality, equivalence and inspection methods class Equalizer < ::Module # Initialize an Equalizer with the given keys # # Will use the keys with which it is initialized to define #cmp?, # #hash, and #inspect # # @param [Array] keys # @param [Hash] options # @option options [Boolean] :inspect whether to define #inspect method # @option options [Boolean] :immutable whether to memoize #hash method # # @return [undefined] # # @api private def initialize(*keys, **options) super() @keys = keys.uniq define_methods(**options) freeze end private # Hook called when module is included # # @param [Module] descendant # the module or class including Equalizer # # @return [self] # # @api private def included(descendant) super descendant.include Methods end # Define the equalizer methods based on #keys # # @param [Boolean] inspect whether to define #inspect method # @param [Boolean] immutable whether to memoize #hash method # # @return [undefined] # # @api private def define_methods(inspect: true, immutable: false) define_cmp_method define_hash_method(immutable: immutable) define_inspect_method if inspect end # Define an #cmp? method based on the instance's values identified by #keys # # @return [undefined] # # @api private def define_cmp_method keys = @keys define_method(:cmp?) do |comparator, other| keys.all? do |key| __send__(key).public_send(comparator, other.__send__(key)) end end private :cmp? end # Define a #hash method based on the instance's values identified by #keys # # @return [undefined] # # @api private def define_hash_method(immutable:) calculate_hash = ->(obj) { @keys.map { |key| obj.__send__(key) }.push(obj.class).hash } if immutable define_method(:hash) do @__hash__ ||= calculate_hash.call(self) end define_method(:freeze) do hash super() end else define_method(:hash) do calculate_hash.call(self) end end end # Define an inspect method that reports the values of the instance's keys # # @return [undefined] # # @api private def define_inspect_method keys = @keys define_method(:inspect) do klass = self.class name = klass.name || klass.inspect "#<#{name}#{keys.map { |key| " #{key}=#{__send__(key).inspect}" }.join}>" end end # The comparison methods module Methods # Compare the object with other object for equality # # @example # object.eql?(other) # => true or false # # @param [Object] other # the other object to compare with # # @return [Boolean] # # @api public def eql?(other) instance_of?(other.class) && cmp?(__method__, other) end # Compare the object with other object for equivalency # # @example # object == other # => true or false # # @param [Object] other # the other object to compare with # # @return [Boolean] # # @api public def ==(other) other.is_a?(self.class) && cmp?(__method__, other) end end end end end dry-core-0.7.1/lib/dry/core/errors.rb000066400000000000000000000004261407232415400174040ustar00rootroot00000000000000# frozen_string_literal: true module Dry module Core class InvalidClassAttributeValue < StandardError def initialize(name, value) super( "Value #{value.inspect} is invalid for class attribute #{name.inspect}" ) end end end end dry-core-0.7.1/lib/dry/core/extensions.rb000066400000000000000000000032611407232415400202670ustar00rootroot00000000000000# frozen_string_literal: true require "set" module Dry module Core # Define extensions that can be later enabled by the user. # # @example # # class Foo # extend Dry::Core::Extensions # # register_extension(:bar) do # def bar; :bar end # end # end # # Foo.new.bar # => NoMethodError # Foo.load_extensions(:bar) # Foo.new.bar # => :bar # module Extensions # @api private def self.extended(obj) super obj.instance_variable_set(:@__available_extensions__, {}) obj.instance_variable_set(:@__loaded_extensions__, Set.new) end # Register an extension # # @param [Symbol] name extension name # @yield extension block. This block guaranteed not to be called more than once def register_extension(name, &block) @__available_extensions__[name] = block end # Whether an extension is available # # @param [Symbol] name extension name # @return [Boolean] Extension availability def available_extension?(name) @__available_extensions__.key?(name) end # Enables specified extensions. Already enabled extensions remain untouched # # @param [Array] extensions list of extension names def load_extensions(*extensions) extensions.each do |ext| block = @__available_extensions__.fetch(ext) do raise ArgumentError, "Unknown extension: #{ext.inspect}" end unless @__loaded_extensions__.include?(ext) block.call @__loaded_extensions__ << ext end end end end end end dry-core-0.7.1/lib/dry/core/inflector.rb000066400000000000000000000073541407232415400200640ustar00rootroot00000000000000# frozen_string_literal: true module Dry module Core # Helper module providing thin interface around an inflection backend. module Inflector # List of supported backends BACKENDS = { activesupport: [ "active_support/inflector", proc { ::ActiveSupport::Inflector } ], dry_inflector: [ "dry/inflector", proc { Dry::Inflector.new } ], inflecto: [ "inflecto", proc { ::Inflecto } ] }.freeze # Try to activate a backend # # @api private def self.realize_backend(path, backend_factory) require path rescue LoadError nil else backend_factory.call end # Set up first available backend # # @api private def self.detect_backend BACKENDS.inject(nil) do |backend, (_, (path, factory))| backend || realize_backend(path, factory) end || raise(LoadError, "No inflector library could be found: "\ "please install either the `inflecto` or `activesupport` gem.") end # Set preferred backend # # @param [Symbol] name backend name (:activesupport or :inflecto) def self.select_backend(name = nil) if name && !BACKENDS.key?(name) raise NameError, "Invalid inflector library selection: '#{name}'" end @inflector = name ? realize_backend(*BACKENDS[name]) : detect_backend end # Inflector accessor. Lazily initializes a backend # # @api private def self.inflector defined?(@inflector) ? @inflector : select_backend end # Transform string to camel case # # @example # Dry::Core::Inflector.camelize('foo_bar') # => 'FooBar' # # @param [String] input input string # @return Transformed string def self.camelize(input) inflector.camelize(input) end # Transform string to snake case # # @example # Dry::Core::Inflector.underscore('FooBar') # => 'foo_bar' # # @param [String] input input string # @return Transformed string def self.underscore(input) inflector.underscore(input) end # Get a singlular form of a word # # @example # Dry::Core::Inflector.singularize('chars') # => 'char' # # @param [String] input input string # @return Transformed string def self.singularize(input) inflector.singularize(input) end # Get a plural form of a word # # @example # Dry::Core::Inflector.pluralize('string') # => 'strings' # # @param [String] input input string # @return Transformed string def self.pluralize(input) inflector.pluralize(input) end # Remove namespaces from a constant name # # @example # Dry::Core::Inflector.demodulize('Deeply::Nested::Name') # => 'Name' # # @param [String] input input string # @return Unnested constant name def self.demodulize(input) inflector.demodulize(input) end # Get a constant value by its name # # @example # Dry::Core::Inflector.constantize('Foo::Bar') # => Foo::Bar # # @param [String] input input constant name # @return Constant value def self.constantize(input) inflector.constantize(input) end # Transform a file path to a constant name # # @example # Dry::Core::Inflector.classify('foo/bar') # => 'Foo::Bar' # # @param [String] input input string # @return Constant name def self.classify(input) inflector.classify(input) end end end end dry-core-0.7.1/lib/dry/core/memoizable.rb000066400000000000000000000135001407232415400202110ustar00rootroot00000000000000# frozen_string_literal: true require "dry/core/deprecations" module Dry module Core module Memoizable MEMOIZED_HASH = {}.freeze PARAM_PLACEHOLDERS = %i[* ** &].freeze module ClassInterface module Base def memoize(*names) prepend(Memoizer.new(self, names)) end end module BasicObject include Base def new(*) obj = super obj.instance_eval { @__memoized__ = MEMOIZED_HASH.dup } obj end end module Object include Base def new(*) obj = super obj.instance_variable_set(:@__memoized__, MEMOIZED_HASH.dup) obj end if respond_to?(:ruby2_keywords, true) ruby2_keywords(:new) end end end def self.included(klass) super if klass <= Object klass.extend(ClassInterface::Object) else klass.extend(ClassInterface::BasicObject) end end # @api private class Memoizer < ::Module # @api private def initialize(klass, names) super() names.each do |name| define_memoizable( method: klass.instance_method(name) ) end end private # @api private def define_memoizable(method:) # rubocop:disable Metrics/AbcSize parameters = method.parameters if parameters.empty? key = method.name.hash module_eval(<<~RUBY, __FILE__, __LINE__ + 1) def #{method.name} # def slow_fetch if @__memoized__.key?(#{key}) # if @__memoized__.key?(12345678) @__memoized__[#{key}] # @__memoized__[12345678] else # else @__memoized__[#{key}] = super # @__memoized__[12345678] = super end # end end # end RUBY else mapping = parameters.to_h { |k, v = nil| [k, v] } params, binds = declaration(parameters, mapping) last_param = parameters.last if last_param[0].eql?(:block) && !last_param[1].eql?(:&) Deprecations.warn(<<~WARN) Memoization for block-accepting methods isn't safe. Every call creates a new block instance bloating cached results. In the future, blocks will still be allowed but won't participate in cache key calculation. WARN end m = module_eval(<<~RUBY, __FILE__, __LINE__ + 1) def #{method.name}(#{params.join(", ")}) # def slow_calc(arg1, arg2, arg3) key = [:"#{method.name}", #{binds.join(", ")}].hash # [:slow_calc, arg1, arg2, arg3].hash # if @__memoized__.key?(key) # if @__memoized__.key?(key) @__memoized__[key] # @__memoized__[key] else # else @__memoized__[key] = super # @__memoized__[key] = super end # end end # end RUBY if respond_to?(:ruby2_keywords, true) && mapping.key?(:reyrest) ruby2_keywords(method.name) end m end end # @api private def declaration(definition, lookup) params = [] binds = [] defined = {} definition.each do |type, name| mapped_type = map_bind_type(type, name, lookup, defined) do raise ::NotImplementedError, "type: #{type}, name: #{name}" end if mapped_type defined[mapped_type] = true bind = name_from_param(name) || make_bind_name(binds.size) binds << bind params << param(bind, mapped_type) end end [params, binds] end # @api private def name_from_param(name) if PARAM_PLACEHOLDERS.include?(name) nil else name end end # @api private def make_bind_name(idx) :"__lv_#{idx}__" end # @api private def map_bind_type(type, name, original_params, defined_types) # rubocop:disable Metrics/PerceivedComplexity case type when :req :reqular when :rest, :keyreq, :keyrest type when :block if name.eql?(:&) # most likely this is a case of delegation # rather than actual block nil else type end when :opt if original_params.key?(:rest) || defined_types[:rest] nil else :rest end when :key if original_params.key?(:keyrest) || defined_types[:keyrest] nil else :keyrest end else yield end end # @api private def param(name, type) case type when :reqular name when :rest "*#{name}" when :keyreq "#{name}:" when :keyrest "**#{name}" when :block "&#{name}" end end end end end end dry-core-0.7.1/lib/dry/core/version.rb000066400000000000000000000001301407232415400175450ustar00rootroot00000000000000# frozen_string_literal: true module Dry module Core VERSION = "0.7.1" end end dry-core-0.7.1/project.yml000066400000000000000000000005231407232415400154360ustar00rootroot00000000000000name: dry-core codacy_id: 40946292b9094624beec604a149a6023 gemspec: authors: ["Nikita Shilnikov"] email: ["fg@flashgordon.ru"] summary: "A toolset of small support modules used throughout the dry-rb ecosystem" development_dependencies: - bundler - rake - rspec runtime_dependencies: - [concurrent-ruby, "~> 1.0"] dry-core-0.7.1/spec/000077500000000000000000000000001407232415400141775ustar00rootroot00000000000000dry-core-0.7.1/spec/dry/000077500000000000000000000000001407232415400147755ustar00rootroot00000000000000dry-core-0.7.1/spec/dry/core/000077500000000000000000000000001407232415400157255ustar00rootroot00000000000000dry-core-0.7.1/spec/dry/core/cache_spec.rb000066400000000000000000000031401407232415400203250ustar00rootroot00000000000000# frozen_string_literal: true require "dry/core/cache" RSpec.describe Dry::Core::Cache do shared_examples_for "class with cache" do describe "#fetch_or_store" do it "stores and fetches a value" do args = [1, 2, 3] value = "foo" expect(klass.fetch_or_store(*args) { value }).to be(value) expect(klass.fetch_or_store(*args)).to be(value) object = klass.new expect(object.fetch_or_store(*args) { value }).to be(value) expect(object.fetch_or_store(*args)).to be(value) end end end let(:base_class) do Class.new do extend Dry::Core::Cache end end let(:child_class) do Class.new(base_class) end it_behaves_like "class with cache" do let(:klass) { base_class } end context "inheritance" do it_behaves_like "class with cache" do let(:klass) { child_class } end it "uses the same values in child and parent" do value = Object.new expect(base_class.fetch_or_store(1, 2) { value }).to be(value) expect(base_class.fetch_or_store(1, 2) { raise }).to be(value) expect(child_class.fetch_or_store(1, 2) { raise }).to be(value) expect(child_class.new.fetch_or_store(1, 2) { raise }).to be(value) end it "does not depend on fetch order" do value = Object.new expect(child_class.fetch_or_store(1, 2) { value }).to be(value) expect(child_class.fetch_or_store(1, 2) { raise }).to be(value) expect(base_class.fetch_or_store(1, 2) { raise }).to be(value) expect(base_class.new.fetch_or_store(1, 2) { raise }).to be(value) end end end dry-core-0.7.1/spec/dry/core/class_attributes_spec.rb000066400000000000000000000102141407232415400226350ustar00rootroot00000000000000# frozen_string_literal: true require "dry/core/class_attributes" require "dry-types" RSpec.describe "Class Macros" do before do module Test class MyClass extend Dry::Core::ClassAttributes defines :one, :two, :three one 1 two 2 three 3 end class OtherClass < Test::MyClass two "two" three nil end end end it "defines accessor like methods on the class and subclasses" do %i[one two three].each do |method_name| expect(Test::MyClass).to respond_to(method_name) expect(Test::OtherClass).to respond_to(method_name) end end it "allows storage of values on the class" do expect(Test::MyClass.one).to eq(1) expect(Test::MyClass.two).to eq(2) expect(Test::MyClass.three).to eq(3) end it "allows overwriting of inherited values with nil" do expect(Test::OtherClass.three).to eq(nil) end context "type option" do let(:klass) do module Test class NewClass extend Dry::Core::ClassAttributes end end Test::NewClass end context "using classes" do before do klass.defines :one, type: String end it "allows to pass type option" do klass.one "1" expect(Test::NewClass.one).to eq "1" end it "raises InvalidClassAttributeValue when invalid value is pass" do expect { klass.one 1 }.to raise_error( Dry::Core::InvalidClassAttributeValue, "Value 1 is invalid for class attribute :one" ) end end context "using dry-types" do before do module Test class Types include Dry::Types() end end klass.defines :one, type: Test::Types::String end it "allows to pass type option" do klass.one "1" expect(Test::NewClass.one).to eq "1" end it "raises InvalidClassAttributeValue when invalid value is pass" do expect { klass.one 1 }.to raise_error(Dry::Core::InvalidClassAttributeValue) end end end context "coerce option" do let(:klass) do module Test class NewClass extend Dry::Core::ClassAttributes end end Test::NewClass end context "using procs" do before do klass.defines :one, coerce: proc(&:to_s) end it "converts value" do klass.one 1 expect(Test::NewClass.one).to eq "1" end end context "using dry-types" do before do module Test class Types include Dry::Types() end end klass.defines :one, coerce: Test::Types::Coercible::String end it "converts value" do klass.one 1 expect(Test::NewClass.one).to eq "1" end end context "using non-callable coerce option" do it "raises InvalidCoerceOption" do expect { klass.defines :one, coerce: String }.to raise_error( ArgumentError, "Non-callable coerce option: String" ) end end end it "allows inheritance of values" do expect(Test::OtherClass.one).to eq(1) end it "allows overwriting of inherited values" do expect(Test::OtherClass.two).to eq("two") end it "copies values from the parent before running hooks" do subclass_value = nil module_with_hook = Module.new do define_method(:inherited) do |klass| super(klass) subclass_value = klass.one end end base_class = Class.new do extend Dry::Core::ClassAttributes extend module_with_hook defines :one one 1 end Class.new(base_class) expect(subclass_value).to be 1 end it "works with private setters/getters and inheritance" do base_class = Class.new do extend Dry::Core::ClassAttributes defines :one class << self; private :one; end one 1 end spec = self child = Class.new(base_class) do |chld| spec.instance_exec { expect(chld.send(:one)).to spec.eql(1) } one "one" end expect(child.send(:one)).to eql("one") end end dry-core-0.7.1/spec/dry/core/class_builder_spec.rb000066400000000000000000000072411407232415400221030ustar00rootroot00000000000000# frozen_string_literal: true require "dry/core/class_builder" RSpec.describe Dry::Core::ClassBuilder do subject(:builder) { described_class.new(**options) } let(:klass) { builder.call } describe "#call" do context "anonymous" do let(:options) do {name: "Test", parent: parent} end let(:parent) { Class.new } it "returns a class constant" do expect(klass).to be_instance_of(Class) end it "sets class name based on provided :name option" do expect(klass.name).to eql(options[:name]) end it "uses a parent class provided by :parent option" do expect(klass).to be < parent end it "defines to_s and inspect" do expect(klass.to_s).to eql(options[:name]) expect(klass.inspect).to eql(options[:name]) end it "yields created class" do klass = builder.call do |yielded_class| yielded_class.class_eval do def self.testing; end end end expect(klass).to respond_to(:testing) end end context "namespaced" do context "without parent" do let(:options) do {name: "User", namespace: Test} end it "creates a class within the given namespace" do expect(klass).to be_instance_of(Class) expect(klass.name).to eql("Test::User") expect(klass.superclass).to be(Test::User) end end context "with parent" do let(:parent) do Test::Parent = Class.new end let(:options) do {name: "User", parent: parent, namespace: Test} end it "creates a class with the given parent" do expect(klass).to be_instance_of(Class) expect(klass.name).to eql("Test::User") expect(klass.superclass).to be(Test::User) expect(klass.superclass.superclass).to be(Test::Parent) end end context "with mismatched parent class" do before do Test::InvalidParent = Class.new Test::User = Class.new(Test::InvalidParent) end let(:parent) do Test::Parent = Class.new end let(:options) do {name: "User", namespace: Test, parent: parent} end it "raises meaningful error on mismatched parent class" do expect { klass }.to raise_error( Dry::Core::ClassBuilder::ParentClassMismatch, "Test::User must be a subclass of Test::Parent" ) end end context "with parent inherited from object" do let(:parent) do Test::Parent = Class.new end let(:options) do {name: "File", namespace: Test, parent: parent} end it "does not search for parent class through inheritance" do expect(klass.name).to eql("Test::File") expect(klass.superclass).to be(Test::File) expect(Test::File).not_to be(::File) end end context "autoloaded constant" do before do Test.module_eval do autoload :Project, File.join(__dir__, "../../fixtures/project") end end after do Test.module_eval do remove_const(:Project) end end let(:parent) do Test::Parent = Class.new end let(:options) do {name: "Project", namespace: Test, parent: parent} end it "autoloads the specified class" do expect(klass.name).to eq("Test::Project") expect(klass.superclass).to be(Test::Project) expect(klass.instance_methods).to include(:to_model) end end end end end dry-core-0.7.1/spec/dry/core/constants_spec.rb000066400000000000000000000066431407232415400213110ustar00rootroot00000000000000# frozen_string_literal: true require "dry/core/constants" RSpec.describe Dry::Core::Constants do before do class ClassWithConstants include Dry::Core::Constants def empty_array EMPTY_ARRAY end def empty_hash EMPTY_HASH end def empty_set EMPTY_SET end def empty_string EMPTY_STRING end def empty_opts EMPTY_OPTS end def undefined Undefined end end end after do Object.send(:remove_const, :ClassWithConstants) end subject { ClassWithConstants.new } it "makes constants available in your class" do expect(subject.empty_array).to be Dry::Core::Constants::EMPTY_ARRAY expect(subject.empty_array).to eql([]) expect(subject.empty_hash).to be Dry::Core::Constants::EMPTY_HASH expect(subject.empty_hash).to eql({}) expect(subject.empty_set).to be Dry::Core::Constants::EMPTY_SET expect(subject.empty_set).to eql(Set.new) expect(subject.empty_string).to be Dry::Core::Constants::EMPTY_STRING expect(subject.empty_string).to eql("") expect(subject.empty_opts).to be Dry::Core::Constants::EMPTY_OPTS expect(subject.empty_opts).to eql({}) expect(subject.undefined).to be Dry::Core::Constants::Undefined end describe "nested" do before do class ClassWithConstants class Nested def empty_array EMPTY_ARRAY end end end end subject { ClassWithConstants::Nested.new } example "constants available in lexical scope" do expect(subject.empty_array).to be Dry::Core::Constants::EMPTY_ARRAY end end describe "Undefined" do subject(:undefined) { Dry::Core::Constants::Undefined } describe ".inspect" do it 'returns "Undefined"' do expect(subject.inspect).to eql("Undefined") end end describe ".to_s" do it 'returns "Undefined"' do expect(subject.to_s).to eql("Undefined") end end describe ".default" do it "returns the first arg if it's not Undefined" do expect(subject.default(:first, :second)).to eql(:first) end it "returns the second arg if the first one is Undefined" do expect(subject.default(subject, :second)).to eql(:second) end it "yields a block" do expect(subject.default(subject) { :second }).to eql(:second) end end describe ".map" do it "maps non-undefined value" do expect(subject.map("foo", &:to_sym)).to be(:foo) expect(subject.map(subject, &:to_sym)).to be(subject) end end describe ".dup" do subject { undefined.dup } it { is_expected.to be(undefined) } end describe ".clone" do subject { undefined.clone } it { is_expected.to be(undefined) } end describe ".coalesce" do it "returns first non-undefined value in a list" do expect(undefined.coalesce(1, 2)).to be(1) expect(undefined.coalesce(undefined, 1, 2)).to be(1) expect(undefined.coalesce(undefined, undefined, 1, 2)).to be(1) expect(undefined.coalesce(undefined, undefined)).to be(undefined) expect(undefined.coalesce(nil)).to be(nil) expect(undefined.coalesce(undefined, nil)).to be(nil) expect(undefined.coalesce(undefined, nil, false)).to be(nil) expect(undefined.coalesce(undefined, false, nil)).to be(false) end end end end dry-core-0.7.1/spec/dry/core/deprecations_spec.rb000066400000000000000000000124151407232415400217470ustar00rootroot00000000000000# frozen_string_literal: true require "dry/core/deprecations" require "tempfile" RSpec.describe Dry::Core::Deprecations do let(:log_file) do Tempfile.new("dry_deprecations") end before do Dry::Core::Deprecations.set_logger!(log_file) end let(:log_output) do log_file.close log_file.open.read end describe ".warn" do it "logs a warning message" do Dry::Core::Deprecations.warn("hello world") expect(log_output).to include("[deprecated] hello world") end it "logs a tagged message" do Dry::Core::Deprecations.warn("hello world", tag: :spec) expect(log_output).to include("[spec] hello world") end it "prints information about the caller frame if uplevel is given" do Dry::Core::Deprecations.warn("hello world", uplevel: 0) expect(log_output).to include("/rspec/") end end describe ".announce" do it "warns about a deprecated method" do Dry::Core::Deprecations.announce(:foo, "hello world", tag: :spec, uplevel: 0) expect(log_output).to include("[spec] foo is deprecated and will be removed") expect(log_output).to include("hello world") expect(log_output).to include("/rspec/") end end shared_examples_for "an entity with deprecated methods" do it "deprecates method that is to be removed" do res = subject.hello("world") expect(res).to eql("hello world") expect(log_output).to match(/\[spec\] Test(\.|#)hello is deprecated and will be removed/) expect(log_output).to include("is no more") end it "deprecates a method in favor of another" do res = subject.logging("foo") expect(res).to eql("log: foo") expect(log_output).to match(/\[spec\] Test(\.|#)logging is deprecated and will be removed/) end it "does not require deprecated method to be defined" do res = subject.missing("bar") expect(res).to eql("log: bar") expect(log_output).to match(/\[spec\] Test(\.|#)missing is deprecated and will be removed/) end end describe ".deprecate_class_method" do subject(:klass) do Class.new do extend Dry::Core::Deprecations[:spec] def self.name "Test" end def self.log(msg) "log: #{msg}" end def self.hello(word) "hello #{word}" end deprecate_class_method :hello, message: "is no more" def self.logging(msg) "logging: #{msg}" end deprecate_class_method :logging, :log deprecate_class_method :missing, :log end end it_behaves_like "an entity with deprecated methods" do subject { klass } end end describe ".deprecate" do subject(:klass) do Class.new do extend Dry::Core::Deprecations[:spec] def self.name "Test" end def log(msg) "log: #{msg}" end def hello(word) "hello #{word}" end deprecate :hello, message: "is no more" def logging(msg) "logging: #{msg}" end deprecate :logging, :log deprecate :missing, :log end end it_behaves_like "an entity with deprecated methods" do subject { klass.new } end end describe ".deprecate_constant" do before do module Test extend Dry::Core::Deprecations[:spec] Obsolete = :no_more deprecate_constant(:Obsolete) Deprecated = :fix deprecate_constant(:Deprecated, message: "Shiny New") end end it "deprecates a constant in favor of another" do expect(Test::Obsolete).to be(:no_more) expect(log_output).to match(/\[spec\] Test::Obsolete is deprecated and will be removed/) end it "can have an optional messaage" do expect(Test::Deprecated).to be(:fix) expect(log_output).to match(/Shiny New/) end end describe ".[]" do subject(:klass) do Class.new do extend Dry::Core::Deprecations[:spec] end end describe ".warn" do it "logs a tagged message" do klass.warn("hello") expect(log_output).to include("[spec] hello") end end end describe ".set_logger!" do let(:logger) do Class.new { attr_reader :messages def initialize @messages = [] end def warn(message) messages << message end }.new end it "accepts preconfigured logger" do Dry::Core::Deprecations.set_logger!(logger) Dry::Core::Deprecations.warn("Don't!") expect(logger.messages).to eql(%w([deprecated]\ Don't!)) end end describe ".logger" do let(:stderr) do Class.new { attr_reader :messages def initialize @messages = [] end def write(message) messages << message end def close messages.freeze end }.new end around do |ex| $stderr = stderr ex.run $stderr = STDERR end before do module Dry::Core::Deprecations remove_instance_variable :@logger end end let(:default_logger) { Dry::Core::Deprecations.logger } it "sets $stderr as a default stream" do default_logger.warn("Test") expect(stderr.messages).not_to be_empty end end end dry-core-0.7.1/spec/dry/core/descendants_tracker_spec.rb000066400000000000000000000006601407232415400232740ustar00rootroot00000000000000# frozen_string_literal: true require "dry/core/descendants_tracker" RSpec.describe Dry::Core::DescendantsTracker do before do module Test class Parent extend Dry::Core::DescendantsTracker end class Child < Parent end class Grandchild < Child end end end it "tracks descendants" do expect(Test::Parent.descendants).to eql([Test::Grandchild, Test::Child]) end end dry-core-0.7.1/spec/dry/core/equalizer/000077500000000000000000000000001407232415400177265ustar00rootroot00000000000000dry-core-0.7.1/spec/dry/core/equalizer/included_spec.rb000066400000000000000000000032241407232415400230550ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" require "dry/core/equalizer" RSpec.describe Dry::Core::Equalizer, "#included" do subject { descendant.instance_exec(object) { |mod| include mod } } let(:object) { described_class.new } let(:descendant) { Class.new } let(:superclass) { described_class.superclass } before do # Prevent Module.included from being called through inheritance allow(described_class::Methods).to receive(:included) end around do |example| # Restore included method after each example superclass.class_eval do alias_method :original_included, :included example.call undef_method :included alias_method :included, :original_included end end it "delegates to the superclass #included method" do # This is the most succinct approach I could think of to test whether the # superclass#included method is called. All of the built-in rspec helpers # did not seem to work for this. included = false superclass.class_eval do define_method(:included) do |_| # Only set the flag when an Dry::Equalizer instance is included. # Otherwise, other module includes (which get triggered internally # in RSpec when `change` is used for the first time, since it uses # autoloading for its matchers) will wrongly set this flag. included = true if is_a?(Dry::Core::Equalizer) end end expect { subject }.to change { included }.from(false).to(true) end it "includes methods into the descendant" do subject expect(descendant.included_modules).to include(described_class::Methods) end end dry-core-0.7.1/spec/dry/core/equalizer/methods/000077500000000000000000000000001407232415400213715ustar00rootroot00000000000000dry-core-0.7.1/spec/dry/core/equalizer/methods/eql_predicate_spec.rb000066400000000000000000000023721407232415400255350ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" require "dry/core/equalizer" RSpec.describe Dry::Core::Equalizer::Methods, "#eql?" do subject { object.eql?(other) } let(:object) { described_class.new(true) } let(:described_class) do Class.new do include Dry::Core::Equalizer::Methods attr_reader :boolean def initialize(boolean) @boolean = boolean end def cmp?(comparator, other) boolean.send(comparator, other.boolean) end end end context "with the same object" do let(:other) { object } it { should be(true) } it "is symmetric" do should eql(other.eql?(object)) end end context "with an equivalent object" do let(:other) { object.dup } it { should be(true) } it "is symmetric" do should eql(other.eql?(object)) end end context "with an equivalent object of a subclass" do let(:other) { Class.new(described_class).new(true) } it { should be(false) } it "is symmetric" do should eql(other.eql?(object)) end end context "with a different object" do let(:other) { described_class.new(false) } it { should be(false) } it "is symmetric" do should eql(other.eql?(object)) end end end dry-core-0.7.1/spec/dry/core/equalizer/methods/equality_operator_spec.rb000066400000000000000000000035411407232415400265030ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" require "dry/core/equalizer" RSpec.describe Dry::Core::Equalizer::Methods, "#==" do subject { object == other } let(:object) { described_class.new(true) } let(:described_class) { Class.new(super_class) } let(:super_class) do Class.new do include Dry::Core::Equalizer::Methods attr_reader :boolean def initialize(boolean) @boolean = boolean end def cmp?(comparator, other) boolean.send(comparator, other.boolean) end end end context "with the same object" do let(:other) { object } it { should be(true) } it "is symmetric" do should eql(other == object) end end context "with an equivalent object" do let(:other) { object.dup } it { should be(true) } it "is symmetric" do should eql(other == object) end end context "with a subclass instance having equivalent obervable state" do let(:other) { Class.new(described_class).new(true) } it { should be(true) } it "is not symmetric" do # the subclass instance should maintain substitutability with the object # (in the LSP sense) the reverse is not true. should_not eql(other == object) end end context "with a superclass instance having equivalent observable state" do let(:other) { super_class.new(true) } it { should be(false) } it "is not symmetric" do should_not eql(other == object) end end context "with an object of another class" do let(:other) { Class.new.new } it { should be(false) } it "is symmetric" do should eql(other == object) end end context "with a different object" do let(:other) { described_class.new(false) } it { should be(false) } it "is symmetric" do should eql(other == object) end end end dry-core-0.7.1/spec/dry/core/equalizer/universal_spec.rb000066400000000000000000000141431407232415400233000ustar00rootroot00000000000000# frozen_string_literal: true require "spec_helper" require "dry/core/equalizer" RSpec.describe Dry::Core::Equalizer do let(:name) { "User" } let(:klass) { ::Class.new } context "with no keys" do subject { Dry::Equalizer() } before do # specify the class #name method allow(klass).to receive(:name).and_return(name) klass.include(subject) end let(:instance) { klass.new } it { should be_instance_of(described_class) } it { should be_frozen } it "defines #hash and #inspect methods dynamically" do expect(subject.public_instance_methods(false).map(&:to_s).sort) .to eql(%w[hash inspect]) end describe "#eql?" do context "when the objects are similar" do let(:other) { instance.dup } it { expect(instance.eql?(other)).to be(true) } end context "when the objects are different" do let(:other) { double("other") } it { expect(instance.eql?(other)).to be(false) } end end describe "#==" do context "when the objects are similar" do let(:other) { instance.dup } it { expect(instance == other).to be(true) } end context "when the objects are different" do let(:other) { double("other") } it { expect(instance == other).to be(false) } end end describe "#hash" do it "has the expected arity" do expect(klass.instance_method(:hash).arity).to be(0) end it { expect(instance.hash).to eql([klass].hash) } end describe "#inspect" do it "has the expected arity" do expect(klass.instance_method(:inspect).arity).to be(0) end it { expect(instance.inspect).to eql("#") } end end context "with keys" do subject { Dry::Equalizer(*keys) } let(:keys) { %i[firstname lastname].freeze } let(:firstname) { "John" } let(:lastname) { "Doe" } let(:instance) { klass.new(firstname, lastname) } let(:klass) do ::Class.new do attr_reader :firstname, :lastname attr_writer :firstname private :firstname, :lastname def initialize(firstname, lastname) @firstname = firstname @lastname = lastname end end end before do # specify the class #inspect method allow(klass).to receive_messages(name: nil, inspect: name) klass.include(subject) end it { should be_instance_of(described_class) } it { should be_frozen } it "defines #hash and #inspect methods dynamically" do expect(subject.public_instance_methods(false).map(&:to_s).sort) .to eql(%w[hash inspect]) end describe "#eql?" do context "when the objects are similar" do let(:other) { instance.dup } it { expect(instance.eql?(other)).to be(true) } end context "when the objects are different" do let(:other) { double("other") } it { expect(instance.eql?(other)).to be(false) } end end describe "#==" do context "when the objects are similar" do let(:other) { instance.dup } it { expect(instance == other).to be(true) } end context "when the objects are different type" do let(:other) { klass.new("Foo", "Bar") } it { expect(instance == other).to be(false) } end context "when the objects are from different type" do let(:other) { double("other") } it { expect(instance == other).to be(false) } end end describe "#hash" do it "returns the expected hash" do expect(instance.hash) .to eql([firstname, lastname, klass].hash) end end describe "#inspect" do it "returns the expected string" do expect(instance.inspect) .to eql('#') end end context "when immutable" do describe "#hash" do subject { Dry::Equalizer(*keys, immutable: true) } it "returns memoized hash" do expect { instance.firstname = "Changed" }.not_to(change { instance.hash }) end context "when frozen" do it "returns memoized hash" do instance.freeze expect(instance.hash) .to eql([firstname, lastname, klass].hash) end end end end end context "with duplicate keys" do subject { Dry::Equalizer(*keys) } let(:keys) { %i[firstname firstname lastname].freeze } let(:firstname) { "John" } let(:lastname) { "Doe" } let(:instance) { klass.new(firstname, lastname) } let(:klass) do ::Class.new do attr_reader :firstname, :lastname private :firstname, :lastname def initialize(firstname, lastname) @firstname = firstname @lastname = lastname end end end before do # specify the class #inspect method allow(klass).to receive_messages(name: nil, inspect: name) klass.include(subject) end it { should be_instance_of(described_class) } it { should be_frozen } describe "#inspect" do it "returns the expected string" do expect(instance.inspect) .to eql('#') end end end context "with options" do context "w/o inspect" do subject { Dry::Equalizer(*keys, inspect: false) } let(:keys) { %i[firstname lastname].freeze } let(:firstname) { "John" } let(:lastname) { "Doe" } let(:instance) { klass.new(firstname, lastname) } let(:klass) do ::Struct.new(:firstname, :lastname) end before { klass.include(subject) } describe "#inspect" do it "returns the default string" do expect(instance.inspect).to eql('#') expect(instance.to_s).to eql('#') end end end end end dry-core-0.7.1/spec/dry/core/extensions_spec.rb000066400000000000000000000021321407232415400214610ustar00rootroot00000000000000# frozen_string_literal: true require "dry/core/extensions" RSpec.describe Dry::Core::Extensions do subject do Class.new do extend Dry::Core::Extensions end end it "allows to register and load extensions" do foo = false bar = false subject.register_extension(:foo) { foo = true } subject.register_extension(:bar) { bar = true } subject.load_extensions(:foo, :bar) expect(foo).to be true expect(bar).to be true end it "swallows double loading" do cnt = 0 subject.register_extension(:foo) { cnt += 1 } subject.load_extensions(:foo) subject.load_extensions(:foo) expect(cnt).to be 1 end it "raise ArgumentError on loading unknown extension" do subject.register_extension(:foo) { raise } expect { subject.load_extensions(:bar) }.to raise_error ArgumentError, "Unknown extension: :bar" end it "allows to query if an extension is available" do subject.register_extension(:foo) {} expect(subject.available_extension?(:foo)).to be true expect(subject.available_extension?(:bar)).to be false end end dry-core-0.7.1/spec/dry/core/inflector_spec.rb000066400000000000000000000046501407232415400212560ustar00rootroot00000000000000# frozen_string_literal: true require "dry/core/inflector" RSpec.describe Dry::Core::Inflector do shared_examples "an inflector" do it "singularises" do expect(api.singularize("tasks")).to eql("task") end it "pluralizes" do expect(api.pluralize("task")).to eql("tasks") end it "camelizes" do expect(api.camelize("task_user")).to eql("TaskUser") end it "underscores" do expect(api.underscore("TaskUser")).to eql("task_user") end it "demodulizes" do expect(api.demodulize("Task::User")).to eql("User") end it "classifies" do expect(api.classify("task_user/name")).to eql("TaskUser::Name") end end shared_examples "an inflector with constantize" do it "constantizes" do expect(api.constantize("String")).to be String end end subject(:api) { Dry::Core::Inflector } context "with detected inflector" do before do if api.instance_variables.include?(:@inflector) api.__send__(:remove_instance_variable, :@inflector) end end it "prefers ActiveSupport::Inflector" do expect(api.inflector).to be ::ActiveSupport::Inflector end end context "with automatic detection" do before do if api.instance_variables.include?(:@inflector) api.__send__(:remove_instance_variable, :@inflector) end end it "automatically selects an inflector backend" do expect(api.inflector).not_to be nil end end context "with ActiveSupport::Inflector" do before do api.select_backend(:activesupport) end it "is ActiveSupport::Inflector" do expect(api.inflector).to be(::ActiveSupport::Inflector) end it_behaves_like "an inflector" it_behaves_like "an inflector with constantize" end context "with Inflecto" do before do api.select_backend(:inflecto) end it "is Inflecto" do expect(api.inflector).to be(::Inflecto) end it_behaves_like "an inflector" it_behaves_like "an inflector with constantize" end context "with Dry::Inflector" do before do api.select_backend(:dry_inflector) end it "is Dry::Inflector" do expect(api.inflector).to be_a(Dry::Inflector) end it_behaves_like "an inflector" end context "an unrecognized inflector library is selected" do it "raises a NameError" do expect { api.select_backend(:foo) }.to raise_error(NameError) end end end dry-core-0.7.1/spec/dry/core/memoizable_spec.rb000066400000000000000000000111311407232415400214050ustar00rootroot00000000000000# frozen_string_literal: true require "concurrent/atomic/atomic_fixnum" require "dry/core/memoizable" require "tempfile" require_relative "../../support/memoized" RSpec.describe Dry::Core::Memoizable do before do Dry::Core::Deprecations.set_logger!(Tempfile.new("dry_deprecations")) end before { Memoized.memoize_methods } describe ".memoize" do describe Object do it_behaves_like "a memoizable class" do context "frozen object" do before { object.freeze } it "works" do expect(object.foo).to be(object.foo) end end end end describe BasicObject do it_behaves_like "a memoizable class" end describe Class.new(Object) do it_behaves_like "a memoizable class" end describe Class.new(BasicObject) do it_behaves_like "a memoizable class" end end describe Memoized.new do let(:block) { -> {} } describe "test1" do it_behaves_like "a memoized method" do let(:new_meth) { described_class.method(:test1) } it "does not raise an error" do 2.times do new_meth.("a", kwarg1: "world", other: "test", &block) end end end end describe "test2" do it_behaves_like "a memoized method" do let(:new_meth) { described_class.method(:test2) } it "does not raise an error" do 2.times { new_meth.("a", &block) } end end end describe "test3" do it_behaves_like "a memoized method" do let(:new_meth) { described_class.method(:test3) } it "does not raise an error" do 2.times { new_meth.(&block) } end end end describe "test4" do it_behaves_like "a memoized method" do let(:new_meth) { described_class.method(:test4) } it "does not raise an error" do 2.times { new_meth.call } end end end end describe ".new" do let(:args) { [double("arg")] } let(:kwargs) { {key: double("value")} } let(:block) { -> { double("block") } } let(:object) do Class.new do include Dry::Core::Memoizable attr_reader :args, :kwargs, :block def initialize(*args, **kwargs, &block) @args = args @kwargs = kwargs @block = block end end.new(*args, **kwargs, &block) end describe "#args" do subject { object.args } it { is_expected.to eq(args) } end describe "#kwargs" do subject { object.kwargs } it { is_expected.to eq(kwargs) } end describe "#block" do subject { object.block } it { is_expected.to eq(block) } end end context "test calls" do let(:klass) { Class.new.include(Dry::Core::Memoizable) } let(:instance) { klass.new } let(:counter) { Concurrent::AtomicFixnum.new } context "no args" do before do counter = self.counter klass.define_method(:meth) { counter.increment } klass.memoize(:meth) end it "gets called only once" do instance.meth instance.meth instance.meth expect(counter.value).to eql(1) end end context "pos arg" do before do counter = self.counter klass.define_method(:meth) { |req| counter.increment } klass.memoize(:meth) end it "memoizes results" do instance.meth(1) instance.meth(1) instance.meth(2) instance.meth(2) expect(counter.value).to eql(2) end end context "splat" do before do counter = self.counter klass.define_method(:meth) { |v, *args| counter.increment } klass.memoize(:meth) end it "memoizes results" do instance.meth(1) instance.meth(1) expect(counter.value).to eql(1) instance.meth(1, 2) instance.meth(1, 2) expect(counter.value).to eql(2) instance.meth(1, 2, 3) instance.meth(1, 2, 3) expect(counter.value).to eql(3) end end context "**kwargs" do before do counter = self.counter klass.define_method(:meth) { |foo:, **kwargs| counter.increment } klass.memoize(:meth) end it "memoizes results" do instance.meth(foo: 1) instance.meth(foo: 1) expect(counter.value).to eql(1) instance.meth(foo: 1, bar: 2) instance.meth(foo: 1, bar: 2) expect(counter.value).to eql(2) instance.meth(foo: 1, baz: 2) instance.meth(foo: 1, baz: 2) expect(counter.value).to eql(3) end end end end dry-core-0.7.1/spec/dry/core_spec.rb000066400000000000000000000002211407232415400172570ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Dry::Core do it "has a version number" do expect(Dry::Core::VERSION).not_to be nil end end dry-core-0.7.1/spec/fixtures/000077500000000000000000000000001407232415400160505ustar00rootroot00000000000000dry-core-0.7.1/spec/fixtures/project.rb000066400000000000000000000001611407232415400200410ustar00rootroot00000000000000# frozen_string_literal: true Test::Project = Class.new(Test::Parent) do def to_model "Project" end end dry-core-0.7.1/spec/spec_helper.rb000066400000000000000000000033411407232415400170160ustar00rootroot00000000000000# frozen_string_literal: true require_relative "support/coverage" require_relative "support/shared_examples/memoizable" $LOAD_PATH.unshift File.expand_path("../lib", __dir__) require "rspec/version" begin require "pry" require "pry-byebug" rescue LoadError end require "dry/core" module Test def self.remove_constants constants.each(&method(:remove_const)) end end # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration RSpec.configure do |config| if RSpec::Version::STRING >= "4.0.0" raise "This condition block can be safely removed" else config.expect_with :rspec do |expectations| expectations.include_chain_clauses_in_custom_matcher_descriptions = true end config.mock_with :rspec do |mocks| mocks.verify_partial_doubles = true end config.shared_context_metadata_behavior = :apply_to_host_groups end config.after do Test.remove_constants end # This allows you to limit a spec run to individual examples or groups # you care about by tagging them with `:focus` metadata. When nothing # is tagged with `:focus`, all examples get run. RSpec also provides # aliases for `it`, `describe`, and `context` that include `:focus` # metadata: `fit`, `fdescribe` and `fcontext`, respectively. config.filter_run_when_matching :focus # Allows RSpec to persist some state between runs in order to support # the `--only-failures` and `--next-failure` CLI options. config.example_status_persistence_file_path = "spec/examples.txt" config.disable_monkey_patching! config.warnings = true # Use the documentation formatter for detailed output config.default_formatter = "doc" if config.files_to_run.one? config.order = :random Kernel.srand config.seed end dry-core-0.7.1/spec/support/000077500000000000000000000000001407232415400157135ustar00rootroot00000000000000dry-core-0.7.1/spec/support/coverage.rb000066400000000000000000000004631407232415400200360ustar00rootroot00000000000000# frozen_string_literal: true # this file is managed by dry-rb/devtools if ENV["COVERAGE"] == "true" require "simplecov" require "simplecov-cobertura" SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter SimpleCov.start do add_filter "/spec/" enable_coverage :branch end end dry-core-0.7.1/spec/support/memoized.rb000066400000000000000000000017311407232415400200530ustar00rootroot00000000000000# frozen_string_literal: true class Memoized include Module.new { def test9 # NOP end } include Dry::Core::Memoizable def test1(arg1, *args, kwarg1:, kwarg2: "default", **kwargs, &block) # NOP end def test2(arg1, arg2 = "default", *args, &block) # NOP end def test3(&block) # NOP end def test4 # NOP end def test5(args, *) # NOP end def test6(kwargs, **) # NOP end def test7(args, kwargs, *, **) # NOP end def test8(args = 1, kwargs = 2, *, **) # NOP end if RUBY_VERSION >= "2.7" module_eval(<<~RUBY, __FILE__, __LINE__ + 1) def test9(...) super end RUBY end def self.memoize_methods @memoized ||= begin if RUBY_VERSION >= "2.7" memoize :test1, :test2, :test3, :test4, :test5, :test6, :test7, :test8, :test9 else memoize :test1, :test2, :test3, :test4, :test5, :test6, :test7, :test8 end true end end end dry-core-0.7.1/spec/support/rspec_options.rb000066400000000000000000000007351407232415400211340ustar00rootroot00000000000000RSpec.configure do |config| # When no filter given, search and run focused tests config.filter_run_when_matching :focus # Disables rspec monkey patches (no reason for their existence tbh) config.disable_monkey_patching! # Run ruby in verbose mode config.warnings = true # Collect all failing expectations automatically, # without calling aggregate_failures everywhere config.define_derived_metadata do |meta| meta[:aggregate_failures] = true end end dry-core-0.7.1/spec/support/shared_examples/000077500000000000000000000000001407232415400210575ustar00rootroot00000000000000dry-core-0.7.1/spec/support/shared_examples/memoizable.rb000066400000000000000000000047751407232415400235450ustar00rootroot00000000000000# frozen_string_literal: true RSpec.shared_examples "a memoizable class" do subject(:object) do Class.new(described_class) do include Dry::Core::Memoizable attr_reader :falsey_call_count def initialize @falsey_call_count = 0 end def foo %w[a ab abc].max end memoize :foo def bar(_arg) {a: "1", b: "2"} end memoize :bar def bar_with_block(&block) block.call end memoize :bar_with_block def bar_with_kwargs(*args, **kwargs) {args: args, kwargs: kwargs} end memoize :bar_with_kwargs def falsey @falsey_call_count += 1 false end memoize :falsey end.new end it "memoizes method return value" do expect(object.foo).to be(object.foo) end it "memoizes method return value with an arg" do expect(object.bar(:a)).to be(object.bar(:a)) expect(object.bar(:b)).to be(object.bar(:b)) end it "memoizes falsey values" do expect(object.falsey).to be(object.falsey) expect(object.falsey_call_count).to eq 1 end describe "keyword arguments" do let(:kwargs) { {key: "value"} } let(:args) { [1] } it "memoizes keyword arguments" do expect(object.bar_with_kwargs(*args, **kwargs)).to eq({args: args, kwargs: kwargs}) end end describe "with block" do let(:spy1) { double(:spy1) } let(:spy2) { double(:spy2) } let(:block1) { -> { spy1.call } } let(:block2) { -> { spy2.call } } let(:returns1) { :return_value1 } let(:returns2) { :return_value2 } before do expect(spy1).to receive(:call).and_return(returns1).once expect(spy2).to receive(:call).and_return(returns2).once end let(:results1) do 2.times.map do object.bar_with_block(&block1) end end let(:results2) do 2.times.map do object.bar_with_block(&block2) end end let(:returns) do [returns1, returns2] * 2 end subject do results1 + results2 end it { is_expected.to match_array(returns) } end end RSpec.shared_examples "a memoized method" do let(:old_meth) { new_meth.super_method } describe "new != old" do subject { new_meth } it { is_expected.not_to eq(old_meth) } end describe "new.arity == old.arity" do subject { new_meth.arity } it { is_expected.to eq(old_meth.arity) } end describe "new.name == old.name" do subject { new_meth.name } it { is_expected.to eq(old_meth.name) } end end dry-core-0.7.1/spec/support/warnings.rb000066400000000000000000000003701407232415400200700ustar00rootroot00000000000000# frozen_string_literal: true # this file is managed by dry-rb/devtools project require "warning" Warning.ignore(%r{rspec/core}) Warning.ignore(%r{rspec/mocks}) Warning.ignore(/codacy/) Warning[:experimental] = false if Warning.respond_to?(:[])