pax_global_header00006660000000000000000000000064143313740310014511gustar00rootroot0000000000000052 comment=7978a525fb441fd7e542a41d4383eb0058df73b2 dry-cli-1.0.0/000077500000000000000000000000001433137403100130525ustar00rootroot00000000000000dry-cli-1.0.0/.action_hero.yml000066400000000000000000000011121433137403100161400ustar00rootroot00000000000000# This is a config synced from dry-rb/template-gem repo sources: - repo: dry-rb/template-gem sync: - "!.github/workflows/ci.yml.erb" - ".action_hero.yml.erb" - ".devtools/templates/*.sync:${{dir}}/${{name}}" - ".github/**/*.*" - "gemspec.erb:dry-cli.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-cli-1.0.0/.devtools/000077500000000000000000000000001433137403100147675ustar00rootroot00000000000000dry-cli-1.0.0/.devtools/templates/000077500000000000000000000000001433137403100167655ustar00rootroot00000000000000dry-cli-1.0.0/.devtools/templates/changelog.erb000066400000000000000000000013711433137403100214100ustar00rootroot00000000000000 <% 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-cli-1.0.0/.devtools/templates/release.erb000066400000000000000000000013171433137403100211010ustar00rootroot00000000000000 <% 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-cli-1.0.0/.github/000077500000000000000000000000001433137403100144125ustar00rootroot00000000000000dry-cli-1.0.0/.github/FUNDING.yml000066400000000000000000000000171433137403100162250ustar00rootroot00000000000000github: hanami dry-cli-1.0.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001433137403100165755ustar00rootroot00000000000000dry-cli-1.0.0/.github/ISSUE_TEMPLATE/bug-report.md000066400000000000000000000007521433137403100212110ustar00rootroot00000000000000--- 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-cli-1.0.0/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000002361433137403100205660ustar00rootroot00000000000000blank_issues_enabled: false contact_links: - name: Community Support url: https://discourse.dry-rb.org about: Please ask and answer questions here. dry-cli-1.0.0/.github/SUPPORT.md000066400000000000000000000004441433137403100161120ustar00rootroot00000000000000## 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-cli-1.0.0/.github/workflows/000077500000000000000000000000001433137403100164475ustar00rootroot00000000000000dry-cli-1.0.0/.github/workflows/ci.yml000066400000000000000000000046301433137403100175700ustar00rootroot00000000000000# 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: - main create: jobs: tests: runs-on: ubuntu-latest strategy: fail-fast: false matrix: ruby: - "3.1" - "3.0" - "2.7" include: - ruby: "3.1" 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}} - name: Install latest bundler run: | gem install bundler --no-document bundle config set without 'tools benchmarks docs' - name: Bundle install run: bundle install --jobs 4 --retry 3 - 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.7 - 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-cli-1.0.0/.github/workflows/docsite.yml000066400000000000000000000032631433137403100206300ustar00rootroot00000000000000# This file is synced from dry-rb/template-gem repo name: docsite on: push: paths: - docsite/** - .github/workflows/docsite.yml branches: - main - 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.7.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 main 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-cli-1.0.0/.github/workflows/rubocop.yml000066400000000000000000000017071433137403100206500ustar00rootroot00000000000000name: "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-cli-1.0.0/.github/workflows/sync_configs.yml000066400000000000000000000027101433137403100216560ustar00rootroot00000000000000# This file is synced from dry-rb/template-gem repo name: sync on: repository_dispatch: push: branches: - "main" 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@v2 - 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.7" - 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 main git push https://dry-bot:${{secrets.GH_PAT}}@github.com/${{github.repository}}.git HEAD:main dry-cli-1.0.0/.gitignore000066400000000000000000000001621433137403100150410ustar00rootroot00000000000000/.bundle/ /.yardoc /Gemfile.lock /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ .byebug_history .rubocop-* dry-cli-1.0.0/.rspec000066400000000000000000000000701433137403100141640ustar00rootroot00000000000000--color --require spec_helper --order random --warnings dry-cli-1.0.0/.rubocop.yml000066400000000000000000000100141433137403100153200ustar00rootroot00000000000000# This is a config synced from dry-rb/template-gem repo AllCops: TargetRubyVersion: 2.7 NewCops: disable Exclude: - benchmarks/*.rb - 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" Lint/LiteralAsCondition: Exclude: - "spec/**/*.rb" Naming/PredicateName: Enabled: false Naming/FileName: Exclude: - "lib/*-*.rb" Naming/MethodName: Enabled: false Naming/MethodParameterName: Enabled: false 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/FormatStringToken: Enabled: false Style/GuardClause: Enabled: false Style/IfUnlessModifier: Enabled: false Style/Lambda: Enabled: false Style/LambdaCall: Enabled: false Style/ParallelAssignment: Enabled: false Style/RaiseArgs: 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 Style/AccessorGrouping: Enabled: false Style/EmptyLiteral: Enabled: false Style/Semicolon: Exclude: - "spec/**/*.rb" Style/HashAsLastArrayItem: Exclude: - "spec/**/*.rb" Style/CaseEquality: Exclude: - "lib/dry/monads/**/*.rb" - "lib/dry/struct/**/*.rb" - "lib/dry/types/**/*.rb" - "spec/**/*.rb" Style/ExplicitBlockArgument: Exclude: - "lib/dry/types/**/*.rb" Style/CombinableLoops: Enabled: false Style/EmptyElse: Enabled: false Style/DoubleNegation: Enabled: false Style/MultilineBlockChain: Enabled: false Style/NumberedParametersLimit: Max: 2 Lint/UnusedBlockArgument: Exclude: - "spec/**/*.rb" Lint/Debugger: Exclude: - "bin/console" Lint/BinaryOperatorWithIdenticalOperands: Exclude: - "spec/**/*.rb" Metrics/ParameterLists: Exclude: - "spec/**/*.rb" Lint/EmptyBlock: Exclude: - "spec/**/*.rb" Lint/UselessMethodDefinition: Exclude: - "spec/**/*.rb" Lint/SelfAssignment: Enabled: false Lint/EmptyClass: Enabled: false Naming/ConstantName: Exclude: - "spec/**/*.rb" Naming/VariableNumber: Exclude: - "spec/**/*.rb" Naming/BinaryOperatorParameterName: Enabled: false dry-cli-1.0.0/CHANGELOG.md000066400000000000000000000136501433137403100146700ustar00rootroot00000000000000 ## 1.0.0 2022-11-05 ### Changed - Version bumped to 1.0.0 (@solnic) [Compare v0.7.0...v1.0.0](https://github.com/dry-rb/dry-cli/compare/v0.7.0...v1.0.0) ## 0.7.0 2020-05-08 ### Added - Inheritable attributes for subclasses of commands (@IvanShamatov) - Ability to register instances, not only classes as Commands (@IvanShamatov) - Add support for subcommands with a parent command (@unrooty) ### Fixed - Safely rescue pipe exception, when you CLI app is producing output for piped CLI app (IvanShamatov) - Safely rescue keyboard interrupts (@IvanShamatov) - [Internal] Don't run specs twice (@jodosha) - Update inline call with keyward arguments (@flash-gordon) ### Changed - Extracted Dry::CLI::Utils::Files into dry-files (@jodosha) - Drop 2.3 ruby support (@IvanShamatov) - [Internal] Changelog, issue templates (@solnic) - Documentation updates (@davydovanton) - Remove concurrent-ruby as runtime dependency (@jodosha) - [Internal] Banner and Parses refactoring (@IvanShamatov) [Compare v0.6.0...v0.7.0](https://github.com/dry-rb/dry-cli/compare/v0.6.0...v0.7.0) ## 0.6.0 2020-03-06 ### Added - Ability to pass command along with registry (for a singular command case) (@IvanShamatov) - [Internal] Backported ability to run gem's CI against ruby 2.3 (@flash-gordon) - Inline syntax for commands (@IvanShamatov) - Introduced stderr to any diagnostic output (@IvanShamatov) ### Fixed - [John Ledbetter & Luca Guidi] Fix ruby 2.7 warnings (@jodosha) - Fix banner, when option is a type of Array (@IvanShamatov) [Compare v0.5.1...v0.6.0](https://github.com/dry-rb/dry-cli/compare/v0.5.1...v0.6.0) ## 0.5.1 2020-01-23 ### Added - Anonymous Registry sintax (@IvanShamatov) - [Internal] Specs refactored, more unit specs added (@IvanShamatov) - [Internal] removed `dry-inflector` as runtime dependency (@jodosha) - [Internal] Refactored Command class (command_name property removed) (@IvanShamatov) - [Internal] Adapt gem to dry-rb style (@jodosha, @flash-gordon, @solnic, @cgeorgii) ### Fixed - Added missing 'set' require (@solnic) [Compare v0.5.0...v0.5.1](https://github.com/dry-rb/dry-cli/compare/v0.5.0...v0.5.1) ## 0.5.0 2019-12-21 ### Added - [Internal] removed runtime and development dependency against `hanami-utils` (@jodosha, @IvanShamatov, @solnic) [Compare v0.4.0...v0.5.0](https://github.com/dry-rb/dry-cli/compare/v0.4.0...v0.5.0) ## 0.4.0 2019-12-10 ### Added - `hanami-cli` => `dry-cli` (@jodosha, @IvanShamatov, @solnic) [Compare v0.3.1...v0.4.0](https://github.com/dry-rb/dry-cli/compare/v0.3.1...v0.4.0) ## 0.3.1 2019-01-18 ### Added - Official support for Ruby: MRI 2.6 (@jodosha) - Support `bundler` 2.0+ (@jodosha) [Compare v0.3.0...v0.3.1](https://github.com/dry-rb/dry-cli/compare/v0.3.0...v0.3.1) ## 0.3.0 2018-10-24 [Compare v0.3.0.beta1...v0.3.0](https://github.com/dry-rb/dry-cli/compare/v0.3.0.beta1...v0.3.0) ## 0.3.0.beta1 2018-08-08 ### Added - Introduce array type for arguments (`foo exec test spec/bookshelf/entities spec/bookshelf/repositories`) (@davydovanton, @AlfonsoUceda) - Introduce array type for options (`foo generate config --apps=web,api`) (@davydovanton, @AlfonsoUceda) - Introduce variadic arguments (`foo run ruby:latest -- ruby -v`) - Official support for JRuby 9.2.0.0 (@jodosha, @AlfonsoUceda) ### Fixed - Print informative message when unknown or wrong option is passed (`"test" was called with arguments "--framework=unknown"`) (@davydovanton) [Compare v0.2.0...v0.3.0.beta1](https://github.com/dry-rb/dry-cli/compare/v0.2.0...v0.3.0.beta1) ## 0.2.0 2018-04-11 [Compare v0.2.0.rc2...v0.2.0](https://github.com/dry-rb/dry-cli/compare/v0.2.0.rc2...v0.2.0) ## 0.2.0.rc2 2018-04-06 [Compare v0.2.0.rc1...v0.2.0.rc2](https://github.com/dry-rb/dry-cli/compare/v0.2.0.rc1...v0.2.0.rc2) ## 0.2.0.rc1 2018-03-30 [Compare v0.2.0.beta2...v0.2.0.rc1](https://github.com/dry-rb/dry-cli/compare/v0.2.0.beta2...v0.2.0.rc1) ## 0.2.0.beta2 2018-03-23 ### Added - Support objects as callbacks (@jodosha, @davydovanton) ### Fixed - Ensure callbacks' context of execution (aka `self`) to be the command that is being executed (@jodosha, @davydovanton) [Compare v0.2.0.beta1...v0.2.0.beta2](https://github.com/dry-rb/dry-cli/compare/v0.2.0.beta1...v0.2.0.beta2) ## 0.2.0.beta1 2018-02-28 ### Added - Register `before`/`after` callbacks for commands (@davydovanton) [Compare v0.1.1...v0.2.0.beta1](https://github.com/dry-rb/dry-cli/compare/v0.1.1...v0.2.0.beta1) ## 0.1.1 2018-02-27 ### Added - Official support for Ruby: MRI 2.5 (@jodosha) ### Fixed - Ensure default values for arguments to be sent to commands (@AlfonsoUceda) - Ensure to fail when a missing required argument isn't provider, but an option is provided instead (@AlfonsoUceda) [Compare v0.1.0...v0.1.1](https://github.com/dry-rb/dry-cli/compare/v0.1.0...v0.1.1) ## 0.1.0 2017-10-25 [Compare v0.1.0.rc1...v0.1.0](https://github.com/dry-rb/dry-cli/compare/v0.1.0.rc1...v0.1.0) ## 0.1.0.rc1 2017-10-16 [Compare v0.1.0.beta3...v0.1.0.rc1](https://github.com/dry-rb/dry-cli/compare/v0.1.0.beta3...v0.1.0.rc1) ## 0.1.0.beta3 2017-10-04 [Compare v0.1.0.beta2...v0.1.0.beta3](https://github.com/dry-rb/dry-cli/compare/v0.1.0.beta2...v0.1.0.beta3) ## 0.1.0.beta2 2017-10-03 ### Added - Allow default value for arguments (@AlfonsoUceda) [Compare v0.1.0.beta1...v0.1.0.beta2](https://github.com/dry-rb/dry-cli/compare/v0.1.0.beta1...v0.1.0.beta2) ## 0.1.0.beta1 2017-08-11 ### Added - Commands banner and usage (@jodosha, @AlfonsoUceda) - Added support for subcommands (@AlfonsoUceda) - Validations for arguments and options (@AlfonsoUceda) - Commands arguments and options (@AlfonsoUceda) - Commands description (@AlfonsoUceda) - Commands aliases (@AlfonsoUceda, @oana-sipos) - Exit on unknown command (@jodosha) - Command lookup (@AlfonsoUceda, @oana-sipos) - Trie based registry to register commands and allow third-parties to override/add commands (@jodosha, @timriley) dry-cli-1.0.0/CODEOWNERS000066400000000000000000000000121433137403100144360ustar00rootroot00000000000000* @solnic dry-cli-1.0.0/CODE_OF_CONDUCT.md000066400000000000000000000026611433137403100156560ustar00rootroot00000000000000# 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-cli-1.0.0/CONTRIBUTING.md000066400000000000000000000026471433137403100153140ustar00rootroot00000000000000# 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-cli-1.0.0/Gemfile000066400000000000000000000003721433137403100143470ustar00rootroot00000000000000# frozen_string_literal: true source "https://rubygems.org" eval_gemfile "Gemfile.devtools" gemspec gem "backports", "~> 3.15.0", require: false unless ENV["CI"] gem "byebug", require: false, platforms: :mri gem "yard", require: false end dry-cli-1.0.0/Gemfile.devtools000066400000000000000000000005231433137403100162030ustar00rootroot00000000000000# 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 gem "rubocop", "~> 1.36.0" end dry-cli-1.0.0/LICENSE000066400000000000000000000020731433137403100140610ustar00rootroot00000000000000The 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-cli-1.0.0/README.md000066400000000000000000000022421433137403100143310ustar00rootroot00000000000000 [gem]: https://rubygems.org/gems/dry-cli [actions]: https://github.com/dry-rb/dry-cli/actions [codacy]: https://www.codacy.com/gh/dry-rb/dry-cli [chat]: https://dry-rb.zulipchat.com [inchpages]: http://inch-ci.org/github/dry-rb/dry-cli # dry-cli [![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-cli.svg)][gem] [![CI Status](https://github.com/dry-rb/dry-cli/workflows/ci/badge.svg)][actions] [![Codacy Badge](https://api.codacy.com/project/badge/Grade/61dd5d070fc74f0cacf575b19d4930e1)][codacy] [![Codacy Badge](https://api.codacy.com/project/badge/Coverage/61dd5d070fc74f0cacf575b19d4930e1)][codacy] [![Inline docs](http://inch-ci.org/github/dry-rb/dry-cli.svg?branch=main)][inchpages] ## Links * [User documentation](https://dry-rb.org/gems/dry-cli) * [API documentation](http://rubydoc.info/gems/dry-cli) ## Supported Ruby versions This library officially supports the following Ruby versions: * MRI `>= 2.4.0` * jruby `>= 9.3` (postponed until 2.7 is supported) ## License See `LICENSE` file. dry-cli-1.0.0/Rakefile000077500000000000000000000004371433137403100145260ustar00rootroot00000000000000#!/usr/bin/env rake # frozen_string_literal: true require "bundler/gem_tasks" $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "lib")) require "rspec/core" require "rspec/core/rake_task" task default: :spec desc "Run all specs in spec directory" RSpec::Core::RakeTask.new(:spec) dry-cli-1.0.0/bin/000077500000000000000000000000001433137403100136225ustar00rootroot00000000000000dry-cli-1.0.0/bin/.gitkeep000066400000000000000000000000001433137403100152410ustar00rootroot00000000000000dry-cli-1.0.0/bin/console000077500000000000000000000005641433137403100152170ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true require "bundler/setup" require "dry/cli" # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. # (If you use this, don't forget to add pry to your Gemfile!) # require "pry" # Pry.start require "irb" IRB.start(__FILE__) dry-cli-1.0.0/bin/setup000077500000000000000000000002031433137403100147030ustar00rootroot00000000000000#!/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-cli-1.0.0/changelog.yml000066400000000000000000000111221433137403100155210ustar00rootroot00000000000000--- - version: 1.0.0 date: 2022-11-05 changed: - "Version bumped to 1.0.0 (@solnic)" - version: 0.7.0 date: '2020-05-08' added: - "Inheritable attributes for subclasses of commands (@IvanShamatov)" - "Ability to register instances, not only classes as Commands (@IvanShamatov)" - "Add support for subcommands with a parent command (@unrooty)" changed: - "Extracted Dry::CLI::Utils::Files into dry-files (@jodosha)" - "Drop 2.3 ruby support (@IvanShamatov)" - "[Internal] Changelog, issue templates (@solnic)" - "Documentation updates (@davydovanton)" - "Remove concurrent-ruby as runtime dependency (@jodosha)" - "[Internal] Banner and Parses refactoring (@IvanShamatov)" fixed: - "Safely rescue pipe exception, when you CLI app is producing output for piped CLI app (IvanShamatov)" - "Safely rescue keyboard interrupts (@IvanShamatov)" - "[Internal] Don't run specs twice (@jodosha)" - "Update inline call with keyward arguments (@flash-gordon)" - version: 0.6.0 date: '2020-03-06' added: - "Ability to pass command along with registry (for a singular command case) (@IvanShamatov)" - "[Internal] Backported ability to run gem's CI against ruby 2.3 (@flash-gordon)" - "Inline syntax for commands (@IvanShamatov)" - "Introduced stderr to any diagnostic output (@IvanShamatov)" fixed: - "[John Ledbetter & Luca Guidi] Fix ruby 2.7 warnings (@jodosha)" - "Fix banner, when option is a type of Array (@IvanShamatov)" - version: 0.5.1 date: '2020-01-23' added: - "Anonymous Registry sintax (@IvanShamatov)" - "[Internal] Specs refactored, more unit specs added (@IvanShamatov)" - "[Internal] removed `dry-inflector` as runtime dependency (@jodosha)" - "[Internal] Refactored Command class (command_name property removed) (@IvanShamatov)" - "[Internal] Adapt gem to dry-rb style (@jodosha, @flash-gordon, @solnic, @cgeorgii)" fixed: - "Added missing 'set' require (@solnic)" - version: 0.5.0 date: '2019-12-21' added: - "[Internal] removed runtime and development dependency against `hanami-utils` (@jodosha, @IvanShamatov, @solnic)" - version: 0.4.0 date: '2019-12-10' added: - "`hanami-cli` => `dry-cli` (@jodosha, @IvanShamatov, @solnic)" - version: 0.3.1 date: '2019-01-18' added: - "Official support for Ruby: MRI 2.6 (@jodosha)" - "Support `bundler` 2.0+ (@jodosha)" - version: 0.3.0 date: '2018-10-24' - version: 0.3.0.beta1 date: '2018-08-08' added: - "Introduce array type for arguments (`foo exec test spec/bookshelf/entities spec/bookshelf/repositories`) (@davydovanton, @AlfonsoUceda)" - "Introduce array type for options (`foo generate config --apps=web,api`) (@davydovanton, @AlfonsoUceda)" - "Introduce variadic arguments (`foo run ruby:latest -- ruby -v`)" - "Official support for JRuby 9.2.0.0 (@jodosha, @AlfonsoUceda)" fixed: - 'Print informative message when unknown or wrong option is passed (`"test" was called with arguments "--framework=unknown"`) (@davydovanton)' - version: 0.2.0 date: '2018-04-11' - version: 0.2.0.rc2 date: '2018-04-06' - version: 0.2.0.rc1 date: '2018-03-30' - version: 0.2.0.beta2 date: '2018-03-23' added: - "Support objects as callbacks (@jodosha, @davydovanton)" fixed: - "Ensure callbacks' context of execution (aka `self`) to be the command that is being executed (@jodosha, @davydovanton)" - version: 0.2.0.beta1 date: '2018-02-28' added: - "Register `before`/`after` callbacks for commands (@davydovanton)" - version: 0.1.1 date: '2018-02-27' added: - "Official support for Ruby: MRI 2.5 (@jodosha)" fixed: - "Ensure default values for arguments to be sent to commands (@AlfonsoUceda)" - "Ensure to fail when a missing required argument isn't provider, but an option is provided instead (@AlfonsoUceda)" - version: 0.1.0 date: '2017-10-25' - version: 0.1.0.rc1 date: '2017-10-16' - version: 0.1.0.beta3 date: '2017-10-04' - version: 0.1.0.beta2 date: '2017-10-03' added: - "Allow default value for arguments (@AlfonsoUceda)" - version: 0.1.0.beta1 date: '2017-08-11' added: - "Commands banner and usage (@jodosha, @AlfonsoUceda)" - "Added support for subcommands (@AlfonsoUceda)" - "Validations for arguments and options (@AlfonsoUceda)" - "Commands arguments and options (@AlfonsoUceda)" - "Commands description (@AlfonsoUceda)" - "Commands aliases (@AlfonsoUceda, @oana-sipos)" - "Exit on unknown command (@jodosha)" - "Command lookup (@AlfonsoUceda, @oana-sipos)" - "Trie based registry to register commands and allow third-parties to override/add commands (@jodosha, @timriley)" dry-cli-1.0.0/docsite/000077500000000000000000000000001433137403100145045ustar00rootroot00000000000000dry-cli-1.0.0/docsite/source/000077500000000000000000000000001433137403100160045ustar00rootroot00000000000000dry-cli-1.0.0/docsite/source/arguments.html.md000066400000000000000000000021661433137403100213030ustar00rootroot00000000000000--- title: Arguments layout: gem-single name: dry-cli --- An argument is a token passed after the _command name_. For instance, given the `foo greet` command, when an user types `foo greet Luca`, then `Luca` is considered an argument. A command can accept none or many arguments. An argument can be declared as _required_. ```ruby #!/usr/bin/env ruby require "bundler/setup" require "dry/cli" module Foo module CLI module Commands extend Dry::CLI::Registry class Greet < Dry::CLI::Command argument :name, required: true, desc: "The name of the person to greet" argument :age, desc: "The age of the person to greet" def call(name:, age: nil, **) result = "Hello, #{name}." result = "#{result} You are #{age} years old." unless age.nil? puts result end end register "greet", Greet end end end Dry::CLI.new(Foo::CLI::Commands).call ``` ```sh $ foo greet Luca Hello, Luca. ``` ```sh $ foo greet Luca 35 Hello, Luca. You are 35 years old. ``` ```sh $ foo greet ERROR: "foo greet" was called with no arguments Usage: "foo greet NAME" ``` dry-cli-1.0.0/docsite/source/callbacks.html.md000066400000000000000000000016651433137403100212200ustar00rootroot00000000000000--- title: Callbacks layout: gem-single name: dry-cli --- Third party gems can register _before_ and _after_ callbacks to enhance a command. ### Example From the `foo` gem we have a command `hello`. ```ruby #!/usr/bin/env ruby require "dry/cli" module Foo module CLI module Commands extend Dry::CLI::Registry class Hello < Dry::CLI::Command argument :name, required: true def call(name:, **) puts "hello #{name}" end end end end end Foo::CLI::Commands.register "hello", Foo::CLI::Commands::Hello cli = Dry::CLI.new(Foo::CLI::Commands) cli.call ``` The `foo-bar` gem enhances `hello` command with callbacks: ```ruby Foo::CLI::Commands.before("hello") { |args| puts "debug: #{args.inspect}" } # syntax 1 Foo::CLI::Commands.after "hello", &->(args) { puts "bye, #{args.fetch(:name)}" } # syntax 2 ``` ```sh $ foo hello Anton debug: {:name=>"Anton"} hello Anton bye, Anton ``` dry-cli-1.0.0/docsite/source/commands-with-subcommands-and-params.md000066400000000000000000000034061433137403100254350ustar00rootroot00000000000000--- title: Commands with subcommands and params layout: gem-single name: dry-cli --- There is a way to register command with arguments, options and subcommands. This helps to implement complex nested logic. Arguments and options can be defined for parent both commands and child commands. ^INFO If you call a command with an argument equal to the name of the subcommand, it will call the subcommand instead of the parent command. ^ ```ruby #!/usr/bin/env ruby require "bundler/setup" require "dry/cli" module Foo module CLI module Commands extend Dry::CLI::Registry class Account < Dry::CLI::Command desc 'Information about account' argument :format, default: "short", values: %w[long short], desc: "Output format" def call(**options) puts "Information about account in #{options.fetch(:format)} format." end class Users < Dry::CLI::Command desc 'Information about account users' def call(**_options) puts "Information about account users." end end end register "account", Account register "account users", Account::Users end end end Dry::CLI.new(Foo::CLI::Commands).call ``` ``` $ foo account -h Command: foo account Usage: foo account [FORMAT] | foo account SUBCOMMAND Description: Information about account Subcommands: users # Information about account users Arguments: FORMAT # Output format: (long/short) Options: --help, -h # Print this help ``` ```sh $ foo account # Information about account in short format. ``` ```sh $ foo account long # Information about account in long format. ``` ```sh $ foo account users # Information about account users. ``` dry-cli-1.0.0/docsite/source/commands.html.md000066400000000000000000000015241433137403100210740ustar00rootroot00000000000000--- title: Commands layout: gem-single name: dry-cli --- A command is a subclass of `Dry::CLI::Command` and it **must** respond to `#call(*)`. For a given _command name_, you can register a corresponding _command_. **Please note:** there is **no** convention between the _command name_ and the _command object_ class name. The manual _registration_ assigns a _command object_ to a _command name_. ```ruby #!/usr/bin/env ruby require "bundler/setup" require "dry/cli" module Foo module CLI module Commands extend Dry::CLI::Registry class Hello < Dry::CLI::Command def call(*) end end end end end class Version < Dry::CLI::Command def call(*) end end Foo::CLI::Commands.register "hi", Foo::CLI::Commands::Hello Foo::CLI::Commands.register "v", Version Dry::CLI.new(Foo::CLI::Commands).call ``` dry-cli-1.0.0/docsite/source/index.html.md000066400000000000000000000144021433137403100204010ustar00rootroot00000000000000--- title: Introduction description: General purpose Command Line Interface (CLI) framework layout: gem-single type: gem name: dry-cli sections: - commands - subcommands - arguments - options - variadic-arguments - commands-with-subcommands-and-params - callbacks --- `dry-cli` is a general-purpose framework for developing Command Line Interface (CLI) applications. It represents commands as objects that can be registered and offers support for arguments, options and forwarding variadic arguments to a sub-command. ## Usage The following is an elaborate example showcasing most of the available features. Imagine you want to build a CLI executable `foo` for your Ruby project. The entire program is defined as below: ```ruby #!/usr/bin/env ruby require "bundler/setup" require "dry/cli" module Foo module CLI module Commands extend Dry::CLI::Registry class Version < Dry::CLI::Command desc "Print version" def call(*) puts "1.0.0" end end class Echo < Dry::CLI::Command desc "Print input" argument :input, desc: "Input to print" example [ " # Prints 'wuh?'", "hello, folks # Prints 'hello, folks'" ] def call(input: nil, **) if input.nil? puts "wuh?" else puts input end end end class Start < Dry::CLI::Command desc "Start Foo machinery" argument :root, required: true, desc: "Root directory" example [ "path/to/root # Start Foo at root directory" ] def call(root:, **) puts "started - root: #{root}" end end class Stop < Dry::CLI::Command desc "Stop Foo machinery" option :graceful, type: :boolean, default: true, desc: "Graceful stop" def call(**options) puts "stopped - graceful: #{options.fetch(:graceful)}" end end class Exec < Dry::CLI::Command desc "Execute a task" argument :task, type: :string, required: true, desc: "Task to be executed" argument :dirs, type: :array, required: false, desc: "Optional directories" def call(task:, dirs: [], **) puts "exec - task: #{task}, dirs: #{dirs.inspect}" end end module Generate class Configuration < Dry::CLI::Command desc "Generate configuration" option :apps, type: :array, default: [], desc: "Generate configuration for specific apps" def call(apps:, **) puts "generated configuration for apps: #{apps.inspect}" end end class Test < Dry::CLI::Command desc "Generate tests" option :framework, default: "minitest", values: %w[minitest rspec] def call(framework:, **) puts "generated tests - framework: #{framework}" end end end register "version", Version, aliases: ["v", "-v", "--version"] register "echo", Echo register "start", Start register "stop", Stop register "exec", Exec register "generate", aliases: ["g"] do |prefix| prefix.register "config", Generate::Configuration prefix.register "test", Generate::Test end end end end Dry::CLI.new(Foo::CLI::Commands).call ``` ### Available commands With this code in place, we can now have a look at the command line usage by issuing the command `foo` without any arguments or options: ```sh $ foo Commands: foo echo [INPUT] # Print input foo exec TASK [DIRS] # Execute a task foo generate [SUBCOMMAND] foo start ROOT # Start Foo machinery foo stop # Stop Foo machinery foo version # Print version ``` ### Help It is also possible to get help for a particular command using the `--help` flag: ```sh $ foo echo --help Command: foo echo Usage: foo echo [INPUT] Description: Print input Arguments: INPUT # Input to print Options: --help, -h # Print this help Examples: foo echo # Prints 'wuh?' foo echo hello, folks # Prints 'hello, folks' ``` ### Optional arguments A command can have optional arguments, which enables a default action in case nothing is provided: ```sh $ foo echo wuh? $ foo echo hello hello ``` ### Required arguments On the other hand, required arguments will throw an error if not provided: ```sh $ foo start . started - root: . ``` ```sh $ foo start ERROR: "foo start" was called with no arguments Usage: "foo start ROOT" ``` ### Array arguments Captures all the remaining arguments in a single array. Please note that `array` argument must be used as the last argument, as it works as a _"catch-all"_. ```sh $ foo exec test exec - task: test, dirs: [] ``` ```sh $ foo exec test spec/bookshelf/entities spec/bookshelf/repositories exec - task: test, dirs: ["spec/bookshelf/entities", "spec/bookshelf/repositories"] ``` ### Options An option is a named argument that is passed after the command name and the arguments: ```sh $ foo generate test generated tests - framework: minitest ``` ```sh $ foo generate test --framework=rspec generated tests - framework: rspec ``` ```sh $ foo generate test --framework=unknown ERROR: "foo generate test" was called with arguments "--framework=unknown" ``` ### Boolean options Boolean options are flags that change the behaviour of a command: ```sh $ foo stop stopped - graceful: true ``` ```sh $ foo stop --no-graceful stopped - graceful: false ``` ### Array options Array options are similar to arguments but must be named: ```sh $ foo generate config --apps=web,api generated configuration for apps: ["web", "api"] ``` ### Subcommands Subcommands are simply commands that have been registered under a nested path: ```sh $ foo generate Commands: foo generate config # Generate configuration foo generate test # Generate tests ``` ### Aliases Aliases are supported: ```sh $ foo version 1.0.0 ``` ```sh $ foo v 1.0.0 ``` ```sh $ foo -v 1.0.0 ``` ```sh $ foo --version 1.0.0 ``` ### Subcommand aliases Work similarly to command aliases ```sh $ foo g config generated configuration for apps: [] ``` dry-cli-1.0.0/docsite/source/options.html.md000066400000000000000000000020471433137403100207670ustar00rootroot00000000000000--- title: Options layout: gem-single name: dry-cli --- An option is a named argument that is passed after the _command name_ **and** the arguments. For instance, given the `foo request` command, when an user types `foo request --mode=http2`, then `--mode=http2` is considered an option. A command can accept none or many options. ```ruby #!/usr/bin/env ruby require "bundler/setup" require "dry/cli" module Foo module CLI module Commands extend Dry::CLI::Registry class Request < Dry::CLI::Command option :mode, default: "http", values: %w[http http2], desc: "The request mode" def call(**options) puts "Performing a request (mode: #{options.fetch(:mode)})" end end register "request", Request end end end Dry::CLI.new(Foo::CLI::Commands).call ``` ```sh $ foo request Performing a request (mode: http) ``` ```sh $ foo request --mode=http2 Performing a request (mode: http2) ``` ```sh $ foo request --mode=unknown ERROR: "foo request" was called with arguments "--mode=unknown" ``` dry-cli-1.0.0/docsite/source/subcommands.html.md000066400000000000000000000013131433137403100216020ustar00rootroot00000000000000--- title: Subcommands layout: gem-single name: dry-cli --- There is nothing special about subcommands, they are simply _command objects_ registered under a **nested** _command name_: ```ruby #!/usr/bin/env ruby require "bundler/setup" require "dry/cli" module Foo module CLI module Commands extend Dry::CLI::Registry module Generate class Configuration < Dry::CLI::Command def call(*) end end end end end end Foo::CLI::Commands.register "generate configuration", Foo::CLI::Commands::Generate::Configuration Dry::CLI.new(Foo::CLI::Commands).call ``` ```sh $ foo generate Commands: foo generate config # Generate configuration ``` dry-cli-1.0.0/docsite/source/variadic-arguments.html.md000066400000000000000000000025021433137403100230550ustar00rootroot00000000000000--- title: Variadic arguments layout: gem-single name: dry-cli --- Sometimes we need extra arguments because those will be forwarded to a sub-command like `ssh`, `docker` or `cat`. By using `--` (double dash, aka hypen), the user indicates the end of the arguments and options belonging to the main command, and the beginning of the variadic arguments that can be forwarded to the sub-command. These extra arguments are included as `:args` in the keyword arguments available for each command. ```ruby #!/usr/bin/env ruby require "bundler/setup" require "dry/cli" module Foo module CLI module Commands extend Dry::CLI::Registry class Runner < Dry::CLI::Command argument :image, required: true, desc: "Docker image" def call(image:, args: [], **) puts `docker run -it --rm #{image} #{args.join(" ")}` end end register "run", Runner end end end Dry::CLI.new(Foo::CLI::Commands).call ``` ```sh $ foo run ruby:latest -- ruby -v ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux] ``` The user separates via `--` the arguments for `foo` and the command has to be run by the Docker container. In this specific case, `ruby:latest` corresponds to the `image` mandatory argument for `foo`, whereas `ruby -v` is the variadic argument that is passed to Docker via `args`. dry-cli-1.0.0/dry-cli.gemspec000066400000000000000000000027141433137403100157660ustar00rootroot00000000000000# 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/cli/version" Gem::Specification.new do |spec| spec.name = "dry-cli" spec.authors = ["Luca Guidi"] spec.email = ["me@lucaguidi.com"] spec.license = "MIT" spec.version = Dry::CLI::VERSION.dup spec.summary = "Common framework to build command line interfaces with Ruby" spec.description = spec.summary spec.homepage = "https://dry-rb.org/gems/dry-cli" spec.files = Dir["CHANGELOG.md", "LICENSE", "README.md", "dry-cli.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-cli/blob/main/CHANGELOG.md" spec.metadata["source_code_uri"] = "https://github.com/dry-rb/dry-cli" spec.metadata["bug_tracker_uri"] = "https://github.com/dry-rb/dry-cli/issues" spec.required_ruby_version = ">= 2.7.0" # to update dependencies edit project.yml spec.add_development_dependency "bundler", ">= 1.6", "< 3" spec.add_development_dependency "rake", "~> 13.0" spec.add_development_dependency "rspec", "~> 3.7" spec.add_development_dependency "rubocop", "~> 0.82" spec.add_development_dependency "simplecov", "~> 0.17.1" end dry-cli-1.0.0/lib/000077500000000000000000000000001433137403100136205ustar00rootroot00000000000000dry-cli-1.0.0/lib/dry/000077500000000000000000000000001433137403100144165ustar00rootroot00000000000000dry-cli-1.0.0/lib/dry/cli.rb000066400000000000000000000127771433137403100155300ustar00rootroot00000000000000# frozen_string_literal: true # Dry # # @since 0.1.0 module Dry # General purpose Command Line Interface (CLI) framework for Ruby # # @since 0.1.0 class CLI require "dry/cli/version" require "dry/cli/errors" require "dry/cli/command" require "dry/cli/registry" require "dry/cli/parser" require "dry/cli/usage" require "dry/cli/banner" require "dry/cli/inflector" # Check if command # # @param command [Object] the command to check # # @return [TrueClass,FalseClass] true if instance of `Dry::CLI::Command` # # @since 0.1.0 # @api private def self.command?(command) case command when Class command.ancestors.include?(Command) else command.is_a?(Command) end end # Create a new instance # # @param command_or_registry [Dry::CLI::Registry, Dry::CLI::Command] # a registry or singular command # @param &block [Block] a configuration block for registry # # @return [Dry::CLI] the new instance # @since 0.1.0 def initialize(command_or_registry = nil, &block) @kommand = command_or_registry if command?(command_or_registry) @registry = if block_given? anonymous_registry(&block) else command_or_registry end end # Invoke the CLI # # @param arguments [Array] the command line arguments (defaults to `ARGV`) # @param out [IO] the standard output (defaults to `$stdout`) # @param err [IO] the error output (defaults to `$stderr`) # # @since 0.1.0 def call(arguments: ARGV, out: $stdout, err: $stderr) @out, @err = out, err kommand ? perform_command(arguments) : perform_registry(arguments) rescue SignalException => e signal_exception(e) rescue Errno::EPIPE # no op end private # @since 0.6.0 # @api private attr_reader :registry # @since 0.6.0 # @api private attr_reader :kommand # @since 0.6.0 # @api private attr_reader :out # @since 0.6.0 # @api private attr_reader :err # Invoke the CLI if singular command passed # # @param arguments [Array] the command line arguments # @param out [IO] the standard output (defaults to `$stdout`) # # @since 0.6.0 # @api private def perform_command(arguments) command, args = parse(kommand, arguments, []) command.call(**args) end # Invoke the CLI if registry passed # # @param arguments [Array] the command line arguments # @param out [IO] the standard output (defaults to `$stdout`) # # @since 0.6.0 # @api private def perform_registry(arguments) result = registry.get(arguments) return usage(result) unless result.found? command, args = parse(result.command, result.arguments, result.names) result.before_callbacks.run(command, args) command.call(**args) result.after_callbacks.run(command, args) end # Parse arguments for a command. # # It may exit in case of error, or in case of help. # # @param result [Dry::CLI::CommandRegistry::LookupResult] # @param out [IO] sta output # # @return [Array] returns an array where the # first element is a command and the second one is the list of arguments # # @since 0.6.0 # @api private def parse(command, arguments, names) prog_name = ProgramName.call(names) result = Parser.call(command, arguments, prog_name) return help(command, prog_name) if result.help? return error(result) if result.error? [build_command(command), result.arguments] end # @since 0.6.0 # @api private def build_command(command) command.is_a?(Class) ? command.new : command end # @since 0.6.0 # @api private def help(command, prog_name) out.puts Banner.call(command, prog_name) exit(0) # Successful exit end # @since 0.6.0 # @api private def error(result) err.puts(result.error) exit(1) end # @since 0.1.0 # @api private def usage(result) err.puts Usage.call(result) exit(1) end # Handles Exit codes for signals # Fatal error signal "n". Say 130 = 128 + 2 (SIGINT) or 137 = 128 + 9 (SIGKILL) # # @since 0.7.0 # @api private def signal_exception(exception) exit(128 + exception.signo) end # Check if command # # @param command [Object] the command to check # # @return [TrueClass,FalseClass] true if instance of `Dry::CLI::Command` # # @since 0.1.0 # @api private # # @see .command? def command?(command) CLI.command?(command) end # Generates registry in runtime # # @param &block [Block] configuration for the registry # # @return [Module] module extended with registry abilities and configured with a block # # @since 0.4.0 # @api private def anonymous_registry(&block) registry = Module.new { extend(Dry::CLI::Registry) } if block.arity.zero? registry.instance_eval(&block) else yield(registry) end registry end end # Create a new instance # # @param registry_or_command [Dry::CLI::Registry, Dry::CLI::Command] # a registry or singular command # @param &block [Block] a configuration block for registry # # @return [Dry::CLI] the new instance # @since 0.4.0 def self.CLI(registry_or_command = nil, &block) CLI.new(registry_or_command, &block) end end dry-cli-1.0.0/lib/dry/cli/000077500000000000000000000000001433137403100151655ustar00rootroot00000000000000dry-cli-1.0.0/lib/dry/cli/banner.rb000066400000000000000000000076621433137403100167720ustar00rootroot00000000000000# frozen_string_literal: true require "dry/cli/program_name" module Dry class CLI # Command banner # # @since 0.1.0 # @api private module Banner # Prints command banner # # @param command [Dry::CLI::Command] the command # @param out [IO] standard output # # @since 0.1.0 # @api private def self.call(command, name) [ command_name(name), command_name_and_arguments(command, name), command_description(command), command_subcommands(command), command_arguments(command), command_options(command), command_examples(command, name) ].compact.join("\n") end # @since 0.1.0 # @api private def self.command_name(name) "Command:\n #{name}" end # @since 0.1.0 # @api private def self.command_name_and_arguments(command, name) usage = "\nUsage:\n #{name}#{arguments(command)}" return usage + " | #{name} SUBCOMMAND" if command.subcommands.any? usage end # @since 0.1.0 # @api private def self.command_examples(command, name) return if command.examples.empty? "\nExamples:\n#{command.examples.map { |example| " #{name} #{example}" }.join("\n")}" end # @since 0.1.0 # @api private def self.command_description(command) return if command.description.nil? "\nDescription:\n #{command.description}" end def self.command_subcommands(command) return if command.subcommands.empty? "\nSubcommands:\n#{build_subcommands_list(command.subcommands)}" end # @since 0.1.0 # @api private def self.command_arguments(command) return if command.arguments.empty? "\nArguments:\n#{extended_command_arguments(command)}" end # @since 0.1.0 # @api private def self.command_options(command) "\nOptions:\n#{extended_command_options(command)}" end # @since 0.1.0 # @api private def self.arguments(command) required_arguments = command.required_arguments optional_arguments = command.optional_arguments required = required_arguments.map { |arg| arg.name.upcase }.join(" ") if required_arguments.any? # rubocop:disable Metrics/LineLength optional = optional_arguments.map { |arg| "[#{arg.name.upcase}]" }.join(" ") if optional_arguments.any? # rubocop:disable Metrics/LineLength result = [required, optional].compact " #{result.join(" ")}" unless result.empty? end # @since 0.1.0 # @api private def self.extended_command_arguments(command) command.arguments.map do |argument| " #{argument.name.to_s.upcase.ljust(32)} # #{"REQUIRED " if argument.required?}#{argument.desc}" # rubocop:disable Metrics/LineLength end.join("\n") end # @since 0.1.0 # @api private # def self.extended_command_options(command) result = command.options.map do |option| name = Inflector.dasherize(option.name) name = if option.boolean? "[no-]#{name}" elsif option.array? "#{name}=VALUE1,VALUE2,.." else "#{name}=VALUE" end name = "#{name}, #{option.alias_names.join(", ")}" if option.aliases.any? name = " --#{name.ljust(30)}" name = "#{name} # #{option.desc}" name = "#{name}, default: #{option.default.inspect}" unless option.default.nil? name end result << " --#{"help, -h".ljust(30)} # Print this help" result.join("\n") end def self.build_subcommands_list(subcommands) subcommands.map do |subcommand_name, subcommand| " #{subcommand_name.ljust(32)} # #{subcommand.command.description}" end.join("\n") end end end end dry-cli-1.0.0/lib/dry/cli/command.rb000066400000000000000000000233131433137403100171320ustar00rootroot00000000000000# frozen_string_literal: true require "forwardable" require "dry/cli/option" module Dry class CLI # Base class for commands # # @since 0.1.0 class Command # @since 0.1.0 # @api private def self.inherited(base) super base.class_eval do @_mutex = Mutex.new @description = nil @examples = [] @subcommands = [] @arguments = base.superclass_arguments || [] @options = base.superclass_options || [] end base.extend ClassMethods end # @since 0.1.0 # @api private module ClassMethods # @since 0.1.0 # @api private attr_reader :description # @since 0.1.0 # @api private attr_reader :examples # @since 0.1.0 # @api private attr_reader :arguments # @since 0.1.0 # @api private attr_reader :options # @since 0.7.0 # @api private attr_reader :subcommands # @since 0.7.0 # @api private attr_writer :subcommands end # Set the description of the command # # @param description [String] the description # # @since 0.1.0 # # @example # require "dry/cli" # # class Echo < Dry::CLI::Command # desc "Prints given input" # # def call(*) # # ... # end # end def self.desc(description) @description = description end # Describe the usage of the command # # @param examples [Array] one or more examples # # @since 0.1.0 # # @example # require "dry/cli" # # class Server < Dry::CLI::Command # example [ # " # Basic usage (it uses the bundled server engine)", # "--server=webrick # Force `webrick` server engine", # "--host=0.0.0.0 # Bind to a host", # "--port=2306 # Bind to a port", # "--no-code-reloading # Disable code reloading" # ] # # def call(*) # # ... # end # end # # # $ foo server --help # # # ... # # # # Examples: # # foo server # Basic usage (it uses the bundled server engine) # # foo server --server=webrick # Force `webrick` server engine # # foo server --host=0.0.0.0 # Bind to a host # # foo server --port=2306 # Bind to a port # # foo server --no-code-reloading # Disable code reloading def self.example(*examples) @examples += examples.flatten(1) end # Specify an argument # # @param name [Symbol] the argument name # @param options [Hash] a set of options # # @since 0.1.0 # # @example Optional argument # require "dry/cli" # # class Hello < Dry::CLI::Command # argument :name # # def call(name: nil, **) # if name.nil? # puts "Hello, stranger" # else # puts "Hello, #{name}" # end # end # end # # # $ foo hello # # Hello, stranger # # # $ foo hello Luca # # Hello, Luca # # @example Required argument # require "dry/cli" # # class Hello < Dry::CLI::Command # argument :name, required: true # # def call(name:, **) # puts "Hello, #{name}" # end # end # # # $ foo hello Luca # # Hello, Luca # # # $ foo hello # # ERROR: "foo hello" was called with no arguments # # Usage: "foo hello NAME" # # @example Multiple arguments # require "dry/cli" # # module Generate # class Action < Dry::CLI::Command # argument :app, required: true # argument :action, required: true # # def call(app:, action:, **) # puts "Generating action: #{action} for app: #{app}" # end # end # end # # # $ foo generate action web home # # Generating action: home for app: web # # # $ foo generate action # # ERROR: "foo generate action" was called with no arguments # # Usage: "foo generate action APP ACTION" # # @example Description # require "dry/cli" # # class Hello < Dry::CLI::Command # argument :name, desc: "The name of the person to greet" # # def call(name: nil, **) # # ... # end # end # # # $ foo hello --help # # Command: # # foo hello # # # # Usage: # # foo hello [NAME] # # # # Arguments: # # NAME # The name of the person to greet # # # # Options: # # --help, -h # Print this help def self.argument(name, options = {}) @arguments << Argument.new(name, options) end # Command line option (aka optional argument) # # @param name [Symbol] the param name # @param options [Hash] a set of options # # @since 0.1.0 # # @example Basic usage # require "dry/cli" # # class Console < Dry::CLI::Command # option :engine # # def call(engine: nil, **) # puts "starting console (engine: #{engine || :irb})" # end # end # # # $ foo console # # starting console (engine: irb) # # # $ foo console --engine=pry # # starting console (engine: pry) # # @example List values # require "dry/cli" # # class Console < Dry::CLI::Command # option :engine, values: %w(irb pry ripl) # # def call(engine: nil, **) # puts "starting console (engine: #{engine || :irb})" # end # end # # # $ foo console # # starting console (engine: irb) # # # $ foo console --engine=pry # # starting console (engine: pry) # # # $ foo console --engine=foo # # ERROR: Invalid param provided # # @example Description # require "dry/cli" # # class Console < Dry::CLI::Command # option :engine, desc: "Force a console engine" # # def call(engine: nil, **) # # ... # end # end # # # $ foo console --help # # # ... # # # # Options: # # --engine=VALUE # Force a console engine: (irb/pry/ripl) # # --help, -h # Print this help # # @example Boolean # require "dry/cli" # # class Server < Dry::CLI::Command # option :code_reloading, type: :boolean, default: true # # def call(code_reloading:, **) # puts "staring server (code reloading: #{code_reloading})" # end # end # # # $ foo server # # starting server (code reloading: true) # # # $ foo server --no-code-reloading # # starting server (code reloading: false) # # # $ foo server --help # # # ... # # # # Options: # # --[no]-code-reloading # # @example Aliases # require "dry/cli" # # class Server < Dry::CLI::Command # option :port, aliases: ["-p"] # # def call(options) # puts "staring server (port: #{options.fetch(:port, 2300)})" # end # end # # # $ foo server # # starting server (port: 2300) # # # $ foo server --port=2306 # # starting server (port: 2306) # # # $ foo server -p 2306 # # starting server (port: 2306) # # # $ foo server --help # # # ... # # # # Options: # # --port=VALUE, -p VALUE def self.option(name, options = {}) @options << Option.new(name, options) end # @since 0.1.0 # @api private def self.params @_mutex.synchronize do (@arguments + @options).uniq end end # @since 0.1.0 # @api private def self.default_params params.each_with_object({}) do |param, result| result[param.name] = param.default unless param.default.nil? end end # @since 0.1.0 # @api private def self.required_arguments arguments.select(&:required?) end # @since 0.1.0 # @api private def self.optional_arguments arguments.reject(&:required?) end # @since 0.7.0 # @api private def self.subcommands subcommands end # @since 0.7.0 # @api private def self.superclass_variable_dup(var) if superclass.instance_variable_defined?(var) superclass.instance_variable_get(var).dup end end # @since 0.7.0 # @api private def self.superclass_arguments superclass_variable_dup(:@arguments) end # @since 0.7.0 # @api private def self.superclass_options superclass_variable_dup(:@options) end extend Forwardable delegate %i[ description examples arguments options params default_params required_arguments optional_arguments subcommands ] => "self.class" end end end dry-cli-1.0.0/lib/dry/cli/command_registry.rb000066400000000000000000000124131433137403100210610ustar00rootroot00000000000000# frozen_string_literal: true require "set" module Dry class CLI # Command registry # # @since 0.1.0 # @api private class CommandRegistry # @since 0.1.0 # @api private def initialize @_mutex = Mutex.new @root = Node.new end # @since 0.1.0 # @api private def set(name, command, aliases) @_mutex.synchronize do node = @root name.split(/[[:space:]]/).each do |token| node = node.put(node, token) end node.aliases!(aliases) if command node.leaf!(command) node.subcommands!(command) end nil end end # @since 0.1.0 # @api private # def get(arguments) @_mutex.synchronize do node = @root args = [] names = [] valid_leaf = nil result = LookupResult.new(node, args, names, node.leaf?) arguments.each_with_index do |token, i| tmp = node.lookup(token) if tmp.nil? && valid_leaf result = valid_leaf break elsif tmp.nil? result = LookupResult.new(node, args, names, false) break elsif tmp.leaf? args = arguments[i + 1..-1] names = arguments[0..i] node = tmp result = LookupResult.new(node, args, names, true) valid_leaf = result break unless tmp.children? else names = arguments[0..i] node = tmp result = LookupResult.new(node, args, names, node.leaf?) end end result end end # Node of the registry # # @since 0.1.0 # @api private class Node # @since 0.1.0 # @api private attr_reader :parent # @since 0.1.0 # @api private attr_reader :children # @since 0.1.0 # @api private attr_reader :aliases # @since 0.1.0 # @api private attr_reader :command # @since 0.1.0 # @api private attr_reader :before_callbacks # @since 0.1.0 # @api private attr_reader :after_callbacks # @since 0.1.0 # @api private def initialize(parent = nil) @parent = parent @children = {} @aliases = {} @command = nil @before_callbacks = Chain.new @after_callbacks = Chain.new end # @since 0.1.0 # @api private def put(parent, key) children[key] ||= self.class.new(parent) end # @since 0.1.0 # @api private def lookup(token) children[token] || aliases[token] end # @since 0.1.0 # @api private def leaf!(command) @command = command end # @since 0.7.0 # @api private def subcommands!(command) command_class = command.is_a?(Class) ? command : command.class command_class.subcommands = children end # @since 0.1.0 # @api private def alias!(key, child) @aliases[key] = child end # @since 0.1.0 # @api private def aliases!(aliases) aliases.each do |a| parent.alias!(a, self) end end # @since 0.1.0 # @api private def leaf? !command.nil? end # @since 0.7.0 # @api private def children? children.any? end end # Result of a registry lookup # # @since 0.1.0 # @api private class LookupResult # @since 0.1.0 # @api private attr_reader :names # @since 0.1.0 # @api private attr_reader :arguments # @since 0.1.0 # @api private def initialize(node, arguments, names, found) @node = node @arguments = arguments @names = names @found = found end # @since 0.1.0 # @api private def found? @found end # @since 0.1.0 # @api private def children @node.children end # @since 0.1.0 # @api private def command @node.command end # @since 0.2.0 # @api private def before_callbacks @node.before_callbacks end # @since 0.2.0 # @api private def after_callbacks @node.after_callbacks end end # Callbacks chain # # @since 0.4.0 # @api private class Chain # @since 0.4.0 # @api private attr_reader :chain # @since 0.4.0 # @api private def initialize @chain = Set.new end # @since 0.4.0 # @api private def append(&callback) chain.add(callback) end # @since 0.4.0 # @api private def run(context, *args) chain.each do |callback| context.instance_exec(*args, &callback) end end end end end end dry-cli-1.0.0/lib/dry/cli/errors.rb000066400000000000000000000015421433137403100170300ustar00rootroot00000000000000# frozen_string_literal: true module Dry # General purpose Command Line Interface (CLI) framework for Ruby # # @since 0.1.0 class CLI # @since 0.2.0 class Error < StandardError end # @since 0.2.1 class UnknownCommandError < Error # @since 0.2.1 # @api private def initialize(command_name) super("unknown command: `#{command_name}'") end end # @since 0.2.0 class InvalidCallbackError < Error # @since 0.2.0 # @api private def initialize(callback) message = case callback when Class "expected `#{callback.inspect}' to respond to `#initialize' with arity 0" else "expected `#{callback.inspect}' to respond to `#call'" end super(message) end end end end dry-cli-1.0.0/lib/dry/cli/inflector.rb000066400000000000000000000004601433137403100174770ustar00rootroot00000000000000# frozen_string_literal: true module Dry class CLI # @api private # @since 0.5.0 module Inflector # @api private # @since 0.5.0 def self.dasherize(input) return nil unless input input.to_s.downcase.gsub(/[[[:space:]]_]/, "-") end end end end dry-cli-1.0.0/lib/dry/cli/inline.rb000066400000000000000000000041551433137403100167750ustar00rootroot00000000000000# frozen_string_literal: true require "backports/2.5.0/module/define_method" if RUBY_VERSION < "2.5" module Dry class CLI require "dry/cli" # Inline Syntax (aka DSL) to implement one-file applications # # `dry/cli/inline` is not required by default # and explicit requirement of this file means that # it is expected of abusing global namespace with # methods below # # DSL consists of 5 methods: # `desc`, `example`, `argument`, `option`  # — are similar to methods from Command class # # `run` accepts a block to execute # # @example # require 'bundler/inline' # gemfile { gem 'dry/cli', require: 'dry/cli/inline' } # # desc 'List files in a directory' # argument :path, required: false, desc: '[DIR]' # option :all, aliases: ['a'], type: :boolean # # run do |path: '.', **options| # puts options.key?(:all) ? Dir.entries(path) : Dir.children(path) # end # # # $ ls -a # # $ ls somepath # # $ ls somepath --all # @since 0.6.0 module Inline extend Forwardable # AnonymousCommand # # @since 0.6.0 AnonymousCommand = Class.new(Dry::CLI::Command) # @since 0.6.0 delegate %i[desc example argument option] => AnonymousCommand # The rule of thumb for implementation of run block # is that for every argument from your CLI # you need to specify that as an mandatory argument for a block. # Optional arguments have to have default value as ruby syntax expect them # # @example # argument :one # argument :two, required: false # option :three # # run do |one:, two: 'default', **options| # puts one, two, options.inspect # end # # @since 0.6.0 def run(arguments: ARGV, out: $stdout) command = AnonymousCommand command.define_method(:call) do |**args| yield(**args) end Dry.CLI(command).call(arguments: arguments, out: out) end end end end include Dry::CLI::Inline # rubocop:disable Style/MixinUsage dry-cli-1.0.0/lib/dry/cli/option.rb000066400000000000000000000050111433137403100170170ustar00rootroot00000000000000# frozen_string_literal: true module Dry class CLI # Command line option # # @since 0.1.0 # @api private class Option # @since 0.1.0 # @api private attr_reader :name # @since 0.1.0 # @api private attr_reader :options # @since 0.1.0 # @api private def initialize(name, options = {}) @name = name @options = options end # @since 0.1.0 # @api private def aliases options[:aliases] || [] end # @since 0.1.0 # @api private def desc desc = options[:desc] values ? "#{desc}: (#{values.join("/")})" : desc end # @since 0.1.0 # @api private def required? options[:required] end # @since 0.1.0 # @api private def type options[:type] end # @since 0.1.0 # @api private def values options[:values] end # @since 0.1.0 # @api private def boolean? type == :boolean end # @since 0.3.0 # @api private def array? type == :array end # @since 0.1.0 # @api private def default options[:default] end # @since 0.1.0 # @api private def description_name options[:label] || name.upcase end # @since 0.1.0 # @api private def argument? false end # @since 0.1.0 # @api private # def parser_options dasherized_name = Inflector.dasherize(name) parser_options = [] if boolean? parser_options << "--[no-]#{dasherized_name}" else parser_options << "--#{dasherized_name}=#{name}" parser_options << "--#{dasherized_name} #{name}" end parser_options << Array if array? parser_options << values if values parser_options.unshift(*alias_names) if aliases.any? parser_options << desc if desc parser_options end # @since 0.1.0 # @api private def alias_names aliases .map { |name| name.gsub(/^-{1,2}/, "") } .compact .uniq .map { |name| name.size == 1 ? "-#{name}" : "--#{name}" } .map { |name| boolean? ? name : "#{name} VALUE" } end end # Command line argument # # @since 0.1.0 # @api private class Argument < Option # @since 0.1.0 # @api private def argument? true end end end end dry-cli-1.0.0/lib/dry/cli/parser.rb000066400000000000000000000100011433137403100167760ustar00rootroot00000000000000# frozen_string_literal: true require "optparse" require "dry/cli/program_name" module Dry class CLI # Parse command line arguments and options # # @since 0.1.0 # @api private module Parser # @since 0.1.0 # @api private # def self.call(command, arguments, prog_name) original_arguments = arguments.dup parsed_options = {} OptionParser.new do |opts| command.options.each do |option| opts.on(*option.parser_options) do |value| parsed_options[option.name.to_sym] = value end end opts.on_tail("-h", "--help") do return Result.help end end.parse!(arguments) parsed_options = command.default_params.merge(parsed_options) parse_required_params(command, arguments, prog_name, parsed_options) rescue ::OptionParser::ParseError Result.failure("ERROR: \"#{prog_name}\" was called with arguments \"#{original_arguments.join(" ")}\"") # rubocop:disable Metrics/LineLength end # @since 0.1.0 # @api private # # rubocop:disable Metrics/AbcSize def self.parse_required_params(command, arguments, prog_name, parsed_options) parsed_params = match_arguments(command.arguments, arguments) parsed_required_params = match_arguments(command.required_arguments, arguments) all_required_params_satisfied = command.required_arguments.all? { |param| !parsed_required_params[param.name].nil? } # rubocop:disable Metrics/LineLength unused_arguments = arguments.drop(command.required_arguments.length) unless all_required_params_satisfied parsed_required_params_values = parsed_required_params.values.compact usage = "\nUsage: \"#{prog_name} #{command.required_arguments.map(&:description_name).join(" ")}" # rubocop:disable Metrics/LineLength usage += " | #{prog_name} SUBCOMMAND" if command.subcommands.any? usage += '"' if parsed_required_params_values.empty? return Result.failure("ERROR: \"#{prog_name}\" was called with no arguments#{usage}") else return Result.failure("ERROR: \"#{prog_name}\" was called with arguments #{parsed_required_params_values}#{usage}") # rubocop:disable Metrics/LineLength end end parsed_params.reject! { |_key, value| value.nil? } parsed_options = parsed_options.merge(parsed_params) parsed_options = parsed_options.merge(args: unused_arguments) if unused_arguments.any? Result.success(parsed_options) end # rubocop:enable Metrics/AbcSize def self.match_arguments(command_arguments, arguments) result = {} command_arguments.each_with_index do |cmd_arg, index| if cmd_arg.array? result[cmd_arg.name] = arguments[index..-1] break else result[cmd_arg.name] = arguments.at(index) end end result end # @since 0.1.0 # @api private class Result # @since 0.1.0 # @api private def self.help new(help: true) end # @since 0.1.0 # @api private def self.success(arguments = {}) new(arguments: arguments) end # @since 0.1.0 # @api private def self.failure(error = "Error: Invalid param provided") new(error: error) end # @since 0.1.0 # @api private attr_reader :arguments # @since 0.1.0 # @api private attr_reader :error # @since 0.1.0 # @api private def initialize(arguments: {}, error: nil, help: false) @arguments = arguments @error = error @help = help end # @since 0.1.0 # @api private def error? !error.nil? end # @since 0.1.0 # @api private def help? @help end end end end end dry-cli-1.0.0/lib/dry/cli/program_name.rb000066400000000000000000000006251433137403100201640ustar00rootroot00000000000000# frozen_string_literal: true module Dry class CLI # Program name # # @since 0.1.0 # @api private module ProgramName # @since 0.1.0 # @api private SEPARATOR = " " # @since 0.1.0 # @api private def self.call(names = [], program_name: $PROGRAM_NAME) [File.basename(program_name), names].flatten.join(SEPARATOR) end end end end dry-cli-1.0.0/lib/dry/cli/registry.rb000066400000000000000000000207621433137403100173710ustar00rootroot00000000000000# frozen_string_literal: true require "dry/cli/command_registry" module Dry class CLI # Registry mixin # # @since 0.1.0 module Registry # @since 0.1.0 # @api private def self.extended(base) base.class_eval do @_mutex = Mutex.new @commands = CommandRegistry.new end end # Register a command # # @param name [String] the command name # @param command [NilClass,Dry::CLI::Command] the optional command # @param aliases [Array] an optional list of aliases # @param options [Hash] a set of options # # @since 0.1.0 # # @example Register a command # require "dry/cli" # # module Foo # module Commands # extend Dry::CLI::Registry # # class Hello < Dry::CLI::Command # end # # register "hi", Hello # end # end # # @example Register a command with aliases # require "dry/cli" # # module Foo # module Commands # extend Dry::CLI::Registry # # class Hello < Dry::CLI::Command # end # # register "hello", Hello, aliases: ["hi", "ciao"] # end # end # # @example Register a group of commands # require "dry/cli" # # module Foo # module Commands # extend Dry::CLI::Registry # # module Generate # class App < Dry::CLI::Command # end # # class Action < Dry::CLI::Command # end # end # # register "generate", aliases: ["g"] do |prefix| # prefix.register "app", Generate::App # prefix.register "action", Generate::Action # end # end # end def register(name, command = nil, aliases: [], &block) @commands.set(name, command, aliases) if block_given? prefix = Prefix.new(@commands, name, aliases) if block.arity.zero? prefix.instance_eval(&block) else yield(prefix) end end end # Register a before callback. # # @param command_name [String] the name used for command registration # @param callback [Class, #call] the callback object. If a class is given, # it MUST respond to `#call`. # @param blk [Proc] the callback espressed as a block # # @raise [Dry::CLI::UnknownCommandError] if the command isn't registered # @raise [Dry::CLI::InvalidCallbackError] if the given callback doesn't # implement the required interface # # @since 0.2.0 # # @example # require "dry/cli" # # module Foo # module Commands # extend Dry::CLI::Registry # # class Hello < Dry::CLI::Command # def call(*) # puts "hello" # end # end # # register "hello", Hello # before "hello", -> { puts "I'm about to say.." } # end # end # # @example Register an object as callback # require "dry/cli" # # module Callbacks # class Hello # def call(*) # puts "world" # end # end # end # # module Foo # module Commands # extend Dry::CLI::Registry # # class Hello < Dry::CLI::Command # def call(*) # puts "I'm about to say.." # end # end # # register "hello", Hello # before "hello", Callbacks::Hello.new # end # end # # @example Register a class as callback # require "dry/cli" # # module Callbacks # class Hello # def call(*) # puts "world" # end # end # end # # module Foo # module Commands # extend Dry::CLI::Registry # # class Hello < Dry::CLI::Command # def call(*) # puts "I'm about to say.." # end # end # # register "hello", Hello # before "hello", Callbacks::Hello # end # end def before(command_name, callback = nil, &blk) @_mutex.synchronize do command(command_name).before_callbacks.append(&_callback(callback, blk)) end end # Register an after callback. # # @param command_name [String] the name used for command registration # @param callback [Class, #call] the callback object. If a class is given, # it MUST respond to `#call`. # @param blk [Proc] the callback espressed as a block # # @raise [Dry::CLI::UnknownCommandError] if the command isn't registered # @raise [Dry::CLI::InvalidCallbackError] if the given callback doesn't # implement the required interface # # @since 0.2.0 # # @example # require "dry/cli" # # module Foo # module Commands # extend Dry::CLI::Registry # # class Hello < Dry::CLI::Command # def call(*) # puts "hello" # end # end # # register "hello", Hello # after "hello", -> { puts "world" } # end # end # # @example Register an object as callback # require "dry/cli" # # module Callbacks # class World # def call(*) # puts "world" # end # end # end # # module Foo # module Commands # extend Dry::CLI::Registry # # class Hello < Dry::CLI::Command # def call(*) # puts "hello" # end # end # # register "hello", Hello # after "hello", Callbacks::World.new # end # end # # @example Register a class as callback # require "dry/cli" # # module Callbacks # class World # def call(*) # puts "world" # end # end # end # # module Foo # module Commands # extend Dry::CLI::Registry # # class Hello < Dry::CLI::Command # def call(*) # puts "hello" # end # end # # register "hello", Hello # after "hello", Callbacks::World # end # end def after(command_name, callback = nil, &blk) @_mutex.synchronize do command(command_name).after_callbacks.append(&_callback(callback, blk)) end end # @since 0.1.0 # @api private def get(arguments) @commands.get(arguments) end private COMMAND_NAME_SEPARATOR = " " # @since 0.2.0 # @api private def command(command_name) get(command_name.split(COMMAND_NAME_SEPARATOR)).tap do |result| raise UnknownCommandError, command_name unless result.found? end end # @since 0.2.0 # @api private # def _callback(callback, blk) return blk if blk.respond_to?(:to_proc) case callback when ->(c) { c.respond_to?(:call) } callback.method(:call) when Class begin _callback(callback.new, blk) rescue ArgumentError raise InvalidCallbackError, callback end else raise InvalidCallbackError, callback end end # Command name prefix # # @since 0.1.0 class Prefix # @since 0.1.0 # @api private def initialize(registry, prefix, aliases) @registry = registry @prefix = prefix registry.set(prefix, nil, aliases) end # @since 0.1.0 # # @see Dry::CLI::Registry#register def register(name, command, aliases: []) command_name = "#{prefix} #{name}" registry.set(command_name, command, aliases) end private # @since 0.1.0 # @api private attr_reader :registry # @since 0.1.0 # @api private attr_reader :prefix end end end end dry-cli-1.0.0/lib/dry/cli/usage.rb000066400000000000000000000052311433137403100166170ustar00rootroot00000000000000# frozen_string_literal: true require "dry/cli/program_name" module Dry class CLI # Command(s) usage # # @since 0.1.0 # @api private module Usage # @since 0.1.0 # @api private SUBCOMMAND_BANNER = " [SUBCOMMAND]" ROOT_COMMAND_WITH_SUBCOMMANDS_BANNER = " [ARGUMENT|SUBCOMMAND]" # @since 0.1.0 # @api private def self.call(result) header = "Commands:" max_length, commands = commands_and_arguments(result) commands.map do |banner, node| usage = description(node.command) if node.leaf? "#{justify(banner, max_length, usage)}#{usage}" end.unshift(header).join("\n") end # @since 0.1.0 # @api private def self.commands_and_arguments(result) max_length = 0 ret = commands(result).each_with_object({}) do |(name, node), memo| args = if node.command && node.leaf? && node.children? ROOT_COMMAND_WITH_SUBCOMMANDS_BANNER elsif node.leaf? arguments(node.command) else SUBCOMMAND_BANNER end partial = " #{command_name(result, name)}#{args}" max_length = partial.bytesize if max_length < partial.bytesize memo[partial] = node end [max_length, ret] end # @since 0.1.0 # @api private def self.arguments(command) return unless CLI.command?(command) required_arguments = command.required_arguments optional_arguments = command.optional_arguments required = required_arguments.map { |arg| arg.name.upcase }.join(" ") if required_arguments.any? # rubocop:disable Metrics/LineLength optional = optional_arguments.map { |arg| "[#{arg.name.upcase}]" }.join(" ") if optional_arguments.any? # rubocop:disable Metrics/LineLength result = [required, optional].compact " #{result.join(" ")}" unless result.empty? end # @since 0.1.0 # @api private def self.description(command) return unless CLI.command?(command) " # #{command.description}" unless command.description.nil? end # @since 0.1.0 # @api private def self.justify(string, padding, usage) return string.chomp(" ") if usage.nil? string.ljust(padding + padding / 2) end # @since 0.1.0 # @api private def self.commands(result) result.children.sort_by { |name, _| name } end # @since 0.1.0 # @api private def self.command_name(result, name) ProgramName.call([result.names, name]) end end end end dry-cli-1.0.0/lib/dry/cli/version.rb000066400000000000000000000001511433137403100171740ustar00rootroot00000000000000# frozen_string_literal: true module Dry class CLI # @since 0.1.0 VERSION = "1.0.0" end end dry-cli-1.0.0/project.yml000066400000000000000000000007451433137403100152510ustar00rootroot00000000000000name: dry-cli custom_ci: true codacy_id: 61dd5d070fc74f0cacf575b19d4930e1 rubies: - "3.0" - "2.7" - "2.6" - "2.5" - "2.4" - "jruby" gemspec: authors: ["Luca Guidi"] email: ["me@lucaguidi.com"] summary: "Common framework to build command line interfaces with Ruby" required_ruby_version: ">= 2.4.0" development_dependencies: - [bundler, ">= 1.6", "< 3"] - [rake, "~> 13.0"] - [rspec, "~> 3.7"] - [simplecov, "~> 0.17.1"] - [rubocop, "~> 0.82"] dry-cli-1.0.0/spec/000077500000000000000000000000001433137403100140045ustar00rootroot00000000000000dry-cli-1.0.0/spec/integration/000077500000000000000000000000001433137403100163275ustar00rootroot00000000000000dry-cli-1.0.0/spec/integration/commands_spec.rb000066400000000000000000000006771433137403100215010ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe "Commands" do context "with extra params" do context "when there is a required argument" do context "and there are options" do it "parses both separately" do output = `foo variadic with-mandatory-and-options uno -- due tre --blah` expect(output).to eq("first: uno\nurl: \nmethod: \nUnused Arguments: due, tre, --blah\n") end end end end end dry-cli-1.0.0/spec/integration/inherited_commands_spec.rb000066400000000000000000000013721433137403100235250ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe "Inherited commands" do context "when --help flag" do it "subclass do not" do output = `based --help 2>&1` expected = <<~OUT Commands: based addons APP # Lists your add-ons and attachments based logs APP # Display recent log output based run APP CMD # Run a one-off process inside your app based subrun APP CMD OUT expect(output).to eq(expected) end end it "works for subclasses" do output = `based subrun application_name command_to_run` expect(output).to eq( "Run - App: application_name - Command: command_to_run - Options: {:verbosity=>\"INFO\"}\n" ) end end dry-cli-1.0.0/spec/integration/inline_spec.rb000066400000000000000000000023331433137403100211450ustar00rootroot00000000000000# frozen_string_literal: true require "open3" RSpec.describe "Inline" do context "with command" do let(:cmd) { "inline" } it "shows help" do output = `inline -h` expected_output = <<~OUTPUT Command: inline Usage: inline MANDATORY_ARG [OPTIONAL_ARG] Description: Baz command line interface Arguments: MANDATORY_ARG # REQUIRED Mandatory argument OPTIONAL_ARG # Optional argument (has to have default value in call method) Options: --option-one=VALUE, -1 VALUE # Option one --[no-]boolean-option, -b # Option boolean --option-with-default=VALUE, -d VALUE # Option default, default: "test" --help, -h # Print this help OUTPUT expect(output).to eq(expected_output) end it "with underscored option_one" do output = `inline first_arg -1 test2 -bd test3` expect(output).to eq( "mandatory_arg: first_arg. optional_arg: optional_arg. " \ 'Options: {:option_with_default=>"test3", :option_one=>"test2", :boolean_option=>true}' \ "\n" ) end end end dry-cli-1.0.0/spec/integration/processes_errors_spec.rb000066400000000000000000000014701433137403100232720ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe "Processes Errors" do context "when SignalException" do it "returns correct statuscode" do pid1, pid2, pid15 = Array.new(3).map { Process.spawn("foo generate app SomeAppName") } sleep 0.5 # time to start app Process.kill("HUP", pid1) Process.kill("INT", pid2) Process.kill("TERM", pid15) _, status1 = Process.wait2(pid1) _, status2 = Process.wait2(pid2) _, status15 = Process.wait2(pid15) expect(status1.exitstatus).to eq(129) expect(status2.exitstatus).to eq(130) expect(status15.exitstatus).to eq(143) end end context "when Pipe Exception" do it "handles EPIPE without stacktrace" do output = `infinites writer | head -n 3` expect(output).to eq("0\n1\n2\n") end end end dry-cli-1.0.0/spec/integration/rendering_spec.rb000066400000000000000000000024671433137403100216540ustar00rootroot00000000000000# frozen_string_literal: true require "open3" RSpec.describe "Rendering" do it "prints available commands for unknown command" do _, stderr, = Open3.capture3("foo unknown") expected = <<~DESC Commands: foo assets [SUBCOMMAND] foo callbacks DIR # Command with callbacks foo console # Starts Foo console foo db [SUBCOMMAND] foo destroy [SUBCOMMAND] foo exec TASK [DIRS] # Execute a task foo generate [SUBCOMMAND] foo greeting [RESPONSE] foo hello # Print a greeting foo new PROJECT # Generate a new Foo project foo root-command [ARGUMENT|SUBCOMMAND] # Root command with arguments and subcommands foo routes # Print routes foo server # Start Foo server (only for development) foo sub [SUBCOMMAND] foo variadic [SUBCOMMAND] foo version # Print Foo version DESC expect(stderr).to eq(expected) end end dry-cli-1.0.0/spec/integration/single_command_spec.rb000066400000000000000000000043241433137403100226500ustar00rootroot00000000000000# frozen_string_literal: true require "open3" RSpec.describe "Single command" do context "with command" do let(:cmd) { "baz" } it "shows usage" do _, stderr, = Open3.capture3("baz") expect(stderr).to eq( "ERROR: \"#{cmd}\" was called with no arguments\nUsage: \"#{cmd} MANDATORY_ARG\"\n" ) end it "shows help" do output = `baz -h` expected_output = <<~OUTPUT Command: #{cmd} Usage: #{cmd} MANDATORY_ARG [OPTIONAL_ARG] Description: Baz command line interface Arguments: MANDATORY_ARG # REQUIRED Mandatory argument OPTIONAL_ARG # Optional argument (has to have default value in call method) Options: --option-one=VALUE, -1 VALUE # Option one --[no-]boolean-option, -b # Option boolean --option-with-default=VALUE, -d VALUE # Option default, default: "test" --help, -h # Print this help OUTPUT expect(output).to eq(expected_output) end it "with option_one" do output = `baz first_arg --option-one=test2` expect(output).to eq( "mandatory_arg: first_arg. optional_arg: optional_arg. " \ "Options: {:option_with_default=>\"test\", :option_one=>\"test2\"}\n" ) end it "with combination of aliases" do output = `baz first_arg -bd test3` expect(output).to eq( "mandatory_arg: first_arg. optional_arg: optional_arg. " \ "Options: {:option_with_default=>\"test3\", :boolean_option=>true}\n" ) end end context "root command with arguments and subcommands" do it "with arguments" do output = `foo root-command "hello world"` expected = <<~DESC I'm a root-command argument:hello world I'm a root-command option: DESC expect(output).to eq(expected) end it "with options" do output = `foo root-command "hello world" --root-command-option="bye world"` expected = <<~DESC I'm a root-command argument:hello world I'm a root-command option:bye world DESC expect(output).to eq(expected) end end end dry-cli-1.0.0/spec/integration/subcommands_spec.rb000066400000000000000000000031241433137403100222010ustar00rootroot00000000000000# frozen_string_literal: true require "open3" RSpec.describe "Subcommands" do context "works with params" do it "with help param" do output = `foo generate model --help` expected = <<~DESC Command: foo generate model Usage: foo generate model MODEL Description: Generate a model Arguments: MODEL # REQUIRED Model name (eg. `user`) Options: --[no-]skip-migration # Skip migration, default: false --help, -h # Print this help Examples: foo generate model user # Generate `User` entity, `UserRepository` repository, and the migration foo generate model user --skip-migration # Generate `User` entity and `UserRepository` repository DESC expect(output).to eq(expected) end end context "works with root command subcommands" do it "with params" do output = `foo root-command sub-command "hello world"` expected = <<~DESC I'm a root-command sub-command argument:hello world I'm a root-command sub-command option: DESC expect(output).to eq(expected) end it "with options" do option = '--root-command-sub-command-option="bye world"' output = `foo root-command sub-command "hello world" #{option}` expected = <<~DESC I'm a root-command sub-command argument:hello world I'm a root-command sub-command option:bye world DESC expect(output).to eq(expected) end end end dry-cli-1.0.0/spec/integration/third_party_gems_spec.rb000066400000000000000000000015221433137403100232320ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe "Third-party gems" do it "allows to add callbacks as a block" do output = `foo callbacks . --url=https://hanamirb.test` expected = <<~OUTPUT before command callback Foo::Webpack::CLI::CallbacksCommand {:url=>"https://hanamirb.test", :dir=>"."} before callback (class), 2 arg(s): {:url=>"https://hanamirb.test", :dir=>"."} before callback (object), 2 arg(s): {:url=>"https://hanamirb.test", :dir=>"."} dir: ., url: "https://hanamirb.test" after command callback Foo::Webpack::CLI::CallbacksCommand {:url=>"https://hanamirb.test", :dir=>"."} after callback (class), 2 arg(s): {:url=>"https://hanamirb.test", :dir=>"."} after callback (object), 2 arg(s): {:url=>"https://hanamirb.test", :dir=>"."} OUTPUT expect(output).to eq(expected) end end dry-cli-1.0.0/spec/spec_helper.rb000066400000000000000000000005061433137403100166230ustar00rootroot00000000000000# frozen_string_literal: true require_relative "support/coverage" $LOAD_PATH.unshift "lib" require "dry/cli" require_relative "./support/rspec" %w[support].each do |dir| Dir[File.join(Dir.pwd, "spec", dir, "**", "*.rb")].each do |file| unless file["support/warnings.rb"] require_relative file end end end dry-cli-1.0.0/spec/support/000077500000000000000000000000001433137403100155205ustar00rootroot00000000000000dry-cli-1.0.0/spec/support/coverage.rb000066400000000000000000000004631433137403100176430ustar00rootroot00000000000000# 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-cli-1.0.0/spec/support/files.rb000066400000000000000000000005411433137403100171470ustar00rootroot00000000000000# frozen_string_literal: true require "rspec/expectations" RSpec::Matchers.define :have_content do |expected| match do |actual| File.read(actual) == expected end failure_message do |actual| "expected that `#{actual}' would be have content '#{expected}', but it has '#{File.read(actual)}'" # rubocop:disable Metrics/LineLength end end dry-cli-1.0.0/spec/support/fixtures/000077500000000000000000000000001433137403100173715ustar00rootroot00000000000000dry-cli-1.0.0/spec/support/fixtures/based000077500000000000000000000027051433137403100204010ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true $LOAD_PATH.unshift __dir__ + "/../../lib" require "dry/cli" require_relative "../../../lib/dry/cli/command" module Based extend Dry::CLI::Registry class Base < Dry::CLI::Command desc "Base description" argument :app, desc: "Application name", type: :string, required: true option :verbosity, desc: "Verbosity level", type: :string, default: "INFO" example "Base example" def call(app:, **options) puts "#{self.class} - App: #{app} - Options: #{options.inspect}" end end class Run < Base desc "Run a one-off process inside your app" argument :cmd, desc: "Command to execute", required: true def call(app:, cmd:, **options) puts "Run - App: #{app} - Command: #{cmd} - Options: #{options.inspect}" end end class SubRun < Run end class Logs < Base desc "Display recent log output" option :num, desc: "number of lines to display" option :tail, desc: "continually stream log", type: :boolean example [ "APP_NAME", "APP_NAME --num=50", "APP_NAME --tail" ] end class Addons < Base desc "Lists your add-ons and attachments" option :json, desc: "return add-ons in json format", type: :boolean, default: false example [ "APP_NAME", "APP_NAME --json" ] end register "run", Run register "subrun", SubRun register "logs", Logs register "addons", Addons end Dry.CLI(Based).call dry-cli-1.0.0/spec/support/fixtures/baz000077500000000000000000000002471433137403100200760ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true $LOAD_PATH.unshift __dir__ + "/../../lib" require "dry/cli" require_relative "baz_command" Dry.CLI(Baz::CLI).call dry-cli-1.0.0/spec/support/fixtures/baz_command.rb000066400000000000000000000014421433137403100221710ustar00rootroot00000000000000# frozen_string_literal: true module Baz class CLI < Dry::CLI::Command desc "Baz command line interface" argument :mandatory_arg, required: true, aliases: %w[m], desc: "Mandatory argument" argument :optional_arg, aliases: %w[o], desc: "Optional argument (has to have default value in call method)" option :option_one, aliases: %w[1], desc: "Option one" option :boolean_option, aliases: %w[b], desc: "Option boolean", type: :boolean option :option_with_default, aliases: %w[d], desc: "Option default", default: "test" def call(mandatory_arg:, optional_arg: "optional_arg", **options) puts "mandatory_arg: #{mandatory_arg}. " \ "optional_arg: #{optional_arg}. " \ "Options: #{options.inspect}" end end end dry-cli-1.0.0/spec/support/fixtures/foo000077500000000000000000000451251433137403100201110ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true # rubocop:disable Layout/LineLength $LOAD_PATH.unshift __dir__ + "/../../../lib" require "dry/cli" module Foo module CLI module Commands extend Dry::CLI::Registry class Command < Dry::CLI::Command end module Assets class Precompile < Dry::CLI::Command desc "Precompile assets for deployment" example [ "FOO_ENV=production # Precompile assets for production environment" ] def call(*); end end end class Console < Dry::CLI::Command desc "Starts Foo console" option :engine, desc: "Force a console engine", values: %w[irb pry ripl] example [ " # Uses the bundled engine", "--engine=pry # Force to use Pry" ] def call(engine: nil, **) puts "console - engine: #{engine}" end end module DB class Apply < Dry::CLI::Command desc "Migrate, dump the SQL schema, and delete the migrations (experimental)" def call(*); end end class Console < Dry::CLI::Command desc "Starts a database console" def call(*); end end class Create < Dry::CLI::Command desc "Create the database (only for development/test)" def call(*); end end class Drop < Dry::CLI::Command desc "Drop the database (only for development/test)" def call(*); end end class Migrate < Dry::CLI::Command desc "Migrate the database" argument :version, desc: "The target version of the migration (see `foo db version`)" example [ " # Migrate to the last version", "20170721120747 # Migrate to a specific version" ] def call(*); end end class Prepare < Dry::CLI::Command desc "Drop, create, and migrate the database (only for development/test)" def call(*); end end class Version < Dry::CLI::Command desc "Print the current migrated version" def call(*); end end class Rollback < Dry::CLI::Command desc "Rollback the database" argument :steps, desc: "Number of versions to rollback", default: 1 def call(steps:, **) puts steps end end end module Destroy class Action < Dry::CLI::Command desc "Destroy an action from app" example [ "web home#index # Basic usage", "admin users#index # Destroy from `admin` app" ] argument :app, required: true, desc: "The application name (eg. `web`)" argument :action, required: true, desc: "The action name (eg. `home#index`)" def call(app:, action:, **) puts "destroy action - app: #{app}, action: #{action}" end end class App < Dry::CLI::Command desc "Destroy an app" argument :app, required: true, desc: "The application name (eg. `web`)" example [ "admin # Destroy `admin` app" ] def call(*); end end class Mailer < Dry::CLI::Command desc "Destroy a mailer" argument :mailer, required: true, desc: "The mailer name (eg. `welcome`)" example [ "welcome # Destroy `WelcomeMailer` mailer" ] def call(*); end end class Migration < Dry::CLI::Command desc "Destroy a migration" argument :migration, required: true, desc: "The migration name (eg. `create_users`)" example [ "create_users # Destroy `db/migrations/20170721120747_create_users.rb`" ] def call(*); end end class Model < Dry::CLI::Command desc "Destroy a model" argument :model, required: true, desc: "The model name (eg. `user`)" example [ "user # Destroy `User` entity and `UserRepository` repository" ] def call(*); end end end module Generate class Action < Dry::CLI::Command desc "Generate an action for app" example [ "web home#index # Basic usage", "admin home#index # Generate for `admin` app", "web home#index --url=/ # Specify URL", "web sessions#destroy --method=GET # Specify HTTP method", "web books#create --skip-view # Skip view and template" ] argument :app, required: true, desc: "The application name (eg. `web`)" argument :action, required: true, desc: "The action name (eg. `home#index`)" option :url, desc: "The action URL" option :method, desc: "The action HTTP method" option :skip_view, type: :boolean, default: false, desc: "Skip view and template" def call(app:, action:, **options) puts "generate action - app: #{app}, action: #{action}, options: #{options.inspect}" end end class App < Dry::CLI::Command desc "Generate an app" argument :app, required: true, desc: "The application name (eg. `web`)" option :application_base_url, desc: "The app base URL (eg. `/api/v1`)" example [ "admin # Generate `admin` app", "api --application-base-url=/api/v1 # Generate `api` app and mount at `/api/v1`" ] def call(*) loop { sleep(0.1) } end end class Mailer < Dry::CLI::Command desc "Generate a mailer" argument :mailer, required: true, desc: "The mailer name (eg. `welcome`)" option :from, desc: "The default `from` field of the mail" option :to, desc: "The default `to` field of the mail" option :subject, desc: "The mail subject" example [ "welcome # Basic usage", 'welcome --from="noreply@example.com" # Generate with default `from` value', 'announcement --to="users@example.com" # Generate with default `to` value', 'forgot_password --subject="Your password reset" # Generate with default `subject`' ] def call(*); end end class Migration < Dry::CLI::Command desc "Generate a migration" argument :migration, required: true, desc: "The migration name (eg. `create_users`)" example [ "create_users # Generate `db/migrations/20170721120747_create_users.rb`" ] def call(*); end end class Model < Dry::CLI::Command desc "Generate a model" argument :model, required: true, desc: "Model name (eg. `user`)" option :skip_migration, type: :boolean, default: false, desc: "Skip migration" example [ "user # Generate `User` entity, `UserRepository` repository, and the migration", "user --skip-migration # Generate `User` entity and `UserRepository` repository" ] def call(model:, **) puts "generate model - model: #{model}" end end class Secret < Dry::CLI::Command desc "Generate session secret" argument :app, desc: "The application name (eg. `web`)" example [ " # Prints secret (eg. `6fad60e21f3f6bfcaf8e56cdb0f835d644b4892c3badc58328126812429bf073`)", "web # Prints session secret (eg. `WEB_SESSIONS_SECRET=6fad60e21f3f6bfcaf8e56cdb0f835d644b4892c3badc58328126812429bf073`)" ] def call(app: nil, **) puts "generate secret - app: #{app}" end end end class New < Dry::CLI::Command desc "Generate a new Foo project" argument :project, required: true option :database, desc: "Database (sqlite/postgres/mysql)", default: "sqlite", aliases: ["-d", "--db"] option :application_name, desc: "App name", default: "web" option :application_base_url, desc: "App base URL", default: "/" option :template, desc: "Template engine (erb/haml/slim)", default: "erb" option :test, desc: "Project testing framework (minitest/rspec)", default: "minitest" option :foo_head, desc: "Use Foo HEAD (true/false)", type: :boolean, default: false example [ "bookshelf # Basic usage", "bookshelf --test=rspec # Setup RSpec testing framework", "bookshelf --database=postgres # Setup Postgres database", "bookshelf --template=slim # Setup Slim template engine", "bookshelf --foo-head # Use Foo HEAD" ] def call(project:, **) puts "new - project: #{project}" end end class Routes < Dry::CLI::Command desc "Print routes" def call(*); end end class Server < Dry::CLI::Command desc "Start Foo server (only for development)" option :server, desc: "Force a server engine (eg, webrick, puma, thin, etc..)" option :host, desc: "The host address to bind to" option :port, desc: "The port to run the server on", aliases: ["-p"] option :debug, desc: "Turn on debug output" option :warn, desc: "Turn on warnings" option :daemonize, desc: "Daemonize the server" option :pid, desc: "Path to write a pid file after daemonize" option :code_reloading, desc: "Code reloading", type: :boolean, default: true example [ " # Basic usage (it uses the bundled server engine)", "--server=webrick # Force `webrick` server engine", "--host=0.0.0.0 # Bind to a host", "--port=2306 # Bind to a port", "--no-code-reloading # Disable code reloading" ] def call(options) puts "server - #{options.inspect}" end end class Version < Dry::CLI::Command desc "Print Foo version" def call(*) puts "v1.0.0" end end class Exec < Dry::CLI::Command desc "Execute a task" argument :task, desc: "Task to execute", type: :string, required: true argument :dirs, desc: "Directories", type: :array, required: false def call(task:, dirs: [], **) puts "exec - Task: #{task} - Directories: #{dirs.inspect}" end end class Hello < Dry::CLI::Command def call(*) raise NotImplementedError end end class Greeting < Dry::CLI::Command argument :response, default: "Hello World" option :person def call(response:, **options) puts "response: #{response}, person: #{options[:person]}" end end class VariadicArguments < Dry::CLI::Command desc "accept multiple arguments at the end of the command" def call(**options) puts "Unused Arguments: #{options[:args].join(", ")}" end end class MandatoryAndVariadicArguments < Dry::CLI::Command desc "require one command and accept multiple unused arguments" argument :first, desc: "mandatory first argument", required: true def call(first:, **options) puts "first: #{first}" puts "Unused Arguments: #{options[:args].join(", ")}" end end class MandatoryOptionsAndVariadicArguments < Dry::CLI::Command desc "require one command, accept options and multiple unused arguments" argument :first, desc: "mandatory first argument", required: true option :url, desc: "The action URL" option :method, desc: "The action HTTP method" def call(first:, **options) puts "first: #{first}" puts "url: #{options[:url]}" puts "method: #{options[:method]}" puts "Unused Arguments: #{options[:args].join(", ")}" end end module Sub class Command < Dry::CLI::Command def call(*) raise NotImplementedError end end end class RootCommand < Dry::CLI::Command desc "Root command with arguments and subcommands" argument :root_command_argument, desc: "Root command argument", required: true option :root_command_option, desc: "Root command option" def call(**params) puts "I'm a root-command argument:#{params[:root_command_argument]}" puts "I'm a root-command option:#{params[:root_command_option]}" end end module RootCommands class SubCommand < Dry::CLI::Command desc "Root command sub command" argument :root_command_sub_command_argument, desc: "Root command sub command argument", required: true option :root_command_sub_command_option, desc: "Root command sub command option" def call(**params) puts "I'm a root-command sub-command argument:#{params[:root_command_sub_command_argument]}" puts "I'm a root-command sub-command option:#{params[:root_command_sub_command_option]}" end end end end end end Foo::CLI::Commands.register "assets precompile", Foo::CLI::Commands::Assets::Precompile Foo::CLI::Commands.register "console", Foo::CLI::Commands::Console Foo::CLI::Commands.register "db" do |prefix| prefix.register "apply", Foo::CLI::Commands::DB::Apply prefix.register "console", Foo::CLI::Commands::DB::Console prefix.register "create", Foo::CLI::Commands::DB::Create prefix.register "drop", Foo::CLI::Commands::DB::Drop prefix.register "migrate", Foo::CLI::Commands::DB::Migrate prefix.register "prepare", Foo::CLI::Commands::DB::Prepare prefix.register "version", Foo::CLI::Commands::DB::Version prefix.register "rollback", Foo::CLI::Commands::DB::Rollback end Foo::CLI::Commands.register "destroy", aliases: ["d"] do |prefix| prefix.register "action", Foo::CLI::Commands::Destroy::Action prefix.register "app", Foo::CLI::Commands::Destroy::App prefix.register "mailer", Foo::CLI::Commands::Destroy::Mailer prefix.register "migration", Foo::CLI::Commands::Destroy::Migration prefix.register "model", Foo::CLI::Commands::Destroy::Model end Foo::CLI::Commands.register "generate", aliases: ["g"] do |prefix| prefix.register "action", Foo::CLI::Commands::Generate::Action prefix.register "app", Foo::CLI::Commands::Generate::App prefix.register "mailer", Foo::CLI::Commands::Generate::Mailer prefix.register "migration", Foo::CLI::Commands::Generate::Migration prefix.register "model", Foo::CLI::Commands::Generate::Model prefix.register "secret", Foo::CLI::Commands::Generate::Secret end Foo::CLI::Commands.register "new", Foo::CLI::Commands::New Foo::CLI::Commands.register "routes", Foo::CLI::Commands::Routes Foo::CLI::Commands.register "server", Foo::CLI::Commands::Server, aliases: ["s"] Foo::CLI::Commands.register "version", Foo::CLI::Commands::Version, aliases: ["v", "-v", "--version"] Foo::CLI::Commands.register "exec", Foo::CLI::Commands::Exec Foo::CLI::Commands.register "hello", Foo::CLI::Commands::Hello Foo::CLI::Commands.register "greeting", Foo::CLI::Commands::Greeting Foo::CLI::Commands.register "sub command", Foo::CLI::Commands::Sub::Command Foo::CLI::Commands.register "root-command", Foo::CLI::Commands::RootCommand Foo::CLI::Commands.register "root-command sub-command", Foo::CLI::Commands::RootCommands::SubCommand Foo::CLI::Commands.register "variadic default", Foo::CLI::Commands::VariadicArguments Foo::CLI::Commands.register "variadic with-mandatory", Foo::CLI::Commands::MandatoryAndVariadicArguments Foo::CLI::Commands.register "variadic with-mandatory-and-options", Foo::CLI::Commands::MandatoryOptionsAndVariadicArguments module Foo module Webpack module CLI class Generate < Dry::CLI::Command desc "Generate webpack configuration" option :apps, desc: "Generate webpack apps", type: :array def call(apps: [], **) puts "generate webpack. Apps: #{apps}" end end class Hello < Dry::CLI::Command desc "Print a greeting" def call(*) puts "hello from webpack" end end class SubCommand < Dry::CLI::Command desc "Override a subcommand" def call(**) puts "override from webpack" end end class CallbacksCommand < Dry::CLI::Command desc "Command with callbacks" argument :dir, required: true option :url def call(dir:, url: nil, **) puts "dir: #{dir}, url: #{url.inspect}" end end end end end Foo::CLI::Commands.register "generate webpack", Foo::Webpack::CLI::Generate Foo::CLI::Commands.register "hello", Foo::Webpack::CLI::Hello Foo::CLI::Commands.register "sub command", Foo::Webpack::CLI::SubCommand Foo::CLI::Commands.register "callbacks", Foo::Webpack::CLI::CallbacksCommand # we need to be sure that command will not override with nil command Foo::CLI::Commands.register "generate webpack", nil Foo::CLI::Commands.before("callbacks") do |args| puts "before command callback #{self.class.name} #{args.inspect}" end Foo::CLI::Commands.after("callbacks") do |args| puts "after command callback #{self.class.name} #{args.inspect}" end module Callbacks class BeforeClass def call(args) puts "before callback (class), #{count(args)} arg(s): #{args.inspect}" end private def count(args) args.count end end class AfterClass def call(args) puts "after callback (class), #{count(args)} arg(s): #{args.inspect}" end private def count(args) args.count end end class Before def call(args) puts "before callback (object), #{count(args)} arg(s): #{args.inspect}" end private def count(args) args.count end end class After def call(args) puts "after callback (object), #{count(args)} arg(s): #{args.inspect}" end private def count(args) args.count end end end # rubocop:enable Layout/LineLength Foo::CLI::Commands.before("callbacks", Callbacks::BeforeClass) Foo::CLI::Commands.after("callbacks", Callbacks::AfterClass) Foo::CLI::Commands.before("callbacks", Callbacks::Before.new) Foo::CLI::Commands.after("callbacks", Callbacks::After.new) cli = Dry::CLI.new(Foo::CLI::Commands) cli.call dry-cli-1.0.0/spec/support/fixtures/infinites000077500000000000000000000010271433137403100213070ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true $LOAD_PATH.unshift __dir__ + "/../../lib" require "dry/cli" module Infinites extend Dry::CLI::Registry class Reader < Dry::CLI::Command def call(*) while (a = $stdin.gets) puts "From stdin: #{a}" sleep 2 end end end class Writer < Dry::CLI::Command def call(*) (0..Float::INFINITY).each do |i| $stdout.puts i end end end register "reader", Reader register "writer", Writer end Dry.CLI(Infinites).call dry-cli-1.0.0/spec/support/fixtures/inline000077500000000000000000000014771433137403100206060ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true $LOAD_PATH.unshift __dir__ + "/../../../lib" require "dry/cli" require_relative "../../../lib/dry/cli/inline" desc "Baz command line interface" argument :mandatory_arg, required: true, aliases: %w[m], desc: "Mandatory argument" argument :optional_arg, aliases: %w[o], desc: "Optional argument (has to have default value in call method)" option :option_one, aliases: %w[1], desc: "Option one" option :boolean_option, aliases: %w[b], desc: "Option boolean", type: :boolean option :option_with_default, aliases: %w[d], desc: "Option default", default: "test" run do |mandatory_arg:, optional_arg: "optional_arg", **options| puts "mandatory_arg: #{mandatory_arg}. " \ "optional_arg: #{optional_arg}. " \ "Options: #{options.inspect}" end dry-cli-1.0.0/spec/support/fixtures/registry.rb000066400000000000000000000003461433137403100215710ustar00rootroot00000000000000# frozen_string_literal: true module Bar module CLI module Commands extend Dry::CLI::Registry class Alpha < Dry::CLI::Command def call(*); end end register "alpha", Alpha end end end dry-cli-1.0.0/spec/support/fixtures/shared_commands.rb000066400000000000000000000377421433137403100230620ustar00rootroot00000000000000# frozen_string_literal: true # rubocop:disable Metrics/LineLength module Commands class Command < Dry::CLI::Command end module Assets class Precompile < Dry::CLI::Command desc "Precompile assets for deployment" example [ "FOO_ENV=production # Precompile assets for production environment" ] def call(*); end end end class Console < Dry::CLI::Command desc "Starts Foo console" option :engine, desc: "Force a console engine", values: %w[irb pry ripl] example [ " # Uses the bundled engine", "--engine=pry # Force to use Pry" ] def call(engine: nil, **) puts "console - engine: #{engine}" end end module DB class Apply < Dry::CLI::Command desc "Migrate, dump the SQL schema, and delete the migrations (experimental)" def call(*); end end class Console < Dry::CLI::Command desc "Starts a database console" def call(*); end end class Create < Dry::CLI::Command desc "Create the database (only for development/test)" def call(*); end end class Drop < Dry::CLI::Command desc "Drop the database (only for development/test)" def call(*); end end class Migrate < Dry::CLI::Command desc "Migrate the database" argument :version, desc: "The target version of the migration (see `foo db version`)" example [ " # Migrate to the last version", "20170721120747 # Migrate to a specific version" ] def call(*); end end class Prepare < Dry::CLI::Command desc "Drop, create, and migrate the database (only for development/test)" def call(*); end end class Version < Dry::CLI::Command desc "Print the current migrated version" def call(*); end end class Rollback < Dry::CLI::Command desc "Rollback the database" argument :steps, desc: "Number of versions to rollback", default: 1 def call(steps:, **) puts steps end end end module Destroy class Action < Dry::CLI::Command desc "Destroy an action from app" example [ "web home#index # Basic usage", "admin users#index # Destroy from `admin` app" ] argument :app, required: true, desc: "The application name (eg. `web`)" argument :action, required: true, desc: "The action name (eg. `home#index`)" def call(app:, action:, **) puts "destroy action - app: #{app}, action: #{action}" end end class App < Dry::CLI::Command desc "Destroy an app" argument :app, required: true, desc: "The application name (eg. `web`)" example [ "admin # Destroy `admin` app" ] def call(*); end end class Mailer < Dry::CLI::Command desc "Destroy a mailer" argument :mailer, required: true, desc: "The mailer name (eg. `welcome`)" example [ "welcome # Destroy `WelcomeMailer` mailer" ] def call(*); end end class Migration < Dry::CLI::Command desc "Destroy a migration" argument :migration, required: true, desc: "The migration name (eg. `create_users`)" example [ "create_users # Destroy `db/migrations/20170721120747_create_users.rb`" ] def call(*); end end class Model < Dry::CLI::Command desc "Destroy a model" argument :model, required: true, desc: "The model name (eg. `user`)" example [ "user # Destroy `User` entity and `UserRepository` repository" ] def call(*); end end end module Generate class Action < Dry::CLI::Command desc "Generate an action for app" example [ "web home#index # Basic usage", "admin home#index # Generate for `admin` app", "web home#index --url=/ # Specify URL", "web sessions#destroy --method=GET # Specify HTTP method", "web books#create --skip-view # Skip view and template" ] argument :app, required: true, desc: "The application name (eg. `web`)" argument :action, required: true, desc: "The action name (eg. `home#index`)" option :url, desc: "The action URL" option :method, desc: "The action HTTP method" option :skip_view, type: :boolean, default: false, desc: "Skip view and template" def call(app:, action:, **options) puts "generate action - app: #{app}, action: #{action}, options: #{options.inspect}" end end class App < Dry::CLI::Command desc "Generate an app" argument :app, required: true, desc: "The application name (eg. `web`)" option :application_base_url, desc: "The app base URL (eg. `/api/v1`)" example [ "admin # Generate `admin` app", "api --application-base-url=/api/v1 # Generate `api` app and mount at `/api/v1`" ] def call(*); end end class Mailer < Dry::CLI::Command desc "Generate a mailer" argument :mailer, required: true, desc: "The mailer name (eg. `welcome`)" option :from, desc: "The default `from` field of the mail" option :to, desc: "The default `to` field of the mail" option :subject, desc: "The mail subject" example [ "welcome # Basic usage", 'welcome --from="noreply@example.com" # Generate with default `from` value', 'announcement --to="users@example.com" # Generate with default `to` value', 'forgot_password --subject="Your password reset" # Generate with default `subject`' ] def call(*); end end class Migration < Dry::CLI::Command desc "Generate a migration" argument :migration, required: true, desc: "The migration name (eg. `create_users`)" example [ "create_users # Generate `db/migrations/20170721120747_create_users.rb`" ] def call(*); end end class Model < Dry::CLI::Command desc "Generate a model" argument :model, required: true, desc: "Model name (eg. `user`)" option :skip_migration, type: :boolean, default: false, desc: "Skip migration" example [ "user # Generate `User` entity, `UserRepository` repository, and the migration", "user --skip-migration # Generate `User` entity and `UserRepository` repository" ] def call(model:, **) puts "generate model - model: #{model}" end end class Secret < Dry::CLI::Command desc "Generate session secret" argument :app, desc: "The application name (eg. `web`)" example [ " # Prints secret (eg. `6fad60e21f3f6bfcaf8e56cdb0f835d644b4892c3badc58328126812429bf073`)", "web # Prints session secret (eg. `WEB_SESSIONS_SECRET=6fad60e21f3f6bfcaf8e56cdb0f835d644b4892c3badc58328126812429bf073`)" ] def call(app: nil, **) puts "generate secret - app: #{app}" end end end class New < Dry::CLI::Command desc "Generate a new Foo project" argument :project, required: true option :database, desc: "Database (sqlite/postgres/mysql)", default: "sqlite", aliases: ["-d", "--db"] option :application_name, desc: "App name", default: "web" option :application_base_url, desc: "App base URL", default: "/" option :template, desc: "Template engine (erb/haml/slim)", default: "erb" option :test, desc: "Project testing framework (minitest/rspec)", default: "minitest" option :foo_head, desc: "Use Foo HEAD (true/false)", type: :boolean, default: false example [ "bookshelf # Basic usage", "bookshelf --test=rspec # Setup RSpec testing framework", "bookshelf --database=postgres # Setup Postgres database", "bookshelf --template=slim # Setup Slim template engine", "bookshelf --foo-head # Use Foo HEAD" ] def call(project:, **) puts "new - project: #{project}" end end class Routes < Dry::CLI::Command desc "Print routes" def call(*); end end class Server < Dry::CLI::Command desc "Start Foo server (only for development)" option :server, desc: "Force a server engine (eg, webrick, puma, thin, etc..)" option :host, desc: "The host address to bind to" option :port, desc: "The port to run the server on", aliases: ["-p", "p", "--p"] option :debug, desc: "Turn on debug output" option :warn, desc: "Turn on warnings" option :daemonize, desc: "Daemonize the server" option :pid, desc: "Path to write a pid file after daemonize" option :code_reloading, desc: "Code reloading", type: :boolean, default: true option :deps, desc: "List of extra dependencies", type: :array, default: %w[dep1 dep2] example [ " # Basic usage (it uses the bundled server engine)", "--server=webrick # Force `webrick` server engine", "--host=0.0.0.0 # Bind to a host", "--port=2306 # Bind to a port", "--no-code-reloading # Disable code reloading" ] def call(**options) puts "server - #{options.inspect}" end end class Version < Dry::CLI::Command desc "Print Foo version" def call(*) puts "v1.0.0" end end class Exec < Dry::CLI::Command desc "Execute a task" argument :task, desc: "Task to execute", type: :string, required: true argument :dirs, desc: "Directories", type: :array, required: false def call(task:, dirs: [], **) puts "exec - Task: #{task} - Directories: #{dirs.inspect}" end end class Hello < Dry::CLI::Command def call(*) raise NotImplementedError end end class Greeting < Dry::CLI::Command argument :response, default: "Hello World" option :person def call(response:, **options) puts "response: #{response}, person: #{options[:person]}" end end class VariadicArguments < Dry::CLI::Command desc "accept multiple arguments at the end of the command" def call(**options) puts "Unused Arguments: #{options[:args].join(", ")}" end end class MandatoryAndVariadicArguments < Dry::CLI::Command desc "require one command and accept multiple unused arguments" argument :first, desc: "mandatory first argument", required: true def call(first:, **options) puts "first: #{first}" puts "Unused Arguments: #{options[:args].join(", ")}" end end class MandatoryOptionsAndVariadicArguments < Dry::CLI::Command desc "require one command, accept options and multiple unused arguments" argument :first, desc: "mandatory first argument", required: true option :url, desc: "The action URL" option :method, desc: "The action HTTP method" def call(first:, **options) puts "first: #{first}" puts "url: #{options[:url]}" puts "method: #{options[:method]}" puts "Unused Arguments: #{options[:args].join(", ")}" end end class OptionsWithAliases < Dry::CLI::Command desc "Accepts options with aliases" option :url, desc: "The action URL", aliases: %w[-u u --u] option :flag, desc: "The flag", type: :boolean, aliases: %w[f] option :opt, desc: "The opt", type: :boolean, aliases: %w[o], default: false def call(**options) puts "options with aliases - #{options.inspect}" end end module Sub class Command < Dry::CLI::Command def call(*) raise NotImplementedError end end end class RootCommand < Dry::CLI::Command desc "Root command with arguments and subcommands" argument :root_command_argument, desc: "Root command argument", required: true option :root_command_option, desc: "Root command option" def call(**params) puts "I'm a root-command argument:#{params[:root_command_argument]}" puts "I'm a root-command option:#{params[:root_command_option]}" end end module RootCommands class SubCommand < Dry::CLI::Command desc "Root command sub command" argument :root_command_sub_command_argument, desc: "Root command sub command argument", required: true option :root_command_sub_command_option, desc: "Root command sub command option" def call(**params) puts "I'm a root-command sub-command argument:#{params[:root_command_sub_command_argument]}" puts "I'm a root-command sub-command option:#{params[:root_command_sub_command_option]}" end end class SubCommand2 < Dry::CLI::Command desc "Root command sub command" argument :root_command_sub_command_argument, desc: "Root command sub command argument", required: true option :root_command_sub_command_option, desc: "Root command sub command option" def call(**params) puts "I'm a root-command sub-command argument:#{params[:root_command_sub_command_argument]}" puts "I'm a root-command sub-command option:#{params[:root_command_sub_command_option]}" end end end class InitializedCommand < Dry::CLI::Command attr_reader :prop def initialize(prop:) @prop = prop end def call(**_params) puts "The value of prop is #{prop}" end end end module Webpack module CLI class Generate < Dry::CLI::Command desc "Generate webpack configuration" option :apps, desc: "Generate webpack apps", type: :array def call(apps: [], **) puts "generate webpack. Apps: #{apps}" end end class Hello < Dry::CLI::Command desc "Print a greeting" def call(*) puts "hello from webpack" end end class SubCommand < Dry::CLI::Command desc "Override a subcommand" def call(**) puts "override from webpack" end end class CallbacksCommand < Dry::CLI::Command desc "Command with callbacks" argument :dir, required: true option :url def call(dir:, url: nil, **) puts "dir: #{dir}, url: #{url.inspect}" end end end end module Callbacks class BeforeClass def call(args) puts "before callback (class), #{count(args)} arg(s): #{args.inspect}" end private def count(args) args.count end end class AfterClass def call(args) puts "after callback (class), #{count(args)} arg(s): #{args.inspect}" end private def count(args) args.count end end class Before def call(args) puts "before callback (object), #{count(args)} arg(s): #{args.inspect}" end private def count(args) args.count end end class After def call(args) puts "after callback (object), #{count(args)} arg(s): #{args.inspect}" end private def count(args) args.count end end end module InheritedCommands class Base < Dry::CLI::Command desc "Base description" argument :app, desc: "Application name", type: :string, required: true option :verbosity, desc: "Verbosity level", type: :string, default: "INFO" example "Base example" def call(app:, **options) puts "Base - App: #{app} - Options: #{options.inspect}" end end class Run < Base desc "Run a one-off process inside your app" argument :cmd, desc: "Command to execute", required: true def call(app:, cmd:, **options) puts "#{self.class} - App: #{app} - Command: #{cmd} - Options: #{options.inspect}" end end class SubRun < Run end class Logs < Base desc "Display recent log output" option :num, desc: "number of lines to display" option :tail, desc: "continually stream log", type: :boolean example [ "APP_NAME", "APP_NAME --num=50", "APP_NAME --tail" ] def call(app:, **options) puts "Logs - App: #{app} - Options: #{options.inspect}" end end class Addons < Base desc "Lists your add-ons and attachments" option :json, desc: "return add-ons in json format", type: :boolean, default: false example [ "APP_NAME", "APP_NAME --json" ] end end # rubocop:enable Metrics/LineLength dry-cli-1.0.0/spec/support/fixtures/with_block.rb000077500000000000000000000070031433137403100220460ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true $LOAD_PATH.unshift __dir__ + "/../../lib" require "dry/cli" require_relative "shared_commands" WithBlock = Dry::CLI.new do |cli| cli.register "assets precompile", Commands::Assets::Precompile cli.register "console", Commands::Console cli.register "new", Commands::New cli.register "routes", Commands::Routes cli.register "server", Commands::Server, aliases: ["s"] cli.register "version", Commands::Version, aliases: ["v", "-v", "--version"] cli.register "exec", Commands::Exec cli.register "hello", Commands::Hello cli.register "greeting", Commands::Greeting cli.register "sub command", Commands::Sub::Command cli.register "with-initializer", Commands::InitializedCommand.new(prop: "prop_val") cli.register "root-command", Commands::RootCommand do |prefix| prefix.register "sub-command", Commands::RootCommands::SubCommand end cli.register "options-with-aliases", Commands::OptionsWithAliases cli.register "variadic default", Commands::VariadicArguments cli.register "variadic with-mandatory", Commands::MandatoryAndVariadicArguments cli.register "variadic with-mandatory-and-options", Commands::MandatoryOptionsAndVariadicArguments cli.register "generate webpack", Webpack::CLI::Generate cli.register "hello", Webpack::CLI::Hello cli.register "sub command", Webpack::CLI::SubCommand cli.register "callbacks", Webpack::CLI::CallbacksCommand cli.register "db" do |prefix| prefix.register "apply", Commands::DB::Apply prefix.register "console", Commands::DB::Console prefix.register "create", Commands::DB::Create prefix.register "drop", Commands::DB::Drop prefix.register "migrate", Commands::DB::Migrate prefix.register "prepare", Commands::DB::Prepare prefix.register "version", Commands::DB::Version prefix.register "rollback", Commands::DB::Rollback end cli.register "destroy", aliases: ["d"] do |prefix| prefix.register "action", Commands::Destroy::Action prefix.register "app", Commands::Destroy::App prefix.register "mailer", Commands::Destroy::Mailer prefix.register "migration", Commands::Destroy::Migration prefix.register "model", Commands::Destroy::Model end cli.register "generate", aliases: ["g"] do |prefix| prefix.register "action", Commands::Generate::Action prefix.register "app", Commands::Generate::App prefix.register "mailer", Commands::Generate::Mailer prefix.register "migration", Commands::Generate::Migration prefix.register "model", Commands::Generate::Model prefix.register "secret", Commands::Generate::Secret end # we need to be sure that command will not override with nil command cli.register "generate webpack", nil cli.before("callbacks") do |args| puts "before command callback #{self.class.name} #{args.inspect}" end cli.after("callbacks") do |args| puts "after command callback #{self.class.name} #{args.inspect}" end cli.register "inherited", aliases: ["i"] do |prefix| prefix.register "run", InheritedCommands::Run prefix.register "subrun", InheritedCommands::SubRun prefix.register "logs", InheritedCommands::Logs prefix.register "addons", InheritedCommands::Addons end cli.before "callbacks", Callbacks::BeforeClass cli.after "callbacks", Callbacks::AfterClass cli.before "callbacks", Callbacks::Before.new cli.after "callbacks", Callbacks::After.new end dry-cli-1.0.0/spec/support/fixtures/with_registry.rb000066400000000000000000000074141433137403100226270ustar00rootroot00000000000000# frozen_string_literal: true $LOAD_PATH.unshift __dir__ + "/../../lib" require "dry/cli" require_relative "shared_commands" module Foo module CLI module Commands extend Dry::CLI::Registry register "assets precompile", ::Commands::Assets::Precompile register "console", ::Commands::Console register "db" do |prefix| prefix.register "apply", ::Commands::DB::Apply prefix.register "console", ::Commands::DB::Console prefix.register "create", ::Commands::DB::Create prefix.register "drop", ::Commands::DB::Drop prefix.register "migrate", ::Commands::DB::Migrate prefix.register "prepare", ::Commands::DB::Prepare prefix.register "version", ::Commands::DB::Version prefix.register "rollback", ::Commands::DB::Rollback end register "destroy", aliases: ["d"] do |prefix| prefix.register "action", ::Commands::Destroy::Action prefix.register "app", ::Commands::Destroy::App prefix.register "mailer", ::Commands::Destroy::Mailer prefix.register "migration", ::Commands::Destroy::Migration prefix.register "model", ::Commands::Destroy::Model end register "generate", aliases: ["g"] do |prefix| prefix.register "action", ::Commands::Generate::Action prefix.register "app", ::Commands::Generate::App prefix.register "mailer", ::Commands::Generate::Mailer prefix.register "migration", ::Commands::Generate::Migration prefix.register "model", ::Commands::Generate::Model prefix.register "secret", ::Commands::Generate::Secret end register "inherited", aliases: ["i"] do |prefix| prefix.register "run", ::InheritedCommands::Run prefix.register "subrun", ::InheritedCommands::SubRun prefix.register "logs", ::InheritedCommands::Logs prefix.register "addons", ::InheritedCommands::Addons end register "new", ::Commands::New register "routes", ::Commands::Routes register "server", ::Commands::Server, aliases: ["s"] register "version", ::Commands::Version, aliases: ["v", "-v", "--version"] register "exec", ::Commands::Exec register "hello", ::Commands::Hello register "greeting", ::Commands::Greeting register "sub command", ::Commands::Sub::Command register "with-initializer", ::Commands::InitializedCommand.new(prop: "prop_val") register "root-command", ::Commands::RootCommand register "root-command sub-command", ::Commands::RootCommands::SubCommand register "options-with-aliases", ::Commands::OptionsWithAliases register "variadic default", ::Commands::VariadicArguments register "variadic with-mandatory", ::Commands::MandatoryAndVariadicArguments register "variadic with-mandatory-and-options", ::Commands::MandatoryOptionsAndVariadicArguments # rubocop:disable Metrics/LineLength register "generate webpack", ::Webpack::CLI::Generate register "hello", ::Webpack::CLI::Hello register "sub command", ::Webpack::CLI::SubCommand register "callbacks", ::Webpack::CLI::CallbacksCommand register "generate webpack", nil before("callbacks") do |args| puts "before command callback #{self.class.name} #{args.inspect}" end after("callbacks") do |args| puts "after command callback #{self.class.name} #{args.inspect}" end before("callbacks", ::Callbacks::BeforeClass) after("callbacks", ::Callbacks::AfterClass) before("callbacks", ::Callbacks::Before.new) after("callbacks", ::Callbacks::After.new) end end end WithRegistry = Dry::CLI.new(Foo::CLI::Commands) dry-cli-1.0.0/spec/support/fixtures/with_zero_arity_block.rb000077500000000000000000000063121433137403100243170ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true $LOAD_PATH.unshift __dir__ + "/../../lib" require "dry/cli" require_relative "shared_commands" WithZeroArityBlock = Dry.CLI do register "assets precompile", Commands::Assets::Precompile register "console", Commands::Console register "new", Commands::New register "routes", Commands::Routes register "server", Commands::Server, aliases: ["s"] register "version", Commands::Version, aliases: ["v", "-v", "--version"] register "exec", Commands::Exec register "hello", Commands::Hello register "greeting", Commands::Greeting register "sub command", Commands::Sub::Command register "with-initializer", Commands::InitializedCommand.new(prop: "prop_val") register "root-command", Commands::RootCommand register "root-command" do register "sub-command", Commands::RootCommands::SubCommand end register "options-with-aliases", Commands::OptionsWithAliases register "variadic default", Commands::VariadicArguments register "variadic with-mandatory", Commands::MandatoryAndVariadicArguments register "variadic with-mandatory-and-options", Commands::MandatoryOptionsAndVariadicArguments register "generate webpack", Webpack::CLI::Generate register "hello", Webpack::CLI::Hello register "sub command", Webpack::CLI::SubCommand register "callbacks", Webpack::CLI::CallbacksCommand register "db" do register "apply", Commands::DB::Apply register "console", Commands::DB::Console register "create", Commands::DB::Create register "drop", Commands::DB::Drop register "migrate", Commands::DB::Migrate register "prepare", Commands::DB::Prepare register "version", Commands::DB::Version register "rollback", Commands::DB::Rollback end register "destroy", aliases: ["d"] do register "action", Commands::Destroy::Action register "app", Commands::Destroy::App register "mailer", Commands::Destroy::Mailer register "migration", Commands::Destroy::Migration register "model", Commands::Destroy::Model end register "generate", aliases: ["g"] do register "action", Commands::Generate::Action register "app", Commands::Generate::App register "mailer", Commands::Generate::Mailer register "migration", Commands::Generate::Migration register "model", Commands::Generate::Model register "secret", Commands::Generate::Secret end register "inherited", aliases: ["i"] do register "run", InheritedCommands::Run register "subrun", InheritedCommands::SubRun register "logs", InheritedCommands::Logs register "addons", InheritedCommands::Addons end # we need to be sure that command will not override with nil command register "generate webpack", nil before("callbacks") do |args| puts "before command callback #{self.class.name} #{args.inspect}" end after("callbacks") do |args| puts "after command callback #{self.class.name} #{args.inspect}" end before "callbacks", Callbacks::BeforeClass after "callbacks", Callbacks::AfterClass before "callbacks", Callbacks::Before.new after "callbacks", Callbacks::After.new end dry-cli-1.0.0/spec/support/helpers.rb000066400000000000000000000013311433137403100175050ustar00rootroot00000000000000# frozen_string_literal: true module RSpec module Support module Helpers def capture_output require "stringio" output = StringIO.new original_stdout = $stdout $stdout = output yield output.string rescue SystemExit output.string ensure $stdout = original_stdout end def capture_error require "stringio" error = StringIO.new original_stderr = $stderr $stderr = error yield error.string rescue SystemExit error.string ensure $stderr = original_stderr end end end end RSpec.configure do |config| config.include(RSpec::Support::Helpers) end dry-cli-1.0.0/spec/support/path.rb000066400000000000000000000007131433137403100170020ustar00rootroot00000000000000# frozen_string_literal: true module RSpec module Support module Path def self.included(base) base.class_eval do before do @original_path = ENV["PATH"] ENV["PATH"] = __dir__ + "/fixtures:" + ENV["PATH"] end after do ENV["PATH"] = @original_path end end end end end end RSpec.configure do |config| config.include(RSpec::Support::Path) end dry-cli-1.0.0/spec/support/rspec.rb000066400000000000000000000011231433137403100171560ustar00rootroot00000000000000# frozen_string_literal: true RSpec.configure do |config| 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 config.filter_run_when_matching :focus config.disable_monkey_patching! config.warnings = true config.default_formatter = "doc" if config.files_to_run.one? config.profile_examples = 10 config.order = :random Kernel.srand config.seed end dry-cli-1.0.0/spec/support/rspec_options.rb000066400000000000000000000007351433137403100207410ustar00rootroot00000000000000RSpec.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-cli-1.0.0/spec/support/shared_examples/000077500000000000000000000000001433137403100206645ustar00rootroot00000000000000dry-cli-1.0.0/spec/support/shared_examples/commands.rb000066400000000000000000000261721433137403100230220ustar00rootroot00000000000000# frozen_string_literal: true # rubocop:disable Metrics/LineLength RSpec.shared_examples "Commands" do |cli| let(:cli) { cli } let(:cmd) { File.basename($PROGRAM_NAME, File.extname($PROGRAM_NAME)) } it "calls basic command" do output = capture_output { cli.call(arguments: ["version"]) } expect(output).to eq("v1.0.0\n") end it "calls basic command with alias" do output = capture_output { cli.call(arguments: ["v"]) } expect(output).to eq("v1.0.0\n") output = capture_output { cli.call(arguments: ["-v"]) } expect(output).to eq("v1.0.0\n") output = capture_output { cli.call(arguments: ["--version"]) } expect(output).to eq("v1.0.0\n") end it "calls subcommand via intermediate alias" do output = capture_output { cli.call(arguments: %w[g secret web]) } expect(output).to eq("generate secret - app: web\n") end context "works with params" do it "without params" do output = capture_output { cli.call(arguments: ["server"]) } expect(output).to eq("server - {:code_reloading=>true, :deps=>[\"dep1\", \"dep2\"]}\n") end it "a param using space" do output = capture_output { cli.call(arguments: %w[server --server thin]) } expect(output).to eq("server - {:code_reloading=>true, :deps=>[\"dep1\", \"dep2\"], :server=>\"thin\"}\n") end it "a param using equal sign" do output = capture_output { cli.call(arguments: %w[server --host=localhost]) } expect(output).to eq("server - {:code_reloading=>true, :deps=>[\"dep1\", \"dep2\"], :host=>\"localhost\"}\n") end it "a param using alias" do output = capture_output { cli.call(arguments: %w[options-with-aliases -u test]) } expect(output).to eq("options with aliases - {:opt=>false, :url=>\"test\"}\n") output = capture_output { cli.call(arguments: %w[options-with-aliases -utest]) } expect(output).to eq("options with aliases - {:opt=>false, :url=>\"test\"}\n") output = capture_output { cli.call(arguments: %w[options-with-aliases -f -u test]) } expect(output).to eq("options with aliases - {:opt=>false, :flag=>true, :url=>\"test\"}\n") output = capture_output { cli.call(arguments: %w[options-with-aliases -o]) } expect(output).to eq("options with aliases - {:opt=>true}\n") output = capture_output { cli.call(arguments: %w[options-with-aliases -of]) } expect(output).to eq("options with aliases - {:opt=>true, :flag=>true}\n") end it "a param with unknown param" do error = capture_error { cli.call(arguments: %w[server --unknown 1234]) } expect(error).to eq("ERROR: \"rspec server\" was called with arguments \"--unknown 1234\"\n") end it "with boolean param" do output = capture_output { cli.call(arguments: ["server"]) } expect(output).to eq("server - {:code_reloading=>true, :deps=>[\"dep1\", \"dep2\"]}\n") output = capture_output { cli.call(arguments: %w[server --no-code-reloading]) } expect(output).to eq("server - {:code_reloading=>false, :deps=>[\"dep1\", \"dep2\"]}\n") end context "with array param" do it "allows to omit optional array argument" do output = capture_output { cli.call(arguments: %w[exec test]) } expect(output).to eq("exec - Task: test - Directories: []\n") end it "capture all the remaining arguments" do output = capture_output { cli.call(arguments: %w[exec test api admin]) } expect(output).to eq("exec - Task: test - Directories: [\"api\", \"admin\"]\n") end end context "with supported values" do context "and with supported value passed" do it "call the command with the option" do output = capture_output { cli.call(arguments: %w[console --engine=pry]) } expect(output).to eq("console - engine: pry\n") end end context "and with an unknown value passed" do it "prints error" do error = capture_error { cli.call(arguments: %w[console --engine=unknown]) } expect(error).to eq("ERROR: \"rspec console\" was called with arguments \"--engine=unknown\"\n") # rubocop:disable Metrics/LineLength end end end it "with help param" do output = capture_output { cli.call(arguments: %w[server --help]) } expected = <<~DESC Command: #{cmd} server Usage: #{cmd} server Description: Start Foo server (only for development) Options: --server=VALUE # Force a server engine (eg, webrick, puma, thin, etc..) --host=VALUE # The host address to bind to --port=VALUE, -p VALUE # The port to run the server on --debug=VALUE # Turn on debug output --warn=VALUE # Turn on warnings --daemonize=VALUE # Daemonize the server --pid=VALUE # Path to write a pid file after daemonize --[no-]code-reloading # Code reloading, default: true --deps=VALUE1,VALUE2,.. # List of extra dependencies, default: ["dep1", "dep2"] --help, -h # Print this help Examples: #{cmd} server # Basic usage (it uses the bundled server engine) #{cmd} server --server=webrick # Force `webrick` server engine #{cmd} server --host=0.0.0.0 # Bind to a host #{cmd} server --port=2306 # Bind to a port #{cmd} server --no-code-reloading # Disable code reloading DESC expect(output).to eq(expected) end context "with required params" do it "can be used" do output = capture_output { cli.call(arguments: %w[new bookshelf]) } expect(output).to eq("new - project: bookshelf\n") end it "with unknown param" do error = capture_error { cli.call(arguments: %w[new bookshelf --unknown 1234]) } expect(error).to eq("ERROR: \"rspec new\" was called with arguments \"bookshelf --unknown 1234\"\n") # rubocop:disable Metrics/LineLength end it "no required" do output = capture_output { cli.call(arguments: %w[generate secret web]) } expect(output).to eq("generate secret - app: web\n") output = capture_output { cli.call(arguments: %w[generate secret]) } expect(output).to eq("generate secret - app: \n") end it "an error is displayed if there aren't required params" do error = capture_error { cli.call(arguments: ["new"]) } expected_error = <<~DESC ERROR: "#{cmd} new" was called with no arguments Usage: "#{cmd} new PROJECT" DESC expect(error).to eq(expected_error) end it "with default value and using options" do output = capture_output { cli.call(arguments: %w[greeting --person=Alfonso]) } expect(output).to eq("response: Hello World, person: Alfonso\n") output = capture_output { cli.call(arguments: %w[greeting bye --person=Alfonso]) } expect(output).to eq("response: bye, person: Alfonso\n") end end context "with extra params" do it "is accessible via options[:args]" do output = capture_output { cli.call(arguments: %w[variadic default bar baz]) } expect(output).to eq("Unused Arguments: bar, baz\n") end context "when there is a required argument" do it "parses both separately" do output = capture_output { cli.call(arguments: ["variadic", "with-mandatory", cmd, "bar", "baz"]) } expect(output).to eq("first: #{cmd}\nUnused Arguments: bar, baz\n") end context "and there are options" do it "parses both separately" do output = capture_output { cli.call(arguments: ["variadic", "with-mandatory-and-options", cmd, "bar", "baz"]) } expect(output).to eq("first: #{cmd}\nurl: \nmethod: \nUnused Arguments: bar, baz\n") output = capture_output { cli.call(arguments: ["variadic", "with-mandatory-and-options", "--url=root", "--method=index", cmd, "bar", "baz"]) } expect(output).to eq("first: #{cmd}\nurl: root\nmethod: index\nUnused Arguments: bar, baz\n") output = capture_output { cli.call(arguments: %w[variadic with-mandatory-and-options uno -- due tre --blah]) } expect(output).to eq("first: uno\nurl: \nmethod: \nUnused Arguments: due, tre, --blah\n") end end end end end context "works with command with arguments and subcommands" do it "shows help" do output = capture_output { cli.call(arguments: %w[root-command -h]) } expected = <<~DESC Command: #{cmd} root-command Usage: #{cmd} root-command ROOT_COMMAND_ARGUMENT | #{cmd} root-command SUBCOMMAND Description: Root command with arguments and subcommands Subcommands: sub-command # Root command sub command Arguments: ROOT_COMMAND_ARGUMENT # REQUIRED Root command argument Options: --root-command-option=VALUE # Root command option --help, -h # Print this help DESC expect(output).to eq(expected) output = capture_output { cli.call(arguments: %w[root-command --help]) } expect(output).to eq(expected) end context "works with params" do it "without params" do error = capture_error { cli.call(arguments: %w[root-command]) } expected = <<~DESC ERROR: "rspec root-command" was called with no arguments Usage: "rspec root-command ROOT_COMMAND_ARGUMENT | rspec root-command SUBCOMMAND" DESC expect(error).to eq(expected) end it "with params" do output = capture_output { cli.call(arguments: ["root-command", '"hello world"']) } expected = <<~DESC I'm a root-command argument:"hello world" I'm a root-command option: DESC expect(output).to eq(expected) end it "with option using space" do output = capture_output { cli.call(arguments: [ "root-command", '"hello world"', "--root-command-option", '"bye world"' ]) } expected = <<~DESC I'm a root-command argument:"hello world" I'm a root-command option:"bye world" DESC expect(output).to eq(expected) end it "with option using equal sign" do output = capture_output { cli.call(arguments: [ "root-command", '"hello world"', '--root-command-option="bye world"' ]) } expected = <<~DESC I'm a root-command argument:"hello world" I'm a root-command option:"bye world" DESC expect(output).to eq(expected) end end end context "works with instances of commands" do it "executes instance" do output = capture_output { cli.call(arguments: %w[with-initializer]) } expect(output).to eq("The value of prop is prop_val\n") end end end # rubocop:enable Metrics/LineLength dry-cli-1.0.0/spec/support/shared_examples/inherited_commands.rb000066400000000000000000000145641433137403100250570ustar00rootroot00000000000000# frozen_string_literal: true RSpec.shared_examples "Inherited commands" do |cli| let(:cli) { cli } let(:cmd) { File.basename($PROGRAM_NAME, File.extname($PROGRAM_NAME)) } context "with help flag" do it "shows subcommands " do error = capture_error { cli.call(arguments: %w[i]) } expected = <<~DESC Commands: rspec i addons APP # Lists your add-ons and attachments rspec i logs APP # Display recent log output rspec i run APP CMD # Run a one-off process inside your app rspec i subrun APP CMD DESC expect(error).to eq(expected) end it "shows run's help" do output = capture_output { cli.call(arguments: %w[i run --help]) } expected = <<~DESC Command: #{cmd} i run Usage: #{cmd} i run APP CMD Description: Run a one-off process inside your app Arguments: APP # REQUIRED Application name CMD # REQUIRED Command to execute Options: --verbosity=VALUE # Verbosity level, default: "INFO" --help, -h # Print this help DESC expect(output).to eq(expected) end it "shows subrun's help" do output = capture_output { cli.call(arguments: %w[i subrun --help]) } expected = <<~DESC Command: #{cmd} i subrun Usage: #{cmd} i subrun APP CMD Arguments: APP # REQUIRED Application name CMD # REQUIRED Command to execute Options: --verbosity=VALUE # Verbosity level, default: "INFO" --help, -h # Print this help DESC expect(output).to eq(expected) end it "shows addon's help" do output = capture_output { cli.call(arguments: %w[i addons --help]) } expected = <<~DESC Command: #{cmd} i addons Usage: #{cmd} i addons APP Description: Lists your add-ons and attachments Arguments: APP # REQUIRED Application name Options: --verbosity=VALUE # Verbosity level, default: "INFO" --[no-]json # return add-ons in json format, default: false --help, -h # Print this help Examples: #{cmd} i addons APP_NAME #{cmd} i addons APP_NAME --json DESC expect(output).to eq(expected) end it "shows log's help" do output = capture_output { cli.call(arguments: %w[i logs --help]) } expected = <<~DESC Command: #{cmd} i logs Usage: #{cmd} i logs APP Description: Display recent log output Arguments: APP # REQUIRED Application name Options: --verbosity=VALUE # Verbosity level, default: "INFO" --num=VALUE # number of lines to display --[no-]tail # continually stream log --help, -h # Print this help Examples: #{cmd} i logs APP_NAME #{cmd} i logs APP_NAME --num=50 #{cmd} i logs APP_NAME --tail DESC expect(output).to eq(expected) end end context "with inherited arguments" do it "run expects APP_NAME and CMD_NAME" do output = capture_output { cli.call(arguments: %w[i run application_name command_name]) } expect(output).to include("App: application_name - Command: command_name") end it "subrun expects APP_NAME and CMD_NAME" do output = capture_output { cli.call(arguments: %w[i subrun application_name command_name]) } expect(output).to include("App: application_name - Command: command_name") end it "addons expects APP_NAME" do output = capture_output { cli.call(arguments: %w[i addons application_name]) } expect(output).to include("App: application_name") end it "logs expects APP_NAME" do output = capture_output { cli.call(arguments: %w[i logs application_name]) } expect(output).to include("App: application_name") end end context "with inherited options" do it "run has default verbosity_level" do output = capture_output { cli.call(arguments: %w[i run application_name command_name]) } expect(output).to include('Options: {:verbosity=>"INFO"}') end it "subrun has default verbosity_level too" do output = capture_output { cli.call(arguments: %w[i subrun application_name command_name]) } expect(output).to include('Options: {:verbosity=>"INFO"}') end it "addons has verbosity_level set to debug" do output = capture_output do cli.call(arguments: %w[i addons application_name --verbosity=DEBUG]) end expect(output).to include("Options: {:verbosity=>\"DEBUG\", :json=>false}") end it "logs has verbosity_level set to WARNING" do output = capture_output do cli.call(arguments: %w[i logs application_name --verbosity=WARNING]) end expect(output).to include("Options: {:verbosity=>\"WARNING\"}") end end context "with description" do it "subclass does not inherit it" do output = capture_output { cli.call(arguments: %w[i run --help]) } expect(output).not_to include("Base description") end it "subclasses subclass does not inherit it" do output = capture_output { cli.call(arguments: %w[i subrun --help]) } expect(output).not_to include("Run a one-off process inside your app") end end context "with example" do it "subclass does not inherit it" do output = capture_output { cli.call(arguments: %w[i logs --help]) } expect(output).not_to include("Base example") end end context "with multible subclasses of Dry::CLI::Command" do it "subclasses do nod share options" do output = capture_output { cli.call(arguments: %w[i logs application_name --tail --num=50]) } expect(output).not_to include(":json=>false}") output = capture_output { cli.call(arguments: %w[i addons application_name]) } expect(output).not_to include(":tail=>false}") end end end dry-cli-1.0.0/spec/support/shared_examples/rendering.rb000066400000000000000000000200401433137403100231620ustar00rootroot00000000000000# frozen_string_literal: true RSpec.shared_examples "Rendering" do |cli| let(:cli) { cli } let(:cmd) { File.basename($PROGRAM_NAME, File.extname($PROGRAM_NAME)) } it "prints required params" do error = capture_error { cli.call } expected = <<~DESC Commands: #{cmd} assets [SUBCOMMAND] #{cmd} callbacks DIR # Command with callbacks #{cmd} console # Starts Foo console #{cmd} db [SUBCOMMAND] #{cmd} destroy [SUBCOMMAND] #{cmd} exec TASK [DIRS] # Execute a task #{cmd} generate [SUBCOMMAND] #{cmd} greeting [RESPONSE] #{cmd} hello # Print a greeting #{cmd} inherited [SUBCOMMAND] #{cmd} new PROJECT # Generate a new Foo project #{cmd} options-with-aliases # Accepts options with aliases #{cmd} root-command [ARGUMENT|SUBCOMMAND] # Root command with arguments and subcommands #{cmd} routes # Print routes #{cmd} server # Start Foo server (only for development) #{cmd} sub [SUBCOMMAND] #{cmd} variadic [SUBCOMMAND] #{cmd} version # Print Foo version #{cmd} with-initializer DESC expect(error).to eq(expected) end it "prints required params with labels" do error = capture_error { cli.call(arguments: ["destroy"]) } expected = <<~DESC Commands: #{cmd} destroy action APP ACTION # Destroy an action from app #{cmd} destroy app APP # Destroy an app #{cmd} destroy mailer MAILER # Destroy a mailer #{cmd} destroy migration MIGRATION # Destroy a migration #{cmd} destroy model MODEL # Destroy a model DESC expect(error).to eq(expected) end it "prints available commands for unknown subcommand" do error = capture_error { cli.call(arguments: %w[generate unknown]) } expected = <<~DESC Commands: #{cmd} generate action APP ACTION # Generate an action for app #{cmd} generate app APP # Generate an app #{cmd} generate mailer MAILER # Generate a mailer #{cmd} generate migration MIGRATION # Generate a migration #{cmd} generate model MODEL # Generate a model #{cmd} generate secret [APP] # Generate session secret #{cmd} generate webpack # Generate webpack configuration DESC expect(error).to eq(expected) end it "prints available commands for unknown command" do error = capture_error { cli.call(arguments: ["unknown"]) } expected = <<~DESC Commands: #{cmd} assets [SUBCOMMAND] #{cmd} callbacks DIR # Command with callbacks #{cmd} console # Starts Foo console #{cmd} db [SUBCOMMAND] #{cmd} destroy [SUBCOMMAND] #{cmd} exec TASK [DIRS] # Execute a task #{cmd} generate [SUBCOMMAND] #{cmd} greeting [RESPONSE] #{cmd} hello # Print a greeting #{cmd} inherited [SUBCOMMAND] #{cmd} new PROJECT # Generate a new Foo project #{cmd} options-with-aliases # Accepts options with aliases #{cmd} root-command [ARGUMENT|SUBCOMMAND] # Root command with arguments and subcommands #{cmd} routes # Print routes #{cmd} server # Start Foo server (only for development) #{cmd} sub [SUBCOMMAND] #{cmd} variadic [SUBCOMMAND] #{cmd} version # Print Foo version #{cmd} with-initializer DESC expect(error).to eq(expected) end it "prints first level" do error = capture_error { cli.call } expected = <<~DESC Commands: #{cmd} assets [SUBCOMMAND] #{cmd} callbacks DIR # Command with callbacks #{cmd} console # Starts Foo console #{cmd} db [SUBCOMMAND] #{cmd} destroy [SUBCOMMAND] #{cmd} exec TASK [DIRS] # Execute a task #{cmd} generate [SUBCOMMAND] #{cmd} greeting [RESPONSE] #{cmd} hello # Print a greeting #{cmd} inherited [SUBCOMMAND] #{cmd} new PROJECT # Generate a new Foo project #{cmd} options-with-aliases # Accepts options with aliases #{cmd} root-command [ARGUMENT|SUBCOMMAND] # Root command with arguments and subcommands #{cmd} routes # Print routes #{cmd} server # Start Foo server (only for development) #{cmd} sub [SUBCOMMAND] #{cmd} variadic [SUBCOMMAND] #{cmd} version # Print Foo version #{cmd} with-initializer DESC expect(error).to eq(expected) end it "prints subcommand's commands" do error = capture_error { cli.call(arguments: ["generate"]) } expected = <<~DESC Commands: #{cmd} generate action APP ACTION # Generate an action for app #{cmd} generate app APP # Generate an app #{cmd} generate mailer MAILER # Generate a mailer #{cmd} generate migration MIGRATION # Generate a migration #{cmd} generate model MODEL # Generate a model #{cmd} generate secret [APP] # Generate session secret #{cmd} generate webpack # Generate webpack configuration DESC expect(error).to eq(expected) end it "prints subcommand's subcommand" do error = capture_error { cli.call(arguments: %w[generate application]) } expected = <<~DESC Commands: #{cmd} generate action APP ACTION # Generate an action for app #{cmd} generate app APP # Generate an app #{cmd} generate mailer MAILER # Generate a mailer #{cmd} generate migration MIGRATION # Generate a migration #{cmd} generate model MODEL # Generate a model #{cmd} generate secret [APP] # Generate session secret #{cmd} generate webpack # Generate webpack configuration DESC expect(error).to eq(expected) end it "prints list options when calling help" do output = capture_output { cli.call(arguments: %w[options-with-aliases --help]) } expected = <<~DESC Command: #{cmd} options-with-aliases Usage: #{cmd} options-with-aliases Description: Accepts options with aliases Options: --url=VALUE, -u VALUE # The action URL --[no-]flag, -f # The flag --[no-]opt, -o # The opt, default: false --help, -h # Print this help DESC expect(output).to eq(expected) end end dry-cli-1.0.0/spec/support/shared_examples/subcommands.rb000066400000000000000000000167021433137403100235320ustar00rootroot00000000000000# frozen_string_literal: true RSpec.shared_examples "Subcommands" do |cli| let(:cli) { cli } let(:cmd) { File.basename($PROGRAM_NAME, File.extname($PROGRAM_NAME)) } it "calls subcommand" do error = capture_error { cli.call(arguments: %w[generate model]) } expected = <<~DESC ERROR: "#{cmd} generate model" was called with no arguments Usage: "#{cmd} generate model MODEL" DESC expect(error).to eq(expected) end context "works with params" do it "without params" do error = capture_error { cli.call(arguments: %w[generate model]) } expected = <<~DESC ERROR: "#{cmd} generate model" was called with no arguments Usage: "#{cmd} generate model MODEL" DESC expect(error).to eq(expected) end it "a param using space" do output = capture_output { cli.call(arguments: %w[server --port 2306]) } expect(output).to eq( "server - {:code_reloading=>true, :deps=>[\"dep1\", \"dep2\"], :port=>\"2306\"}\n" ) end it "a param using equal sign" do output = capture_output { cli.call(arguments: %w[server --port=2306]) } expect(output).to eq( "server - {:code_reloading=>true, :deps=>[\"dep1\", \"dep2\"], :port=>\"2306\"}\n" ) end it "a param using alias" do output = capture_output { cli.call(arguments: %w[server -p 2306]) } expect(output).to eq( "server - {:code_reloading=>true, :deps=>[\"dep1\", \"dep2\"], :port=>\"2306\"}\n" ) end it "with help param" do output = capture_output { cli.call(arguments: %w[generate model --help]) } expected = <<~DESC Command: #{cmd} generate model Usage: #{cmd} generate model MODEL Description: Generate a model Arguments: MODEL # REQUIRED Model name (eg. `user`) Options: --[no-]skip-migration # Skip migration, default: false --help, -h # Print this help Examples: #{cmd} generate model user # Generate `User` entity, `UserRepository` repository, and the migration #{cmd} generate model user --skip-migration # Generate `User` entity and `UserRepository` repository DESC expect(output).to eq(expected) end context "with required params" do it "only one param" do output = capture_output { cli.call(arguments: %w[generate model user]) } expect(output).to eq("generate model - model: user\n") end it "more than one param" do output = capture_output { cli.call(arguments: %w[destroy action web users#index]) } expect(output).to eq("destroy action - app: web, action: users#index\n") end it "more than one param and with optional params" do output = capture_output { cli.call(arguments: %w[generate action web users#index --url=/signin]) } # rubocop:disable Metrics/LineLength expect(output).to eq("generate action - app: web, action: users#index, options: {:skip_view=>false, :url=>\"/signin\"}\n") # rubocop:disable Metrics/LineLength end it "more than one param and with boolean params" do output = capture_output { cli.call(arguments: %w[generate action web users#index --skip-view --url=/signin]) } # rubocop:disable Metrics/LineLength expect(output).to eq("generate action - app: web, action: users#index, options: {:skip_view=>true, :url=>\"/signin\"}\n") # rubocop:disable Metrics/LineLength end it "more than required params" do output = capture_output { cli.call(arguments: %w[destroy action web users#index unexpected_param]) } # rubocop:disable Metrics/LineLength expect(output).to eq("destroy action - app: web, action: users#index\n") end it "an error is displayed if there aren't required params" do error = capture_error { cli.call(arguments: %w[destroy action]) } expected = <<~DESC ERROR: "#{cmd} destroy action" was called with no arguments Usage: "#{cmd} destroy action APP ACTION" DESC expect(error).to eq(expected) end it "an error is displayed if there are some required params" do error = capture_error { cli.call(arguments: %w[destroy action web]) } expected = <<~DESC ERROR: "#{cmd} destroy action" was called with arguments [\"web\"] Usage: "#{cmd} destroy action APP ACTION" DESC expect(error).to eq(expected) end context "and a default value" do it "returns the default value if nothing is passed" do output = capture_output { cli.call(arguments: %w[db rollback]) } expect(output).to eq("1\n") end it "returns the passed value" do output = capture_output { cli.call(arguments: %w[db rollback 3]) } expect(output).to eq("3\n") end end end end context "works with root command" do it "shows help" do output = capture_output { cli.call(arguments: %w[root-command sub-command -h]) } expected = <<~DESC Command: rspec root-command sub-command Usage: rspec root-command sub-command ROOT_COMMAND_SUB_COMMAND_ARGUMENT Description: Root command sub command Arguments: ROOT_COMMAND_SUB_COMMAND_ARGUMENT # REQUIRED Root command sub command argument Options: --root-command-sub-command-option=VALUE # Root command sub command option --help, -h # Print this help DESC expect(output).to eq(expected) output = capture_output { cli.call(arguments: %w[root-command sub-command --help]) } expect(output).to eq(expected) end context "works with params" do it "without params" do error = capture_error { cli.call(arguments: %w[root-command sub-command]) } expected = <<~DESC ERROR: "rspec root-command sub-command" was called with no arguments Usage: "rspec root-command sub-command ROOT_COMMAND_SUB_COMMAND_ARGUMENT" DESC expect(error).to eq(expected) end it "with params" do output = capture_output { cli.call(arguments: ["root-command", "sub-command", '"hello world"']) } expected = <<~DESC I'm a root-command sub-command argument:"hello world" I'm a root-command sub-command option: DESC expect(output).to eq(expected) end it "with option using space" do output = capture_output { cli.call(arguments: [ "root-command", "sub-command", '"hello world"', "--root-command-sub-command-option", '"bye world"' ]) } expected = <<~DESC I'm a root-command sub-command argument:"hello world" I'm a root-command sub-command option:"bye world" DESC expect(output).to eq(expected) end it "with option using equal sign" do output = capture_output { cli.call(arguments: [ "root-command", "sub-command", '"hello world"', '--root-command-sub-command-option="bye world"' ]) } expected = <<~DESC I'm a root-command sub-command argument:"hello world" I'm a root-command sub-command option:"bye world" DESC expect(output).to eq(expected) end end end end dry-cli-1.0.0/spec/support/shared_examples/third_party_gems.rb000066400000000000000000000037001433137403100245550ustar00rootroot00000000000000# frozen_string_literal: true RSpec.shared_examples "Third-party gems" do |cli| let(:cli) { cli } let(:cmd) { File.basename($PROGRAM_NAME, File.extname($PROGRAM_NAME)) } it "allows to add a subcommand" do output = capture_output { cli.call(arguments: %w[generate webpack]) } expect(output).to eq("generate webpack. Apps: []\n") end it "allows to invoke a subcommand via an inherited subcomand aliases" do output = capture_output { cli.call(arguments: %w[g webpack]) } expect(output).to eq("generate webpack. Apps: []\n") end it "allows to override basic commands" do output = capture_output { cli.call(arguments: ["hello"]) } expect(output).to eq("hello from webpack\n") end it "allows to override a subcommand" do output = capture_output { cli.call(arguments: %w[sub command]) } expect(output).to eq("override from webpack\n") end context "callbacks" do it "allows to add callbacks as a block" do expected = <<~OUTPUT before command callback Webpack::CLI::CallbacksCommand {:url=>"https://hanamirb.test", :dir=>"."} before callback (class), 2 arg(s): {:url=>"https://hanamirb.test", :dir=>"."} before callback (object), 2 arg(s): {:url=>"https://hanamirb.test", :dir=>"."} dir: ., url: "https://hanamirb.test" after command callback Webpack::CLI::CallbacksCommand {:url=>"https://hanamirb.test", :dir=>"."} after callback (class), 2 arg(s): {:url=>"https://hanamirb.test", :dir=>"."} after callback (object), 2 arg(s): {:url=>"https://hanamirb.test", :dir=>"."} OUTPUT output = capture_output { cli.call(arguments: %w[callbacks . --url=https://hanamirb.test]) } expect(output).to eq(expected) end end it "allows to call array option" do output = capture_output { cli.call(arguments: %w[generate webpack --apps=test,api,admin]) } expect(output).to eq("generate webpack. Apps: [\"test\", \"api\", \"admin\"]\n") end end dry-cli-1.0.0/spec/support/warnings.rb000066400000000000000000000003701433137403100176750ustar00rootroot00000000000000# 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?(:[]) dry-cli-1.0.0/spec/unit/000077500000000000000000000000001433137403100147635ustar00rootroot00000000000000dry-cli-1.0.0/spec/unit/dry/000077500000000000000000000000001433137403100155615ustar00rootroot00000000000000dry-cli-1.0.0/spec/unit/dry/cli/000077500000000000000000000000001433137403100163305ustar00rootroot00000000000000dry-cli-1.0.0/spec/unit/dry/cli/cli_spec.rb000066400000000000000000000105121433137403100204350ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe "CLI" do context "when registry" do context "passing module" do include_examples "Commands", WithRegistry include_examples "Rendering", WithRegistry include_examples "Subcommands", WithRegistry include_examples "Inherited commands", WithBlock include_examples "Third-party gems", WithRegistry end context "passing block" do include_examples "Commands", WithBlock include_examples "Rendering", WithBlock include_examples "Subcommands", WithBlock include_examples "Inherited commands", WithBlock include_examples "Third-party gems", WithBlock end context "passing block with no arguments" do include_examples "Commands", WithZeroArityBlock include_examples "Rendering", WithZeroArityBlock include_examples "Subcommands", WithZeroArityBlock include_examples "Inherited commands", WithZeroArityBlock include_examples "Third-party gems", WithZeroArityBlock end end context "with command" do let(:cli) { Dry.CLI(Baz::CLI) } let(:cmd) { File.basename($PROGRAM_NAME, File.extname($PROGRAM_NAME)) } it "shows help" do output = capture_output { cli.call(arguments: ["-h"]) } expected_output = <<~OUTPUT Command: #{cmd} Usage: #{cmd} MANDATORY_ARG [OPTIONAL_ARG] Description: Baz command line interface Arguments: MANDATORY_ARG # REQUIRED Mandatory argument OPTIONAL_ARG # Optional argument (has to have default value in call method) Options: --option-one=VALUE, -1 VALUE # Option one --[no-]boolean-option, -b # Option boolean --option-with-default=VALUE, -d VALUE # Option default, default: "test" --help, -h # Print this help OUTPUT expect(output).to eq(expected_output) end it "with required_argument" do output = capture_output { cli.call(arguments: ["first_arg"]) } expect(output).to eq( "mandatory_arg: first_arg. optional_arg: optional_arg. " \ "Options: {:option_with_default=>\"test\"}\n" ) end it "with optional_arg" do output = capture_output { cli.call(arguments: %w[first_arg opt_arg]) } expect(output).to eq( "mandatory_arg: first_arg. optional_arg: opt_arg. " \ "Options: {:option_with_default=>\"test\", :args=>[\"opt_arg\"]}\n" ) end it "with underscored option_one" do output = capture_output { cli.call(arguments: %w[first_arg --option_one=test2]) } expect(output).to eq( "mandatory_arg: first_arg. optional_arg: optional_arg. " \ "Options: {:option_with_default=>\"test\", :option_one=>\"test2\"}\n" ) end it "with option_one alias" do output = capture_output { cli.call(arguments: %w[first_arg -1 test2]) } expect(output).to eq( "mandatory_arg: first_arg. optional_arg: optional_arg. " \ "Options: {:option_with_default=>\"test\", :option_one=>\"test2\"}\n" ) end it "with underscored boolean_option" do output = capture_output { cli.call(arguments: %w[first_arg --boolean_option]) } expect(output).to eq( "mandatory_arg: first_arg. optional_arg: optional_arg. " \ "Options: {:option_with_default=>\"test\", :boolean_option=>true}\n" ) end it "with boolean_option alias" do output = capture_output { cli.call(arguments: %w[first_arg -b]) } expect(output).to eq( "mandatory_arg: first_arg. optional_arg: optional_arg. " \ "Options: {:option_with_default=>\"test\", :boolean_option=>true}\n" ) end it "with underscoreed option_with_default alias" do output = capture_output { cli.call(arguments: %w[first_arg --option_with_default=test3]) } expect(output).to eq( "mandatory_arg: first_arg. optional_arg: optional_arg. " \ "Options: {:option_with_default=>\"test3\"}\n" ) end it "with combination of aliases" do output = capture_output { cli.call(arguments: %w[first_arg -bd test3]) } expect(output).to eq( "mandatory_arg: first_arg. optional_arg: optional_arg. " \ "Options: {:option_with_default=>\"test3\", :boolean_option=>true}\n" ) end end end dry-cli-1.0.0/spec/unit/dry/cli/inflector_spec.rb000066400000000000000000000014151433137403100216550ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Dry::CLI::Inflector do describe ".dasherize" do it "returns nil if input is nil" do expect(described_class.dasherize(nil)).to be(nil) end it "downcases input" do expect(described_class.dasherize("Dry")).to eq("dry") expect(described_class.dasherize("CLI")).to eq("cli") end it "replaces spaces with dashes" do expect(described_class.dasherize("Command Line Interface")).to eq("command-line-interface") end it "replaces underscores with dashes" do expect(described_class.dasherize("fast_code_reloading")).to eq("fast-code-reloading") end it "accepts any object that respond to #to_s" do expect(described_class.dasherize(:dry)).to eq("dry") end end end dry-cli-1.0.0/spec/unit/dry/cli/registry_spec.rb000066400000000000000000000042011433137403100215340ustar00rootroot00000000000000# frozen_string_literal: true RSpec.describe Dry::CLI::Registry do describe ".before" do context "when command can't be found" do it "raises error" do expect do Bar::CLI::Commands.before("pixel") { puts "hello" } end.to raise_error( Dry::CLI::UnknownCommandError, "unknown command: `pixel'" ) end end context "when object is given" do it "raises error when it doesn't respond to #call" do callback = Object.new expect do Bar::CLI::Commands.before("alpha", callback) end.to raise_error( Dry::CLI::InvalidCallbackError, "expected `#{callback.inspect}' to respond to `#call'" ) end end context "when class is given" do it "raises error when #initialize arity is not equal to 0" do callback = Struct expect do Bar::CLI::Commands.before("alpha", callback) end.to raise_error( Dry::CLI::InvalidCallbackError, "expected `#{callback.inspect}' to respond to `#initialize' with arity 0" ) end end end describe ".after" do context "when command can't be found" do it "raises error" do expect do Bar::CLI::Commands.after("peta") { puts "hello" } end.to raise_error(Dry::CLI::UnknownCommandError, "unknown command: `peta'") end end context "when object is given" do it "raises error when it doesn't respond to #call" do callback = Object.new expect do Bar::CLI::Commands.after("alpha", callback) end.to raise_error( Dry::CLI::InvalidCallbackError, "expected `#{callback.inspect}' to respond to `#call'" ) end end context "when class is given" do it "raises error when #initialize arity is not equal to 0" do callback = Struct expect do Bar::CLI::Commands.after("alpha", callback) end.to raise_error( Dry::CLI::InvalidCallbackError, "expected `#{callback.inspect}' to respond to `#initialize' with arity 0" ) end end end end